11import type { SQLiteInsertValue , SQLiteUpdateSetSource } from "drizzle-orm/sqlite-core" ;
2+ import type { MessageEntity , User } from "grammy/types" ;
23
34import { config } from "#config" ;
45import { db , schema as s } from "#db" ;
5- import { createCommand , zs } from "#telegram" ;
6+ import { createCommand , extractGitHubUsername , zs } from "#telegram" ;
67import z from "zod" ;
78
89import type { BotContext } from "../../bot.ts" ;
910
11+ import { toTelegramFullName } from "../../../lib/telegram/telegram-user.ts" ;
12+
1013const schema = z . object ( {
11- ghUsername : zs . ghUsername ,
14+ username : z . string ( ) . min ( 1 ) ,
1215 tgUsername : zs . tgUsername . optional ( ) ,
1316} ) ;
1417
15- export async function handler ( ctx : BotContext < z . infer < typeof schema > > ) {
16- const repliedMessage = ctx . message . reply_to_message ;
17- const isActualReply = repliedMessage && ! repliedMessage . forum_topic_created ;
18+ type GitHubUsername = z . infer < typeof zs . ghUsername > ;
19+ type TelegramUsername = z . infer < typeof zs . tgUsername > ;
20+ type ResolvedLinkArguments =
21+ | { error : "cmd_link_help" | "cmd_link_no_github_user" }
22+ | { ghUsername : GitHubUsername ; tgUsername : TelegramUsername | undefined } ;
23+
24+ function resolveLinkArguments (
25+ args : z . infer < typeof schema > ,
26+ isReplyToBot : boolean ,
27+ entities : readonly MessageEntity [ ] | undefined ,
28+ ) : ResolvedLinkArguments {
29+ if ( ! isReplyToBot ) {
30+ const result = zs . ghUsername . safeParse ( args . username ) ;
31+ return result . success ? { ghUsername : result . data , tgUsername : args . tgUsername } : { error : "cmd_link_help" } ;
32+ }
33+
34+ if ( args . tgUsername ) return { error : "cmd_link_help" } ;
1835
19- const { ghUsername } = ctx . args ;
20- let { tgUsername } = ctx . args ;
36+ const telegramResult = zs . tgUsername . safeParse ( args . username ) ;
37+ if ( ! telegramResult . success ) return { error : "cmd_link_help" } ;
2138
22- const from = isActualReply ? repliedMessage . from : undefined ;
23- const tgId = from ?. id ?? null ;
24- const tgName = from ? [ from . first_name , from . last_name ] . filter ( Boolean ) . join ( " " ) : null ;
39+ const ghUsername = extractGitHubUsername ( entities ) ;
40+ if ( ! ghUsername ) return { error : "cmd_link_no_github_user" } ;
41+
42+ return { ghUsername, tgUsername : telegramResult . data } ;
43+ }
44+
45+ function getTelegramTarget ( from : User | undefined , tgUsername : TelegramUsername | undefined ) {
46+ const target = {
47+ tgId : from ?. id ?? null ,
48+ tgName : from ? toTelegramFullName ( from ) : null ,
49+ tgUsername,
50+ } ;
2551
2652 if ( from ?. username ) {
2753 const result = zs . tgUsername . safeParse ( from . username ) ;
28- if ( result . success ) {
29- tgUsername = result . data ;
30- }
54+ if ( result . success ) target . tgUsername = result . data ;
3155 }
3256
33- if ( ! tgId && ! tgUsername ) {
57+ return target ;
58+ }
59+
60+ function getContributorValues ( ghUsername : GitHubUsername , target : ReturnType < typeof getTelegramTarget > ) {
61+ const set : SQLiteInsertValue < typeof s . contributors > = { ghUsername } ;
62+ if ( target . tgId ) set . tgId = target . tgId ;
63+ if ( target . tgName ) set . tgName = target . tgName ;
64+ if ( target . tgUsername ) set . tgUsername = target . tgUsername ;
65+ return set ;
66+ }
67+
68+ export async function handler ( ctx : BotContext < z . infer < typeof schema > > ) {
69+ const repliedMessage = ctx . message . reply_to_message ;
70+ const entities = repliedMessage && "entities" in repliedMessage ? repliedMessage . entities : undefined ;
71+
72+ const resolved = resolveLinkArguments ( ctx . args , ctx . isReplyToBot , entities ) ;
73+
74+ if ( "error" in resolved ) return await ctx . html . replyToMessage ( ctx . t ( resolved . error ) ) ;
75+
76+ const from = ctx . isReply && ! ctx . isReplyToBot ? repliedMessage ?. from : undefined ;
77+ const target = getTelegramTarget ( from , resolved . tgUsername ) ;
78+
79+ if ( ! target . tgId && ! target . tgUsername ) {
3480 return await ctx . html . replyToMessage ( ctx . t ( "cmd_link_no_user" ) ) ;
3581 }
3682
37- const set : SQLiteInsertValue < typeof s . contributors > = { ghUsername } ;
38- if ( tgId ) set . tgId = tgId ;
39- if ( tgName ) set . tgName = tgName ;
40- if ( tgUsername ) set . tgUsername = tgUsername ;
83+ const set = getContributorValues ( resolved . ghUsername , target ) ;
4184
4285 await db
4386 . insert ( s . contributors )
@@ -47,11 +90,20 @@ export async function handler(ctx: BotContext<z.infer<typeof schema>>) {
4790 set : set as SQLiteUpdateSetSource < typeof s . contributors > ,
4891 } ) ;
4992
93+ if ( ctx . isReplyToBot ) {
94+ return await ctx . html . replyToMessage (
95+ ctx . t ( "cmd_link_event" , {
96+ ghUsername : resolved . ghUsername ,
97+ tgUsername : `@${ target . tgUsername } ` ,
98+ } ) ,
99+ ) ;
100+ }
101+
50102 return await ctx . html . replyToMessage ( ctx . t ( "cmd_link" ) ) ;
51103}
52104
53105export const cmdLink = createCommand ( {
54- template : "link $ghUsername $tgUsername" ,
106+ template : "link $username $tgUsername" ,
55107 description : "🛡 Link Telegram and GitHub accounts" ,
56108 handler,
57109 schema,
0 commit comments