@qihoo/tuitui-openclaw-channel 1.0.19 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qihoo/tuitui-openclaw-channel",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "maintainers": [
5
5
  {
6
6
  "name": "huzunjie",
package/src/accounts.ts CHANGED
@@ -19,10 +19,10 @@ export const getDefaultAccountConfig = (acct?: any) => ({
19
19
  });
20
20
 
21
21
  export const resolveAccount = (cfg: any, accountId?: string | null) => {
22
- accountId = accountId || DEFAULT_ACCOUNT_ID;
22
+ const accId = accountId || DEFAULT_ACCOUNT_ID;
23
23
  let currAccount = cfg?.channels?.[CHANNEL_ID] || {};
24
- if (accountId && accountId !== DEFAULT_ACCOUNT_ID) {
25
- currAccount = currAccount.accounts?.[accountId];
24
+ if (accId !== DEFAULT_ACCOUNT_ID) {
25
+ currAccount = currAccount.accounts?.[accId];
26
26
  }
27
- return { accountId, ...getDefaultAccountConfig(currAccount) };
27
+ return { accountId: accId, ...getDefaultAccountConfig(currAccount) };
28
28
  };
package/src/channel.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id';
7
7
  import { setAccountEnabledInConfigSection, deleteAccountFromConfigSection } from 'openclaw/plugin-sdk/core';
8
8
  import { CHANNEL_ID, CHANNEL_NAME } from "./const";
9
+ import { handleInboundMessage } from './inbound';
9
10
  import {
10
11
  checkAccount,
11
12
  sendTextMsg,
@@ -256,6 +257,14 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
256
257
  apiRuntime,
257
258
  onConnected: () => _setStatus({ running: true, connected: true }),
258
259
  onDisconnected: () => _setStatus({ running: false, connected: false }),
260
+ onInbounMessage: (json: any) => {
261
+ _setStatus({ running: true, connected: true, lastInboundAt: Date.now() });
262
+ if (apiRuntime) {
263
+ handleInboundMessage({ json, account, apiRuntime, log });
264
+ } else {
265
+ log?.error?.(`[${CHANNEL_ID}] AccountId: ${accountId}, WebSocket.onInbounMessage TuiTuiRuntime error`);
266
+ }
267
+ },
259
268
  });
260
269
 
261
270
  // 保持账户运行状态,直至触发“abortSignal”信号为止。
package/src/env.ts ADDED
@@ -0,0 +1,15 @@
1
+ const DEFAULT_ONLINE = true;
2
+
3
+ export const getTuituiHost = (online: boolean = DEFAULT_ONLINE): string => {
4
+ return online ? "im.live.360.cn" : "im.qihoo.net";
5
+ };
6
+
7
+ export const getTuituiWebsocketHost = (online: boolean = DEFAULT_ONLINE): string => {
8
+ return `wss://${getTuituiHost(online)}:8282/robot`;
9
+ };
10
+
11
+ export const getTuituiApiHost = (online: boolean = DEFAULT_ONLINE): string => {
12
+ return `https://${getTuituiHost(online)}:8282/robot`;
13
+ };
14
+
15
+ export const TUITUI_SSRF_POLICY = { allowedHostnames: ['im.live.360.cn', 'im.qihoo.net'] } as const;
package/src/outbound.ts CHANGED
@@ -2,6 +2,7 @@ import { readFileSync, existsSync, statSync } from 'node:fs';
2
2
  import { basename } from 'node:path';
3
3
  import { fetchWithSsrFGuard } from 'openclaw/plugin-sdk/tlon';
4
4
  import { CHANNEL_ID } from "./const";
5
+ import { TUITUI_SSRF_POLICY, getTuituiApiHost } from "./env"
5
6
 
6
7
  import type {
7
8
  TuiTuiMessageData,
@@ -17,8 +18,6 @@ import type {
17
18
  TuiTuiTeamsTarget
18
19
  } from './types';
19
20
 
20
- /* 一些常量配置 */
21
- export const TUITUI_SSRF_POLICY = { allowedHostnames: ['im.live.360.cn'] } as const;
22
21
 
23
22
  // ChatType定义与SessionKey定义一致,不可随意修改
24
23
  // https://docs.openclaw.ai/channels/channel-routing#session-key-shapes-examples
@@ -33,8 +32,8 @@ export function guessChatType(chatId: string): ChatType {
33
32
  return CHAT_TYPE_DIRECT;
34
33
  }
35
34
 
36
- export function addParams2Url(urlStr: string, params: any) {
37
- const url = new URL(urlStr);
35
+ export function addTuituiParams2Url(urlStr: string, params: any) {
36
+ const url = new URL(getTuituiApiHost() + urlStr);
38
37
  for (let k in params) url.searchParams.set(k, params[k]);
39
38
  return url.toString();
40
39
  }
@@ -82,7 +81,7 @@ function _fetchJson(url: string, json: any, auditCtx: string): Promise<any> {
82
81
  export async function postTuituiMsg(account: any, json: any, auditCtx: string): Promise<any> {
83
82
  const { appId: appid, appSecret: secret } = account;
84
83
  const { response, release } = await _fetchJson(
85
- addParams2Url('https://im.live.360.cn:8282/robot/message/custom/send', { appid, secret }),
84
+ addTuituiParams2Url('/message/custom/send', { appid, secret }),
86
85
  json,
87
86
  auditCtx,
88
87
  );
@@ -132,7 +131,7 @@ interface tuituiUploadResult {
132
131
  * @param type - Media type: 'image' or "file" (auto-detected if not specified)
133
132
  * @returns The media_id from TuiTui
134
133
  */
135
- export async function uploadFileToTuiTui(fileSrc: string, account: any, type: 'image' | 'file'): Promise<tuituiUploadResult | undefined> {
134
+ export async function uploadFileToTuiTui(fileSrc: string, account: any, type: 'image' | 'file'): Promise<tuituiUploadResult> {
136
135
  checkAccount(account, 'uploadFileToTuiTui');
137
136
 
138
137
  const { appId: appid, appSecret: secret } = account;
@@ -219,23 +218,23 @@ export async function uploadFileToTuiTui(fileSrc: string, account: any, type: 'i
219
218
  body.append('media', new Blob([fileBuffer], { type: contentType }), filename);
220
219
 
221
220
  const { response, release } = await _fetch({
222
- url: addParams2Url('https://im.live.360.cn:8282/robot/media/upload', { appid, secret, type }),
221
+ url: addTuituiParams2Url('/media/upload', { appid, secret, type }),
223
222
  init: { method: "POST", body },
224
223
  auditCtx: "tuitui.media.upload",
225
224
  });
226
225
  try {
227
226
  if (!response.ok) {
228
- throw new Error(`[${CHANNEL_ID}] Failed to upload media to TuiTui: ${response.status}`);
227
+ throw new Error(`[${CHANNEL_ID}] Failed to upload file to TuiTui: HTTP ${response.status}`);
229
228
  }
230
229
 
231
230
  const result: TuiTuiMediaUploadResponse = await response.json();
232
231
  if (result.errcode !== 0 || !result.media_id) {
233
- throw new Error(`[${CHANNEL_ID}] media upload failed: ${result.errmsg || "Unknown error"}`);
232
+ throw new Error(`[${CHANNEL_ID}] file upload failed: ${result.errmsg || "Unknown error"}`);
234
233
  }
235
234
 
236
235
  return {fid: result.media_id, filename};
237
236
  } catch(err) {
238
- console.error(`[${CHANNEL_ID}] uploadFileToTuiTui error:`, err, `filename: ${filename}, fileSrc: ${fileSrc}`);
237
+ throw err;
239
238
  } finally {
240
239
  await release();
241
240
  }
@@ -264,7 +263,7 @@ export async function tuituiEmojiReaction(
264
263
  const toTarget = (payload.togroups || payload.tousers || payload.toteams)[0];
265
264
  const _logTxt =`[${CHANNEL_ID}] emoji_reaction "${emoji}"`;
266
265
  console.log(`${_logTxt} request`, toTarget);
267
- const sendUrl = addParams2Url('https://im.live.360.cn:8282/robot/message/custom/modify', { appid, secret });
266
+ const sendUrl = addTuituiParams2Url('/message/custom/modify', { appid, secret });
268
267
  const { response, release } = await _fetchJson(sendUrl, payload, 'tuitui.emoji_reaction');
269
268
 
270
269
  try {
@@ -428,11 +427,6 @@ export async function sendMediaMsg(
428
427
  const mediaType = isImage ? 'image' : 'file';
429
428
 
430
429
  const uploadResult = await uploadFileToTuiTui(mediaUrl, account, mediaType);
431
- if (!uploadResult || !uploadResult?.fid) {
432
- console.error(`[${CHANNEL_ID}] uploadFileToTuiTui failed ${auditCtx}, {mediaUrl: ${mediaUrl}, mediaType: ${mediaType}}`);
433
- return;
434
- }
435
-
436
430
  const {fid, filename} = uploadResult;
437
431
 
438
432
  const targets = getTargets(chatId, chatType);
@@ -536,9 +530,9 @@ export async function getChatRecord(
536
530
 
537
531
  let baseurl = "";
538
532
  if (chatType == CHAT_TYPE_DIRECT) {
539
- baseurl = "https://im.live.360.cn:8282/robot/message/single/sync";
533
+ baseurl = "/message/single/sync";
540
534
  } else if (chatType == CHAT_TYPE_GROUP){
541
- baseurl = "https://im.live.360.cn:8282/robot/message/group/sync";
535
+ baseurl = "/message/group/sync";
542
536
  } else {
543
537
  console.log(`[${CHANNEL_ID}] getChatRecord: chatType "${chatType}" is not supported`);
544
538
  return undefined;
@@ -561,7 +555,7 @@ export async function getChatRecord(
561
555
 
562
556
  const { appId: appid, appSecret: secret } = account;
563
557
 
564
- const url = addParams2Url(baseurl, { appid, secret });
558
+ const url = addTuituiParams2Url(baseurl, { appid, secret });
565
559
 
566
560
  console.log(`[${CHANNEL_ID}] getChatRecord request `, body);
567
561
 
package/src/websocket.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import WebSocket from 'ws';
2
2
  import { CHANNEL_ID } from "./const";
3
- import { handleInboundMessage } from './inbound';
3
+ import { getTuituiWebsocketHost } from "./env"
4
4
 
5
5
  const wsReadyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'] as const;
6
6
  let wsNum = 0;
7
7
 
8
- export default function createWebSocket({ account, log, abortSignal, onConnected, onDisconnected, apiRuntime }: any): () => void {
8
+ export default function createWebSocket({ account, log, abortSignal, onConnected, onDisconnected, onInbounMessage }: any): () => void {
9
9
  const { accountId, appId, appSecret } = account;
10
10
  let ws: any = null;
11
11
  const _closeWS = () => ws && ws.readyState !== WebSocket.CLOSED && ws.close();
@@ -20,7 +20,7 @@ export default function createWebSocket({ account, log, abortSignal, onConnected
20
20
  const _clearTimeoutTimer = () => _timeoutId && clearTimeout(_timeoutId);
21
21
 
22
22
  const wsEvtIds = new Set<string>();
23
- const wsUrl = `wss://im.live.360.cn:8282/robot/callback/ws?auth=${appId}.${appSecret}`;
23
+ const wsUrl = getTuituiWebsocketHost() + `/callback/ws?auth=${appId}.${appSecret}`;
24
24
  const _startWS = () => {
25
25
  _closeWS(); // 启动新链接前先关闭旧链接,防止产生空指针链接
26
26
 
@@ -81,11 +81,7 @@ export default function createWebSocket({ account, log, abortSignal, onConnected
81
81
  if (!json?.header || !wsEvent || !json?.body?.data) {
82
82
  return log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, WebSocket[${wsId}] invalid message`);
83
83
  }
84
- if (!apiRuntime) {
85
- return log?.error?.(`[${CHANNEL_ID}] AccountId: ${accountId}, WebSocket[${wsId}] TuiTuiRuntime error`);
86
- }
87
-
88
- handleInboundMessage({ json, account, apiRuntime, log });
84
+ onInbounMessage(json);
89
85
  });
90
86
 
91
87
  const onErrOrClose = (errStr: string) => {