@@ -4,7 +4,7 @@ import fs from "node:fs";
44import path from "node:path" ;
55import os from "node:os" ;
66import readline from "node:readline" ;
7- import { pathToFileURL } from "node:url" ;
7+ import { fileURLToPath , pathToFileURL } from "node:url" ;
88
99function emit ( event , payload = { } ) {
1010 process . stdout . write ( `${ JSON . stringify ( { type : "event" , event, ...payload } ) } \n` ) ;
@@ -49,7 +49,7 @@ function writeState(stateFile, patch) {
4949}
5050
5151async function loadWeixinInternalModule ( packageEntryUrl , relCandidates ) {
52- const entryPath = new URL ( packageEntryUrl ) . pathname ;
52+ const entryPath = fileURLToPath ( packageEntryUrl ) ;
5353 const packageRoot = path . resolve ( path . dirname ( entryPath ) , ".." ) ;
5454 for ( const rel of relCandidates ) {
5555 const abs = path . resolve ( packageRoot , rel ) ;
@@ -66,13 +66,30 @@ async function loadWeixinInternalModule(packageEntryUrl, relCandidates) {
6666async function loadWeixinBundledInternals ( packageEntryUrl ) {
6767 if ( ! packageEntryUrl ) return null ;
6868 try {
69- const entryPath = new URL ( packageEntryUrl ) . pathname ;
69+ const entryPath = fileURLToPath ( packageEntryUrl ) ;
7070 const source = fs . readFileSync ( entryPath , "utf8" ) ;
7171 const tempPath = path . join (
7272 os . tmpdir ( ) ,
7373 `cccc-weixin-sdk-internals-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } .mjs` ,
7474 ) ;
75- const extra = "\nexport { startWeixinLoginWithQr, waitForWeixinLogin, normalizeAccountId, saveWeixinAccount, registerWeixinAccountId };\nexport const DEFAULT_ILINK_BOT_TYPE = '3';\n" ;
75+ const extra = `
76+ export {
77+ startWeixinLoginWithQr,
78+ waitForWeixinLogin,
79+ normalizeAccountId,
80+ saveWeixinAccount,
81+ registerWeixinAccountId,
82+ resolveWeixinAccount,
83+ listWeixinAccountIds,
84+ markdownToPlainText,
85+ sendMessageWeixin,
86+ sendWeixinMediaFile
87+ };
88+ export const DEFAULT_ILINK_BOT_TYPE = '3';
89+ export function __ccccGetContextToken(accountId, userId) {
90+ return contextTokenStore.get(contextTokenKey(accountId, userId)) || "";
91+ }
92+ ` ;
7693 fs . writeFileSync ( tempPath , source + extra , "utf8" ) ;
7794 return await import ( pathToFileURL ( tempPath ) . href ) ;
7895 } catch {
@@ -84,9 +101,11 @@ async function main() {
84101 const args = parseArgs ( process . argv ) ;
85102 let sdk ;
86103 let sdkEntryUrl = "" ;
104+ let bundledInternals = null ;
87105 try {
88106 sdkEntryUrl = await import . meta. resolve ( "weixin-agent-sdk" ) ;
89107 sdk = await import ( "weixin-agent-sdk" ) ;
108+ bundledInternals = await loadWeixinBundledInternals ( sdkEntryUrl ) ;
90109 } catch ( error ) {
91110 writeState ( args . stateFile , {
92111 status : "error" ,
@@ -100,7 +119,8 @@ async function main() {
100119 process . exit ( 1 ) ;
101120 }
102121
103- const { isLoggedIn, start, login, logout } = sdk ;
122+ const runtimeSdk = bundledInternals || sdk ;
123+ const { isLoggedIn, start, login, logout } = runtimeSdk ;
104124 if ( typeof isLoggedIn !== "function" ) {
105125 writeState ( args . stateFile , {
106126 status : "error" ,
@@ -162,12 +182,12 @@ async function main() {
162182 "dist/auth/accounts.js" ,
163183 ] )
164184 : null ;
165- const bundledInternals =
166- ! loginQrModule || ! accountsModule ? await loadWeixinBundledInternals ( sdkEntryUrl ) : null ;
185+ const loginBundledInternals =
186+ ! loginQrModule || ! accountsModule ? bundledInternals : null ;
167187
168188 let accountId = "" ;
169- const loginInternals = loginQrModule || bundledInternals ;
170- const accountInternals = accountsModule || bundledInternals ;
189+ const loginInternals = loginQrModule || loginBundledInternals ;
190+ const accountInternals = accountsModule || loginBundledInternals ;
171191 if ( loginInternals && accountInternals ) {
172192 const startWeixinLoginWithQr = loginInternals . startWeixinLoginWithQr ;
173193 const waitForWeixinLogin = loginInternals . waitForWeixinLogin ;
@@ -313,14 +333,169 @@ async function main() {
313333 const accountId = String ( process . env . CCCC_IM_WEIXIN_ACCOUNT_ID || "" ) . trim ( ) || undefined ;
314334 const pendingReplies = new Map ( ) ;
315335 const abortController = new AbortController ( ) ;
336+ const sendMessageWeixin = typeof runtimeSdk . sendMessageWeixin === "function"
337+ ? runtimeSdk . sendMessageWeixin
338+ : null ;
339+ const sendWeixinMediaFile = typeof runtimeSdk . sendWeixinMediaFile === "function"
340+ ? runtimeSdk . sendWeixinMediaFile
341+ : null ;
342+ const resolveWeixinAccount = typeof runtimeSdk . resolveWeixinAccount === "function"
343+ ? runtimeSdk . resolveWeixinAccount
344+ : null ;
345+ const listWeixinAccountIds = typeof runtimeSdk . listWeixinAccountIds === "function"
346+ ? runtimeSdk . listWeixinAccountIds
347+ : null ;
348+ const markdownToPlainText = typeof runtimeSdk . markdownToPlainText === "function"
349+ ? runtimeSdk . markdownToPlainText
350+ : ( text ) => String ( text || "" ) ;
351+ const getContextToken = typeof runtimeSdk . __ccccGetContextToken === "function"
352+ ? runtimeSdk . __ccccGetContextToken
353+ : null ;
354+ const runtimeAccountId = ( ( ) => {
355+ if ( accountId ) return accountId ;
356+ if ( ! listWeixinAccountIds ) return "" ;
357+ const ids = listWeixinAccountIds ( ) ;
358+ if ( ! Array . isArray ( ids ) || ids . length <= 0 ) return "" ;
359+ if ( ids . length > 1 ) emitLog ( `[weixin] detected multiple accounts, using the first: ${ ids [ 0 ] } ` ) ;
360+ return String ( ids [ 0 ] || "" ) ;
361+ } ) ( ) ;
362+ const outboundAccount = ( ( ) => {
363+ if ( ! resolveWeixinAccount || ! runtimeAccountId ) return null ;
364+ try {
365+ return resolveWeixinAccount ( runtimeAccountId ) ;
366+ } catch ( error ) {
367+ emitLog (
368+ `[weixin] failed to resolve outbound account ${ runtimeAccountId } : ${
369+ error instanceof Error ? error . message : String ( error )
370+ } `,
371+ ) ;
372+ return null ;
373+ }
374+ } ) ( ) ;
316375 writeState ( args . stateFile , {
317376 status : "running" ,
318377 logged_in : true ,
319- account_id : accountId || "" ,
378+ account_id : runtimeAccountId || "" ,
320379 qrcode_url : "" ,
321380 error : "" ,
322381 } ) ;
323382
383+ async function sendDirectText ( chatId , text ) {
384+ if ( ! sendMessageWeixin || ! getContextToken || ! outboundAccount ?. token ) {
385+ emitLog ( "[weixin] proactive text send is unavailable" ) ;
386+ return ;
387+ }
388+ const contextToken = String ( getContextToken ( outboundAccount . accountId , chatId ) || "" ) . trim ( ) ;
389+ if ( ! contextToken ) {
390+ emitLog ( `[weixin] missing outbound context token for chat_id=${ chatId } ` ) ;
391+ return ;
392+ }
393+ await sendMessageWeixin ( {
394+ to : chatId ,
395+ text : markdownToPlainText ( String ( text || "" ) ) ,
396+ opts : {
397+ baseUrl : outboundAccount . baseUrl ,
398+ token : outboundAccount . token ,
399+ contextToken,
400+ } ,
401+ } ) ;
402+ }
403+
404+ async function sendDirectFile ( chatId , filePath , caption ) {
405+ if ( ! sendWeixinMediaFile || ! getContextToken || ! outboundAccount ?. token ) {
406+ emitLog ( "[weixin] proactive file send is unavailable" ) ;
407+ return ;
408+ }
409+ const contextToken = String ( getContextToken ( outboundAccount . accountId , chatId ) || "" ) . trim ( ) ;
410+ if ( ! contextToken ) {
411+ emitLog ( `[weixin] missing outbound context token for chat_id=${ chatId } ` ) ;
412+ return ;
413+ }
414+ await sendWeixinMediaFile ( {
415+ filePath,
416+ to : chatId ,
417+ text : markdownToPlainText ( String ( caption || "" ) ) ,
418+ opts : {
419+ baseUrl : outboundAccount . baseUrl ,
420+ token : outboundAccount . token ,
421+ contextToken,
422+ } ,
423+ cdnBaseUrl : outboundAccount . cdnBaseUrl ,
424+ } ) ;
425+ }
426+
427+ async function handleCommand ( payload ) {
428+ const cmd = String ( payload ?. cmd || "" ) . trim ( ) ;
429+ if ( cmd === "shutdown" ) {
430+ abortController . abort ( ) ;
431+ return ;
432+ }
433+
434+ if ( cmd === "reply" ) {
435+ const requestId = String ( payload . request_id || "" ) . trim ( ) ;
436+ if ( ! requestId ) return ;
437+ const pending = pendingReplies . get ( requestId ) ;
438+ if ( ! pending ) {
439+ emitLog ( `missing pending reply handle for request_id=${ requestId } ` ) ;
440+ return ;
441+ }
442+ pendingReplies . delete ( requestId ) ;
443+ pending . resolve ( {
444+ text : String ( payload . text || "" ) ,
445+ } ) ;
446+ return ;
447+ }
448+
449+ if ( cmd === "reply_file" ) {
450+ const requestId = String ( payload . request_id || "" ) . trim ( ) ;
451+ const filePath = String ( payload . file_path || "" ) . trim ( ) ;
452+ if ( ! requestId || ! filePath ) return ;
453+ const pending = pendingReplies . get ( requestId ) ;
454+ if ( ! pending ) {
455+ emitLog ( `missing pending reply handle for request_id=${ requestId } ` ) ;
456+ return ;
457+ }
458+ pendingReplies . delete ( requestId ) ;
459+ pending . resolve ( {
460+ text : String ( payload . caption || "" ) ,
461+ media : {
462+ url : path . resolve ( filePath ) ,
463+ } ,
464+ } ) ;
465+ return ;
466+ }
467+
468+ if ( cmd === "send" ) {
469+ const chatId = String ( payload . chat_id || "" ) . trim ( ) ;
470+ if ( ! chatId ) return ;
471+ try {
472+ await sendDirectText ( chatId , String ( payload . text || "" ) ) ;
473+ } catch ( error ) {
474+ emitLog (
475+ `[weixin] proactive send failed for ${ chatId } : ${
476+ error instanceof Error ? error . message : String ( error )
477+ } `,
478+ ) ;
479+ }
480+ return ;
481+ }
482+
483+ if ( cmd === "send_file" ) {
484+ const chatId = String ( payload . chat_id || "" ) . trim ( ) ;
485+ const filePath = String ( payload . file_path || "" ) . trim ( ) ;
486+ if ( ! chatId || ! filePath ) return ;
487+ try {
488+ await sendDirectFile ( chatId , path . resolve ( filePath ) , String ( payload . caption || "" ) ) ;
489+ } catch ( error ) {
490+ emitLog (
491+ `[weixin] proactive file send failed for ${ chatId } : ${
492+ error instanceof Error ? error . message : String ( error )
493+ } `,
494+ ) ;
495+ }
496+ }
497+ }
498+
324499 const rl = readline . createInterface ( {
325500 input : process . stdin ,
326501 crlfDelay : Infinity ,
@@ -337,24 +512,7 @@ async function main() {
337512 return ;
338513 }
339514 if ( ! payload || payload . type !== "cmd" ) return ;
340-
341- if ( payload . cmd === "shutdown" ) {
342- abortController . abort ( ) ;
343- return ;
344- }
345-
346- if ( payload . cmd !== "reply" ) return ;
347- const requestId = String ( payload . request_id || "" ) . trim ( ) ;
348- if ( ! requestId ) return ;
349- const pending = pendingReplies . get ( requestId ) ;
350- if ( ! pending ) {
351- emitLog ( `missing pending reply handle for request_id=${ requestId } ` ) ;
352- return ;
353- }
354- pendingReplies . delete ( requestId ) ;
355- pending . resolve ( {
356- text : String ( payload . text || "" ) ,
357- } ) ;
515+ void handleCommand ( payload ) ;
358516 } ) ;
359517
360518 process . on ( "SIGINT" , ( ) => abortController . abort ( ) ) ;
@@ -413,19 +571,19 @@ async function main() {
413571 } ,
414572 } ;
415573
416- emit ( "ready" , { account_id : accountId || "" } ) ;
574+ emit ( "ready" , { account_id : runtimeAccountId || "" } ) ;
417575
418576 try {
419577 await start ( agent , {
420- accountId,
578+ accountId : runtimeAccountId || undefined ,
421579 abortSignal : abortController . signal ,
422580 log : emitLog ,
423581 } ) ;
424582 } catch ( error ) {
425583 writeState ( args . stateFile , {
426584 status : "error" ,
427585 logged_in : isLoggedIn ( ) ,
428- account_id : accountId || "" ,
586+ account_id : runtimeAccountId || "" ,
429587 qrcode_url : "" ,
430588 error : error instanceof Error ? error . message : String ( error ) ,
431589 } ) ;
0 commit comments