11import type { Logger } from '@guiiai/logg'
2- import type { Entity } from 'telegram/define'
32
43import type { MessageResolver , MessageResolverOpts } from '.'
54import type { CoreContext } from '../context'
@@ -13,70 +12,80 @@ import { resolveEntity } from '../utils/entity'
1312export function createUserResolver ( ctx : CoreContext , logger : Logger , userModels : UserModels ) : MessageResolver {
1413 logger = logger . withContext ( 'core:resolver:user' )
1514
16- // In-memory cache for entities fetched from Telegram API during this session
17- const entities = new Map < string , Entity > ( )
18-
1915 // In-memory cache for user database records to avoid repeated DB queries
2016 const userCache = new Map < string , DBSelectUser > ( )
2117 const userBlockedList = new Set < string > ( )
2218
19+ const resolveUser = async ( fromId : string ) : Promise < DBSelectUser | undefined > => {
20+ const cacheKey = `telegram:${ fromId } `
21+
22+ // 1. Check in-memory cache
23+ const cached = userCache . get ( cacheKey )
24+ if ( cached ) {
25+ return cached
26+ }
27+
28+ // 2. Check database
29+ const dbUser = ( await userModels . findUserByPlatformId ( ctx . getDB ( ) , 'telegram' , fromId ) ) . orUndefined ( )
30+ if ( dbUser ) {
31+ userCache . set ( cacheKey , dbUser )
32+ logger . withFields ( { userId : dbUser . id , fromId } ) . debug ( 'User found in database' )
33+ return dbUser
34+ }
35+
36+ // 3. Fallback: Fetch from Telegram API
37+ if ( userBlockedList . has ( fromId ) ) {
38+ return undefined
39+ }
40+
41+ try {
42+ const rawEntity = await ctx . getClient ( ) . getEntity ( fromId )
43+ const entity = resolveEntity ( rawEntity ) . orUndefined ( )
44+
45+ if ( ! entity ) {
46+ return undefined
47+ }
48+
49+ logger . withFields ( rawEntity ) . debug ( 'Resolved entity from Telegram API' )
50+
51+ // 4. Record new user to database
52+ const recordedUser = await userModels . recordUser ( ctx . getDB ( ) , entity )
53+ userCache . set ( cacheKey , recordedUser )
54+ logger . withFields ( { userId : recordedUser . id , fromId } ) . debug ( 'User saved to database' )
55+
56+ return recordedUser
57+ }
58+ catch ( err ) {
59+ // Only block if it's a Telegram API error, not DB error?
60+ // Actually if DB error occurs we might want to retry.
61+ // But distinguishing them is hard here without type guards.
62+ // Assuming 'catch' catches both getEntity and recordUser errors.
63+ // If getEntity fails, we should block.
64+ // If recordUser fails, maybe not block?
65+
66+ // Let's refine:
67+ // We need to differentiate where the error came from to know if we should block.
68+ // But for simplicity/safety, let's treat it as a "failed to resolve" for this session.
69+
70+ // Wait, if recordUser fails, we probably shouldn't block the user ID forever in this session.
71+ // But re-trying on every message might spam logs.
72+
73+ // Let's assume most errors are API errors here or transient DB errors.
74+ userBlockedList . add ( fromId )
75+ logger . withFields ( { fromId, err } ) . warn ( 'Failed to resolve or save user' )
76+ return undefined
77+ }
78+ }
79+
2380 return {
2481 run : async ( opts : MessageResolverOpts ) => {
2582 logger . verbose ( 'Executing user resolver' )
2683
2784 const { messages } = opts
2885
2986 for ( const message of messages ) {
30- const cacheKey = `telegram:${ message . fromId } `
31-
32- // Check in-memory cache first
33- let dbUser = userCache . get ( cacheKey )
34-
35- if ( ! dbUser ) {
36- // Check database
37- const dbUserOrNull = ( await userModels . findUserByPlatformId ( ctx . getDB ( ) , 'telegram' , message . fromId ) ) . orUndefined ( )
38-
39- if ( dbUserOrNull ) {
40- dbUser = dbUserOrNull
41- userCache . set ( cacheKey , dbUser )
42- logger . withFields ( { userId : dbUser . id , fromId : message . fromId } ) . debug ( 'User found in database' )
43- }
44- }
45-
46- // If user not found in cache or database, fetch from Telegram API
47- if ( ! dbUser ) {
48- if ( userBlockedList . has ( message . fromId ) ) {
49- continue
50- }
51-
52- if ( ! entities . has ( message . fromId ) ) {
53- try {
54- const entity = await ctx . getClient ( ) . getEntity ( message . fromId )
55- entities . set ( message . fromId , entity )
56- logger . withFields ( entity ) . debug ( 'Resolved entity from Telegram API' )
57- }
58- catch {
59- // TODO: is there needs access_hash?
60- userBlockedList . add ( message . fromId )
61- logger . withFields ( { fromId : message . fromId } ) . warn ( 'Failed to get entity from Telegram API' )
62- }
63- }
64-
65- const entity = entities . get ( message . fromId ) !
66- const coreEntity = resolveEntity ( entity ) . orUndefined ( )
67-
68- if ( ! coreEntity ) {
69- continue
70- }
71-
72- // Save to database
73- const recordedUser = await userModels . recordUser ( ctx . getDB ( ) , coreEntity )
74- dbUser = recordedUser
75- userCache . set ( cacheKey , dbUser )
76- logger . withFields ( { userId : dbUser . id , fromId : message . fromId } ) . debug ( 'User saved to database' )
77- }
87+ const dbUser = await resolveUser ( message . fromId )
7888
79- // Update message with user information
8089 if ( dbUser ) {
8190 message . fromName = dbUser . name
8291 message . fromUserUuid = dbUser . id
0 commit comments