cliclaw 1.0.41 → 1.0.43
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/index.ts +24 -4
- package/package.json +1 -1
- package/src/agents/claude.ts +9 -6
- package/src/handlers/commands.ts +39 -0
- package/src/handlers/send.ts +261 -0
- package/src/i18n.ts +4 -0
package/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Storage } from './src/storage'
|
|
|
4
4
|
import { registerCommands } from './src/handlers/commands'
|
|
5
5
|
import { registerMessageHandler } from './src/handlers/messages'
|
|
6
6
|
import { respondApproval } from './src/handlers/approvals'
|
|
7
|
+
import { handleSndCallback, handleUsoCallback } from './src/handlers/send'
|
|
7
8
|
|
|
8
9
|
const config = loadConfig()
|
|
9
10
|
const storage = new Storage(config.DATA_DIR)
|
|
@@ -36,10 +37,29 @@ bot.on('callback_query:data', async (ctx) => {
|
|
|
36
37
|
const data = ctx.callbackQuery.data
|
|
37
38
|
console.log(`[callback] recebido: ${data.slice(0, 60)}`)
|
|
38
39
|
|
|
39
|
-
const colonIdx
|
|
40
|
-
const prefix
|
|
41
|
-
const
|
|
42
|
-
|
|
40
|
+
const colonIdx = data.indexOf(':')
|
|
41
|
+
const prefix = data.slice(0, colonIdx)
|
|
42
|
+
const rest = data.slice(colonIdx + 1)
|
|
43
|
+
|
|
44
|
+
// ── uso:* menu callbacks ────────────────────────────────────────────────
|
|
45
|
+
if (prefix === 'uso') {
|
|
46
|
+
await handleUsoCallback(ctx, storage, config, rest).catch(e =>
|
|
47
|
+
console.error(`[callback] uso erro: ${e.message}`)
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── snd:* send/files callbacks ──────────────────────────────────────────
|
|
53
|
+
if (prefix === 'snd') {
|
|
54
|
+
await handleSndCallback(ctx, storage, config, rest).catch(e =>
|
|
55
|
+
console.error(`[callback] snd erro: ${e.message}`)
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── codex approval callbacks ────────────────────────────────────────────
|
|
61
|
+
const approvalId = rest
|
|
62
|
+
const entry = APPROVAL_PREFIXES[prefix]
|
|
43
63
|
if (!entry) return
|
|
44
64
|
|
|
45
65
|
console.log(`[callback] clicou ${prefix} id=${approvalId.slice(0, 8)}...`)
|
package/package.json
CHANGED
package/src/agents/claude.ts
CHANGED
|
@@ -34,7 +34,7 @@ interface ClaudeResult {
|
|
|
34
34
|
usage?: TokenUsage
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function spawnClaude(args: string[]): Promise<ClaudeResult> {
|
|
37
|
+
function spawnClaude(args: string[], stdinText?: string): Promise<ClaudeResult> {
|
|
38
38
|
return new Promise((resolve, reject) => {
|
|
39
39
|
let stdout = ''
|
|
40
40
|
let stderr = ''
|
|
@@ -43,8 +43,11 @@ function spawnClaude(args: string[]): Promise<ClaudeResult> {
|
|
|
43
43
|
env: buildEnv(),
|
|
44
44
|
shell: isWin,
|
|
45
45
|
windowsHide: true,
|
|
46
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
46
|
+
stdio: [stdinText !== undefined ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
47
47
|
})
|
|
48
|
+
if (stdinText !== undefined) {
|
|
49
|
+
proc.stdin!.end(stdinText, 'utf8')
|
|
50
|
+
}
|
|
48
51
|
proc.stdout.on('data', (d: Buffer) => { stdout += d.toString() })
|
|
49
52
|
proc.stderr.on('data', (d: Buffer) => { stderr += d.toString() })
|
|
50
53
|
proc.on('close', (code) => {
|
|
@@ -80,18 +83,18 @@ export async function askClaude(
|
|
|
80
83
|
if (session.claudeSessionId) {
|
|
81
84
|
return await spawnClaude([
|
|
82
85
|
'--resume', session.claudeSessionId,
|
|
83
|
-
'-p',
|
|
86
|
+
'-p',
|
|
84
87
|
'--output-format', 'json',
|
|
85
88
|
'--dangerously-skip-permissions',
|
|
86
|
-
])
|
|
89
|
+
], userMessage)
|
|
87
90
|
} else {
|
|
88
91
|
const newId = randomUUID()
|
|
89
92
|
const result = await spawnClaude([
|
|
90
|
-
'-p',
|
|
93
|
+
'-p',
|
|
91
94
|
'--output-format', 'json',
|
|
92
95
|
'--session-id', newId,
|
|
93
96
|
'--dangerously-skip-permissions',
|
|
94
|
-
])
|
|
97
|
+
], userMessage)
|
|
95
98
|
onNewSessionId?.(result.sessionId ?? newId)
|
|
96
99
|
return result
|
|
97
100
|
}
|
package/src/handlers/commands.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { killCodexSession } from '../agents/codex'
|
|
|
6
6
|
import { formatTelegramMarkdown, splitTelegramMessage, TELEGRAM_MARKDOWN_OPTS } from '../telegram'
|
|
7
7
|
import { dockerHelpText, pm2HelpText, runDockerCommand, runPm2Command } from '../admin'
|
|
8
8
|
import { getLang, t } from '../i18n'
|
|
9
|
+
import { getCacheDir, handleSendCommand } from './send'
|
|
9
10
|
|
|
10
11
|
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
11
12
|
|
|
@@ -252,6 +253,44 @@ export function registerCommands(bot: Bot<Context>, storage: Storage, config: Co
|
|
|
252
253
|
await replyChunks(ctx, msg)
|
|
253
254
|
})
|
|
254
255
|
|
|
256
|
+
// /uso
|
|
257
|
+
bot.command('uso', async (ctx) => {
|
|
258
|
+
const lang = getLang(ctx)
|
|
259
|
+
const chatId = String(ctx.chat!.id)
|
|
260
|
+
const threadId = ctx.message?.message_thread_id ?? 0
|
|
261
|
+
const session = threadId
|
|
262
|
+
? (storage.getSessionByThreadId(chatId, threadId) || storage.getActiveSession(chatId))
|
|
263
|
+
: storage.getActiveSession(chatId)
|
|
264
|
+
|
|
265
|
+
const cacheDir = getCacheDir(config.DATA_DIR, chatId)
|
|
266
|
+
const emoji = session ? (session.model === 'claude' ? '🟣' : '🟢') : '🤖'
|
|
267
|
+
const name = session ? session.name : (lang === 'pt' ? 'Nenhuma sessão' : 'No session')
|
|
268
|
+
const title = `${emoji} <b>${name}</b>`
|
|
269
|
+
|
|
270
|
+
const keyboard = {
|
|
271
|
+
inline_keyboard: [
|
|
272
|
+
[
|
|
273
|
+
{ text: lang === 'pt' ? '📊 Status' : '📊 Status', callback_data: 'uso:stat' },
|
|
274
|
+
{ text: lang === 'pt' ? '🧹 Limpar' : '🧹 Clear', callback_data: 'uso:clr' },
|
|
275
|
+
],
|
|
276
|
+
[
|
|
277
|
+
{ text: lang === 'pt' ? '📁 Enviar arquivo' : '📁 Send file', callback_data: 'uso:snd' },
|
|
278
|
+
],
|
|
279
|
+
],
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const opts: any = { parse_mode: 'HTML', reply_markup: keyboard }
|
|
283
|
+
if (threadId) opts.message_thread_id = threadId
|
|
284
|
+
await ctx.reply(title, opts)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// /send + /enviar
|
|
288
|
+
const sendHandler = async (ctx: Context) => {
|
|
289
|
+
await handleSendCommand(ctx, storage, config)
|
|
290
|
+
}
|
|
291
|
+
bot.command('send', sendHandler)
|
|
292
|
+
bot.command('enviar', sendHandler)
|
|
293
|
+
|
|
255
294
|
// /docker
|
|
256
295
|
bot.command('docker', async (ctx) => {
|
|
257
296
|
if (!await guardAdmin(ctx, config)) return
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { existsSync, readdirSync, mkdirSync, statSync } from 'fs'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { InputFile } from 'grammy'
|
|
4
|
+
import type { Context } from 'grammy'
|
|
5
|
+
import type { Storage } from '../storage'
|
|
6
|
+
import type { Config } from '../config'
|
|
7
|
+
import { getLang } from '../i18n'
|
|
8
|
+
import { mdToTg, splitHtml } from '../utils/markdown'
|
|
9
|
+
import { askClaude } from '../agents/claude'
|
|
10
|
+
import { askCodex } from '../agents/codex'
|
|
11
|
+
|
|
12
|
+
// ─── cache helpers ────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export function getCacheDir(dataDir: string, chatId: string): string {
|
|
15
|
+
const dir = join(dataDir, 'send_cache', chatId)
|
|
16
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
17
|
+
return dir
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function listCacheFiles(dataDir: string, chatId: string): string[] {
|
|
21
|
+
const dir = getCacheDir(dataDir, chatId)
|
|
22
|
+
try {
|
|
23
|
+
return readdirSync(dir).filter(f => statSync(join(dir, f)).isFile()).sort()
|
|
24
|
+
} catch { return [] }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── guide text ──────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function getSendGuide(cacheDir: string): string {
|
|
30
|
+
return (
|
|
31
|
+
'📁 **Guia de envio de arquivos / File Transfer Guide**\n\n' +
|
|
32
|
+
'**🇧🇷 Português:**\n' +
|
|
33
|
+
'O usuário quer receber um arquivo via Telegram. Para enviar:\n' +
|
|
34
|
+
`1. Copie o arquivo para esta pasta: \`${cacheDir}\`\n` +
|
|
35
|
+
'2. Avise o usuário para abrir `/send` no bot e clicar em 📥.\n' +
|
|
36
|
+
`Exemplo: \`cp meu_arquivo.txt "${cacheDir}/"\`\n\n` +
|
|
37
|
+
'**🇺🇸 English:**\n' +
|
|
38
|
+
'The user wants to receive a file via Telegram. To send it:\n' +
|
|
39
|
+
`1. Copy the file to this folder: \`${cacheDir}\`\n` +
|
|
40
|
+
'2. Tell the user to open `/send` in the bot and click 📥.\n' +
|
|
41
|
+
`Example: \`cp my_file.txt "${cacheDir}/"\``
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── menu builders ───────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function escHtml(s: string) {
|
|
48
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildSendMenuHtml(cacheDir: string, files: string[], lang: 'pt' | 'en'): string {
|
|
52
|
+
const title = lang === 'pt' ? '📁 <b>Arquivos disponíveis</b>' : '📁 <b>Available Files</b>'
|
|
53
|
+
const pathLine = `${lang === 'pt' ? 'Pasta' : 'Folder'}: <code>${escHtml(cacheDir)}</code>`
|
|
54
|
+
const none = lang === 'pt'
|
|
55
|
+
? '<i>Nenhum arquivo ainda — copie um para a pasta acima</i>'
|
|
56
|
+
: '<i>No files yet — copy one to the folder above</i>'
|
|
57
|
+
const list = files.length
|
|
58
|
+
? files.map((f, i) => `${i + 1}. <code>${escHtml(f)}</code>`).join('\n')
|
|
59
|
+
: none
|
|
60
|
+
return `${title}\n${pathLine}\n\n${list}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function buildSendKeyboard(files: string[], lang: 'pt' | 'en') {
|
|
64
|
+
const rows: Array<Array<{ text: string; callback_data: string }>> = []
|
|
65
|
+
|
|
66
|
+
// File buttons (max 8, 2 per row)
|
|
67
|
+
const fbtns = files.slice(0, 8).map((f, i) => ({
|
|
68
|
+
text: `📥 ${f.slice(0, 18)}${f.length > 18 ? '…' : ''}`,
|
|
69
|
+
callback_data: `snd:f:${i}`,
|
|
70
|
+
}))
|
|
71
|
+
for (let i = 0; i < fbtns.length; i += 2) rows.push(fbtns.slice(i, i + 2))
|
|
72
|
+
|
|
73
|
+
// Action row
|
|
74
|
+
rows.push([
|
|
75
|
+
{ text: lang === 'pt' ? '📬 Pedir ao agente' : '📬 Ask agent', callback_data: 'snd:guide' },
|
|
76
|
+
{ text: lang === 'pt' ? '🔄 Atualizar' : '🔄 Refresh', callback_data: 'snd:ref' },
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
return { inline_keyboard: rows }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── /send command handler ───────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
export async function handleSendCommand(ctx: Context, storage: Storage, config: Config) {
|
|
85
|
+
const lang = getLang(ctx)
|
|
86
|
+
const chatId = String(ctx.chat!.id)
|
|
87
|
+
const threadId = ctx.message?.message_thread_id ?? 0
|
|
88
|
+
const cacheDir = getCacheDir(config.DATA_DIR, chatId)
|
|
89
|
+
const files = listCacheFiles(config.DATA_DIR, chatId)
|
|
90
|
+
|
|
91
|
+
const opts: any = {
|
|
92
|
+
parse_mode: 'HTML',
|
|
93
|
+
reply_markup: buildSendKeyboard(files, lang),
|
|
94
|
+
}
|
|
95
|
+
if (threadId) opts.message_thread_id = threadId
|
|
96
|
+
|
|
97
|
+
await ctx.reply(buildSendMenuHtml(cacheDir, files, lang), opts)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── snd:* callback handler ──────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export async function handleSndCallback(
|
|
103
|
+
ctx: Context,
|
|
104
|
+
storage: Storage,
|
|
105
|
+
config: Config,
|
|
106
|
+
action: string,
|
|
107
|
+
) {
|
|
108
|
+
const lang = getLang(ctx)
|
|
109
|
+
const chatId = String(ctx.chat!.id)
|
|
110
|
+
const threadId = (ctx.callbackQuery as any)?.message?.message_thread_id ?? 0
|
|
111
|
+
const cacheDir = getCacheDir(config.DATA_DIR, chatId)
|
|
112
|
+
const replyOpts: any = threadId ? { message_thread_id: threadId } : {}
|
|
113
|
+
|
|
114
|
+
await ctx.answerCallbackQuery().catch(() => {})
|
|
115
|
+
|
|
116
|
+
// ── refresh ──────────────────────────────────────────────────────────────
|
|
117
|
+
if (action === 'ref') {
|
|
118
|
+
const files = listCacheFiles(config.DATA_DIR, chatId)
|
|
119
|
+
try {
|
|
120
|
+
await ctx.editMessageText(buildSendMenuHtml(cacheDir, files, lang), {
|
|
121
|
+
parse_mode: 'HTML',
|
|
122
|
+
reply_markup: buildSendKeyboard(files, lang),
|
|
123
|
+
})
|
|
124
|
+
} catch {}
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── inject guide into agent ──────────────────────────────────────────────
|
|
129
|
+
if (action === 'guide') {
|
|
130
|
+
const session = threadId
|
|
131
|
+
? (storage.getSessionByThreadId(chatId, threadId) || storage.getActiveSession(chatId))
|
|
132
|
+
: storage.getActiveSession(chatId)
|
|
133
|
+
|
|
134
|
+
if (!session) {
|
|
135
|
+
await ctx.reply(
|
|
136
|
+
lang === 'pt' ? '⚠️ Nenhuma sessão ativa.' : '⚠️ No active session.',
|
|
137
|
+
replyOpts
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const guide = getSendGuide(cacheDir)
|
|
143
|
+
const emoji = session.model === 'claude' ? '🟣' : '🟢'
|
|
144
|
+
const status = await ctx.reply(
|
|
145
|
+
`${emoji} <i>${lang === 'pt' ? 'Enviando guia ao agente...' : 'Sending guide to agent...'}</i>`,
|
|
146
|
+
{ ...replyOpts, parse_mode: 'HTML' }
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
let result: { text: string }
|
|
151
|
+
if (session.model === 'claude') {
|
|
152
|
+
result = await askClaude(session, guide,
|
|
153
|
+
(id) => storage.setClaudeSessionId(chatId, session.id, id))
|
|
154
|
+
} else {
|
|
155
|
+
result = await askCodex(session, guide,
|
|
156
|
+
(id) => storage.setCodexThreadId(chatId, session.id, id))
|
|
157
|
+
}
|
|
158
|
+
try { await ctx.api.deleteMessage(chatId, status.message_id) } catch {}
|
|
159
|
+
const html = mdToTg(result.text)
|
|
160
|
+
for (const chunk of splitHtml(html)) {
|
|
161
|
+
if (chunk) await ctx.api.sendMessage(chatId, `${emoji} ${chunk}`, { ...replyOpts, parse_mode: 'HTML' })
|
|
162
|
+
}
|
|
163
|
+
} catch (e: any) {
|
|
164
|
+
try { await ctx.api.deleteMessage(chatId, status.message_id) } catch {}
|
|
165
|
+
await ctx.reply(`❌ ${e.message}`, replyOpts)
|
|
166
|
+
}
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── download file ─────────────────────────────────────────────────────────
|
|
171
|
+
if (action.startsWith('f:')) {
|
|
172
|
+
const idx = parseInt(action.slice(2), 10)
|
|
173
|
+
const files = listCacheFiles(config.DATA_DIR, chatId)
|
|
174
|
+
const fname = files[idx]
|
|
175
|
+
|
|
176
|
+
if (!fname) {
|
|
177
|
+
await ctx.reply(
|
|
178
|
+
lang === 'pt' ? '⚠️ Arquivo não encontrado.' : '⚠️ File not found.',
|
|
179
|
+
replyOpts
|
|
180
|
+
)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await ctx.replyWithDocument(new InputFile(join(cacheDir, fname), fname), replyOpts)
|
|
186
|
+
} catch (e: any) {
|
|
187
|
+
await ctx.reply(`❌ ${e.message}`, replyOpts)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── uso:* callback handler ──────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
export async function handleUsoCallback(
|
|
195
|
+
ctx: Context,
|
|
196
|
+
storage: Storage,
|
|
197
|
+
config: Config,
|
|
198
|
+
action: string,
|
|
199
|
+
) {
|
|
200
|
+
const lang = getLang(ctx)
|
|
201
|
+
const chatId = String(ctx.chat!.id)
|
|
202
|
+
const threadId = (ctx.callbackQuery as any)?.message?.message_thread_id ?? 0
|
|
203
|
+
const replyOpts: any = threadId ? { message_thread_id: threadId } : {}
|
|
204
|
+
|
|
205
|
+
await ctx.answerCallbackQuery().catch(() => {})
|
|
206
|
+
|
|
207
|
+
const getSession = () => threadId
|
|
208
|
+
? (storage.getSessionByThreadId(chatId, threadId) || storage.getActiveSession(chatId))
|
|
209
|
+
: storage.getActiveSession(chatId)
|
|
210
|
+
|
|
211
|
+
// ── status ───────────────────────────────────────────────────────────────
|
|
212
|
+
if (action === 'stat') {
|
|
213
|
+
const session = getSession()
|
|
214
|
+
if (!session) {
|
|
215
|
+
await ctx.reply(lang === 'pt' ? '⚠️ Nenhuma sessão ativa.' : '⚠️ No active session.', replyOpts)
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
const emoji = session.model === 'claude' ? '🟣' : '🟢'
|
|
219
|
+
const msgs = lang === 'pt' ? `${session.history.length} mensagens` : `${session.history.length} messages`
|
|
220
|
+
const since = lang === 'pt' ? 'desde' : 'since'
|
|
221
|
+
await ctx.reply(
|
|
222
|
+
`${emoji} <b>${escHtml(session.name)}</b>\n` +
|
|
223
|
+
`💬 ${msgs}\n` +
|
|
224
|
+
`🕐 ${since} ${new Date(session.createdAt).toLocaleString()}`,
|
|
225
|
+
{ ...replyOpts, parse_mode: 'HTML' }
|
|
226
|
+
)
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── clear ────────────────────────────────────────────────────────────────
|
|
231
|
+
if (action === 'clr') {
|
|
232
|
+
const { killSession } = await import('../agents/claude')
|
|
233
|
+
const { killCodexSession } = await import('../agents/codex')
|
|
234
|
+
const session = getSession()
|
|
235
|
+
if (!session) {
|
|
236
|
+
await ctx.reply(lang === 'pt' ? '⚠️ Nenhuma sessão ativa.' : '⚠️ No active session.', replyOpts)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
killSession(session.id)
|
|
240
|
+
killCodexSession(session.id)
|
|
241
|
+
storage.clearSession(chatId, session.id)
|
|
242
|
+
const emoji = session.model === 'claude' ? '🟣' : '🟢'
|
|
243
|
+
await ctx.reply(
|
|
244
|
+
`🧹 <b>${escHtml(session.name)}</b> ${lang === 'pt' ? 'reiniciada!' : 'cleared!'}`,
|
|
245
|
+
{ ...replyOpts, parse_mode: 'HTML' }
|
|
246
|
+
)
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── send files (show send menu) ───────────────────────────────────────────
|
|
251
|
+
if (action === 'snd') {
|
|
252
|
+
const cacheDir = getCacheDir(config.DATA_DIR, chatId)
|
|
253
|
+
const files = listCacheFiles(config.DATA_DIR, chatId)
|
|
254
|
+
await ctx.reply(buildSendMenuHtml(cacheDir, files, lang), {
|
|
255
|
+
...replyOpts,
|
|
256
|
+
parse_mode: 'HTML',
|
|
257
|
+
reply_markup: buildSendKeyboard(files, lang),
|
|
258
|
+
})
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
}
|
package/src/i18n.ts
CHANGED
|
@@ -21,6 +21,8 @@ const strings = {
|
|
|
21
21
|
'`/sessoes` ou `/sessions` — listar sessões\n' +
|
|
22
22
|
'`/limpar` ou `/clear` — reiniciar sessão atual\n' +
|
|
23
23
|
'`/status` — info da sessão ativa\n' +
|
|
24
|
+
'`/uso` — menu rápido (status, limpar, arquivos)\n' +
|
|
25
|
+
'`/send` ou `/enviar` — enviar arquivos via Telegram\n' +
|
|
24
26
|
'`/id` — ID deste chat\n' +
|
|
25
27
|
'`/ajuda` ou `/help` — exibir ajuda',
|
|
26
28
|
en:
|
|
@@ -29,6 +31,8 @@ const strings = {
|
|
|
29
31
|
'`/sessions` or `/sessoes` — list sessions\n' +
|
|
30
32
|
'`/clear` or `/limpar` — reset current session\n' +
|
|
31
33
|
'`/status` — active session info\n' +
|
|
34
|
+
'`/uso` — quick menu (status, clear, files)\n' +
|
|
35
|
+
'`/send` or `/enviar` — send files via Telegram\n' +
|
|
32
36
|
'`/id` — this chat ID\n' +
|
|
33
37
|
'`/help` or `/ajuda` — show help',
|
|
34
38
|
},
|