@coffer-org/plugin-telegram 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/bot.d.ts +1 -0
- package/dist/runtime/bot.js +143 -39
- package/dist/runtime/chain-store.d.ts +28 -0
- package/dist/runtime/chain-store.js +46 -0
- package/dist/runtime/index.js +11 -0
- package/dist/schema.js +151 -23
- package/package.json +5 -5
package/dist/runtime/bot.d.ts
CHANGED
package/dist/runtime/bot.js
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { TelegramClient, Api } from '
|
|
4
|
-
import { LogLevel } from '
|
|
5
|
-
import { StringSession } from '
|
|
6
|
-
import { NewMessage } from '
|
|
7
|
-
import { handleIncoming } from '@coffer-org/plugin-orchestrator/runtime';
|
|
3
|
+
import { TelegramClient, Api } from 'teleproto';
|
|
4
|
+
import { LogLevel } from 'teleproto/extensions/Logger.js';
|
|
5
|
+
import { StringSession } from 'teleproto/sessions/index.js';
|
|
6
|
+
import { NewMessage } from 'teleproto/events/index.js';
|
|
7
|
+
import { handleIncoming, loadGatePolicy } from '@coffer-org/plugin-orchestrator/runtime';
|
|
8
|
+
import { recordUser, recordAssistant, buildChain } from "./chain-store.js";
|
|
8
9
|
import { loadBotConfig, hasCredentials } from "./config.js";
|
|
9
10
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
10
11
|
const log = getLogger('telegram');
|
|
12
|
+
const FALLBACK_REPLY_WINDOW_MS = 1_800_000;
|
|
13
|
+
const lastByChat = new Map();
|
|
14
|
+
async function replyWindowMs() {
|
|
15
|
+
try {
|
|
16
|
+
return (await loadGatePolicy()).replyWindow * 1000;
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
log.warn(`gate policy load: ${err instanceof Error ? err.message : String(err)} — using default reply window`);
|
|
20
|
+
return FALLBACK_REPLY_WINDOW_MS;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function stripHtml(s) {
|
|
24
|
+
return s
|
|
25
|
+
.replace(/<[^>]*>/g, '')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/"/g, '"')
|
|
29
|
+
.replace(/'/g, "'")
|
|
30
|
+
.replace(/&/g, '&');
|
|
31
|
+
}
|
|
32
|
+
const TELEGRAM_FORMAT = [
|
|
33
|
+
'CRITICAL OUTPUT FORMAT — Telegram HTML ONLY. Format the reply strictly with Telegram\'s HTML subset (<b>, <i>, <code>, <a>, …) and NOTHING else. NEVER use Markdown — no *bold*, _italic_, **bold**, `code`, ```blocks```, # headings, - bullets, or | tables. Markdown does not render here; it shows up as literal characters and breaks the message. When in doubt, use an HTML tag or plain text.',
|
|
34
|
+
'Inline styling (tags may nest): <b>bold</b>, <i>italic</i>, <u>underline</u>, <s>strike</s>, <code>inline code</code>, <spoiler>spoiler</spoiler>. Links: <a href="https://…">text</a> (also <a href="tg://user?id=123">mention</a>).',
|
|
35
|
+
'Blocks: <pre>code block</pre> or, for syntax highlight, <pre><code class="language-python">…</code></pre>. Quotes: <blockquote>quote</blockquote>, or <blockquote expandable>long collapsible quote</blockquote>. Bullet lists: start plain lines with "• ".',
|
|
36
|
+
'Only those tags are parsed. NOT supported here (do not use): <tg-spoiler>, <span>, <ins>, <strike>, <tg-time> — use <spoiler>, <u>, <s> instead. Avoid <tg-emoji> (needs a custom-emoji id + Premium); use ordinary emoji.',
|
|
37
|
+
'DO NOT use markdown (*, _, #, ```), tables, or images. Escape literal <, >, and & in prose as <, >, and & so they are not read as tags.',
|
|
38
|
+
'Render any TABLE as a <pre> block with space-aligned columns — real tables are unsupported.',
|
|
39
|
+
'Beyond the tags, use Unicode symbols and emoji to structure and enrich replies — they render everywhere: bullets (•, ▪, –), separators (─────), arrows (→), status marks (✅ ⚠️ ❌ ⏳), and topic emoji (📅 💰 🏥 🛒 🌱 …). They make messages scannable and friendly.',
|
|
40
|
+
'Keep replies short and skimmable (messages cap at 4096 chars): prefer short paragraphs and "• " bullet lists over long prose.',
|
|
41
|
+
].join('\n');
|
|
11
42
|
function loadSessionString(file) {
|
|
12
43
|
try {
|
|
13
44
|
return fs.readFileSync(file, 'utf-8');
|
|
@@ -46,36 +77,62 @@ export async function startBot(opts = {}) {
|
|
|
46
77
|
const me = (await tg.getMe());
|
|
47
78
|
const meId = me?.id?.toString();
|
|
48
79
|
log.info(`connected as @${me?.username || me?.firstName || meId}`);
|
|
49
|
-
async function
|
|
80
|
+
async function sendHtml(peer, text, replyTo) {
|
|
50
81
|
try {
|
|
51
|
-
return await tg.sendMessage(peer, { message: text, parseMode: '
|
|
82
|
+
return await tg.sendMessage(peer, { message: text, parseMode: 'html', replyTo });
|
|
52
83
|
}
|
|
53
84
|
catch {
|
|
54
85
|
try {
|
|
55
|
-
return await tg.sendMessage(peer, { message: text });
|
|
86
|
+
return await tg.sendMessage(peer, { message: stripHtml(text), replyTo });
|
|
56
87
|
}
|
|
57
88
|
catch {
|
|
58
89
|
return undefined;
|
|
59
90
|
}
|
|
60
91
|
}
|
|
61
92
|
}
|
|
62
|
-
async function
|
|
93
|
+
async function editHtml(peer, msgId, text) {
|
|
63
94
|
try {
|
|
64
|
-
await tg.editMessage(peer, { message: msgId, text, parseMode: '
|
|
95
|
+
await tg.editMessage(peer, { message: msgId, text, parseMode: 'html' });
|
|
65
96
|
}
|
|
66
97
|
catch {
|
|
67
98
|
try {
|
|
68
|
-
await tg.editMessage(peer, { message: msgId, text });
|
|
99
|
+
await tg.editMessage(peer, { message: msgId, text: stripHtml(text) });
|
|
69
100
|
}
|
|
70
101
|
catch {
|
|
71
102
|
}
|
|
72
103
|
}
|
|
73
104
|
}
|
|
74
105
|
function startTypingPeer(peer) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
let logged = false;
|
|
107
|
+
let stopped = false;
|
|
108
|
+
let timer;
|
|
109
|
+
const warn = (err) => {
|
|
110
|
+
if (logged)
|
|
111
|
+
return;
|
|
112
|
+
logged = true;
|
|
113
|
+
log.warn(`typing indicator: ${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
};
|
|
115
|
+
void (async () => {
|
|
116
|
+
let input;
|
|
117
|
+
try {
|
|
118
|
+
input = await tg.getInputEntity(peer);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
warn(err);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (stopped)
|
|
125
|
+
return;
|
|
126
|
+
const ping = () => tg
|
|
127
|
+
.invoke(new Api.messages.SetTyping({ peer: input, action: new Api.SendMessageTypingAction() }))
|
|
128
|
+
.catch(warn);
|
|
129
|
+
void ping();
|
|
130
|
+
timer = setInterval(() => void ping(), 5000);
|
|
131
|
+
})();
|
|
132
|
+
return () => {
|
|
133
|
+
stopped = true;
|
|
134
|
+
clearInterval(timer);
|
|
135
|
+
};
|
|
79
136
|
}
|
|
80
137
|
tg.addEventHandler(async (event) => {
|
|
81
138
|
try {
|
|
@@ -100,31 +157,78 @@ export async function startBot(opts = {}) {
|
|
|
100
157
|
catch {
|
|
101
158
|
}
|
|
102
159
|
log.info(`msg [${chatLabel}]: ${text.slice(0, 80)}`);
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
id
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
160
|
+
const stopTyping = startTypingPeer(peer);
|
|
161
|
+
try {
|
|
162
|
+
const chatId = String(event.chatId ?? '?');
|
|
163
|
+
const userId = message.senderId?.toString() ?? '?';
|
|
164
|
+
const incomingMsgId = String(message.id);
|
|
165
|
+
const replyTo = message.replyTo?.replyToMsgId ?? null;
|
|
166
|
+
let replyToId = replyTo != null ? String(replyTo) : null;
|
|
167
|
+
const nowMs = Date.now();
|
|
168
|
+
if (!replyToId) {
|
|
169
|
+
const prev = lastByChat.get(chatId);
|
|
170
|
+
if (prev && nowMs - prev.ts < (await replyWindowMs()))
|
|
171
|
+
replyToId = prev.msgId;
|
|
172
|
+
}
|
|
173
|
+
lastByChat.set(chatId, { msgId: incomingMsgId, ts: nowMs });
|
|
174
|
+
let displayName;
|
|
175
|
+
try {
|
|
176
|
+
const s = (await message.getSender());
|
|
177
|
+
displayName = s?.firstName || s?.username || undefined;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
}
|
|
181
|
+
await recordUser({ chatId, msgId: incomingMsgId, sender: displayName, text, ts: message.date ?? 0, replyToId });
|
|
182
|
+
const fetch = async (id) => {
|
|
183
|
+
try {
|
|
184
|
+
const arr = (await tg.getMessages(peer, { ids: [Number(id)] }));
|
|
185
|
+
const m = arr?.[0];
|
|
186
|
+
if (!m)
|
|
187
|
+
return null;
|
|
188
|
+
return {
|
|
189
|
+
text: m.message ?? '',
|
|
190
|
+
senderId: m.senderId?.toString() ?? '',
|
|
191
|
+
replyToId: m.replyTo?.replyToMsgId?.toString() ?? null,
|
|
192
|
+
date: m.date ?? 0,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
112
196
|
return null;
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const messages = await buildChain(incomingMsgId, { chatId, fetch, botId: meId ?? '' });
|
|
200
|
+
const connector = {
|
|
201
|
+
id: 'telegram',
|
|
202
|
+
capabilities: { streaming: true, typing: true, maxMessageLength: 4096 },
|
|
203
|
+
async sendMessage(_chatId, msgText) {
|
|
204
|
+
const sent = await sendHtml(peer, msgText, Number(incomingMsgId));
|
|
205
|
+
if (!sent)
|
|
206
|
+
return null;
|
|
207
|
+
return { msgId: String(sent.id) };
|
|
208
|
+
},
|
|
209
|
+
async editMessage(_chatId, editId, msgText) {
|
|
210
|
+
await editHtml(peer, Number(editId), msgText);
|
|
211
|
+
},
|
|
212
|
+
startTyping(_chatId) {
|
|
213
|
+
return () => { };
|
|
214
|
+
},
|
|
215
|
+
async recordAssistant(m) {
|
|
216
|
+
await recordAssistant({ ...m, chatId, now: Math.floor(Date.now() / 1000) });
|
|
217
|
+
if (m.botMsgId)
|
|
218
|
+
lastByChat.set(chatId, { msgId: m.botMsgId, ts: Date.now() });
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
await handleIncoming(connector, {
|
|
222
|
+
connectorId: 'telegram',
|
|
223
|
+
chatId,
|
|
224
|
+
channelSystem: TELEGRAM_FORMAT,
|
|
225
|
+
sender: { id: userId, displayName },
|
|
226
|
+
messages,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
stopTyping();
|
|
231
|
+
}
|
|
128
232
|
}
|
|
129
233
|
catch (err) {
|
|
130
234
|
log.error(`message handling: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ConvMessage } from '@coffer-org/plugin-orchestrator/runtime';
|
|
2
|
+
export type FetchMsg = (msgId: string) => Promise<{
|
|
3
|
+
text: string;
|
|
4
|
+
senderId: string;
|
|
5
|
+
replyToId: string | null;
|
|
6
|
+
date: number;
|
|
7
|
+
} | null>;
|
|
8
|
+
export declare function recordUser(m: {
|
|
9
|
+
chatId: string;
|
|
10
|
+
msgId: string;
|
|
11
|
+
sender?: string | null;
|
|
12
|
+
text: string;
|
|
13
|
+
ts: number;
|
|
14
|
+
replyToId: string | null;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export declare function recordAssistant(m: {
|
|
17
|
+
chatId: string;
|
|
18
|
+
parentMsgId: string | null;
|
|
19
|
+
botMsgId: string | null;
|
|
20
|
+
text: string;
|
|
21
|
+
now: number;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
export declare function buildChain(headMsgId: string, opts: {
|
|
24
|
+
chatId: string;
|
|
25
|
+
fetch: FetchMsg;
|
|
26
|
+
botId: string;
|
|
27
|
+
maxDepth?: number;
|
|
28
|
+
}): Promise<ConvMessage[]>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getThreadMessage, putThreadMessage } from '@coffer-org/server/thread-store';
|
|
2
|
+
import { getLogger } from '@coffer-org/sdk/logger';
|
|
3
|
+
const log = getLogger('telegram');
|
|
4
|
+
const CONNECTOR = 'telegram';
|
|
5
|
+
const DEFAULT_MAX_DEPTH = 30;
|
|
6
|
+
export async function recordUser(m) {
|
|
7
|
+
await putThreadMessage({ connector: CONNECTOR, chatId: m.chatId, msgId: m.msgId, role: 'user', sender: m.sender ?? null, text: m.text, ts: m.ts, replyToId: m.replyToId });
|
|
8
|
+
}
|
|
9
|
+
export async function recordAssistant(m) {
|
|
10
|
+
if (m.botMsgId == null)
|
|
11
|
+
return;
|
|
12
|
+
await putThreadMessage({
|
|
13
|
+
connector: CONNECTOR,
|
|
14
|
+
chatId: m.chatId,
|
|
15
|
+
msgId: m.botMsgId,
|
|
16
|
+
role: 'assistant',
|
|
17
|
+
text: m.text,
|
|
18
|
+
ts: m.now,
|
|
19
|
+
replyToId: m.parentMsgId,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function buildChain(headMsgId, opts) {
|
|
23
|
+
const max = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
24
|
+
const acc = [];
|
|
25
|
+
let cur = headMsgId;
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
while (cur && acc.length < max && !seen.has(cur)) {
|
|
28
|
+
seen.add(cur);
|
|
29
|
+
let stored = await getThreadMessage(CONNECTOR, opts.chatId, cur);
|
|
30
|
+
if (!stored) {
|
|
31
|
+
const fetched = await opts.fetch(cur);
|
|
32
|
+
if (!fetched)
|
|
33
|
+
break;
|
|
34
|
+
const role = fetched.senderId === opts.botId ? 'assistant' : 'user';
|
|
35
|
+
await putThreadMessage({ connector: CONNECTOR, chatId: opts.chatId, msgId: cur, role, sender: null, text: fetched.text, ts: fetched.date, replyToId: fetched.replyToId });
|
|
36
|
+
stored = { msgId: cur, role, sender: null, text: fetched.text, ts: fetched.date, replyToId: fetched.replyToId };
|
|
37
|
+
}
|
|
38
|
+
acc.push({ role: stored.role, content: stored.text, sender: stored.sender, msgId: stored.msgId, ts: stored.ts });
|
|
39
|
+
cur = stored.replyToId;
|
|
40
|
+
}
|
|
41
|
+
if (acc.length >= max)
|
|
42
|
+
log.info(`chain capped at ${max} for ${headMsgId}`);
|
|
43
|
+
while (acc.length && acc.at(-1)?.role !== 'user')
|
|
44
|
+
acc.pop();
|
|
45
|
+
return acc.reverse();
|
|
46
|
+
}
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getPluginSettings } from '@coffer-org/server/plugin-runtime';
|
|
2
|
+
import { pruneThreadMessages } from '@coffer-org/server/thread-store';
|
|
2
3
|
import { startBot, stopBot } from "./bot.js";
|
|
3
4
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
4
5
|
const log = getLogger('telegram');
|
|
6
|
+
const THREAD_TTL_MS = Number(process.env['TELEGRAM_THREAD_TTL_MS'] ?? 7 * 86_400_000);
|
|
5
7
|
export { startBot, stopBot };
|
|
6
8
|
export { loadBotConfig, hasCredentials } from "./config.js";
|
|
7
9
|
export const serverHooks = {
|
|
@@ -12,4 +14,13 @@ export const serverHooks = {
|
|
|
12
14
|
teardown: async () => {
|
|
13
15
|
await stopBot();
|
|
14
16
|
},
|
|
17
|
+
backgroundTasks: [
|
|
18
|
+
{
|
|
19
|
+
name: 'telegram-thread-prune',
|
|
20
|
+
intervalMs: 86_400_000,
|
|
21
|
+
run: async () => {
|
|
22
|
+
await pruneThreadMessages('telegram', Math.floor((Date.now() - THREAD_TTL_MS) / 1000));
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
15
26
|
};
|
package/dist/schema.js
CHANGED
|
@@ -21,14 +21,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
//#region ../sdk/
|
|
24
|
+
//#region ../sdk/src/plugin.ts
|
|
25
25
|
function definePlugin(p) {
|
|
26
26
|
if (!p.id) throw new Error("[plugin] missing id");
|
|
27
27
|
if (!p.version) console.warn(`[plugin] ${p.id}: missing version`);
|
|
28
28
|
return p;
|
|
29
29
|
}
|
|
30
30
|
//#endregion
|
|
31
|
-
//#region ../sdk/
|
|
31
|
+
//#region ../sdk/src/settings.ts
|
|
32
32
|
function defineSettings(s) {
|
|
33
33
|
return s;
|
|
34
34
|
}
|
|
@@ -154,7 +154,10 @@ function assignProp(target, prop, value) {
|
|
|
154
154
|
}
|
|
155
155
|
function mergeDefs(...defs) {
|
|
156
156
|
const mergedDescriptors = {};
|
|
157
|
-
for (const def of defs)
|
|
157
|
+
for (const def of defs) {
|
|
158
|
+
const descriptors = Object.getOwnPropertyDescriptors(def);
|
|
159
|
+
Object.assign(mergedDescriptors, descriptors);
|
|
160
|
+
}
|
|
158
161
|
return Object.defineProperties({}, mergedDescriptors);
|
|
159
162
|
}
|
|
160
163
|
function esc(str) {
|
|
@@ -476,7 +479,7 @@ var _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
|
476
479
|
}, ctx);
|
|
477
480
|
if (result instanceof Promise) throw new $ZodAsyncError();
|
|
478
481
|
if (result.issues.length) {
|
|
479
|
-
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
482
|
+
const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
480
483
|
captureStackTrace(e, _params?.callee);
|
|
481
484
|
throw e;
|
|
482
485
|
}
|
|
@@ -493,7 +496,7 @@ var _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
|
|
|
493
496
|
}, ctx);
|
|
494
497
|
if (result instanceof Promise) result = await result;
|
|
495
498
|
if (result.issues.length) {
|
|
496
|
-
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
499
|
+
const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
497
500
|
captureStackTrace(e, params?.callee);
|
|
498
501
|
throw e;
|
|
499
502
|
}
|
|
@@ -1589,13 +1592,13 @@ var $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
|
|
|
1589
1592
|
}
|
|
1590
1593
|
return propValues;
|
|
1591
1594
|
});
|
|
1592
|
-
const isObject$
|
|
1595
|
+
const isObject$2 = isObject;
|
|
1593
1596
|
const catchall = def.catchall;
|
|
1594
1597
|
let value;
|
|
1595
1598
|
inst._zod.parse = (payload, ctx) => {
|
|
1596
1599
|
value ?? (value = _normalized.value);
|
|
1597
1600
|
const input = payload.value;
|
|
1598
|
-
if (!isObject$
|
|
1601
|
+
if (!isObject$2(input)) {
|
|
1599
1602
|
payload.issues.push({
|
|
1600
1603
|
expected: "object",
|
|
1601
1604
|
code: "invalid_type",
|
|
@@ -1718,7 +1721,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
|
|
|
1718
1721
|
return (payload, ctx) => fn(shape, payload, ctx);
|
|
1719
1722
|
};
|
|
1720
1723
|
let fastpass;
|
|
1721
|
-
const isObject$
|
|
1724
|
+
const isObject$1 = isObject;
|
|
1722
1725
|
const jit = !globalConfig.jitless;
|
|
1723
1726
|
const fastEnabled = jit && allowsEval.value;
|
|
1724
1727
|
const catchall = def.catchall;
|
|
@@ -1726,7 +1729,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
|
|
|
1726
1729
|
inst._zod.parse = (payload, ctx) => {
|
|
1727
1730
|
value ?? (value = _normalized.value);
|
|
1728
1731
|
const input = payload.value;
|
|
1729
|
-
if (!isObject$
|
|
1732
|
+
if (!isObject$1(input)) {
|
|
1730
1733
|
payload.issues.push({
|
|
1731
1734
|
expected: "object",
|
|
1732
1735
|
code: "invalid_type",
|
|
@@ -1945,7 +1948,7 @@ var $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => {
|
|
|
1945
1948
|
inst._zod.optin = "optional";
|
|
1946
1949
|
inst._zod.optout = "optional";
|
|
1947
1950
|
defineLazy(inst._zod, "values", () => {
|
|
1948
|
-
return def.innerType._zod.values ? new Set([...def.innerType._zod.values, void 0]) : void 0;
|
|
1951
|
+
return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, void 0]) : void 0;
|
|
1949
1952
|
});
|
|
1950
1953
|
defineLazy(inst._zod, "pattern", () => {
|
|
1951
1954
|
const pattern = def.innerType._zod.pattern;
|
|
@@ -1979,7 +1982,7 @@ var $ZodNullable = /*@__PURE__*/ $constructor("$ZodNullable", (inst, def) => {
|
|
|
1979
1982
|
return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;
|
|
1980
1983
|
});
|
|
1981
1984
|
defineLazy(inst._zod, "values", () => {
|
|
1982
|
-
return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : void 0;
|
|
1985
|
+
return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, null]) : void 0;
|
|
1983
1986
|
});
|
|
1984
1987
|
inst._zod.parse = (payload, ctx) => {
|
|
1985
1988
|
if (payload.value === null) return payload;
|
|
@@ -4078,7 +4081,7 @@ function number(params) {
|
|
|
4078
4081
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
4079
4082
|
}
|
|
4080
4083
|
//#endregion
|
|
4081
|
-
//#region ../sdk/
|
|
4084
|
+
//#region ../sdk/src/units.ts
|
|
4082
4085
|
var UNITS_CURRENCY = [
|
|
4083
4086
|
{
|
|
4084
4087
|
value: "UAH",
|
|
@@ -4386,7 +4389,13 @@ function resolveUnits(u) {
|
|
|
4386
4389
|
return typeof u === "string" ? UNITS_MAP[u] : u;
|
|
4387
4390
|
}
|
|
4388
4391
|
//#endregion
|
|
4389
|
-
//#region ../sdk/
|
|
4392
|
+
//#region ../sdk/src/currencies.ts
|
|
4393
|
+
/**
|
|
4394
|
+
* Валюти з вбудованого `Intl` (ISO 4217) — БЕЗ hand-list і без npm-залежності.
|
|
4395
|
+
* Коди — `Intl.supportedValuesOf('currency')`; символи/назви локалізовані через
|
|
4396
|
+
* `Intl.NumberFormat`/`Intl.DisplayNames`.
|
|
4397
|
+
*/
|
|
4398
|
+
/** Усі ISO 4217 коди. Fallback — кілька основних (на випадок старого рантайму). */
|
|
4390
4399
|
var CURRENCY_CODES = (() => {
|
|
4391
4400
|
try {
|
|
4392
4401
|
return Intl.supportedValuesOf("currency");
|
|
@@ -4401,24 +4410,34 @@ var CURRENCY_CODES = (() => {
|
|
|
4401
4410
|
}
|
|
4402
4411
|
})();
|
|
4403
4412
|
var CODE_SET = new Set(CURRENCY_CODES);
|
|
4413
|
+
/** Чи валідний ISO 4217 код. */
|
|
4404
4414
|
var isCurrencyCode = (c) => CODE_SET.has(c);
|
|
4405
4415
|
//#endregion
|
|
4406
|
-
//#region ../sdk/
|
|
4416
|
+
//#region ../sdk/src/fields/validation.ts
|
|
4417
|
+
/** Структуроване повідомлення для zod: JSON {code, params}. Декодує mutate.ts. */
|
|
4407
4418
|
function vmsg(code, params) {
|
|
4408
4419
|
return JSON.stringify(params ? {
|
|
4409
4420
|
code,
|
|
4410
4421
|
params
|
|
4411
4422
|
} : { code });
|
|
4412
4423
|
}
|
|
4424
|
+
/** v4 error-map: повідомлення для відсутнього значення (колишній required_error). */
|
|
4413
4425
|
function reqErr(code = "required") {
|
|
4414
4426
|
return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
|
|
4415
4427
|
}
|
|
4428
|
+
/** v4 error-map: повідомлення для невалідного типу (колишній invalid_type_error). */
|
|
4416
4429
|
function typeErr(code = "invalid_type") {
|
|
4417
4430
|
return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
|
|
4418
4431
|
}
|
|
4432
|
+
/** v4 error-map: required + invalid_type разом (колишні required_error + invalid_type_error). */
|
|
4419
4433
|
function reqTypeErr() {
|
|
4420
4434
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4421
4435
|
}
|
|
4436
|
+
/** Parse a JSON string, else return the native value (object/array) as-is.
|
|
4437
|
+
* Tolerant input for native form-state AND legacy JSON-string payloads.
|
|
4438
|
+
* A string that fails JSON.parse is returned unchanged — callers detect
|
|
4439
|
+
* parse failure via `typeof result === 'string'` (inners here are object/array,
|
|
4440
|
+
* never a bare string). */
|
|
4422
4441
|
function jsonValue(raw) {
|
|
4423
4442
|
if (typeof raw === "string") try {
|
|
4424
4443
|
return JSON.parse(raw);
|
|
@@ -4427,6 +4446,12 @@ function jsonValue(raw) {
|
|
|
4427
4446
|
}
|
|
4428
4447
|
return raw;
|
|
4429
4448
|
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Фабрика для полів, що зберігають JSON і валідуються вкладеною zod-схемою.
|
|
4451
|
+
* Приймає нативне object/array (native form-state) АБО JSON-рядок (legacy).
|
|
4452
|
+
* Single-parse через jsonValue → inner.safeParse → issue `code`; рядок, що не
|
|
4453
|
+
* розпарсився, лишається рядком → код 'json'.
|
|
4454
|
+
*/
|
|
4430
4455
|
function jsonRefined(inner, code) {
|
|
4431
4456
|
return unknown().superRefine((raw, ctx) => {
|
|
4432
4457
|
const parsed = jsonValue(raw);
|
|
@@ -4455,7 +4480,7 @@ function optionalize(schema, required) {
|
|
|
4455
4480
|
}).pipe(schema));
|
|
4456
4481
|
}
|
|
4457
4482
|
//#endregion
|
|
4458
|
-
//#region ../sdk/
|
|
4483
|
+
//#region ../sdk/src/fields/normalize.ts
|
|
4459
4484
|
function normalizeOpts(rawIn) {
|
|
4460
4485
|
const raw = rawIn;
|
|
4461
4486
|
const r = raw.rules ?? {};
|
|
@@ -4495,7 +4520,19 @@ function normalizeOpts(rawIn) {
|
|
|
4495
4520
|
return Object.fromEntries(Object.entries(result).filter(([, v]) => v !== void 0));
|
|
4496
4521
|
}
|
|
4497
4522
|
//#endregion
|
|
4498
|
-
//#region ../sdk/
|
|
4523
|
+
//#region ../sdk/src/field-presets.ts
|
|
4524
|
+
/**
|
|
4525
|
+
* Пресети полів — тонкі обгортки над базовими примітивами з fields.ts.
|
|
4526
|
+
*
|
|
4527
|
+
* Кожен пресет = один kind (один віджет). Пресети не приймають `format` —
|
|
4528
|
+
* вони самі є семантичними типами. min/max/step — через `config`.
|
|
4529
|
+
*
|
|
4530
|
+
* Пресети додаються до `f` через `composeF`, який гарантує, що жоден пресет
|
|
4531
|
+
* не перебиває примітив.
|
|
4532
|
+
*
|
|
4533
|
+
* Циклічний імпорт з fields.ts безпечний: фабрики/хелпери — hoisted-декларації,
|
|
4534
|
+
* а пресети викликають їх лише в тілі своїх функцій.
|
|
4535
|
+
*/
|
|
4499
4536
|
function email(raw) {
|
|
4500
4537
|
const o = normalizeOpts(raw);
|
|
4501
4538
|
const required = o.required ?? false;
|
|
@@ -4549,6 +4586,14 @@ function password(raw) {
|
|
|
4549
4586
|
zod: optionalize(s, required)
|
|
4550
4587
|
});
|
|
4551
4588
|
}
|
|
4589
|
+
/**
|
|
4590
|
+
* Internal API-token list — keyValue-shaped collection (name + write-only
|
|
4591
|
+
* token + timestamps), custom renderer (`kind:'internalApiToken'`, own
|
|
4592
|
+
* create/revoke UI — same trick as url()/perWeekday()). "Internal" = not a
|
|
4593
|
+
* general-purpose field type for plugin shelves, only for the core account
|
|
4594
|
+
* settings page. `token` is `kind:'password'` so maskSecrets/preserveTree
|
|
4595
|
+
* already mask/preserve it for free.
|
|
4596
|
+
*/
|
|
4552
4597
|
function internalApiToken(o) {
|
|
4553
4598
|
return group({
|
|
4554
4599
|
key: o.key,
|
|
@@ -4641,8 +4686,10 @@ function link(raw) {
|
|
|
4641
4686
|
}, o.multiple ?? false));
|
|
4642
4687
|
}
|
|
4643
4688
|
var TEL_RE = /^\+?[\d\s()-]{4,}$/;
|
|
4689
|
+
/** Loose URL: будь-який scheme:// АБО host-з-крапкою (+опц. порт/шлях). Без пробілів. */
|
|
4644
4690
|
var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
|
|
4645
|
-
|
|
4691
|
+
/** CSS named colors (CSS Color Module L4) — для f.colorname. */
|
|
4692
|
+
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
4646
4693
|
"aliceblue",
|
|
4647
4694
|
"antiquewhite",
|
|
4648
4695
|
"aqua",
|
|
@@ -4900,6 +4947,7 @@ function reminder(raw) {
|
|
|
4900
4947
|
}
|
|
4901
4948
|
var _real = real;
|
|
4902
4949
|
var _int = int;
|
|
4950
|
+
/** Відсоток 0..100 — real з rules:{min:0,max:100}. */
|
|
4903
4951
|
function percent(o) {
|
|
4904
4952
|
return _real({
|
|
4905
4953
|
...o,
|
|
@@ -4910,6 +4958,7 @@ function percent(o) {
|
|
|
4910
4958
|
}
|
|
4911
4959
|
});
|
|
4912
4960
|
}
|
|
4961
|
+
/** Рік — int з rules:{min:1900,max:2100}; межі можна перекрити через rules.min/max. */
|
|
4913
4962
|
function year(o) {
|
|
4914
4963
|
return _int({
|
|
4915
4964
|
...o,
|
|
@@ -5101,7 +5150,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
5101
5150
|
},
|
|
5102
5151
|
cs: {
|
|
5103
5152
|
name: "Czech",
|
|
5104
|
-
nativeName: "
|
|
5153
|
+
nativeName: "čeština"
|
|
5105
5154
|
},
|
|
5106
5155
|
cu: {
|
|
5107
5156
|
name: "Old Church Slavonic",
|
|
@@ -5565,7 +5614,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
5565
5614
|
},
|
|
5566
5615
|
sk: {
|
|
5567
5616
|
name: "Slovak",
|
|
5568
|
-
nativeName: "
|
|
5617
|
+
nativeName: "slovenčtina"
|
|
5569
5618
|
},
|
|
5570
5619
|
sl: {
|
|
5571
5620
|
name: "Slovenian",
|
|
@@ -5730,7 +5779,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
5730
5779
|
};
|
|
5731
5780
|
}));
|
|
5732
5781
|
//#endregion
|
|
5733
|
-
//#region ../sdk/
|
|
5782
|
+
//#region ../sdk/src/fields/constants.ts
|
|
5734
5783
|
var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5735
5784
|
var LANGUAGES_LIST = require_data();
|
|
5736
5785
|
var LANGUAGES = {};
|
|
@@ -5786,7 +5835,21 @@ import_src.default.getAllCodes().map((code) => ({
|
|
|
5786
5835
|
name: import_src.default.getNativeName(code)
|
|
5787
5836
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
5788
5837
|
//#endregion
|
|
5789
|
-
//#region ../sdk/
|
|
5838
|
+
//#region ../sdk/src/fields.ts
|
|
5839
|
+
/**
|
|
5840
|
+
* The field type system — the heart of the platform. A pure, isomorphic module.
|
|
5841
|
+
* One field description → three consumers:
|
|
5842
|
+
* 1. `zod` — validation (identical on server and client)
|
|
5843
|
+
* 2. `column` — the DB column type
|
|
5844
|
+
* 3. `prim`/`kind` — keys for resolving the renderer (see web/render/registry)
|
|
5845
|
+
*/
|
|
5846
|
+
/**
|
|
5847
|
+
* A visual or storage group of fields (one level of nesting — no groups inside groups).
|
|
5848
|
+
* Without `key` → a layout group (not stored). With `key` → storage:
|
|
5849
|
+
* - embedded (key, no multiple): stored as a column or a nested object.
|
|
5850
|
+
* - collection (key + multiple): a child table (array of rows).
|
|
5851
|
+
* A storage group requires a `label` and only `FieldItem` children (with a storage key).
|
|
5852
|
+
*/
|
|
5790
5853
|
function group(o) {
|
|
5791
5854
|
const r = o.rules ?? {};
|
|
5792
5855
|
const v = o.view ?? {};
|
|
@@ -5809,6 +5872,7 @@ function group(o) {
|
|
|
5809
5872
|
fields: o.fields
|
|
5810
5873
|
};
|
|
5811
5874
|
}
|
|
5875
|
+
/** Layout group, flow nowrap + horizontal scroll (single line). Sugar over group(). */
|
|
5812
5876
|
function row(o) {
|
|
5813
5877
|
return group({
|
|
5814
5878
|
label: o.label,
|
|
@@ -5819,6 +5883,7 @@ function row(o) {
|
|
|
5819
5883
|
}
|
|
5820
5884
|
});
|
|
5821
5885
|
}
|
|
5886
|
+
/** Tabular collection (array of rows → <table>, aligned by columns). Sugar. */
|
|
5822
5887
|
function table(o) {
|
|
5823
5888
|
return group({
|
|
5824
5889
|
key: o.key,
|
|
@@ -5833,6 +5898,11 @@ function table(o) {
|
|
|
5833
5898
|
}
|
|
5834
5899
|
});
|
|
5835
5900
|
}
|
|
5901
|
+
/**
|
|
5902
|
+
* Layout group with a 2D layout (rows×columns → aligned CSS grid).
|
|
5903
|
+
* Keyless (flat parent fields). Rows are glued together with brk() separators
|
|
5904
|
+
* into a flat fields; the renderer reconstructs the 2D by splitting on brk. Sugar over group().
|
|
5905
|
+
*/
|
|
5836
5906
|
function sheet(o) {
|
|
5837
5907
|
const flat = [];
|
|
5838
5908
|
o.fields.forEach((rowFields, i) => {
|
|
@@ -5848,6 +5918,12 @@ function sheet(o) {
|
|
|
5848
5918
|
}
|
|
5849
5919
|
});
|
|
5850
5920
|
}
|
|
5921
|
+
/**
|
|
5922
|
+
* Composite URL — embedded-group sugar. Storage: separate nested columns
|
|
5923
|
+
* (`key__scheme`, `key__host`, `key__port`, …) in the same table. The renderer
|
|
5924
|
+
* is custom (a single link/input, `kind:'url'`) — string↔parts via
|
|
5925
|
+
* parseUrl/buildUrl. Partial/invalid URLs are stored by parts.
|
|
5926
|
+
*/
|
|
5851
5927
|
function url(o) {
|
|
5852
5928
|
return group({
|
|
5853
5929
|
key: o.key,
|
|
@@ -5881,18 +5957,22 @@ function keyed(o) {
|
|
|
5881
5957
|
fixed: o.fixed
|
|
5882
5958
|
};
|
|
5883
5959
|
}
|
|
5960
|
+
/** Horizontal divider. */
|
|
5884
5961
|
function divider() {
|
|
5885
5962
|
return { el: "divider" };
|
|
5886
5963
|
}
|
|
5964
|
+
/** Forced flow-row wrap (subsequent fields start on a new row). No <hr>. */
|
|
5887
5965
|
function brk() {
|
|
5888
5966
|
return { el: "break" };
|
|
5889
5967
|
}
|
|
5968
|
+
/** Reference markdown block by i18n key. */
|
|
5890
5969
|
function info(textKey) {
|
|
5891
5970
|
return {
|
|
5892
5971
|
el: "info",
|
|
5893
5972
|
text: textKey
|
|
5894
5973
|
};
|
|
5895
5974
|
}
|
|
5975
|
+
/** Action button: invokes the handler registered in actionRegistry under the key `value`. */
|
|
5896
5976
|
function button(o) {
|
|
5897
5977
|
return {
|
|
5898
5978
|
el: "button",
|
|
@@ -5902,6 +5982,7 @@ function button(o) {
|
|
|
5902
5982
|
variant: o.variant
|
|
5903
5983
|
};
|
|
5904
5984
|
}
|
|
5985
|
+
/** Wraps FieldMeta into FieldItem/StaticEl depending on opts.key/opts.value. */
|
|
5905
5986
|
function wrapKey(opts, meta) {
|
|
5906
5987
|
let m = meta;
|
|
5907
5988
|
if (opts.noEditControl) m = {
|
|
@@ -5911,6 +5992,34 @@ function wrapKey(opts, meta) {
|
|
|
5911
5992
|
noEditControl: true
|
|
5912
5993
|
}
|
|
5913
5994
|
};
|
|
5995
|
+
if (opts.emphasis) m = {
|
|
5996
|
+
...m,
|
|
5997
|
+
hints: {
|
|
5998
|
+
...m.hints,
|
|
5999
|
+
emphasis: opts.emphasis
|
|
6000
|
+
}
|
|
6001
|
+
};
|
|
6002
|
+
if (opts.noLabel) m = {
|
|
6003
|
+
...m,
|
|
6004
|
+
hints: {
|
|
6005
|
+
...m.hints,
|
|
6006
|
+
noLabel: true
|
|
6007
|
+
}
|
|
6008
|
+
};
|
|
6009
|
+
if (opts.role) m = {
|
|
6010
|
+
...m,
|
|
6011
|
+
hints: {
|
|
6012
|
+
...m.hints,
|
|
6013
|
+
role: opts.role
|
|
6014
|
+
}
|
|
6015
|
+
};
|
|
6016
|
+
if (opts.pinned) m = {
|
|
6017
|
+
...m,
|
|
6018
|
+
hints: {
|
|
6019
|
+
...m.hints,
|
|
6020
|
+
pinned: true
|
|
6021
|
+
}
|
|
6022
|
+
};
|
|
5914
6023
|
if (opts.default !== void 0) m = {
|
|
5915
6024
|
...m,
|
|
5916
6025
|
default: opts.default
|
|
@@ -5940,6 +6049,14 @@ function safeJsonParse(s) {
|
|
|
5940
6049
|
return;
|
|
5941
6050
|
}
|
|
5942
6051
|
}
|
|
6052
|
+
/**
|
|
6053
|
+
* Wraps a field into a JSON array of values (storage: TEXT). The inner zod is `base.zod`.
|
|
6054
|
+
* Modifier order: base → applyMultiple.
|
|
6055
|
+
*
|
|
6056
|
+
* Exception: kind 'image'/'media' handle multiple themselves in the renderer (gallery) —
|
|
6057
|
+
* the MultipleField* wrappers on the web skip them.
|
|
6058
|
+
*/
|
|
6059
|
+
/** Zod "JSON array of inner values" — shared by applyMultiple and bindSelectSourceZod. */
|
|
5943
6060
|
function multipleZod(inner) {
|
|
5944
6061
|
return unknown().superRefine((raw, ctx) => {
|
|
5945
6062
|
let arr;
|
|
@@ -6266,8 +6383,8 @@ function relation(raw) {
|
|
|
6266
6383
|
displayKey: o.displayKey ?? "name"
|
|
6267
6384
|
},
|
|
6268
6385
|
relation: {
|
|
6269
|
-
|
|
6270
|
-
type: raw.options.
|
|
6386
|
+
library: raw.options.library,
|
|
6387
|
+
type: raw.options.shelf
|
|
6271
6388
|
},
|
|
6272
6389
|
...multi && { json: true },
|
|
6273
6390
|
zod: optionalize(s, required)
|
|
@@ -6532,6 +6649,11 @@ function geo(raw) {
|
|
|
6532
6649
|
zod: optionalize(s, required)
|
|
6533
6650
|
});
|
|
6534
6651
|
}
|
|
6652
|
+
/**
|
|
6653
|
+
* 7 values, Mon–Sun. A thin wrapper over keyed({fixed}): a fixed collection,
|
|
6654
|
+
* by=select('weekdays'), one real value per day. Its own kind 'per-weekday'
|
|
6655
|
+
* dispatches a compact renderer. Storage: a grandchild table of {day, value} rows.
|
|
6656
|
+
*/
|
|
6535
6657
|
function perWeekday(o) {
|
|
6536
6658
|
return {
|
|
6537
6659
|
...keyed({
|
|
@@ -6824,6 +6946,11 @@ function embed(raw) {
|
|
|
6824
6946
|
zod: optionalize(s, required)
|
|
6825
6947
|
});
|
|
6826
6948
|
}
|
|
6949
|
+
/**
|
|
6950
|
+
* Base primitives (storage-aligned) + structural types. Presets (thin wrappers
|
|
6951
|
+
* over these primitives) are added below from field-presets.ts and CANNOT override
|
|
6952
|
+
* any key from here (guard in composeF).
|
|
6953
|
+
*/
|
|
6827
6954
|
var PRIMITIVES = {
|
|
6828
6955
|
string,
|
|
6829
6956
|
text,
|
|
@@ -6873,6 +7000,7 @@ var PRIMITIVES = {
|
|
|
6873
7000
|
info,
|
|
6874
7001
|
button
|
|
6875
7002
|
};
|
|
7003
|
+
/** Assembles `f`, guaranteeing no preset shadows a primitive. */
|
|
6876
7004
|
function composeF(presets) {
|
|
6877
7005
|
for (const k of Object.keys(presets)) if (k in PRIMITIVES) throw new Error(`[f] preset '${k}' overrides a primitive`);
|
|
6878
7006
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-telegram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"test": "node --import tsx --test \"src/runtime/*.test.ts\""
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@coffer-org/plugin-orchestrator": "^1.
|
|
29
|
-
"@coffer-org/sdk": "^1.
|
|
30
|
-
"@coffer-org/server": "^1.
|
|
28
|
+
"@coffer-org/plugin-orchestrator": "^1.4.0",
|
|
29
|
+
"@coffer-org/sdk": "^1.5.0",
|
|
30
|
+
"@coffer-org/server": "^1.8.0",
|
|
31
31
|
"input": "^1.0.1",
|
|
32
|
-
"
|
|
32
|
+
"teleproto": "^1.228.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {},
|
|
35
35
|
"coffer": {
|