opencode-feishu 0.3.7 → 0.4.1
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 +333 -53
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 NeverMore93
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
CHANGED
|
@@ -98761,6 +98761,256 @@ function isDuplicate(messageId) {
|
|
|
98761
98761
|
return false;
|
|
98762
98762
|
}
|
|
98763
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
|
+
|
|
98764
99014
|
// src/feishu/group-filter.ts
|
|
98765
99015
|
function isBotMentioned(mentions, botOpenId) {
|
|
98766
99016
|
return mentions.some((m) => m.id?.open_id === botOpenId);
|
|
@@ -98796,21 +99046,18 @@ function startFeishuGateway(options) {
|
|
|
98796
99046
|
const messageId = message.message_id;
|
|
98797
99047
|
if (isDuplicate(messageId)) return;
|
|
98798
99048
|
const messageType = message.message_type ?? "text";
|
|
99049
|
+
const rawContent = message.content ?? "";
|
|
98799
99050
|
log("info", "\u98DE\u4E66\u6D88\u606F\u5143\u4FE1\u606F", {
|
|
98800
99051
|
chatId,
|
|
98801
99052
|
messageId: messageId ?? "",
|
|
98802
99053
|
messageType,
|
|
98803
|
-
hasContent: !!
|
|
99054
|
+
hasContent: !!rawContent
|
|
98804
99055
|
});
|
|
98805
|
-
if (
|
|
98806
|
-
let text2;
|
|
98807
|
-
|
|
98808
|
-
|
|
98809
|
-
text2 = (parsed.text ?? "").trim();
|
|
98810
|
-
} catch {
|
|
98811
|
-
return;
|
|
99056
|
+
if (!rawContent) return;
|
|
99057
|
+
let text2 = describeMessageType(messageType, rawContent);
|
|
99058
|
+
if (messageType === "text") {
|
|
99059
|
+
text2 = text2.replace(/@_user_\d+\s*/g, "").trim();
|
|
98812
99060
|
}
|
|
98813
|
-
text2 = text2.replace(/@_user_\d+\s*/g, "").trim();
|
|
98814
99061
|
if (!text2) return;
|
|
98815
99062
|
const chatType = message.chat_type === "group" ? "group" : "p2p";
|
|
98816
99063
|
let shouldReply = true;
|
|
@@ -98830,6 +99077,7 @@ function startFeishuGateway(options) {
|
|
|
98830
99077
|
messageId: messageId ?? "",
|
|
98831
99078
|
messageType,
|
|
98832
99079
|
content: text2,
|
|
99080
|
+
rawContent,
|
|
98833
99081
|
chatType,
|
|
98834
99082
|
senderId,
|
|
98835
99083
|
rootId,
|
|
@@ -98945,14 +99193,19 @@ async function handleEvent(event) {
|
|
|
98945
99193
|
if (!sessionId) break;
|
|
98946
99194
|
const payload = pendingBySession.get(sessionId);
|
|
98947
99195
|
if (!payload) break;
|
|
98948
|
-
const
|
|
98949
|
-
if (
|
|
98950
|
-
payload.textBuffer +=
|
|
98951
|
-
|
|
98952
|
-
|
|
98953
|
-
|
|
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;
|
|
98954
99203
|
}
|
|
98955
99204
|
}
|
|
99205
|
+
if (payload.textBuffer) {
|
|
99206
|
+
const res = await updateMessage(payload.feishuClient, payload.placeholderId, payload.textBuffer.trim());
|
|
99207
|
+
if (!res.ok) ;
|
|
99208
|
+
}
|
|
98956
99209
|
break;
|
|
98957
99210
|
}
|
|
98958
99211
|
case "session.error": {
|
|
@@ -98962,9 +99215,8 @@ async function handleEvent(event) {
|
|
|
98962
99215
|
const payload = pendingBySession.get(sessionId);
|
|
98963
99216
|
if (!payload) break;
|
|
98964
99217
|
const errMsg = props.error?.message ?? String(props.error);
|
|
98965
|
-
|
|
98966
|
-
|
|
98967
|
-
} catch {
|
|
99218
|
+
const updateRes = await updateMessage(payload.feishuClient, payload.placeholderId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
|
|
99219
|
+
if (!updateRes.ok) {
|
|
98968
99220
|
await sendTextMessage(payload.feishuClient, payload.chatId, `\u274C \u4F1A\u8BDD\u9519\u8BEF: ${errMsg}`);
|
|
98969
99221
|
}
|
|
98970
99222
|
break;
|
|
@@ -99016,26 +99268,21 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
99016
99268
|
|
|
99017
99269
|
// src/handler/chat.ts
|
|
99018
99270
|
async function handleChat(ctx, deps) {
|
|
99019
|
-
const { content, chatId, chatType, senderId,
|
|
99020
|
-
if (!content.trim()) return;
|
|
99271
|
+
const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
|
|
99272
|
+
if (!content.trim() && messageType === "text") return;
|
|
99021
99273
|
const { config, client, feishuClient, log, directory } = deps;
|
|
99022
99274
|
const query = directory ? { directory } : void 0;
|
|
99023
99275
|
const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
|
|
99024
99276
|
const session = await getOrCreateSession(client, sessionKey, directory);
|
|
99025
|
-
const
|
|
99026
|
-
|
|
99027
|
-
if (chatType === "group" && senderId) {
|
|
99028
|
-
promptContent = timeStr ? `[${timeStr}] [${senderId}]: ${content}` : `[${senderId}]: ${content}`;
|
|
99029
|
-
} else if (timeStr) {
|
|
99030
|
-
promptContent = `[${timeStr}] ${content}`;
|
|
99031
|
-
}
|
|
99277
|
+
const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
|
|
99278
|
+
if (!parts.length) return;
|
|
99032
99279
|
if (!shouldReply) {
|
|
99033
99280
|
try {
|
|
99034
99281
|
await client.session.prompt({
|
|
99035
99282
|
path: { id: session.id },
|
|
99036
99283
|
query,
|
|
99037
99284
|
body: {
|
|
99038
|
-
parts
|
|
99285
|
+
parts,
|
|
99039
99286
|
noReply: true
|
|
99040
99287
|
}
|
|
99041
99288
|
});
|
|
@@ -99056,11 +99303,16 @@ async function handleChat(ctx, deps) {
|
|
|
99056
99303
|
if (done) return;
|
|
99057
99304
|
try {
|
|
99058
99305
|
const res = await sendTextMessage(feishuClient, chatId, "\u6B63\u5728\u601D\u8003\u2026");
|
|
99306
|
+
if (done) return;
|
|
99059
99307
|
if (res.ok && res.messageId) {
|
|
99060
99308
|
placeholderId = res.messageId;
|
|
99061
99309
|
registerPending(session.id, { chatId, placeholderId, feishuClient });
|
|
99062
99310
|
}
|
|
99063
|
-
} 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
|
+
});
|
|
99064
99316
|
}
|
|
99065
99317
|
}, thinkingDelay) : null;
|
|
99066
99318
|
try {
|
|
@@ -99068,7 +99320,7 @@ async function handleChat(ctx, deps) {
|
|
|
99068
99320
|
path: { id: session.id },
|
|
99069
99321
|
query,
|
|
99070
99322
|
body: {
|
|
99071
|
-
parts
|
|
99323
|
+
parts
|
|
99072
99324
|
}
|
|
99073
99325
|
});
|
|
99074
99326
|
const start = Date.now();
|
|
@@ -99081,12 +99333,6 @@ async function handleChat(ctx, deps) {
|
|
|
99081
99333
|
if (text2 && text2 !== lastText) {
|
|
99082
99334
|
lastText = text2;
|
|
99083
99335
|
sameCount = 0;
|
|
99084
|
-
if (placeholderId) {
|
|
99085
|
-
try {
|
|
99086
|
-
await updateMessage(feishuClient, placeholderId, text2);
|
|
99087
|
-
} catch {
|
|
99088
|
-
}
|
|
99089
|
-
}
|
|
99090
99336
|
} else if (text2 && text2.length > 0) {
|
|
99091
99337
|
sameCount++;
|
|
99092
99338
|
if (sameCount >= stablePolls) break;
|
|
@@ -99107,11 +99353,24 @@ async function handleChat(ctx, deps) {
|
|
|
99107
99353
|
unregisterPending(session.id);
|
|
99108
99354
|
}
|
|
99109
99355
|
}
|
|
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
|
+
}
|
|
99110
99370
|
async function replyOrUpdate(feishuClient, chatId, placeholderId, text2) {
|
|
99111
99371
|
if (placeholderId) {
|
|
99112
|
-
|
|
99113
|
-
|
|
99114
|
-
} catch {
|
|
99372
|
+
const res = await updateMessage(feishuClient, placeholderId, text2);
|
|
99373
|
+
if (!res.ok) {
|
|
99115
99374
|
await sendTextMessage(feishuClient, chatId, text2);
|
|
99116
99375
|
}
|
|
99117
99376
|
} else {
|
|
@@ -99127,7 +99386,8 @@ function extractLastAssistantText(messages) {
|
|
|
99127
99386
|
// src/feishu/history.ts
|
|
99128
99387
|
var DEFAULT_PAGE_SIZE = 50;
|
|
99129
99388
|
async function ingestGroupHistory(feishuClient, opencodeClient, chatId, options) {
|
|
99130
|
-
const { maxMessages, log } = options;
|
|
99389
|
+
const { maxMessages, log, directory } = options;
|
|
99390
|
+
const query = directory ? { directory } : void 0;
|
|
99131
99391
|
log("info", "\u5F00\u59CB\u6444\u5165\u7FA4\u804A\u5386\u53F2\u4E0A\u4E0B\u6587", { chatId, maxMessages });
|
|
99132
99392
|
const messages = await fetchRecentMessages(feishuClient, chatId, maxMessages, log);
|
|
99133
99393
|
if (!messages.length) {
|
|
@@ -99135,10 +99395,11 @@ async function ingestGroupHistory(feishuClient, opencodeClient, chatId, options)
|
|
|
99135
99395
|
return;
|
|
99136
99396
|
}
|
|
99137
99397
|
const sessionKey = buildSessionKey("group", chatId);
|
|
99138
|
-
const session = await getOrCreateSession(opencodeClient, sessionKey);
|
|
99398
|
+
const session = await getOrCreateSession(opencodeClient, sessionKey, directory);
|
|
99139
99399
|
const contextText = formatHistoryAsContext(messages);
|
|
99140
99400
|
await opencodeClient.session.prompt({
|
|
99141
99401
|
path: { id: session.id },
|
|
99402
|
+
query,
|
|
99142
99403
|
body: {
|
|
99143
99404
|
parts: [{ type: "text", text: contextText }],
|
|
99144
99405
|
noReply: true
|
|
@@ -99164,14 +99425,10 @@ async function fetchRecentMessages(client, chatId, maxMessages, log) {
|
|
|
99164
99425
|
if (!items || items.length === 0) break;
|
|
99165
99426
|
for (const item of items) {
|
|
99166
99427
|
if (item.deleted) continue;
|
|
99167
|
-
if (
|
|
99168
|
-
|
|
99169
|
-
|
|
99170
|
-
|
|
99171
|
-
text2 = (parsed.text ?? "").trim();
|
|
99172
|
-
} catch {
|
|
99173
|
-
continue;
|
|
99174
|
-
}
|
|
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);
|
|
99175
99432
|
if (!text2) continue;
|
|
99176
99433
|
result.push({
|
|
99177
99434
|
senderType: item.sender?.sender_type ?? "unknown",
|
|
@@ -99218,7 +99475,8 @@ var DEFAULT_CONFIG = {
|
|
|
99218
99475
|
maxHistoryMessages: 200,
|
|
99219
99476
|
pollInterval: 1e3,
|
|
99220
99477
|
stablePolls: 3,
|
|
99221
|
-
dedupTtl: 10 * 60 * 1e3
|
|
99478
|
+
dedupTtl: 10 * 60 * 1e3,
|
|
99479
|
+
directory: ""
|
|
99222
99480
|
};
|
|
99223
99481
|
var FeishuPlugin = async (ctx) => {
|
|
99224
99482
|
const { client } = ctx;
|
|
@@ -99251,6 +99509,12 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99251
99509
|
} catch (parseErr) {
|
|
99252
99510
|
throw new Error(`\u98DE\u4E66\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A${configPath} \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON (${parseErr})`);
|
|
99253
99511
|
}
|
|
99512
|
+
if (feishuRaw.directory !== void 0 && typeof feishuRaw.directory !== "string") {
|
|
99513
|
+
log("warn", `\u98DE\u4E66\u914D\u7F6E\u8B66\u544A\uFF1A${configPath} \u4E2D\u7684 'directory' \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF0C\u5DF2\u5FFD\u7565`, {
|
|
99514
|
+
actualType: typeof feishuRaw.directory
|
|
99515
|
+
});
|
|
99516
|
+
feishuRaw.directory = void 0;
|
|
99517
|
+
}
|
|
99254
99518
|
if (!feishuRaw.appId || !feishuRaw.appSecret) {
|
|
99255
99519
|
throw new Error(
|
|
99256
99520
|
`\u98DE\u4E66\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF1A${configPath} \u4E2D\u5FC5\u987B\u5305\u542B appId \u548C appSecret`
|
|
@@ -99265,7 +99529,8 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99265
99529
|
maxHistoryMessages: feishuRaw.maxHistoryMessages ?? DEFAULT_CONFIG.maxHistoryMessages,
|
|
99266
99530
|
pollInterval: feishuRaw.pollInterval ?? DEFAULT_CONFIG.pollInterval,
|
|
99267
99531
|
stablePolls: feishuRaw.stablePolls ?? DEFAULT_CONFIG.stablePolls,
|
|
99268
|
-
dedupTtl: feishuRaw.dedupTtl ?? DEFAULT_CONFIG.dedupTtl
|
|
99532
|
+
dedupTtl: feishuRaw.dedupTtl ?? DEFAULT_CONFIG.dedupTtl,
|
|
99533
|
+
directory: expandDirectoryPath(feishuRaw.directory ?? ctx.directory ?? DEFAULT_CONFIG.directory)
|
|
99269
99534
|
};
|
|
99270
99535
|
initDedup(resolvedConfig.dedupTtl);
|
|
99271
99536
|
const botOpenId = await fetchBotOpenId(resolvedConfig.appId, resolvedConfig.appSecret, log);
|
|
@@ -99279,14 +99544,15 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99279
99544
|
client,
|
|
99280
99545
|
feishuClient: gateway.client,
|
|
99281
99546
|
log,
|
|
99282
|
-
directory:
|
|
99547
|
+
directory: resolvedConfig.directory
|
|
99283
99548
|
});
|
|
99284
99549
|
},
|
|
99285
99550
|
onBotAdded: (chatId) => {
|
|
99286
99551
|
if (!gateway) return;
|
|
99287
99552
|
ingestGroupHistory(gateway.client, client, chatId, {
|
|
99288
99553
|
maxMessages: resolvedConfig.maxHistoryMessages,
|
|
99289
|
-
log
|
|
99554
|
+
log,
|
|
99555
|
+
directory: resolvedConfig.directory
|
|
99290
99556
|
}).catch((err) => {
|
|
99291
99557
|
log("error", "\u7FA4\u804A\u5386\u53F2\u6444\u5165\u5931\u8D25", {
|
|
99292
99558
|
chatId,
|
|
@@ -99308,6 +99574,20 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99308
99574
|
};
|
|
99309
99575
|
return hooks;
|
|
99310
99576
|
};
|
|
99577
|
+
function expandDirectoryPath(dir) {
|
|
99578
|
+
if (!dir) return dir;
|
|
99579
|
+
if (dir.startsWith("~")) {
|
|
99580
|
+
dir = join(homedir(), dir.slice(1));
|
|
99581
|
+
}
|
|
99582
|
+
dir = dir.replace(/\$\{(\w+)\}/g, (_match, name) => {
|
|
99583
|
+
const val = process.env[name];
|
|
99584
|
+
if (val === void 0) {
|
|
99585
|
+
throw new Error(`\u73AF\u5883\u53D8\u91CF ${name} \u672A\u8BBE\u7F6E\uFF08directory \u5F15\u7528\u4E86 \${${name}}\uFF09`);
|
|
99586
|
+
}
|
|
99587
|
+
return val;
|
|
99588
|
+
});
|
|
99589
|
+
return dir;
|
|
99590
|
+
}
|
|
99311
99591
|
function resolveEnvPlaceholders(obj) {
|
|
99312
99592
|
if (typeof obj === "string") {
|
|
99313
99593
|
if (!obj.includes("${")) return obj;
|