@satorijs/adapter-lark 2.0.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/lib/bot.d.ts +29 -0
- package/lib/http.d.ts +21 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +635 -0
- package/lib/index.js.map +7 -0
- package/lib/message.d.ts +18 -0
- package/lib/types/auth.d.ts +40 -0
- package/lib/types/event.d.ts +18 -0
- package/lib/types/guild.d.ts +58 -0
- package/lib/types/index.d.ts +34 -0
- package/lib/types/internal.d.ts +17 -0
- package/lib/types/message/asset.d.ts +41 -0
- package/lib/types/message/content.d.ts +69 -0
- package/lib/types/message/index.d.ts +171 -0
- package/lib/types/utils.d.ts +9 -0
- package/lib/utils.d.ts +27 -0
- package/package.json +38 -0
package/lib/bot.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Bot, Context, Quester, Schema, SendOptions } from '@satorijs/satori';
|
|
3
|
+
import { HttpServer } from './http';
|
|
4
|
+
import { Internal } from './types';
|
|
5
|
+
export declare class LarkBot extends Bot<LarkBot.Config> {
|
|
6
|
+
_token?: string;
|
|
7
|
+
_refresher?: NodeJS.Timeout;
|
|
8
|
+
http: Quester;
|
|
9
|
+
assetsQuester: Quester;
|
|
10
|
+
internal: Internal;
|
|
11
|
+
constructor(ctx: Context, config: LarkBot.Config);
|
|
12
|
+
initialize(): Promise<void>;
|
|
13
|
+
private refreshToken;
|
|
14
|
+
get token(): string;
|
|
15
|
+
set token(v: string);
|
|
16
|
+
sendMessage(channelId: string, content: string, guildId?: string, options?: SendOptions): Promise<string[]>;
|
|
17
|
+
sendPrivateMessage(userId: string, content: string, options?: SendOptions): Promise<string[]>;
|
|
18
|
+
deleteMessage(channelId: string, messageId: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare namespace LarkBot {
|
|
21
|
+
interface Config extends Bot.Config, HttpServer.Config, Quester.Config {
|
|
22
|
+
appId: string;
|
|
23
|
+
appSecret: string;
|
|
24
|
+
encryptKey?: string;
|
|
25
|
+
verificationToken?: string;
|
|
26
|
+
}
|
|
27
|
+
const Config: Schema<Config>;
|
|
28
|
+
}
|
|
29
|
+
export { LarkBot as FeishuBot };
|
package/lib/http.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Adapter, Context, Schema } from '@satorijs/satori';
|
|
2
|
+
import { FeishuBot } from './bot';
|
|
3
|
+
import { AllEvents } from './types';
|
|
4
|
+
export declare class HttpServer extends Adapter.Server<FeishuBot> {
|
|
5
|
+
private ciphers;
|
|
6
|
+
fork(ctx: Context, bot: FeishuBot): Promise<void>;
|
|
7
|
+
start(bot: FeishuBot): Promise<void>;
|
|
8
|
+
stop(): Promise<void>;
|
|
9
|
+
dispatchSession(body: AllEvents): void;
|
|
10
|
+
private _tryDecryptBody;
|
|
11
|
+
private _refreshCipher;
|
|
12
|
+
}
|
|
13
|
+
export declare namespace HttpServer {
|
|
14
|
+
interface Config {
|
|
15
|
+
selfUrl?: string;
|
|
16
|
+
path?: string;
|
|
17
|
+
verifyToken?: boolean;
|
|
18
|
+
verifySignature?: boolean;
|
|
19
|
+
}
|
|
20
|
+
const createConfig: (path: string) => Schema<Config>;
|
|
21
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FeishuBot } from './bot';
|
|
2
|
+
import * as Lark from './types';
|
|
3
|
+
export * from './bot';
|
|
4
|
+
export { Lark, Lark as Feishu };
|
|
5
|
+
export default FeishuBot;
|
|
6
|
+
declare module '@satorijs/core' {
|
|
7
|
+
interface Session {
|
|
8
|
+
feishu: Lark.Internal;
|
|
9
|
+
lark: Lark.Internal;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// satori/adapters/lark/src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Feishu: () => types_exports,
|
|
34
|
+
FeishuBot: () => LarkBot,
|
|
35
|
+
Lark: () => types_exports,
|
|
36
|
+
LarkBot: () => LarkBot,
|
|
37
|
+
default: () => src_default
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(src_exports);
|
|
40
|
+
|
|
41
|
+
// satori/adapters/lark/src/bot.ts
|
|
42
|
+
var import_satori5 = require("@satorijs/satori");
|
|
43
|
+
|
|
44
|
+
// satori/adapters/lark/src/http.ts
|
|
45
|
+
var import_satori2 = require("@satorijs/satori");
|
|
46
|
+
|
|
47
|
+
// satori/adapters/lark/src/utils.ts
|
|
48
|
+
var import_crypto = __toESM(require("crypto"));
|
|
49
|
+
var import_satori = require("@satorijs/satori");
|
|
50
|
+
function adaptSender(sender, session) {
|
|
51
|
+
var _a;
|
|
52
|
+
let userId;
|
|
53
|
+
if ("sender_id" in sender) {
|
|
54
|
+
userId = sender.sender_id.open_id;
|
|
55
|
+
} else {
|
|
56
|
+
userId = sender.id;
|
|
57
|
+
}
|
|
58
|
+
(_a = session.author) != null ? _a : session.author = { userId };
|
|
59
|
+
session.author.userId = userId;
|
|
60
|
+
session.userId = userId;
|
|
61
|
+
return session;
|
|
62
|
+
}
|
|
63
|
+
__name(adaptSender, "adaptSender");
|
|
64
|
+
function adaptMessage(bot, data, session) {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
const json = JSON.parse(data.message.content);
|
|
67
|
+
const assetEndpoint = (0, import_satori.trimSlash)((_a = bot.config.selfUrl) != null ? _a : bot.ctx.root.config.selfUrl) + bot.config.path + "/assets";
|
|
68
|
+
const content = [];
|
|
69
|
+
switch (data.message.message_type) {
|
|
70
|
+
case "text": {
|
|
71
|
+
const text = json.text;
|
|
72
|
+
if (!((_b = data.message.mentions) == null ? void 0 : _b.length)) {
|
|
73
|
+
content.push(text);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
text.split(" ").forEach((word) => {
|
|
77
|
+
if (word.startsWith("@")) {
|
|
78
|
+
const mention = data.message.mentions.find((mention2) => mention2.key === word);
|
|
79
|
+
content.push(import_satori.h.at(mention.id.open_id, { name: mention.name }));
|
|
80
|
+
} else {
|
|
81
|
+
content.push(word);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "image":
|
|
87
|
+
content.push(import_satori.h.image(`${assetEndpoint}/image/${data.message.message_id}/${json.image_key}?self_id=${bot.selfId}`));
|
|
88
|
+
break;
|
|
89
|
+
case "audio":
|
|
90
|
+
content.push(import_satori.h.audio(`${assetEndpoint}/file/${data.message.message_id}/${json.file_key}?self_id=${bot.selfId}`));
|
|
91
|
+
break;
|
|
92
|
+
case "media":
|
|
93
|
+
content.push(import_satori.h.video(`${assetEndpoint}/file/${data.message.message_id}/${json.file_key}?self_id=${bot.selfId}`, json.image_key));
|
|
94
|
+
break;
|
|
95
|
+
case "file":
|
|
96
|
+
content.push(import_satori.h.file(`${assetEndpoint}/file/${data.message.message_id}/${json.file_key}?self_id=${bot.selfId}`));
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
session.timestamp = +data.message.create_time;
|
|
100
|
+
session.messageId = data.message.message_id;
|
|
101
|
+
session.channelId = data.message.chat_id;
|
|
102
|
+
session.content = content.map((c) => c.toString()).join(" ");
|
|
103
|
+
return session;
|
|
104
|
+
}
|
|
105
|
+
__name(adaptMessage, "adaptMessage");
|
|
106
|
+
function adaptSession(bot, body) {
|
|
107
|
+
const session = bot.session();
|
|
108
|
+
session.selfId = bot.selfId;
|
|
109
|
+
const internal = Object.create(bot.internal);
|
|
110
|
+
Object.assign(internal, body);
|
|
111
|
+
(0, import_satori.defineProperty)(session, "feishu", internal);
|
|
112
|
+
(0, import_satori.defineProperty)(session, "lark", internal);
|
|
113
|
+
switch (body.type) {
|
|
114
|
+
case "im.message.receive_v1":
|
|
115
|
+
session.type = "message";
|
|
116
|
+
session.subtype = body.event.message.chat_type;
|
|
117
|
+
adaptSender(body.event.sender, session);
|
|
118
|
+
adaptMessage(bot, body.event, session);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
return session;
|
|
122
|
+
}
|
|
123
|
+
__name(adaptSession, "adaptSession");
|
|
124
|
+
function extractIdType(id) {
|
|
125
|
+
if (id.startsWith("ou"))
|
|
126
|
+
return "open_id";
|
|
127
|
+
if (id.startsWith("on"))
|
|
128
|
+
return "union_id";
|
|
129
|
+
if (id.startsWith("oc"))
|
|
130
|
+
return "chat_id";
|
|
131
|
+
if (id.includes("@"))
|
|
132
|
+
return "email";
|
|
133
|
+
return "user_id";
|
|
134
|
+
}
|
|
135
|
+
__name(extractIdType, "extractIdType");
|
|
136
|
+
var Cipher = class {
|
|
137
|
+
constructor(key) {
|
|
138
|
+
this.encryptKey = key;
|
|
139
|
+
const hash = import_crypto.default.createHash("sha256");
|
|
140
|
+
hash.update(key);
|
|
141
|
+
this.key = hash.digest();
|
|
142
|
+
}
|
|
143
|
+
decrypt(encrypt) {
|
|
144
|
+
const encryptBuffer = Buffer.from(encrypt, "base64");
|
|
145
|
+
const decipher = import_crypto.default.createDecipheriv("aes-256-cbc", this.key, encryptBuffer.slice(0, 16));
|
|
146
|
+
let decrypted = decipher.update(encryptBuffer.slice(16).toString("hex"), "hex", "utf8");
|
|
147
|
+
decrypted += decipher.final("utf8");
|
|
148
|
+
return decrypted;
|
|
149
|
+
}
|
|
150
|
+
calculateSignature(timestamp, nonce, body) {
|
|
151
|
+
const content = timestamp + nonce + this.encryptKey + body;
|
|
152
|
+
const sign = import_crypto.default.createHash("sha256").update(content).digest("hex");
|
|
153
|
+
return sign;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
__name(Cipher, "Cipher");
|
|
157
|
+
|
|
158
|
+
// satori/adapters/lark/src/http.ts
|
|
159
|
+
var logger = new import_satori2.Logger("lark");
|
|
160
|
+
var HttpServer = class extends import_satori2.Adapter.Server {
|
|
161
|
+
constructor() {
|
|
162
|
+
super(...arguments);
|
|
163
|
+
this.ciphers = {};
|
|
164
|
+
}
|
|
165
|
+
fork(ctx, bot) {
|
|
166
|
+
super.fork(ctx, bot);
|
|
167
|
+
this._refreshCipher();
|
|
168
|
+
return bot.initialize();
|
|
169
|
+
}
|
|
170
|
+
async start(bot) {
|
|
171
|
+
const { path } = bot.config;
|
|
172
|
+
bot.ctx.router.post(path, (ctx) => {
|
|
173
|
+
this._refreshCipher();
|
|
174
|
+
const signature = ctx.get("X-Lark-Signature");
|
|
175
|
+
const enabledSignatureVerify = this.bots.filter((bot2) => bot2.config.verifySignature);
|
|
176
|
+
if (signature && enabledSignatureVerify.length) {
|
|
177
|
+
const result = enabledSignatureVerify.some((bot2) => {
|
|
178
|
+
var _a;
|
|
179
|
+
const timestamp = ctx.get("X-Lark-Request-Timestamp");
|
|
180
|
+
const nonce = ctx.get("X-Lark-Request-Nonce");
|
|
181
|
+
const body2 = ctx.request.rawBody;
|
|
182
|
+
const actualSignature = (_a = this.ciphers[bot2.config.appId]) == null ? void 0 : _a.calculateSignature(timestamp, nonce, body2);
|
|
183
|
+
if (actualSignature === signature)
|
|
184
|
+
return true;
|
|
185
|
+
else
|
|
186
|
+
return false;
|
|
187
|
+
});
|
|
188
|
+
if (!result)
|
|
189
|
+
return ctx.status = 403;
|
|
190
|
+
}
|
|
191
|
+
const body = this._tryDecryptBody(ctx.request.body);
|
|
192
|
+
if ((body == null ? void 0 : body.type) === "url_verification" && (body == null ? void 0 : body.challenge) && typeof body.challenge === "string") {
|
|
193
|
+
ctx.response.body = { challenge: body.challenge };
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const enabledVeirfyTokenVerify = this.bots.filter((bot2) => bot2.config.verifyToken && bot2.config.verificationToken);
|
|
197
|
+
if (enabledVeirfyTokenVerify.length) {
|
|
198
|
+
const result = enabledVeirfyTokenVerify.some((bot2) => {
|
|
199
|
+
var _a;
|
|
200
|
+
const token = (_a = ctx.request.body) == null ? void 0 : _a.token;
|
|
201
|
+
if (token === bot2.config.verificationToken)
|
|
202
|
+
return true;
|
|
203
|
+
else
|
|
204
|
+
return false;
|
|
205
|
+
});
|
|
206
|
+
if (!result)
|
|
207
|
+
return ctx.status = 403;
|
|
208
|
+
}
|
|
209
|
+
logger.debug("received decryped event: %o", body);
|
|
210
|
+
this.dispatchSession(body);
|
|
211
|
+
return ctx.status = 200;
|
|
212
|
+
});
|
|
213
|
+
bot.ctx.router.get(path + "/assets/:type/:message_id/:key", async (ctx) => {
|
|
214
|
+
const type = ctx.params.type === "image" ? "image" : "file";
|
|
215
|
+
const key = ctx.params.key;
|
|
216
|
+
const messageId = ctx.params.message_id;
|
|
217
|
+
const selfId = ctx.request.query.self_id;
|
|
218
|
+
const bot2 = this.bots.find((bot3) => bot3.selfId === selfId);
|
|
219
|
+
if (!bot2)
|
|
220
|
+
return ctx.status = 404;
|
|
221
|
+
const resp = await bot2.http.axios(`/im/v1/messages/${messageId}/resources/${key}`, {
|
|
222
|
+
method: "GET",
|
|
223
|
+
params: { type },
|
|
224
|
+
responseType: "stream"
|
|
225
|
+
});
|
|
226
|
+
ctx.status = 200;
|
|
227
|
+
ctx.response.headers["Content-Type"] = resp.headers["content-type"];
|
|
228
|
+
ctx.response.body = resp.data;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async stop() {
|
|
232
|
+
logger.debug("http server stopped");
|
|
233
|
+
}
|
|
234
|
+
dispatchSession(body) {
|
|
235
|
+
const { header } = body;
|
|
236
|
+
const { app_id, event_type } = header;
|
|
237
|
+
body.type = event_type;
|
|
238
|
+
const bot = this.bots.find((bot2) => bot2.selfId === app_id);
|
|
239
|
+
const session = adaptSession(bot, body);
|
|
240
|
+
bot.dispatch(session);
|
|
241
|
+
}
|
|
242
|
+
_tryDecryptBody(body) {
|
|
243
|
+
this._refreshCipher();
|
|
244
|
+
const ciphers = Object.values(this.ciphers);
|
|
245
|
+
if (ciphers.length && typeof body.encrypt === "string") {
|
|
246
|
+
for (const cipher of ciphers) {
|
|
247
|
+
try {
|
|
248
|
+
return JSON.parse(cipher.decrypt(body.encrypt));
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
logger.warn("failed to decrypt message: %o", body);
|
|
253
|
+
}
|
|
254
|
+
if (typeof body.encrypt === "string" && !ciphers.length) {
|
|
255
|
+
logger.warn("encryptKey is not set, but received encrypted message: %o", body);
|
|
256
|
+
}
|
|
257
|
+
return body;
|
|
258
|
+
}
|
|
259
|
+
_refreshCipher() {
|
|
260
|
+
const ciphers = Object.keys(this.ciphers);
|
|
261
|
+
const bots = this.bots.map((bot) => bot.config.appId);
|
|
262
|
+
if (bots.length === ciphers.length && bots.every((bot) => ciphers.includes(bot)))
|
|
263
|
+
return;
|
|
264
|
+
this.ciphers = {};
|
|
265
|
+
for (const bot of this.bots) {
|
|
266
|
+
this.ciphers[bot.config.appId] = new Cipher(bot.config.encryptKey);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
__name(HttpServer, "HttpServer");
|
|
271
|
+
((HttpServer2) => {
|
|
272
|
+
HttpServer2.createConfig = /* @__PURE__ */ __name((path) => import_satori2.Schema.object({
|
|
273
|
+
path: import_satori2.Schema.string().role("url").description("要连接的服务器地址。").default(path),
|
|
274
|
+
selfUrl: import_satori2.Schema.string().role("link").description("服务器暴露在公网的地址。缺省时将使用全局配置。"),
|
|
275
|
+
verifyToken: import_satori2.Schema.boolean().description("是否验证令牌。"),
|
|
276
|
+
verifySignature: import_satori2.Schema.boolean().description("是否验证签名。")
|
|
277
|
+
}).description("服务端设置"), "createConfig");
|
|
278
|
+
})(HttpServer || (HttpServer = {}));
|
|
279
|
+
|
|
280
|
+
// satori/adapters/lark/src/message.ts
|
|
281
|
+
var import_fs = require("fs");
|
|
282
|
+
var import_satori3 = require("@satorijs/satori");
|
|
283
|
+
var import_form_data = __toESM(require("form-data"));
|
|
284
|
+
var LarkMessenger = class extends import_satori3.Messenger {
|
|
285
|
+
constructor() {
|
|
286
|
+
super(...arguments);
|
|
287
|
+
this.content = "";
|
|
288
|
+
}
|
|
289
|
+
async post(data) {
|
|
290
|
+
var _a, _b, _c, _d, _e;
|
|
291
|
+
try {
|
|
292
|
+
let resp;
|
|
293
|
+
if (this.quote) {
|
|
294
|
+
resp = await ((_a = this.bot.internal) == null ? void 0 : _a.replyMessage(this.quote, data));
|
|
295
|
+
} else {
|
|
296
|
+
data.receive_id = this.channelId;
|
|
297
|
+
resp = await ((_b = this.bot.internal) == null ? void 0 : _b.sendMessage(extractIdType(this.channelId), data));
|
|
298
|
+
}
|
|
299
|
+
const session = this.bot.session();
|
|
300
|
+
session.messageId = resp.data.message_id;
|
|
301
|
+
session.timestamp = Number(resp.data.create_time) * 1e3;
|
|
302
|
+
session.userId = resp.data.sender.id;
|
|
303
|
+
session.app.emit(session, "send", session);
|
|
304
|
+
this.results.push(session);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (import_satori3.Quester.isAxiosError(e)) {
|
|
307
|
+
if ((_d = (_c = e.response) == null ? void 0 : _c.data) == null ? void 0 : _d.code) {
|
|
308
|
+
const generalErrorMsg = `Check error code at https://open.larksuite.com/document/ukTMukTMukTM/ugjM14COyUjL4ITN`;
|
|
309
|
+
e.message += ` (Feishu error code ${e.response.data.code}: ${(_e = e.response.data.msg) != null ? _e : generalErrorMsg})`;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
this.errors.push(e);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async flush() {
|
|
316
|
+
if (this.content === "" && !this.addition && !this.richText)
|
|
317
|
+
return;
|
|
318
|
+
let message;
|
|
319
|
+
if (this.addition) {
|
|
320
|
+
message = {
|
|
321
|
+
...message,
|
|
322
|
+
...this.addition.file
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (this.richText) {
|
|
326
|
+
message = { zh_cn: this.richText };
|
|
327
|
+
}
|
|
328
|
+
if (this.content) {
|
|
329
|
+
message = { text: this.content };
|
|
330
|
+
}
|
|
331
|
+
await this.post({
|
|
332
|
+
msg_type: this.richText ? "post" : this.addition ? this.addition.type : "text",
|
|
333
|
+
content: JSON.stringify(message)
|
|
334
|
+
});
|
|
335
|
+
this.quote = void 0;
|
|
336
|
+
this.content = "";
|
|
337
|
+
this.addition = void 0;
|
|
338
|
+
this.richText = void 0;
|
|
339
|
+
}
|
|
340
|
+
async sendFile(type, url) {
|
|
341
|
+
const payload = new import_form_data.default();
|
|
342
|
+
const assetKey = type === "image" ? "image" : "file";
|
|
343
|
+
const [schema, file] = url.split("://");
|
|
344
|
+
const filename = schema === "base64" ? "unknown" : new URL(url).pathname.split("/").pop();
|
|
345
|
+
if (schema === "file") {
|
|
346
|
+
payload.append(assetKey, (0, import_fs.createReadStream)(file));
|
|
347
|
+
} else if (schema === "base64") {
|
|
348
|
+
payload.append(assetKey, Buffer.from(file, "base64"));
|
|
349
|
+
} else {
|
|
350
|
+
const resp = await this.bot.assetsQuester.get(url, { responseType: "stream" });
|
|
351
|
+
payload.append(assetKey, resp);
|
|
352
|
+
}
|
|
353
|
+
if (type === "image") {
|
|
354
|
+
payload.append("image_type", "message");
|
|
355
|
+
const { data } = await this.bot.internal.uploadImage(payload);
|
|
356
|
+
return {
|
|
357
|
+
type: "image",
|
|
358
|
+
file: {
|
|
359
|
+
image_key: data.image_key
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
} else {
|
|
363
|
+
let msgType = "file";
|
|
364
|
+
if (type === "audio") {
|
|
365
|
+
payload.append("file_type", "opus");
|
|
366
|
+
msgType = "audio";
|
|
367
|
+
} else if (type === "video") {
|
|
368
|
+
payload.append("file_type", "mp4");
|
|
369
|
+
msgType = "media";
|
|
370
|
+
} else {
|
|
371
|
+
const ext = filename.split(".").pop();
|
|
372
|
+
if (["xls", "ppt", "pdf"].includes(ext)) {
|
|
373
|
+
payload.append("file_type", ext);
|
|
374
|
+
} else {
|
|
375
|
+
payload.append("file_type", "stream");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
payload.append("file_name", filename);
|
|
379
|
+
const { data } = await this.bot.internal.uploadFile(payload);
|
|
380
|
+
return {
|
|
381
|
+
type: msgType,
|
|
382
|
+
file: {
|
|
383
|
+
file_key: data.file_key
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async visit(element) {
|
|
389
|
+
var _a;
|
|
390
|
+
const { type, attrs, children } = element;
|
|
391
|
+
switch (type) {
|
|
392
|
+
case "text":
|
|
393
|
+
this.content += attrs.content;
|
|
394
|
+
break;
|
|
395
|
+
case "at": {
|
|
396
|
+
if (attrs.type === "all") {
|
|
397
|
+
this.content += `<at user_id="all">${(_a = attrs.name) != null ? _a : "所有人"}</at>`;
|
|
398
|
+
} else {
|
|
399
|
+
this.content += `<at user_id="${attrs.id}">${attrs.name}</at>`;
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case "a":
|
|
404
|
+
await this.render(children);
|
|
405
|
+
if (attrs.href)
|
|
406
|
+
this.content += ` (${attrs.href})`;
|
|
407
|
+
break;
|
|
408
|
+
case "sharp":
|
|
409
|
+
break;
|
|
410
|
+
case "quote":
|
|
411
|
+
await this.flush();
|
|
412
|
+
this.quote = attrs.id;
|
|
413
|
+
break;
|
|
414
|
+
case "image":
|
|
415
|
+
case "video":
|
|
416
|
+
case "audio":
|
|
417
|
+
case "file":
|
|
418
|
+
if (attrs.url) {
|
|
419
|
+
await this.flush();
|
|
420
|
+
this.addition = await this.sendFile(type, attrs.url);
|
|
421
|
+
}
|
|
422
|
+
break;
|
|
423
|
+
case "figure":
|
|
424
|
+
case "message":
|
|
425
|
+
await this.flush();
|
|
426
|
+
await this.render(children, true);
|
|
427
|
+
break;
|
|
428
|
+
default:
|
|
429
|
+
await this.render(children);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
__name(LarkMessenger, "LarkMessenger");
|
|
434
|
+
|
|
435
|
+
// satori/adapters/lark/src/types/index.ts
|
|
436
|
+
var types_exports = {};
|
|
437
|
+
__export(types_exports, {
|
|
438
|
+
Internal: () => Internal
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// satori/adapters/lark/src/types/internal.ts
|
|
442
|
+
var import_form_data2 = __toESM(require("form-data"));
|
|
443
|
+
var import_satori4 = require("@satorijs/satori");
|
|
444
|
+
var logger2 = new import_satori4.Logger("lark");
|
|
445
|
+
var Internal = class {
|
|
446
|
+
constructor(http) {
|
|
447
|
+
this.http = http;
|
|
448
|
+
}
|
|
449
|
+
processReponse(response) {
|
|
450
|
+
const { code, msg } = response;
|
|
451
|
+
if (code === 0) {
|
|
452
|
+
return response;
|
|
453
|
+
} else {
|
|
454
|
+
logger2.debug("response: %o", response);
|
|
455
|
+
throw new Error(`HTTP response with non-zero status (${code}) with message "${msg}"`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
static define(routes) {
|
|
459
|
+
for (const path in routes) {
|
|
460
|
+
for (const key in routes[path]) {
|
|
461
|
+
const method = key;
|
|
462
|
+
for (const name of (0, import_satori4.makeArray)(routes[path][method])) {
|
|
463
|
+
Internal.prototype[name] = async function(...args) {
|
|
464
|
+
const raw = args.join(", ");
|
|
465
|
+
const url = path.replace(/\{([^}]+)\}/g, () => {
|
|
466
|
+
if (!args.length)
|
|
467
|
+
throw new Error(`too few arguments for ${path}, received ${raw}`);
|
|
468
|
+
return args.shift();
|
|
469
|
+
});
|
|
470
|
+
const config = {};
|
|
471
|
+
if (args.length === 1) {
|
|
472
|
+
if (method === "GET" || method === "DELETE") {
|
|
473
|
+
config.params = args[0];
|
|
474
|
+
} else {
|
|
475
|
+
if (method === "POST" && args[0] instanceof import_form_data2.default) {
|
|
476
|
+
config.headers = args[0].getHeaders();
|
|
477
|
+
}
|
|
478
|
+
config.data = args[0];
|
|
479
|
+
}
|
|
480
|
+
} else if (args.length === 2 && method !== "GET" && method !== "DELETE") {
|
|
481
|
+
config.data = args[0];
|
|
482
|
+
config.params = args[1];
|
|
483
|
+
} else if (args.length > 1) {
|
|
484
|
+
throw new Error(`too many arguments for ${path}, received ${raw}`);
|
|
485
|
+
}
|
|
486
|
+
return this.processReponse(await this.http(method, url, config));
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
__name(Internal, "Internal");
|
|
494
|
+
|
|
495
|
+
// satori/adapters/lark/src/types/auth.ts
|
|
496
|
+
Internal.define({
|
|
497
|
+
"/auth/v3/app_access_token/internal": {
|
|
498
|
+
POST: "getAppAccessToken"
|
|
499
|
+
},
|
|
500
|
+
"/auth/v3/tenant_access_token/internal": {
|
|
501
|
+
POST: "getTenantAccessToken"
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// satori/adapters/lark/src/types/guild.ts
|
|
506
|
+
Internal.define({
|
|
507
|
+
"/im/v1/chats": {
|
|
508
|
+
GET: "getCurrentUserGuilds"
|
|
509
|
+
},
|
|
510
|
+
"/im/v1/chats/{chat_id}": {
|
|
511
|
+
GET: "getGuildInfo"
|
|
512
|
+
},
|
|
513
|
+
"/im/v1/chats/{chat_id}/members": {
|
|
514
|
+
GET: "getGuildMembers"
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// satori/adapters/lark/src/types/message/asset.ts
|
|
519
|
+
Internal.define({
|
|
520
|
+
"/im/v1/images": {
|
|
521
|
+
POST: "uploadImage"
|
|
522
|
+
},
|
|
523
|
+
"/im/v1/files": {
|
|
524
|
+
POST: "uploadFile"
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// satori/adapters/lark/src/types/message/index.ts
|
|
529
|
+
Internal.define({
|
|
530
|
+
"/im/v1/messages?receive_id_type={receive_id_type}": {
|
|
531
|
+
POST: "sendMessage"
|
|
532
|
+
},
|
|
533
|
+
"/im/v1/messages/{message_id}/reply": {
|
|
534
|
+
POST: "replyMessage"
|
|
535
|
+
},
|
|
536
|
+
"/im/v1/messages/{message_id}": {
|
|
537
|
+
GET: "getMessage",
|
|
538
|
+
DELETE: "deleteMessage"
|
|
539
|
+
},
|
|
540
|
+
"/im/v1/messages/{message_id}/read_users": {
|
|
541
|
+
GET: "getMessageReadUsers"
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// satori/adapters/lark/src/bot.ts
|
|
546
|
+
var logger3 = new import_satori5.Logger("lark");
|
|
547
|
+
var LarkBot = class extends import_satori5.Bot {
|
|
548
|
+
constructor(ctx, config) {
|
|
549
|
+
super(ctx, config);
|
|
550
|
+
if (!config.selfUrl && !ctx.root.config.selfUrl) {
|
|
551
|
+
logger3.warn("selfUrl is not set, some features may not work");
|
|
552
|
+
}
|
|
553
|
+
this.selfId = config.appId;
|
|
554
|
+
this.http = ctx.http.extend({
|
|
555
|
+
endpoint: config.endpoint,
|
|
556
|
+
headers: {
|
|
557
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
this.assetsQuester = import_satori5.Quester.create();
|
|
561
|
+
this.internal = new Internal(this.http);
|
|
562
|
+
ctx.plugin(HttpServer, this);
|
|
563
|
+
}
|
|
564
|
+
async initialize() {
|
|
565
|
+
await this.refreshToken();
|
|
566
|
+
this.online();
|
|
567
|
+
}
|
|
568
|
+
async refreshToken() {
|
|
569
|
+
const { tenant_access_token: token } = await this.internal.getTenantAccessToken({
|
|
570
|
+
app_id: this.config.appId,
|
|
571
|
+
app_secret: this.config.appSecret
|
|
572
|
+
});
|
|
573
|
+
logger3.debug("refreshed token %s", token);
|
|
574
|
+
this.token = token;
|
|
575
|
+
if (this._refresher)
|
|
576
|
+
clearTimeout(this._refresher);
|
|
577
|
+
this._refresher = setTimeout(() => this.refreshToken(), 3600 * 1e3);
|
|
578
|
+
this.online();
|
|
579
|
+
}
|
|
580
|
+
get token() {
|
|
581
|
+
return this._token;
|
|
582
|
+
}
|
|
583
|
+
set token(v) {
|
|
584
|
+
this._token = v;
|
|
585
|
+
this.http.config.headers.Authorization = `Bearer ${v}`;
|
|
586
|
+
}
|
|
587
|
+
async sendMessage(channelId, content, guildId, options) {
|
|
588
|
+
return new LarkMessenger(this, channelId, guildId, options).send(content);
|
|
589
|
+
}
|
|
590
|
+
async sendPrivateMessage(userId, content, options) {
|
|
591
|
+
return this.sendMessage(userId, content, null, options);
|
|
592
|
+
}
|
|
593
|
+
async deleteMessage(channelId, messageId) {
|
|
594
|
+
await this.internal.deleteMessage(messageId);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
__name(LarkBot, "LarkBot");
|
|
598
|
+
((LarkBot2) => {
|
|
599
|
+
LarkBot2.Config = import_satori5.Schema.intersect([
|
|
600
|
+
import_satori5.Schema.object({
|
|
601
|
+
protocol: import_satori5.Schema.union(["feishu", "lark"]).required().description("平台名称。"),
|
|
602
|
+
appId: import_satori5.Schema.string().required().description("机器人的应用 ID。"),
|
|
603
|
+
appSecret: import_satori5.Schema.string().role("secret").required().description("机器人的应用密钥。"),
|
|
604
|
+
encryptKey: import_satori5.Schema.string().role("secret").description("机器人的 Encrypt Key。"),
|
|
605
|
+
verificationToken: import_satori5.Schema.string().description("事件推送的验证令牌。")
|
|
606
|
+
}),
|
|
607
|
+
import_satori5.Schema.union([
|
|
608
|
+
import_satori5.Schema.intersect([
|
|
609
|
+
import_satori5.Schema.object({
|
|
610
|
+
protocol: import_satori5.Schema.const("feishu").required()
|
|
611
|
+
}),
|
|
612
|
+
import_satori5.Quester.createConfig("https://open.feishu.cn/open-apis/"),
|
|
613
|
+
HttpServer.createConfig("/feishu")
|
|
614
|
+
]),
|
|
615
|
+
import_satori5.Schema.intersect([
|
|
616
|
+
import_satori5.Schema.object({
|
|
617
|
+
protocol: import_satori5.Schema.const("lark").required()
|
|
618
|
+
}),
|
|
619
|
+
import_satori5.Quester.createConfig("https://open.larksuite.com/open-apis/"),
|
|
620
|
+
HttpServer.createConfig("/lark")
|
|
621
|
+
])
|
|
622
|
+
])
|
|
623
|
+
]);
|
|
624
|
+
})(LarkBot || (LarkBot = {}));
|
|
625
|
+
|
|
626
|
+
// satori/adapters/lark/src/index.ts
|
|
627
|
+
var src_default = LarkBot;
|
|
628
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
629
|
+
0 && (module.exports = {
|
|
630
|
+
Feishu,
|
|
631
|
+
FeishuBot,
|
|
632
|
+
Lark,
|
|
633
|
+
LarkBot
|
|
634
|
+
});
|
|
635
|
+
//# sourceMappingURL=index.js.map
|