opencode-feishu 0.3.6 → 0.4.0
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/LICENSE +21 -0
- package/dist/index.js +371 -81
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs, { existsSync, readFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
+
import https, { request } from 'https';
|
|
5
|
+
import { text } from 'stream/consumers';
|
|
6
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
7
|
import crypto2 from 'crypto';
|
|
5
8
|
import url from 'url';
|
|
6
9
|
import http from 'http';
|
|
7
|
-
import https from 'https';
|
|
8
10
|
import http2 from 'http2';
|
|
9
11
|
import util2 from 'util';
|
|
10
12
|
import zlib from 'zlib';
|
|
@@ -12,7 +14,6 @@ import stream3, { Readable } from 'stream';
|
|
|
12
14
|
import { EventEmitter } from 'events';
|
|
13
15
|
import qs from 'querystring';
|
|
14
16
|
import WebSocket from 'ws';
|
|
15
|
-
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
16
17
|
|
|
17
18
|
var __create = Object.create;
|
|
18
19
|
var __defProp = Object.defineProperty;
|
|
@@ -18949,9 +18950,9 @@ var trackStream = (stream4, chunkSize, onProgress, onFinish) => {
|
|
|
18949
18950
|
// node_modules/axios/lib/adapters/fetch.js
|
|
18950
18951
|
var DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|
18951
18952
|
var { isFunction: isFunction2 } = utils_default;
|
|
18952
|
-
var globalFetchAPI = (({ Request, Response }) => ({
|
|
18953
|
+
var globalFetchAPI = (({ Request, Response: Response2 }) => ({
|
|
18953
18954
|
Request,
|
|
18954
|
-
Response
|
|
18955
|
+
Response: Response2
|
|
18955
18956
|
}))(utils_default.global);
|
|
18956
18957
|
var {
|
|
18957
18958
|
ReadableStream: ReadableStream2,
|
|
@@ -18968,10 +18969,10 @@ var factory = (env) => {
|
|
|
18968
18969
|
env = utils_default.merge.call({
|
|
18969
18970
|
skipUndefined: true
|
|
18970
18971
|
}, globalFetchAPI, env);
|
|
18971
|
-
const { fetch: envFetch, Request, Response } = env;
|
|
18972
|
+
const { fetch: envFetch, Request, Response: Response2 } = env;
|
|
18972
18973
|
const isFetchSupported = envFetch ? isFunction2(envFetch) : typeof fetch === "function";
|
|
18973
18974
|
const isRequestSupported = isFunction2(Request);
|
|
18974
|
-
const isResponseSupported = isFunction2(
|
|
18975
|
+
const isResponseSupported = isFunction2(Response2);
|
|
18975
18976
|
if (!isFetchSupported) {
|
|
18976
18977
|
return false;
|
|
18977
18978
|
}
|
|
@@ -18989,7 +18990,7 @@ var factory = (env) => {
|
|
|
18989
18990
|
}).headers.has("Content-Type");
|
|
18990
18991
|
return duplexAccessed && !hasContentType;
|
|
18991
18992
|
});
|
|
18992
|
-
const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils_default.isReadableStream(new
|
|
18993
|
+
const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils_default.isReadableStream(new Response2("").body));
|
|
18993
18994
|
const resolvers = {
|
|
18994
18995
|
stream: supportsResponseStream && ((res) => res.body)
|
|
18995
18996
|
};
|
|
@@ -19100,7 +19101,7 @@ var factory = (env) => {
|
|
|
19100
19101
|
responseContentLength,
|
|
19101
19102
|
progressEventReducer(asyncDecorator(onDownloadProgress), true)
|
|
19102
19103
|
) || [];
|
|
19103
|
-
response = new
|
|
19104
|
+
response = new Response2(
|
|
19104
19105
|
trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => {
|
|
19105
19106
|
flush && flush();
|
|
19106
19107
|
unsubscribe && unsubscribe();
|
|
@@ -19138,10 +19139,10 @@ var factory = (env) => {
|
|
|
19138
19139
|
var seedCache = /* @__PURE__ */ new Map();
|
|
19139
19140
|
var getFetch = (config) => {
|
|
19140
19141
|
let env = config && config.env || {};
|
|
19141
|
-
const { fetch: fetch2, Request, Response } = env;
|
|
19142
|
+
const { fetch: fetch2, Request, Response: Response2 } = env;
|
|
19142
19143
|
const seeds = [
|
|
19143
19144
|
Request,
|
|
19144
|
-
|
|
19145
|
+
Response2,
|
|
19145
19146
|
fetch2
|
|
19146
19147
|
];
|
|
19147
19148
|
let len = seeds.length, i = len, seed, target, map = seedCache;
|
|
@@ -98760,6 +98761,256 @@ function isDuplicate(messageId) {
|
|
|
98760
98761
|
return false;
|
|
98761
98762
|
}
|
|
98762
98763
|
|
|
98764
|
+
// src/feishu/resource.ts
|
|
98765
|
+
var MAX_RESOURCE_SIZE = 10 * 1024 * 1024;
|
|
98766
|
+
async function downloadMessageResource(client, messageId, fileKey, type, log) {
|
|
98767
|
+
try {
|
|
98768
|
+
const res = await client.im.messageResource.get({
|
|
98769
|
+
path: { message_id: messageId, file_key: fileKey },
|
|
98770
|
+
params: { type }
|
|
98771
|
+
});
|
|
98772
|
+
if (!res) {
|
|
98773
|
+
log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u8FD4\u56DE\u7A7A\u6570\u636E", { messageId, fileKey, type });
|
|
98774
|
+
return null;
|
|
98775
|
+
}
|
|
98776
|
+
const stream4 = res.getReadableStream();
|
|
98777
|
+
const chunks = [];
|
|
98778
|
+
let totalSize = 0;
|
|
98779
|
+
for await (const chunk of stream4) {
|
|
98780
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
98781
|
+
totalSize += buf.length;
|
|
98782
|
+
if (totalSize > MAX_RESOURCE_SIZE) {
|
|
98783
|
+
log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize });
|
|
98784
|
+
stream4.destroy();
|
|
98785
|
+
return null;
|
|
98786
|
+
}
|
|
98787
|
+
chunks.push(buf);
|
|
98788
|
+
}
|
|
98789
|
+
const buffer = Buffer.concat(chunks);
|
|
98790
|
+
const headers = res.headers;
|
|
98791
|
+
const contentType = headers?.["content-type"] ?? guessMimeByType(type);
|
|
98792
|
+
const base64 = buffer.toString("base64");
|
|
98793
|
+
const dataUrl = `data:${contentType};base64,${base64}`;
|
|
98794
|
+
return { dataUrl, mime: contentType };
|
|
98795
|
+
} catch (err) {
|
|
98796
|
+
log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u5931\u8D25", {
|
|
98797
|
+
messageId,
|
|
98798
|
+
fileKey,
|
|
98799
|
+
type,
|
|
98800
|
+
error: err instanceof Error ? err.message : String(err)
|
|
98801
|
+
});
|
|
98802
|
+
return null;
|
|
98803
|
+
}
|
|
98804
|
+
}
|
|
98805
|
+
function guessMimeByType(type) {
|
|
98806
|
+
return type === "image" ? "image/png" : "application/octet-stream";
|
|
98807
|
+
}
|
|
98808
|
+
function guessMimeByFilename(filename) {
|
|
98809
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
98810
|
+
const map = {
|
|
98811
|
+
png: "image/png",
|
|
98812
|
+
jpg: "image/jpeg",
|
|
98813
|
+
jpeg: "image/jpeg",
|
|
98814
|
+
gif: "image/gif",
|
|
98815
|
+
webp: "image/webp",
|
|
98816
|
+
svg: "image/svg+xml",
|
|
98817
|
+
bmp: "image/bmp",
|
|
98818
|
+
pdf: "application/pdf",
|
|
98819
|
+
doc: "application/msword",
|
|
98820
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
98821
|
+
xls: "application/vnd.ms-excel",
|
|
98822
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
98823
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
98824
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
98825
|
+
txt: "text/plain",
|
|
98826
|
+
csv: "text/csv",
|
|
98827
|
+
json: "application/json",
|
|
98828
|
+
xml: "application/xml",
|
|
98829
|
+
zip: "application/zip",
|
|
98830
|
+
mp3: "audio/mpeg",
|
|
98831
|
+
wav: "audio/wav",
|
|
98832
|
+
ogg: "audio/ogg",
|
|
98833
|
+
opus: "audio/opus",
|
|
98834
|
+
mp4: "video/mp4",
|
|
98835
|
+
webm: "video/webm",
|
|
98836
|
+
mov: "video/quicktime"
|
|
98837
|
+
};
|
|
98838
|
+
return map[ext] ?? "application/octet-stream";
|
|
98839
|
+
}
|
|
98840
|
+
|
|
98841
|
+
// src/feishu/content-extractor.ts
|
|
98842
|
+
async function extractParts(feishuClient, messageId, messageType, rawContent, log) {
|
|
98843
|
+
try {
|
|
98844
|
+
switch (messageType) {
|
|
98845
|
+
case "text":
|
|
98846
|
+
return extractText(rawContent);
|
|
98847
|
+
case "image":
|
|
98848
|
+
return await extractImage(feishuClient, messageId, rawContent, log);
|
|
98849
|
+
case "post":
|
|
98850
|
+
return extractPost(rawContent);
|
|
98851
|
+
case "file":
|
|
98852
|
+
return await extractFile(feishuClient, messageId, rawContent, log);
|
|
98853
|
+
case "audio":
|
|
98854
|
+
return await extractAudio(feishuClient, messageId, rawContent, log);
|
|
98855
|
+
case "media":
|
|
98856
|
+
return extractMediaFallback();
|
|
98857
|
+
case "sticker":
|
|
98858
|
+
return [{ type: "text", text: "[\u8868\u60C5\u5305]" }];
|
|
98859
|
+
case "interactive":
|
|
98860
|
+
return extractInteractive(rawContent);
|
|
98861
|
+
case "share_chat":
|
|
98862
|
+
return extractShareChat(rawContent);
|
|
98863
|
+
case "share_user":
|
|
98864
|
+
return [{ type: "text", text: "[\u5206\u4EAB\u4E86\u4E00\u4E2A\u7528\u6237\u540D\u7247]" }];
|
|
98865
|
+
case "merge_forward":
|
|
98866
|
+
return [{ type: "text", text: "[\u5408\u5E76\u8F6C\u53D1\u6D88\u606F]" }];
|
|
98867
|
+
default:
|
|
98868
|
+
return [{ type: "text", text: `[\u4E0D\u652F\u6301\u7684\u6D88\u606F\u7C7B\u578B: ${messageType}]` }];
|
|
98869
|
+
}
|
|
98870
|
+
} catch (err) {
|
|
98871
|
+
log("warn", "\u6D88\u606F\u5185\u5BB9\u63D0\u53D6\u5931\u8D25", {
|
|
98872
|
+
messageId,
|
|
98873
|
+
messageType,
|
|
98874
|
+
error: err instanceof Error ? err.message : String(err)
|
|
98875
|
+
});
|
|
98876
|
+
return [{ type: "text", text: `[\u6D88\u606F\u5185\u5BB9\u63D0\u53D6\u5931\u8D25: ${messageType}]` }];
|
|
98877
|
+
}
|
|
98878
|
+
}
|
|
98879
|
+
function describeMessageType(messageType, rawContent) {
|
|
98880
|
+
switch (messageType) {
|
|
98881
|
+
case "text": {
|
|
98882
|
+
try {
|
|
98883
|
+
const parsed = JSON.parse(rawContent);
|
|
98884
|
+
return (parsed.text ?? "").trim();
|
|
98885
|
+
} catch {
|
|
98886
|
+
return "";
|
|
98887
|
+
}
|
|
98888
|
+
}
|
|
98889
|
+
case "image":
|
|
98890
|
+
return "[\u56FE\u7247]";
|
|
98891
|
+
case "post":
|
|
98892
|
+
return extractPostText(rawContent);
|
|
98893
|
+
case "file": {
|
|
98894
|
+
try {
|
|
98895
|
+
const parsed = JSON.parse(rawContent);
|
|
98896
|
+
return `[\u6587\u4EF6: ${parsed.file_name ?? "\u672A\u77E5\u6587\u4EF6"}]`;
|
|
98897
|
+
} catch {
|
|
98898
|
+
return "[\u6587\u4EF6]";
|
|
98899
|
+
}
|
|
98900
|
+
}
|
|
98901
|
+
case "audio":
|
|
98902
|
+
return "[\u8BED\u97F3\u6D88\u606F]";
|
|
98903
|
+
case "media":
|
|
98904
|
+
return "[\u89C6\u9891\u6D88\u606F]";
|
|
98905
|
+
case "sticker":
|
|
98906
|
+
return "[\u8868\u60C5\u5305]";
|
|
98907
|
+
case "interactive":
|
|
98908
|
+
return "[\u5361\u7247\u6D88\u606F]";
|
|
98909
|
+
case "share_chat":
|
|
98910
|
+
return "[\u7FA4\u5206\u4EAB]";
|
|
98911
|
+
case "share_user":
|
|
98912
|
+
return "[\u7528\u6237\u540D\u7247]";
|
|
98913
|
+
case "merge_forward":
|
|
98914
|
+
return "[\u5408\u5E76\u8F6C\u53D1]";
|
|
98915
|
+
default:
|
|
98916
|
+
return `[${messageType}]`;
|
|
98917
|
+
}
|
|
98918
|
+
}
|
|
98919
|
+
function extractText(rawContent) {
|
|
98920
|
+
const parsed = JSON.parse(rawContent);
|
|
98921
|
+
const text2 = (parsed.text ?? "").trim();
|
|
98922
|
+
if (!text2) return [];
|
|
98923
|
+
return [{ type: "text", text: text2 }];
|
|
98924
|
+
}
|
|
98925
|
+
async function extractImage(client, messageId, rawContent, log) {
|
|
98926
|
+
const parsed = JSON.parse(rawContent);
|
|
98927
|
+
const imageKey = parsed.image_key;
|
|
98928
|
+
if (!imageKey) return [{ type: "text", text: "[\u56FE\u7247: \u65E0\u6CD5\u83B7\u53D6]" }];
|
|
98929
|
+
const resource = await downloadMessageResource(client, messageId, imageKey, "image", log);
|
|
98930
|
+
if (!resource) return [{ type: "text", text: "[\u56FE\u7247: \u4E0B\u8F7D\u5931\u8D25]" }];
|
|
98931
|
+
return [{ type: "file", mime: resource.mime, url: resource.dataUrl }];
|
|
98932
|
+
}
|
|
98933
|
+
function extractPost(rawContent) {
|
|
98934
|
+
const text2 = extractPostText(rawContent);
|
|
98935
|
+
if (!text2) return [];
|
|
98936
|
+
return [{ type: "text", text: text2 }];
|
|
98937
|
+
}
|
|
98938
|
+
function extractPostText(rawContent) {
|
|
98939
|
+
try {
|
|
98940
|
+
const parsed = JSON.parse(rawContent);
|
|
98941
|
+
const lines = [];
|
|
98942
|
+
if (parsed.title) lines.push(parsed.title);
|
|
98943
|
+
if (Array.isArray(parsed.content)) {
|
|
98944
|
+
for (const paragraph of parsed.content) {
|
|
98945
|
+
if (!Array.isArray(paragraph)) continue;
|
|
98946
|
+
const segments = [];
|
|
98947
|
+
for (const element of paragraph) {
|
|
98948
|
+
if (element.tag === "text" && element.text) {
|
|
98949
|
+
segments.push(element.text);
|
|
98950
|
+
} else if (element.tag === "a" && element.text) {
|
|
98951
|
+
segments.push(element.href ? `${element.text}(${element.href})` : element.text);
|
|
98952
|
+
} else if (element.tag === "at" && element.text) {
|
|
98953
|
+
segments.push(element.text);
|
|
98954
|
+
} else if (element.tag === "img") {
|
|
98955
|
+
segments.push("[\u56FE\u7247]");
|
|
98956
|
+
}
|
|
98957
|
+
}
|
|
98958
|
+
if (segments.length) lines.push(segments.join(""));
|
|
98959
|
+
}
|
|
98960
|
+
}
|
|
98961
|
+
return lines.join("\n").trim();
|
|
98962
|
+
} catch {
|
|
98963
|
+
return "";
|
|
98964
|
+
}
|
|
98965
|
+
}
|
|
98966
|
+
async function extractFile(client, messageId, rawContent, log) {
|
|
98967
|
+
const parsed = JSON.parse(rawContent);
|
|
98968
|
+
const fileKey = parsed.file_key;
|
|
98969
|
+
const fileName = parsed.file_name ?? "\u672A\u77E5\u6587\u4EF6";
|
|
98970
|
+
if (!fileKey) return [{ type: "text", text: `[\u6587\u4EF6: ${fileName}]` }];
|
|
98971
|
+
const mime = guessMimeByFilename(fileName);
|
|
98972
|
+
const resource = await downloadMessageResource(client, messageId, fileKey, "file", log);
|
|
98973
|
+
if (!resource) return [{ type: "text", text: `[\u6587\u4EF6\u4E0B\u8F7D\u5931\u8D25: ${fileName}]` }];
|
|
98974
|
+
return [{ type: "file", mime: resource.mime || mime, url: resource.dataUrl, filename: fileName }];
|
|
98975
|
+
}
|
|
98976
|
+
async function extractAudio(client, messageId, rawContent, log) {
|
|
98977
|
+
const parsed = JSON.parse(rawContent);
|
|
98978
|
+
const fileKey = parsed.file_key;
|
|
98979
|
+
if (!fileKey) return [{ type: "text", text: "[\u8BED\u97F3: \u65E0\u6CD5\u83B7\u53D6]" }];
|
|
98980
|
+
const resource = await downloadMessageResource(client, messageId, fileKey, "file", log);
|
|
98981
|
+
if (!resource) return [{ type: "text", text: "[\u8BED\u97F3: \u4E0B\u8F7D\u5931\u8D25]" }];
|
|
98982
|
+
return [{ type: "file", mime: resource.mime || "audio/opus", url: resource.dataUrl }];
|
|
98983
|
+
}
|
|
98984
|
+
function extractMediaFallback() {
|
|
98985
|
+
return [{ type: "text", text: "[\u89C6\u9891\u6D88\u606F]" }];
|
|
98986
|
+
}
|
|
98987
|
+
function extractInteractive(rawContent) {
|
|
98988
|
+
try {
|
|
98989
|
+
const parsed = JSON.parse(rawContent);
|
|
98990
|
+
const texts = [];
|
|
98991
|
+
if (parsed.header?.title?.content) texts.push(parsed.header.title.content);
|
|
98992
|
+
if (Array.isArray(parsed.elements)) {
|
|
98993
|
+
for (const el of parsed.elements) {
|
|
98994
|
+
if (el.tag === "div" && el.text?.content) texts.push(el.text.content);
|
|
98995
|
+
else if (el.tag === "markdown" && el.content) texts.push(el.content);
|
|
98996
|
+
}
|
|
98997
|
+
}
|
|
98998
|
+
const text2 = texts.join("\n").trim();
|
|
98999
|
+
return text2 ? [{ type: "text", text: `[\u5361\u7247\u6D88\u606F]
|
|
99000
|
+
${text2}` }] : [{ type: "text", text: "[\u5361\u7247\u6D88\u606F]" }];
|
|
99001
|
+
} catch {
|
|
99002
|
+
return [{ type: "text", text: "[\u5361\u7247\u6D88\u606F]" }];
|
|
99003
|
+
}
|
|
99004
|
+
}
|
|
99005
|
+
function extractShareChat(rawContent) {
|
|
99006
|
+
try {
|
|
99007
|
+
const parsed = JSON.parse(rawContent);
|
|
99008
|
+
return [{ type: "text", text: `[\u5206\u4EAB\u4E86\u4E00\u4E2A\u7FA4\u804A${parsed.chat_id ? `: ${parsed.chat_id}` : ""}]` }];
|
|
99009
|
+
} catch {
|
|
99010
|
+
return [{ type: "text", text: "[\u7FA4\u5206\u4EAB]" }];
|
|
99011
|
+
}
|
|
99012
|
+
}
|
|
99013
|
+
|
|
98763
99014
|
// src/feishu/group-filter.ts
|
|
98764
99015
|
function isBotMentioned(mentions, botOpenId) {
|
|
98765
99016
|
return mentions.some((m) => m.id?.open_id === botOpenId);
|
|
@@ -98795,22 +99046,19 @@ function startFeishuGateway(options) {
|
|
|
98795
99046
|
const messageId = message.message_id;
|
|
98796
99047
|
if (isDuplicate(messageId)) return;
|
|
98797
99048
|
const messageType = message.message_type ?? "text";
|
|
99049
|
+
const rawContent = message.content ?? "";
|
|
98798
99050
|
log("info", "\u98DE\u4E66\u6D88\u606F\u5143\u4FE1\u606F", {
|
|
98799
99051
|
chatId,
|
|
98800
99052
|
messageId: messageId ?? "",
|
|
98801
99053
|
messageType,
|
|
98802
|
-
hasContent: !!
|
|
99054
|
+
hasContent: !!rawContent
|
|
98803
99055
|
});
|
|
98804
|
-
if (
|
|
98805
|
-
let
|
|
98806
|
-
|
|
98807
|
-
|
|
98808
|
-
text = (parsed.text ?? "").trim();
|
|
98809
|
-
} catch {
|
|
98810
|
-
return;
|
|
99056
|
+
if (!rawContent) return;
|
|
99057
|
+
let text2 = describeMessageType(messageType, rawContent);
|
|
99058
|
+
if (messageType === "text") {
|
|
99059
|
+
text2 = text2.replace(/@_user_\d+\s*/g, "").trim();
|
|
98811
99060
|
}
|
|
98812
|
-
|
|
98813
|
-
if (!text) return;
|
|
99061
|
+
if (!text2) return;
|
|
98814
99062
|
const chatType = message.chat_type === "group" ? "group" : "p2p";
|
|
98815
99063
|
let shouldReply = true;
|
|
98816
99064
|
if (chatType === "group") {
|
|
@@ -98828,7 +99076,8 @@ function startFeishuGateway(options) {
|
|
|
98828
99076
|
chatId: String(chatId),
|
|
98829
99077
|
messageId: messageId ?? "",
|
|
98830
99078
|
messageType,
|
|
98831
|
-
content:
|
|
99079
|
+
content: text2,
|
|
99080
|
+
rawContent,
|
|
98832
99081
|
chatType,
|
|
98833
99082
|
senderId,
|
|
98834
99083
|
rootId,
|
|
@@ -98840,7 +99089,7 @@ function startFeishuGateway(options) {
|
|
|
98840
99089
|
messageId: messageId ?? "",
|
|
98841
99090
|
chatType,
|
|
98842
99091
|
shouldReply,
|
|
98843
|
-
textPreview:
|
|
99092
|
+
textPreview: text2.slice(0, 80)
|
|
98844
99093
|
});
|
|
98845
99094
|
await onMessage(ctx);
|
|
98846
99095
|
} catch (err) {
|
|
@@ -98894,7 +99143,7 @@ function startFeishuGateway(options) {
|
|
|
98894
99143
|
}
|
|
98895
99144
|
|
|
98896
99145
|
// src/feishu/sender.ts
|
|
98897
|
-
async function sendTextMessage(client, chatId,
|
|
99146
|
+
async function sendTextMessage(client, chatId, text2) {
|
|
98898
99147
|
if (!chatId?.trim()) {
|
|
98899
99148
|
return { ok: false, error: "No chat_id provided" };
|
|
98900
99149
|
}
|
|
@@ -98904,7 +99153,7 @@ async function sendTextMessage(client, chatId, text) {
|
|
|
98904
99153
|
data: {
|
|
98905
99154
|
receive_id: chatId.trim(),
|
|
98906
99155
|
msg_type: "text",
|
|
98907
|
-
content: JSON.stringify({ text })
|
|
99156
|
+
content: JSON.stringify({ text: text2 })
|
|
98908
99157
|
}
|
|
98909
99158
|
});
|
|
98910
99159
|
return { ok: true, messageId: res?.data?.message_id ?? "" };
|
|
@@ -98912,13 +99161,13 @@ async function sendTextMessage(client, chatId, text) {
|
|
|
98912
99161
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
98913
99162
|
}
|
|
98914
99163
|
}
|
|
98915
|
-
async function updateMessage(client, messageId,
|
|
99164
|
+
async function updateMessage(client, messageId, text2) {
|
|
98916
99165
|
try {
|
|
98917
99166
|
await client.im.message.update({
|
|
98918
99167
|
path: { message_id: messageId },
|
|
98919
99168
|
data: {
|
|
98920
99169
|
msg_type: "text",
|
|
98921
|
-
content: JSON.stringify({ text })
|
|
99170
|
+
content: JSON.stringify({ text: text2 })
|
|
98922
99171
|
}
|
|
98923
99172
|
});
|
|
98924
99173
|
return { ok: true, messageId };
|
|
@@ -98944,14 +99193,19 @@ async function handleEvent(event) {
|
|
|
98944
99193
|
if (!sessionId) break;
|
|
98945
99194
|
const payload = pendingBySession.get(sessionId);
|
|
98946
99195
|
if (!payload) break;
|
|
98947
|
-
const
|
|
98948
|
-
if (
|
|
98949
|
-
payload.textBuffer +=
|
|
98950
|
-
|
|
98951
|
-
|
|
98952
|
-
|
|
99196
|
+
const delta = event.properties.delta;
|
|
99197
|
+
if (delta) {
|
|
99198
|
+
payload.textBuffer += delta;
|
|
99199
|
+
} else {
|
|
99200
|
+
const fullText = extractPartText(part);
|
|
99201
|
+
if (fullText) {
|
|
99202
|
+
payload.textBuffer = fullText;
|
|
98953
99203
|
}
|
|
98954
99204
|
}
|
|
99205
|
+
if (payload.textBuffer) {
|
|
99206
|
+
const res = await updateMessage(payload.feishuClient, payload.placeholderId, payload.textBuffer.trim());
|
|
99207
|
+
if (!res.ok) ;
|
|
99208
|
+
}
|
|
98955
99209
|
break;
|
|
98956
99210
|
}
|
|
98957
99211
|
case "session.error": {
|
|
@@ -98961,9 +99215,8 @@ async function handleEvent(event) {
|
|
|
98961
99215
|
const payload = pendingBySession.get(sessionId);
|
|
98962
99216
|
if (!payload) break;
|
|
98963
99217
|
const errMsg = props.error?.message ?? String(props.error);
|
|
98964
|
-
|
|
98965
|
-
|
|
98966
|
-
} catch {
|
|
99218
|
+
const updateRes = await updateMessage(payload.feishuClient, payload.placeholderId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
|
|
99219
|
+
if (!updateRes.ok) {
|
|
98967
99220
|
await sendTextMessage(payload.feishuClient, payload.chatId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
|
|
98968
99221
|
}
|
|
98969
99222
|
break;
|
|
@@ -99015,26 +99268,21 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
99015
99268
|
|
|
99016
99269
|
// src/handler/chat.ts
|
|
99017
99270
|
async function handleChat(ctx, deps) {
|
|
99018
|
-
const { content, chatId, chatType, senderId,
|
|
99019
|
-
if (!content.trim()) return;
|
|
99271
|
+
const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
|
|
99272
|
+
if (!content.trim() && messageType === "text") return;
|
|
99020
99273
|
const { config, client, feishuClient, log, directory } = deps;
|
|
99021
99274
|
const query = directory ? { directory } : void 0;
|
|
99022
99275
|
const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
|
|
99023
99276
|
const session = await getOrCreateSession(client, sessionKey, directory);
|
|
99024
|
-
const
|
|
99025
|
-
|
|
99026
|
-
if (chatType === "group" && senderId) {
|
|
99027
|
-
promptContent = timeStr ? `[${timeStr}] [${senderId}]: ${content}` : `[${senderId}]: ${content}`;
|
|
99028
|
-
} else if (timeStr) {
|
|
99029
|
-
promptContent = `[${timeStr}] ${content}`;
|
|
99030
|
-
}
|
|
99277
|
+
const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
|
|
99278
|
+
if (!parts.length) return;
|
|
99031
99279
|
if (!shouldReply) {
|
|
99032
99280
|
try {
|
|
99033
99281
|
await client.session.prompt({
|
|
99034
99282
|
path: { id: session.id },
|
|
99035
99283
|
query,
|
|
99036
99284
|
body: {
|
|
99037
|
-
parts
|
|
99285
|
+
parts,
|
|
99038
99286
|
noReply: true
|
|
99039
99287
|
}
|
|
99040
99288
|
});
|
|
@@ -99055,11 +99303,16 @@ async function handleChat(ctx, deps) {
|
|
|
99055
99303
|
if (done) return;
|
|
99056
99304
|
try {
|
|
99057
99305
|
const res = await sendTextMessage(feishuClient, chatId, "\u6B63\u5728\u601D\u8003\u2026");
|
|
99306
|
+
if (done) return;
|
|
99058
99307
|
if (res.ok && res.messageId) {
|
|
99059
99308
|
placeholderId = res.messageId;
|
|
99060
99309
|
registerPending(session.id, { chatId, placeholderId, feishuClient });
|
|
99061
99310
|
}
|
|
99062
|
-
} catch {
|
|
99311
|
+
} catch (err) {
|
|
99312
|
+
log("warn", "\u53D1\u9001\u5360\u4F4D\u6D88\u606F\u5931\u8D25", {
|
|
99313
|
+
chatId,
|
|
99314
|
+
error: err instanceof Error ? err.message : String(err)
|
|
99315
|
+
});
|
|
99063
99316
|
}
|
|
99064
99317
|
}, thinkingDelay) : null;
|
|
99065
99318
|
try {
|
|
@@ -99067,7 +99320,7 @@ async function handleChat(ctx, deps) {
|
|
|
99067
99320
|
path: { id: session.id },
|
|
99068
99321
|
query,
|
|
99069
99322
|
body: {
|
|
99070
|
-
parts
|
|
99323
|
+
parts
|
|
99071
99324
|
}
|
|
99072
99325
|
});
|
|
99073
99326
|
const start = Date.now();
|
|
@@ -99076,17 +99329,11 @@ async function handleChat(ctx, deps) {
|
|
|
99076
99329
|
while (Date.now() - start < timeout) {
|
|
99077
99330
|
await new Promise((r) => setTimeout(r, pollInterval));
|
|
99078
99331
|
const { data: messages } = await client.session.messages({ path: { id: session.id }, query });
|
|
99079
|
-
const
|
|
99080
|
-
if (
|
|
99081
|
-
lastText =
|
|
99332
|
+
const text2 = extractLastAssistantText(messages ?? []);
|
|
99333
|
+
if (text2 && text2 !== lastText) {
|
|
99334
|
+
lastText = text2;
|
|
99082
99335
|
sameCount = 0;
|
|
99083
|
-
|
|
99084
|
-
try {
|
|
99085
|
-
await updateMessage(feishuClient, placeholderId, text);
|
|
99086
|
-
} catch {
|
|
99087
|
-
}
|
|
99088
|
-
}
|
|
99089
|
-
} else if (text && text.length > 0) {
|
|
99336
|
+
} else if (text2 && text2.length > 0) {
|
|
99090
99337
|
sameCount++;
|
|
99091
99338
|
if (sameCount >= stablePolls) break;
|
|
99092
99339
|
}
|
|
@@ -99106,15 +99353,28 @@ async function handleChat(ctx, deps) {
|
|
|
99106
99353
|
unregisterPending(session.id);
|
|
99107
99354
|
}
|
|
99108
99355
|
}
|
|
99109
|
-
async function
|
|
99356
|
+
async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
|
|
99357
|
+
if (messageType === "text") {
|
|
99358
|
+
let promptText = textContent;
|
|
99359
|
+
if (chatType === "group" && senderId) {
|
|
99360
|
+
promptText = `[${senderId}]: ${textContent}`;
|
|
99361
|
+
}
|
|
99362
|
+
return [{ type: "text", text: promptText }];
|
|
99363
|
+
}
|
|
99364
|
+
const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log);
|
|
99365
|
+
if (chatType === "group" && senderId && parts.length > 0) {
|
|
99366
|
+
return [{ type: "text", text: `[${senderId}]:` }, ...parts];
|
|
99367
|
+
}
|
|
99368
|
+
return parts;
|
|
99369
|
+
}
|
|
99370
|
+
async function replyOrUpdate(feishuClient, chatId, placeholderId, text2) {
|
|
99110
99371
|
if (placeholderId) {
|
|
99111
|
-
|
|
99112
|
-
|
|
99113
|
-
|
|
99114
|
-
await sendTextMessage(feishuClient, chatId, text);
|
|
99372
|
+
const res = await updateMessage(feishuClient, placeholderId, text2);
|
|
99373
|
+
if (!res.ok) {
|
|
99374
|
+
await sendTextMessage(feishuClient, chatId, text2);
|
|
99115
99375
|
}
|
|
99116
99376
|
} else {
|
|
99117
|
-
await sendTextMessage(feishuClient, chatId,
|
|
99377
|
+
await sendTextMessage(feishuClient, chatId, text2);
|
|
99118
99378
|
}
|
|
99119
99379
|
}
|
|
99120
99380
|
function extractLastAssistantText(messages) {
|
|
@@ -99126,7 +99386,8 @@ function extractLastAssistantText(messages) {
|
|
|
99126
99386
|
// src/feishu/history.ts
|
|
99127
99387
|
var DEFAULT_PAGE_SIZE = 50;
|
|
99128
99388
|
async function ingestGroupHistory(feishuClient, opencodeClient, chatId, options) {
|
|
99129
|
-
const { maxMessages, log } = options;
|
|
99389
|
+
const { maxMessages, log, directory } = options;
|
|
99390
|
+
const query = directory ? { directory } : void 0;
|
|
99130
99391
|
log("info", "\u5F00\u59CB\u6444\u5165\u7FA4\u804A\u5386\u53F2\u4E0A\u4E0B\u6587", { chatId, maxMessages });
|
|
99131
99392
|
const messages = await fetchRecentMessages(feishuClient, chatId, maxMessages, log);
|
|
99132
99393
|
if (!messages.length) {
|
|
@@ -99134,10 +99395,11 @@ async function ingestGroupHistory(feishuClient, opencodeClient, chatId, options)
|
|
|
99134
99395
|
return;
|
|
99135
99396
|
}
|
|
99136
99397
|
const sessionKey = buildSessionKey("group", chatId);
|
|
99137
|
-
const session = await getOrCreateSession(opencodeClient, sessionKey);
|
|
99398
|
+
const session = await getOrCreateSession(opencodeClient, sessionKey, directory);
|
|
99138
99399
|
const contextText = formatHistoryAsContext(messages);
|
|
99139
99400
|
await opencodeClient.session.prompt({
|
|
99140
99401
|
path: { id: session.id },
|
|
99402
|
+
query,
|
|
99141
99403
|
body: {
|
|
99142
99404
|
parts: [{ type: "text", text: contextText }],
|
|
99143
99405
|
noReply: true
|
|
@@ -99163,19 +99425,15 @@ async function fetchRecentMessages(client, chatId, maxMessages, log) {
|
|
|
99163
99425
|
if (!items || items.length === 0) break;
|
|
99164
99426
|
for (const item of items) {
|
|
99165
99427
|
if (item.deleted) continue;
|
|
99166
|
-
if (
|
|
99167
|
-
|
|
99168
|
-
|
|
99169
|
-
|
|
99170
|
-
|
|
99171
|
-
} catch {
|
|
99172
|
-
continue;
|
|
99173
|
-
}
|
|
99174
|
-
if (!text) continue;
|
|
99428
|
+
if (!item.body?.content) continue;
|
|
99429
|
+
const msgType = item.msg_type ?? "text";
|
|
99430
|
+
const rawContent = item.body.content;
|
|
99431
|
+
const text2 = describeMessageType(msgType, rawContent);
|
|
99432
|
+
if (!text2) continue;
|
|
99175
99433
|
result.push({
|
|
99176
99434
|
senderType: item.sender?.sender_type ?? "unknown",
|
|
99177
99435
|
senderId: item.sender?.id ?? "",
|
|
99178
|
-
content:
|
|
99436
|
+
content: text2,
|
|
99179
99437
|
createTime: item.create_time ?? ""
|
|
99180
99438
|
});
|
|
99181
99439
|
if (result.length >= maxMessages) break;
|
|
@@ -99217,7 +99475,8 @@ var DEFAULT_CONFIG = {
|
|
|
99217
99475
|
maxHistoryMessages: 200,
|
|
99218
99476
|
pollInterval: 1e3,
|
|
99219
99477
|
stablePolls: 3,
|
|
99220
|
-
dedupTtl: 10 * 60 * 1e3
|
|
99478
|
+
dedupTtl: 10 * 60 * 1e3,
|
|
99479
|
+
directory: ""
|
|
99221
99480
|
};
|
|
99222
99481
|
var FeishuPlugin = async (ctx) => {
|
|
99223
99482
|
const { client } = ctx;
|
|
@@ -99250,6 +99509,11 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99250
99509
|
} catch (parseErr) {
|
|
99251
99510
|
throw new Error(`\u98DE\u4E66\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A${configPath} \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON (${parseErr})`);
|
|
99252
99511
|
}
|
|
99512
|
+
if (feishuRaw.directory !== void 0 && typeof feishuRaw.directory !== "string") {
|
|
99513
|
+
throw new Error(
|
|
99514
|
+
`\u98DE\u4E66\u914D\u7F6E\u9519\u8BEF\uFF1A${configPath} \u4E2D\u7684 'directory' \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`
|
|
99515
|
+
);
|
|
99516
|
+
}
|
|
99253
99517
|
if (!feishuRaw.appId || !feishuRaw.appSecret) {
|
|
99254
99518
|
throw new Error(
|
|
99255
99519
|
`\u98DE\u4E66\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF1A${configPath} \u4E2D\u5FC5\u987B\u5305\u542B appId \u548C appSecret`
|
|
@@ -99264,7 +99528,8 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99264
99528
|
maxHistoryMessages: feishuRaw.maxHistoryMessages ?? DEFAULT_CONFIG.maxHistoryMessages,
|
|
99265
99529
|
pollInterval: feishuRaw.pollInterval ?? DEFAULT_CONFIG.pollInterval,
|
|
99266
99530
|
stablePolls: feishuRaw.stablePolls ?? DEFAULT_CONFIG.stablePolls,
|
|
99267
|
-
dedupTtl: feishuRaw.dedupTtl ?? DEFAULT_CONFIG.dedupTtl
|
|
99531
|
+
dedupTtl: feishuRaw.dedupTtl ?? DEFAULT_CONFIG.dedupTtl,
|
|
99532
|
+
directory: feishuRaw.directory ?? ctx.directory ?? DEFAULT_CONFIG.directory
|
|
99268
99533
|
};
|
|
99269
99534
|
initDedup(resolvedConfig.dedupTtl);
|
|
99270
99535
|
const botOpenId = await fetchBotOpenId(resolvedConfig.appId, resolvedConfig.appSecret, log);
|
|
@@ -99278,14 +99543,15 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99278
99543
|
client,
|
|
99279
99544
|
feishuClient: gateway.client,
|
|
99280
99545
|
log,
|
|
99281
|
-
directory:
|
|
99546
|
+
directory: resolvedConfig.directory
|
|
99282
99547
|
});
|
|
99283
99548
|
},
|
|
99284
99549
|
onBotAdded: (chatId) => {
|
|
99285
99550
|
if (!gateway) return;
|
|
99286
99551
|
ingestGroupHistory(gateway.client, client, chatId, {
|
|
99287
99552
|
maxMessages: resolvedConfig.maxHistoryMessages,
|
|
99288
|
-
log
|
|
99553
|
+
log,
|
|
99554
|
+
directory: resolvedConfig.directory
|
|
99289
99555
|
}).catch((err) => {
|
|
99290
99556
|
log("error", "\u7FA4\u804A\u5386\u53F2\u6444\u5165\u5931\u8D25", {
|
|
99291
99557
|
chatId,
|
|
@@ -99330,8 +99596,32 @@ function resolveEnvPlaceholders(obj) {
|
|
|
99330
99596
|
}
|
|
99331
99597
|
return obj;
|
|
99332
99598
|
}
|
|
99599
|
+
function proxyFetch(url2, init) {
|
|
99600
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.ALL_PROXY || "";
|
|
99601
|
+
if (!proxyUrl) return fetch(url2, init);
|
|
99602
|
+
const parsed = new URL(url2);
|
|
99603
|
+
return new Promise((resolve, reject) => {
|
|
99604
|
+
const req = request(
|
|
99605
|
+
{
|
|
99606
|
+
hostname: parsed.hostname,
|
|
99607
|
+
path: parsed.pathname + parsed.search,
|
|
99608
|
+
method: init?.method ?? "GET",
|
|
99609
|
+
headers: init?.headers,
|
|
99610
|
+
agent: new HttpsProxyAgent(proxyUrl)
|
|
99611
|
+
},
|
|
99612
|
+
(res) => {
|
|
99613
|
+
text(res).then(
|
|
99614
|
+
(body) => resolve(new Response(body, { status: res.statusCode ?? 0 }))
|
|
99615
|
+
).catch(reject);
|
|
99616
|
+
}
|
|
99617
|
+
);
|
|
99618
|
+
req.on("error", reject);
|
|
99619
|
+
if (init?.body) req.write(init.body);
|
|
99620
|
+
req.end();
|
|
99621
|
+
});
|
|
99622
|
+
}
|
|
99333
99623
|
async function fetchBotOpenId(appId, appSecret, log) {
|
|
99334
|
-
const tokenRes = await
|
|
99624
|
+
const tokenRes = await proxyFetch("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", {
|
|
99335
99625
|
method: "POST",
|
|
99336
99626
|
headers: { "Content-Type": "application/json" },
|
|
99337
99627
|
body: JSON.stringify({ app_id: appId, app_secret: appSecret })
|
|
@@ -99341,7 +99631,7 @@ async function fetchBotOpenId(appId, appSecret, log) {
|
|
|
99341
99631
|
if (!token) {
|
|
99342
99632
|
throw new Error("\u83B7\u53D6 tenant_access_token \u5931\u8D25\uFF0C\u65E0\u6CD5\u542F\u52A8\u7FA4\u804A @\u63D0\u53CA\u68C0\u6D4B");
|
|
99343
99633
|
}
|
|
99344
|
-
const botRes = await
|
|
99634
|
+
const botRes = await proxyFetch("https://open.feishu.cn/open-apis/bot/v3/info", {
|
|
99345
99635
|
method: "GET",
|
|
99346
99636
|
headers: { Authorization: `Bearer ${token}` }
|
|
99347
99637
|
});
|