xiaozuoassistant 0.2.48 → 0.2.50
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/dist/server/index.js
CHANGED
|
@@ -509,8 +509,44 @@ app.get('/api/plugins', (req, res) => {
|
|
|
509
509
|
});
|
|
510
510
|
// 存储 OAuth 状态以防止 CSRF 攻击
|
|
511
511
|
const oauthStates = new Map();
|
|
512
|
-
// 模拟的 Token 存储 (
|
|
512
|
+
// 模拟的 Token 存储 (持久化到文件)
|
|
513
|
+
const tokensFilePath = path.join(process.cwd(), 'data', 'oauth-tokens.json');
|
|
513
514
|
const oauthTokens = new Map();
|
|
515
|
+
// 初始化时加载 Token
|
|
516
|
+
try {
|
|
517
|
+
if (fs.existsSync(tokensFilePath)) {
|
|
518
|
+
const data = JSON.parse(fs.readFileSync(tokensFilePath, 'utf8'));
|
|
519
|
+
for (const key of Object.keys(data)) {
|
|
520
|
+
oauthTokens.set(key, data[key]);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
console.warn('[OAuth] Failed to load tokens:', e);
|
|
526
|
+
}
|
|
527
|
+
function saveTokens() {
|
|
528
|
+
try {
|
|
529
|
+
const dir = path.dirname(tokensFilePath);
|
|
530
|
+
if (!fs.existsSync(dir))
|
|
531
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
532
|
+
const data = Object.fromEntries(oauthTokens);
|
|
533
|
+
fs.writeFileSync(tokensFilePath, JSON.stringify(data, null, 2));
|
|
534
|
+
}
|
|
535
|
+
catch (e) {
|
|
536
|
+
console.error('[OAuth] Failed to save tokens:', e);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// 暴露一个简单的内部 API 供插件获取 Token
|
|
540
|
+
app.get('/internal/api/auth/tokens/:provider', (req, res) => {
|
|
541
|
+
// 这个接口仅供本地/内部调用
|
|
542
|
+
const token = oauthTokens.get(req.params.provider);
|
|
543
|
+
if (token) {
|
|
544
|
+
res.json(token);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
res.status(404).json({ error: 'Token not found' });
|
|
548
|
+
}
|
|
549
|
+
});
|
|
514
550
|
// Mock OAuth 授权接口
|
|
515
551
|
app.get('/api/auth/:provider/authorize', (req, res) => {
|
|
516
552
|
const provider = req.params.provider;
|
|
@@ -583,7 +619,8 @@ app.get('/api/auth/:provider/callback', async (req, res) => {
|
|
|
583
619
|
headers: { 'Authorization': `Bearer ${appAccessToken}` }
|
|
584
620
|
});
|
|
585
621
|
tokenData = userTokenRes.data.data;
|
|
586
|
-
oauthTokens.set('lark', tokenData);
|
|
622
|
+
oauthTokens.set('lark', tokenData);
|
|
623
|
+
saveTokens();
|
|
587
624
|
}
|
|
588
625
|
else if (provider === 'wps') {
|
|
589
626
|
const appId = process.env.WPS_APP_ID || '';
|
|
@@ -599,7 +636,8 @@ app.get('/api/auth/:provider/callback', async (req, res) => {
|
|
|
599
636
|
code: code
|
|
600
637
|
});
|
|
601
638
|
tokenData = tokenRes.data;
|
|
602
|
-
oauthTokens.set('wps', tokenData);
|
|
639
|
+
oauthTokens.set('wps', tokenData);
|
|
640
|
+
saveTokens();
|
|
603
641
|
}
|
|
604
642
|
console.log(`[OAuth] Successfully authenticated with ${provider}`);
|
|
605
643
|
// 更新内存中的插件状态为已启用
|
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
import { BaseSkill } from '../../../skills/base-skill.js';
|
|
2
2
|
import * as lark from '@larksuiteoapi/node-sdk';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
// 获取可能通过网页授权获取到的 user_access_token
|
|
6
|
+
function getUserAccessToken() {
|
|
7
|
+
try {
|
|
8
|
+
const tokensFilePath = path.join(process.cwd(), 'data', 'oauth-tokens.json');
|
|
9
|
+
if (fs.existsSync(tokensFilePath)) {
|
|
10
|
+
const data = JSON.parse(fs.readFileSync(tokensFilePath, 'utf8'));
|
|
11
|
+
if (data.lark && data.lark.access_token) {
|
|
12
|
+
return data.lark.access_token;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
// Ignore errors
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
// Helper function to resolve document token, especially handling /wiki/ URLs
|
|
22
|
+
async function resolveDocumentToken(client, urlOrToken) {
|
|
23
|
+
let token = urlOrToken;
|
|
24
|
+
let isWiki = false;
|
|
25
|
+
if (urlOrToken.includes('/docx/')) {
|
|
26
|
+
token = urlOrToken.split('/docx/')[1].split(/[?#]/)[0];
|
|
27
|
+
return { token, type: 'docx' };
|
|
28
|
+
}
|
|
29
|
+
else if (urlOrToken.includes('/doc/')) {
|
|
30
|
+
token = urlOrToken.split('/doc/')[1].split(/[?#]/)[0];
|
|
31
|
+
return { token, type: 'doc' };
|
|
32
|
+
}
|
|
33
|
+
else if (urlOrToken.includes('/wiki/')) {
|
|
34
|
+
token = urlOrToken.split('/wiki/')[1].split(/[?#]/)[0];
|
|
35
|
+
isWiki = true;
|
|
36
|
+
}
|
|
37
|
+
else if (urlOrToken.startsWith('wikcn')) {
|
|
38
|
+
isWiki = true;
|
|
39
|
+
}
|
|
40
|
+
const userToken = getUserAccessToken();
|
|
41
|
+
const options = userToken ? lark.withUserAccessToken(userToken) : undefined;
|
|
42
|
+
if (isWiki) {
|
|
43
|
+
const res = await client.wiki.space.getNode({
|
|
44
|
+
params: { token }
|
|
45
|
+
}, options);
|
|
46
|
+
if (res.code !== 0) {
|
|
47
|
+
throw new Error(`Failed to resolve wiki token. Code: ${res.code}, Msg: ${res.msg}`);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
token: res.data?.node?.obj_token || token,
|
|
51
|
+
type: res.data?.node?.obj_type || 'docx'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Fallback assuming it's a docx token if not wiki
|
|
55
|
+
return { token, type: 'docx' };
|
|
56
|
+
}
|
|
3
57
|
export class ReadLarkDocSkill extends BaseSkill {
|
|
4
58
|
constructor() {
|
|
5
59
|
super(...arguments);
|
|
@@ -26,18 +80,6 @@ export class ReadLarkDocSkill extends BaseSkill {
|
|
|
26
80
|
}
|
|
27
81
|
async execute(args, ctx) {
|
|
28
82
|
try {
|
|
29
|
-
// Extract document token from URL
|
|
30
|
-
// Typical Lark doc URL formats:
|
|
31
|
-
// https://domain.feishu.cn/docx/TOKEN
|
|
32
|
-
// https://domain.feishu.cn/wiki/WIKITOKEN
|
|
33
|
-
let documentToken = args.document_url;
|
|
34
|
-
// Basic extraction logic
|
|
35
|
-
if (args.document_url.includes('/docx/')) {
|
|
36
|
-
documentToken = args.document_url.split('/docx/')[1].split(/[?#]/)[0];
|
|
37
|
-
}
|
|
38
|
-
else if (args.document_url.includes('/doc/')) {
|
|
39
|
-
documentToken = args.document_url.split('/doc/')[1].split(/[?#]/)[0];
|
|
40
|
-
}
|
|
41
83
|
// Fetch config to get app_id and app_secret if not provided
|
|
42
84
|
const configLoader = require('../../../config/loader.js').config;
|
|
43
85
|
let appId = args.app_id;
|
|
@@ -67,13 +109,20 @@ export class ReadLarkDocSkill extends BaseSkill {
|
|
|
67
109
|
appId: appId,
|
|
68
110
|
appSecret: appSecret,
|
|
69
111
|
});
|
|
112
|
+
// Extract and resolve document token from URL
|
|
113
|
+
const { token: documentToken, type: objType } = await resolveDocumentToken(client, args.document_url);
|
|
114
|
+
if (objType !== 'docx' && objType !== 'doc') {
|
|
115
|
+
return { error: `Unsupported document type: ${objType}. Only docx and doc are supported for reading raw text content.` };
|
|
116
|
+
}
|
|
70
117
|
// We use the drive/v1/export API or docx/v1/documents API to read content
|
|
71
118
|
// Let's try the newer DOCX API first to get raw content
|
|
119
|
+
const userToken = getUserAccessToken();
|
|
120
|
+
const options = userToken ? lark.withUserAccessToken(userToken) : undefined;
|
|
72
121
|
const res = await client.docx.document.rawContent({
|
|
73
122
|
path: {
|
|
74
123
|
document_id: documentToken,
|
|
75
124
|
},
|
|
76
|
-
});
|
|
125
|
+
}, options);
|
|
77
126
|
if (res.code !== 0) {
|
|
78
127
|
return { error: `Failed to read Lark document. Code: ${res.code}, Msg: ${res.msg}. Make sure the App has the "docs:doc:readonly" permission and has been added as a collaborator to the document.` };
|
|
79
128
|
}
|
|
@@ -140,12 +189,14 @@ export class CreateLarkDocSkill extends BaseSkill {
|
|
|
140
189
|
}
|
|
141
190
|
}
|
|
142
191
|
const client = new lark.Client({ appId: appId, appSecret: appSecret });
|
|
192
|
+
const userToken = getUserAccessToken();
|
|
193
|
+
const options = userToken ? lark.withUserAccessToken(userToken) : undefined;
|
|
143
194
|
const res = await client.docx.document.create({
|
|
144
195
|
data: {
|
|
145
196
|
title: args.title,
|
|
146
197
|
folder_token: args.folder_token || ''
|
|
147
198
|
}
|
|
148
|
-
});
|
|
199
|
+
}, options);
|
|
149
200
|
if (res.code !== 0) {
|
|
150
201
|
return { error: `Failed to create document. Code: ${res.code}, Msg: ${res.msg}` };
|
|
151
202
|
}
|
|
@@ -185,13 +236,6 @@ export class AppendLarkDocSkill extends BaseSkill {
|
|
|
185
236
|
}
|
|
186
237
|
async execute(args, ctx) {
|
|
187
238
|
try {
|
|
188
|
-
let documentToken = args.document_url;
|
|
189
|
-
if (args.document_url.includes('/docx/')) {
|
|
190
|
-
documentToken = args.document_url.split('/docx/')[1].split(/[?#]/)[0];
|
|
191
|
-
}
|
|
192
|
-
else if (args.document_url.includes('/doc/')) {
|
|
193
|
-
documentToken = args.document_url.split('/doc/')[1].split(/[?#]/)[0];
|
|
194
|
-
}
|
|
195
239
|
const configLoader = require('../../../config/loader.js').config;
|
|
196
240
|
let appId = args.app_id;
|
|
197
241
|
let appSecret = args.app_secret;
|
|
@@ -215,6 +259,10 @@ export class AppendLarkDocSkill extends BaseSkill {
|
|
|
215
259
|
}
|
|
216
260
|
}
|
|
217
261
|
const client = new lark.Client({ appId: appId, appSecret: appSecret });
|
|
262
|
+
const { token: documentToken, type: objType } = await resolveDocumentToken(client, args.document_url);
|
|
263
|
+
if (objType !== 'docx') {
|
|
264
|
+
return { error: `Unsupported document type: ${objType}. Append operation currently only supports docx documents.` };
|
|
265
|
+
}
|
|
218
266
|
// Create blocks from content
|
|
219
267
|
const lines = args.content.split('\n').filter(l => l.trim() !== '');
|
|
220
268
|
const children = lines.map(line => ({
|
|
@@ -229,6 +277,8 @@ export class AppendLarkDocSkill extends BaseSkill {
|
|
|
229
277
|
}));
|
|
230
278
|
if (children.length === 0)
|
|
231
279
|
return { success: true, message: 'Nothing to append' };
|
|
280
|
+
const userToken = getUserAccessToken();
|
|
281
|
+
const options = userToken ? lark.withUserAccessToken(userToken) : undefined;
|
|
232
282
|
const res = await client.docx.documentBlockChildren.create({
|
|
233
283
|
path: {
|
|
234
284
|
document_id: documentToken,
|
|
@@ -238,7 +288,7 @@ export class AppendLarkDocSkill extends BaseSkill {
|
|
|
238
288
|
children: children,
|
|
239
289
|
index: -1
|
|
240
290
|
}
|
|
241
|
-
});
|
|
291
|
+
}, options);
|
|
242
292
|
if (res.code !== 0) {
|
|
243
293
|
return { error: `Failed to append to document. Code: ${res.code}, Msg: ${res.msg}` };
|
|
244
294
|
}
|