clawgram 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/LICENSE +22 -0
- package/README.md +589 -0
- package/dist/channel.js +1233 -0
- package/dist/clawgram-cli.js +17 -0
- package/dist/cli-core.js +236 -0
- package/dist/cli.js +25 -0
- package/dist/constants.js +7 -0
- package/dist/gramjs-client.js +437 -0
- package/dist/group-reply-address.js +79 -0
- package/dist/group-visible-reply-guard.js +48 -0
- package/dist/helpers.js +495 -0
- package/dist/history.js +286 -0
- package/dist/index.js +20 -0
- package/dist/joins.js +147 -0
- package/dist/normalize.js +125 -0
- package/dist/proxy-config.js +106 -0
- package/dist/types.js +2 -0
- package/dist/update-config.js +381 -0
- package/openclaw.plugin.json +253 -0
- package/package.json +68 -0
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SILENT_REPLY_TOKEN = void 0;
|
|
7
|
+
exports.normalizeOutboundTarget = normalizeOutboundTarget;
|
|
8
|
+
exports.resolveConfiguredAccountId = resolveConfiguredAccountId;
|
|
9
|
+
exports.inferOutboundTargetKind = inferOutboundTargetKind;
|
|
10
|
+
exports.routeKindFromChatType = routeKindFromChatType;
|
|
11
|
+
exports.buildConversationTarget = buildConversationTarget;
|
|
12
|
+
exports.buildScopedGroupPeerId = buildScopedGroupPeerId;
|
|
13
|
+
exports.stripReplyDirectiveTags = stripReplyDirectiveTags;
|
|
14
|
+
exports.readLatestAssistantFallbackFromTranscript = readLatestAssistantFallbackFromTranscript;
|
|
15
|
+
exports.resolveActionTarget = resolveActionTarget;
|
|
16
|
+
exports.resolveReplyToMessageIdForTarget = resolveReplyToMessageIdForTarget;
|
|
17
|
+
exports.readMessageText = readMessageText;
|
|
18
|
+
exports.resolveAllowFrom = resolveAllowFrom;
|
|
19
|
+
exports.resolveGroupPolicy = resolveGroupPolicy;
|
|
20
|
+
exports.resolveGroups = resolveGroups;
|
|
21
|
+
exports.resolveGroupConfig = resolveGroupConfig;
|
|
22
|
+
exports.resolveActiveUsername = resolveActiveUsername;
|
|
23
|
+
exports.normalizeAllowEntry = normalizeAllowEntry;
|
|
24
|
+
exports.isSenderAllowed = isSenderAllowed;
|
|
25
|
+
exports.hasTelegramMention = hasTelegramMention;
|
|
26
|
+
exports.toDisplayName = toDisplayName;
|
|
27
|
+
exports.withTimeout = withTimeout;
|
|
28
|
+
exports.stripSilentReplyToken = stripSilentReplyToken;
|
|
29
|
+
exports.isSilentReplyText = isSilentReplyText;
|
|
30
|
+
exports.prefixReplyTextToAddress = prefixReplyTextToAddress;
|
|
31
|
+
exports.resolveReplyTarget = resolveReplyTarget;
|
|
32
|
+
exports.resolveChatTarget = resolveChatTarget;
|
|
33
|
+
exports.isReplyToSelfMessage = isReplyToSelfMessage;
|
|
34
|
+
exports.resolveSenderProfile = resolveSenderProfile;
|
|
35
|
+
exports.resolveSenderProfileWithTimeout = resolveSenderProfileWithTimeout;
|
|
36
|
+
const node_fs_1 = require("node:fs");
|
|
37
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
38
|
+
const core_1 = require("openclaw/plugin-sdk/core");
|
|
39
|
+
const channel_inbound_1 = require("openclaw/plugin-sdk/channel-inbound");
|
|
40
|
+
const constants_1 = require("./constants");
|
|
41
|
+
function resolveConfiguredAccountId(cfg, preferred) {
|
|
42
|
+
if (preferred?.trim()) {
|
|
43
|
+
return preferred.trim();
|
|
44
|
+
}
|
|
45
|
+
const accounts = cfg?.channels?.[constants_1.CHANNEL_ID]?.accounts;
|
|
46
|
+
if (!accounts || typeof accounts !== "object") {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return Object.keys(accounts).find((accountId) => accounts?.[accountId]?.enabled !== false);
|
|
50
|
+
}
|
|
51
|
+
function normalizeOutboundTarget(rawTarget) {
|
|
52
|
+
const withoutChannel = (0, core_1.stripChannelTargetPrefix)(rawTarget, constants_1.CHANNEL_ID, "tguserbot", "telegram", "tg");
|
|
53
|
+
return (0, core_1.stripTargetKindPrefix)(withoutChannel).trim();
|
|
54
|
+
}
|
|
55
|
+
function inferOutboundTargetKind(rawTarget, resolvedKind) {
|
|
56
|
+
if (resolvedKind) {
|
|
57
|
+
return resolvedKind;
|
|
58
|
+
}
|
|
59
|
+
const withoutChannel = (0, core_1.stripChannelTargetPrefix)(rawTarget, constants_1.CHANNEL_ID, "tguserbot", "telegram", "tg").trim();
|
|
60
|
+
const prefix = withoutChannel.match(/^(user|channel|group|conversation|room|dm):/i)?.[1]?.toLowerCase();
|
|
61
|
+
if (prefix === "group" || prefix === "room" || prefix === "conversation") {
|
|
62
|
+
return "group";
|
|
63
|
+
}
|
|
64
|
+
if (prefix === "channel") {
|
|
65
|
+
return "channel";
|
|
66
|
+
}
|
|
67
|
+
if (prefix === "user" || prefix === "dm") {
|
|
68
|
+
return "user";
|
|
69
|
+
}
|
|
70
|
+
const target = normalizeOutboundTarget(rawTarget);
|
|
71
|
+
if (target.startsWith("-")) {
|
|
72
|
+
return "group";
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
function routeKindFromChatType(chatType) {
|
|
77
|
+
return chatType === "group" || chatType === "channel" ? chatType : "direct";
|
|
78
|
+
}
|
|
79
|
+
function buildConversationTarget(chatId) {
|
|
80
|
+
return `${constants_1.CHANNEL_ID}:${chatId}`;
|
|
81
|
+
}
|
|
82
|
+
function buildScopedGroupPeerId(accountId, chatId) {
|
|
83
|
+
const scopedAccountId = (accountId ?? "default").trim() || "default";
|
|
84
|
+
return `${scopedAccountId}:${chatId}`;
|
|
85
|
+
}
|
|
86
|
+
function stripReplyDirectiveTags(text) {
|
|
87
|
+
return text
|
|
88
|
+
.replace(/\[\[\s*reply_to_current\s*\]\]/gi, " ")
|
|
89
|
+
.replace(/\[\[\s*reply_to\s*:\s*[^\]\n]+\s*\]\]/gi, " ")
|
|
90
|
+
.replace(/\[\[\s*audio_as_voice\s*\]\]/gi, " ")
|
|
91
|
+
.replace(/\s+/g, " ")
|
|
92
|
+
.trim();
|
|
93
|
+
}
|
|
94
|
+
function resolveTranscriptPathFromStoreEntry(input) {
|
|
95
|
+
const sessionId = typeof input.entry?.sessionId === "string" && input.entry.sessionId.trim()
|
|
96
|
+
? input.entry.sessionId.trim()
|
|
97
|
+
: input.sessionKey.trim();
|
|
98
|
+
if (!sessionId) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const sessionsDir = node_path_1.default.dirname(node_path_1.default.resolve(input.storePath));
|
|
102
|
+
const sessionFile = typeof input.entry?.sessionFile === "string" ? input.entry.sessionFile.trim() : "";
|
|
103
|
+
const candidateFileName = sessionFile || `${sessionId}.jsonl`;
|
|
104
|
+
try {
|
|
105
|
+
return node_path_1.default.resolve(sessionsDir, candidateFileName);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function readLatestAssistantFallbackFromTranscript(sessionKey, storePath) {
|
|
112
|
+
if (!storePath?.trim()) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const rawStore = (0, node_fs_1.readFileSync)(storePath, "utf8");
|
|
117
|
+
const store = JSON.parse(rawStore);
|
|
118
|
+
const sessionFile = resolveTranscriptPathFromStoreEntry({
|
|
119
|
+
storePath,
|
|
120
|
+
sessionKey,
|
|
121
|
+
entry: store?.[sessionKey],
|
|
122
|
+
});
|
|
123
|
+
if (!sessionFile) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const lines = (0, node_fs_1.readFileSync)(sessionFile, "utf8")
|
|
127
|
+
.split("\n")
|
|
128
|
+
.map((line) => line.trim())
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
131
|
+
try {
|
|
132
|
+
const entry = JSON.parse(lines[index]);
|
|
133
|
+
if (entry?.type !== "message" || entry?.message?.role !== "assistant" || !Array.isArray(entry.message.content)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const textPart = entry.message.content.find((part) => part?.type === "text" && typeof part.text === "string" && part.text.trim());
|
|
137
|
+
if (!textPart?.text) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const cleaned = stripReplyDirectiveTags(textPart.text);
|
|
141
|
+
if (cleaned) {
|
|
142
|
+
return cleaned;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
function resolveActionTarget(params, toolContext) {
|
|
156
|
+
const explicitTo = (0, core_1.readStringParam)(params, "to") ?? (0, core_1.readStringParam)(params, "target");
|
|
157
|
+
if (explicitTo?.trim()) {
|
|
158
|
+
return explicitTo.trim();
|
|
159
|
+
}
|
|
160
|
+
const contextTarget = toolContext?.currentChannelId?.trim();
|
|
161
|
+
if (contextTarget) {
|
|
162
|
+
return contextTarget;
|
|
163
|
+
}
|
|
164
|
+
throw new Error("clawgram: message target is required");
|
|
165
|
+
}
|
|
166
|
+
function resolveReplyToMessageIdForTarget(rawTarget, replyToId) {
|
|
167
|
+
if (replyToId === null || replyToId === undefined || replyToId === "") {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const targetKind = inferOutboundTargetKind(rawTarget);
|
|
171
|
+
if (targetKind === "group" || targetKind === "channel") {
|
|
172
|
+
return Number(replyToId);
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
function readMessageText(params) {
|
|
177
|
+
const message = (0, core_1.readStringParam)(params, "message", { allowEmpty: true });
|
|
178
|
+
if (typeof message === "string") {
|
|
179
|
+
return message;
|
|
180
|
+
}
|
|
181
|
+
const text = (0, core_1.readStringParam)(params, "text", { allowEmpty: true });
|
|
182
|
+
if (typeof text === "string") {
|
|
183
|
+
return text;
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
function resolveAllowFrom(value) {
|
|
188
|
+
if (value === "*") {
|
|
189
|
+
return ["*"];
|
|
190
|
+
}
|
|
191
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
192
|
+
const entry = String(value).trim();
|
|
193
|
+
return entry ? [entry] : ["*"];
|
|
194
|
+
}
|
|
195
|
+
if (!Array.isArray(value)) {
|
|
196
|
+
return ["*"];
|
|
197
|
+
}
|
|
198
|
+
const entries = value.map((entry) => String(entry).trim()).filter(Boolean);
|
|
199
|
+
return entries.length > 0 ? entries : ["*"];
|
|
200
|
+
}
|
|
201
|
+
function resolveGroupPolicy(value) {
|
|
202
|
+
return value === "open" ? "open" : "mention";
|
|
203
|
+
}
|
|
204
|
+
function resolveGroups(value) {
|
|
205
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
const entries = Object.entries(value);
|
|
209
|
+
return Object.fromEntries(entries.map(([groupId, rawConfig]) => {
|
|
210
|
+
const groupConfig = rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig)
|
|
211
|
+
? rawConfig
|
|
212
|
+
: {};
|
|
213
|
+
return [
|
|
214
|
+
String(groupId).trim(),
|
|
215
|
+
{
|
|
216
|
+
enabled: groupConfig.enabled !== false,
|
|
217
|
+
groupPolicy: resolveGroupPolicy(groupConfig.groupPolicy),
|
|
218
|
+
allowFrom: resolveAllowFrom(groupConfig.allowFrom),
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
}).filter(([groupId]) => Boolean(groupId)));
|
|
222
|
+
}
|
|
223
|
+
function resolveGroupConfig(groups, chatId) {
|
|
224
|
+
return groups[chatId] ?? groups["*"];
|
|
225
|
+
}
|
|
226
|
+
function resolveActiveUsername(source) {
|
|
227
|
+
if (typeof source?.username === "string" && source.username.trim()) {
|
|
228
|
+
return source.username.trim();
|
|
229
|
+
}
|
|
230
|
+
const activeUsername = Array.isArray(source?.usernames)
|
|
231
|
+
? source.usernames.find((entry) => entry?.active !== false && typeof entry?.username === "string")?.username
|
|
232
|
+
: undefined;
|
|
233
|
+
return typeof activeUsername === "string" && activeUsername.trim() ? activeUsername.trim() : undefined;
|
|
234
|
+
}
|
|
235
|
+
function normalizeAllowEntry(value) {
|
|
236
|
+
return value.trim().replace(/^@/, "").toLowerCase();
|
|
237
|
+
}
|
|
238
|
+
function isSenderAllowed(input) {
|
|
239
|
+
if (input.allowFrom.includes("*")) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
const senderIds = [
|
|
243
|
+
input.senderId,
|
|
244
|
+
input.senderUsername,
|
|
245
|
+
input.senderUsername ? `@${input.senderUsername}` : undefined,
|
|
246
|
+
].filter((value) => Boolean(value)).map(normalizeAllowEntry);
|
|
247
|
+
return input.allowFrom.map(normalizeAllowEntry).some((entry) => senderIds.includes(entry));
|
|
248
|
+
}
|
|
249
|
+
function hasTelegramMention(input) {
|
|
250
|
+
const normalizedText = input.text.trim();
|
|
251
|
+
const message = input.message;
|
|
252
|
+
const mentionRegexes = (0, channel_inbound_1.buildMentionRegexes)(input.cfg, input.agentId);
|
|
253
|
+
const selfUsername = input.selfUsername?.replace(/^@/, "").trim();
|
|
254
|
+
const entities = Array.isArray(message?.entities) ? message.entities : [];
|
|
255
|
+
const hasAnyMention = Boolean(message?.mentioned) ||
|
|
256
|
+
entities.some((entity) => {
|
|
257
|
+
const kind = typeof entity?.className === "string" ? entity.className : entity?.type;
|
|
258
|
+
return kind === "MessageEntityMention" || kind === "mention" || kind === "MessageEntityMentionName" || kind === "InputMessageEntityMentionName";
|
|
259
|
+
}) ||
|
|
260
|
+
/(^|\s)@[a-zA-Z0-9_]{5,}\b/.test(normalizedText);
|
|
261
|
+
const entityExplicitMention = Boolean(selfUsername) && entities.some((entity) => {
|
|
262
|
+
const kind = typeof entity?.className === "string" ? entity.className : entity?.type;
|
|
263
|
+
if (kind !== "MessageEntityMention" && kind !== "mention") {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const offset = typeof entity?.offset === "number" ? entity.offset : -1;
|
|
267
|
+
const length = typeof entity?.length === "number" ? entity.length : 0;
|
|
268
|
+
if (offset < 0 || length <= 0) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return normalizedText.slice(offset, offset + length).replace(/^@/, "").trim().toLowerCase() === selfUsername.toLowerCase();
|
|
272
|
+
});
|
|
273
|
+
const explicitlyMentioned = Boolean(selfUsername) &&
|
|
274
|
+
(message?.mentioned === true ||
|
|
275
|
+
entityExplicitMention ||
|
|
276
|
+
new RegExp(`(^|\\s)@${selfUsername?.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(normalizedText));
|
|
277
|
+
return (0, channel_inbound_1.matchesMentionWithExplicit)({
|
|
278
|
+
text: normalizedText,
|
|
279
|
+
mentionRegexes,
|
|
280
|
+
explicit: {
|
|
281
|
+
hasAnyMention,
|
|
282
|
+
isExplicitlyMentioned: explicitlyMentioned,
|
|
283
|
+
canResolveExplicit: Boolean(selfUsername),
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function toDisplayName(input) {
|
|
288
|
+
if (input.username) {
|
|
289
|
+
return `@${input.username}`;
|
|
290
|
+
}
|
|
291
|
+
const fullName = [input.firstName, input.lastName].filter(Boolean).join(" ").trim();
|
|
292
|
+
return fullName || input.fallback || "Telegram";
|
|
293
|
+
}
|
|
294
|
+
async function withTimeout(promise, timeoutMs) {
|
|
295
|
+
let timer;
|
|
296
|
+
try {
|
|
297
|
+
return await Promise.race([
|
|
298
|
+
promise,
|
|
299
|
+
new Promise((resolve) => {
|
|
300
|
+
timer = setTimeout(() => resolve(undefined), timeoutMs);
|
|
301
|
+
}),
|
|
302
|
+
]);
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
if (timer) {
|
|
306
|
+
clearTimeout(timer);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* OpenClaw's shared silent-reply sentinel. When the agent decides not to answer
|
|
312
|
+
* it returns this token instead of text, and surfaces are expected to drop the
|
|
313
|
+
* message rather than deliver the token.
|
|
314
|
+
*
|
|
315
|
+
* Core owns the canonical helpers (`SILENT_REPLY_TOKEN`, `isSilentReplyText`,
|
|
316
|
+
* `stripSilentToken` in `src/auto-reply/tokens`), but they are not re-exported
|
|
317
|
+
* through any of the public `openclaw/plugin-sdk/*` entry points, so the
|
|
318
|
+
* behaviour is mirrored here. If the SDK ever exposes them, drop this block and
|
|
319
|
+
* import instead.
|
|
320
|
+
*/
|
|
321
|
+
const SILENT_REPLY_TOKEN = "NO_REPLY";
|
|
322
|
+
exports.SILENT_REPLY_TOKEN = SILENT_REPLY_TOKEN;
|
|
323
|
+
function escapeRegExp(value) {
|
|
324
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Remove leading and trailing occurrences of the silent token.
|
|
328
|
+
*
|
|
329
|
+
* Leading tokens may be glued to the following text ("NO_REPLYstill thinking"),
|
|
330
|
+
* so the match is not anchored on a word boundary, and punctuation directly
|
|
331
|
+
* after a leading token belongs to the marker rather than to the text.
|
|
332
|
+
*
|
|
333
|
+
* Only whitespace is consumed before a trailing token: punctuation there ends
|
|
334
|
+
* the preceding sentence and must survive ("Done. NO_REPLY" -> "Done.").
|
|
335
|
+
*
|
|
336
|
+
* Occurrences in the middle of a sentence are left alone: there the token is
|
|
337
|
+
* content, not a control marker.
|
|
338
|
+
*
|
|
339
|
+
* Returns the remaining visible text. An empty result means the whole payload
|
|
340
|
+
* was the token and nothing should be sent.
|
|
341
|
+
*/
|
|
342
|
+
function stripSilentReplyToken(text, token = SILENT_REPLY_TOKEN) {
|
|
343
|
+
const escaped = escapeRegExp(token);
|
|
344
|
+
const leading = new RegExp(`^(?:${escaped})[\\s,.:;!—-]*`, "i");
|
|
345
|
+
const trailing = new RegExp(`\\s*(?:${escaped})$`, "i");
|
|
346
|
+
let result = text.trim();
|
|
347
|
+
while (leading.test(result)) {
|
|
348
|
+
const next = result.replace(leading, "").trim();
|
|
349
|
+
if (next === result) {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
result = next;
|
|
353
|
+
}
|
|
354
|
+
return result.replace(trailing, "").trim();
|
|
355
|
+
}
|
|
356
|
+
/** True when the payload carries no visible text beyond the silent token. */
|
|
357
|
+
function isSilentReplyText(text, token = SILENT_REPLY_TOKEN) {
|
|
358
|
+
const trimmed = typeof text === "string" ? text.trim() : "";
|
|
359
|
+
if (!trimmed) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
return stripSilentReplyToken(trimmed, token).length === 0;
|
|
363
|
+
}
|
|
364
|
+
function prefixReplyTextToAddress(text, address) {
|
|
365
|
+
const outboundText = text.trim();
|
|
366
|
+
if (!address) {
|
|
367
|
+
return outboundText;
|
|
368
|
+
}
|
|
369
|
+
const lowerText = outboundText.toLowerCase();
|
|
370
|
+
const lowerAddress = address.toLowerCase();
|
|
371
|
+
if (lowerText === lowerAddress ||
|
|
372
|
+
lowerText.startsWith(`${lowerAddress},`) ||
|
|
373
|
+
lowerText.startsWith(`${lowerAddress}:`) ||
|
|
374
|
+
lowerText.startsWith(`${lowerAddress} `)) {
|
|
375
|
+
return outboundText;
|
|
376
|
+
}
|
|
377
|
+
return `${address}, ${outboundText}`;
|
|
378
|
+
}
|
|
379
|
+
async function resolveReplyTarget(message) {
|
|
380
|
+
const directInputSender = typeof message?.getInputSender === "function"
|
|
381
|
+
? await message.getInputSender().catch(() => undefined)
|
|
382
|
+
: undefined;
|
|
383
|
+
if (directInputSender) {
|
|
384
|
+
return directInputSender;
|
|
385
|
+
}
|
|
386
|
+
const sender = typeof message?.getSender === "function"
|
|
387
|
+
? await message.getSender().catch(() => undefined)
|
|
388
|
+
: undefined;
|
|
389
|
+
if (sender) {
|
|
390
|
+
return sender;
|
|
391
|
+
}
|
|
392
|
+
const directInputChat = typeof message?.getInputChat === "function"
|
|
393
|
+
? await message.getInputChat().catch(() => undefined)
|
|
394
|
+
: undefined;
|
|
395
|
+
if (directInputChat) {
|
|
396
|
+
return directInputChat;
|
|
397
|
+
}
|
|
398
|
+
const chat = typeof message?.getChat === "function"
|
|
399
|
+
? await message.getChat().catch(() => undefined)
|
|
400
|
+
: undefined;
|
|
401
|
+
if (chat) {
|
|
402
|
+
return chat;
|
|
403
|
+
}
|
|
404
|
+
return message?.inputSender ?? message?._inputSender ?? message?.sender ?? message?._sender ?? message?.inputChat ?? message?._inputChat ?? message?.chat ?? message?._chat ?? message?.peerId;
|
|
405
|
+
}
|
|
406
|
+
async function resolveChatTarget(message) {
|
|
407
|
+
const directInputChat = typeof message?.getInputChat === "function"
|
|
408
|
+
? await message.getInputChat().catch(() => undefined)
|
|
409
|
+
: undefined;
|
|
410
|
+
if (directInputChat) {
|
|
411
|
+
return directInputChat;
|
|
412
|
+
}
|
|
413
|
+
const chat = typeof message?.getChat === "function"
|
|
414
|
+
? await message.getChat().catch(() => undefined)
|
|
415
|
+
: undefined;
|
|
416
|
+
if (chat) {
|
|
417
|
+
return chat;
|
|
418
|
+
}
|
|
419
|
+
return message?.inputChat ?? message?._inputChat ?? message?.chat ?? message?._chat ?? message?.peerId;
|
|
420
|
+
}
|
|
421
|
+
async function isReplyToSelfMessage(message, selfId) {
|
|
422
|
+
if (!selfId) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
const replyToMessageId = message?.replyTo?.replyToMsgId ?? message?.replyToMsgId;
|
|
426
|
+
if (!replyToMessageId) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
const replied = typeof message?.getReplyMessage === "function"
|
|
430
|
+
? await message.getReplyMessage().catch(() => undefined)
|
|
431
|
+
: undefined;
|
|
432
|
+
if (!replied) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
if (replied.out === true) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
const replySenderId = replied.senderId ??
|
|
439
|
+
replied.fromId?.userId ??
|
|
440
|
+
replied.fromId?.channelId;
|
|
441
|
+
return replySenderId !== undefined && String(replySenderId) === selfId;
|
|
442
|
+
}
|
|
443
|
+
async function resolveSenderProfile(message, input) {
|
|
444
|
+
const pickProfile = (source) => {
|
|
445
|
+
const activeUsername = Array.isArray(source?.usernames)
|
|
446
|
+
? source.usernames.find((entry) => entry?.active !== false && typeof entry?.username === "string")?.username
|
|
447
|
+
: undefined;
|
|
448
|
+
return {
|
|
449
|
+
username: typeof source?.username === "string" ? source.username : activeUsername,
|
|
450
|
+
firstName: typeof source?.firstName === "string" ? source.firstName : undefined,
|
|
451
|
+
lastName: typeof source?.lastName === "string" ? source.lastName : undefined,
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
const sender = typeof message?.getSender === "function"
|
|
455
|
+
? await message.getSender().catch(() => undefined)
|
|
456
|
+
: undefined;
|
|
457
|
+
const inputSender = typeof message?.getInputSender === "function"
|
|
458
|
+
? await message.getInputSender().catch(() => undefined)
|
|
459
|
+
: undefined;
|
|
460
|
+
const inputSenderEntity = inputSender && typeof input?.client?.getEntity === "function"
|
|
461
|
+
? await input.client.getEntity(inputSender).catch(() => undefined)
|
|
462
|
+
: undefined;
|
|
463
|
+
const fromEntity = message?.fromId && typeof input?.client?.getEntity === "function"
|
|
464
|
+
? await input.client.getEntity(message.fromId).catch(() => undefined)
|
|
465
|
+
: undefined;
|
|
466
|
+
const numericSenderId = input?.senderId && /^\d+$/.test(input.senderId) && Number.isSafeInteger(Number(input.senderId))
|
|
467
|
+
? Number(input.senderId)
|
|
468
|
+
: undefined;
|
|
469
|
+
const entity = input?.senderId && typeof input?.client?.getEntity === "function"
|
|
470
|
+
? await input.client.getEntity(numericSenderId ?? input.senderId).catch(() => undefined)
|
|
471
|
+
: undefined;
|
|
472
|
+
const profiles = [
|
|
473
|
+
pickProfile(sender),
|
|
474
|
+
pickProfile(inputSenderEntity),
|
|
475
|
+
pickProfile(fromEntity),
|
|
476
|
+
pickProfile(entity),
|
|
477
|
+
pickProfile(message?.sender),
|
|
478
|
+
pickProfile(message?._sender),
|
|
479
|
+
];
|
|
480
|
+
const profile = profiles.find((candidate) => candidate.username) ??
|
|
481
|
+
profiles.find((candidate) => candidate.firstName || candidate.lastName);
|
|
482
|
+
const username = profile?.username;
|
|
483
|
+
const display = toDisplayName({
|
|
484
|
+
username,
|
|
485
|
+
firstName: profile?.firstName,
|
|
486
|
+
lastName: profile?.lastName,
|
|
487
|
+
});
|
|
488
|
+
return {
|
|
489
|
+
username,
|
|
490
|
+
display,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
async function resolveSenderProfileWithTimeout(message, input, timeoutMs = 1500) {
|
|
494
|
+
return await withTimeout(resolveSenderProfile(message, input), timeoutMs) ?? {};
|
|
495
|
+
}
|