@grinev/opencode-telegram-bot 0.7.0 → 0.9.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/.env.example +7 -1
- package/README.md +13 -3
- package/dist/bot/commands/sessions.js +2 -2
- package/dist/bot/handlers/model.js +19 -3
- package/dist/bot/handlers/prompt.js +24 -4
- package/dist/bot/index.js +65 -16
- package/dist/bot/utils/file-download.js +75 -0
- package/dist/bot/utils/send-with-markdown-fallback.js +58 -0
- package/dist/config.js +14 -11
- package/dist/i18n/de.js +247 -0
- package/dist/i18n/en.js +10 -1
- package/dist/i18n/es.js +247 -0
- package/dist/i18n/index.js +64 -12
- package/dist/i18n/ru.js +10 -1
- package/dist/i18n/zh.js +247 -0
- package/dist/interaction/guard.js +4 -0
- package/dist/model/capabilities.js +62 -0
- package/dist/runtime/bootstrap.js +43 -1
- package/dist/session/cache-manager.js +56 -1
- package/dist/summary/formatter.js +117 -5
- package/package.json +3 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
+
import { convert } from "telegram-markdown-v2";
|
|
2
3
|
import { config } from "../config.js";
|
|
3
4
|
import { logger } from "../utils/logger.js";
|
|
4
5
|
import { t } from "../i18n/index.js";
|
|
@@ -22,6 +23,87 @@ function splitText(text, maxLength) {
|
|
|
22
23
|
}
|
|
23
24
|
return parts;
|
|
24
25
|
}
|
|
26
|
+
function isCodeFenceLine(line) {
|
|
27
|
+
return line.trimStart().startsWith("```");
|
|
28
|
+
}
|
|
29
|
+
function isHorizontalRuleLine(line) {
|
|
30
|
+
const normalized = line.trim();
|
|
31
|
+
if (!normalized) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return /^([-*_])(?:\s*\1){2,}$/.test(normalized);
|
|
35
|
+
}
|
|
36
|
+
function isHeadingLine(line) {
|
|
37
|
+
return /^\s{0,3}#{1,6}\s+\S/.test(line);
|
|
38
|
+
}
|
|
39
|
+
function normalizeHeadingLine(line) {
|
|
40
|
+
const match = line.match(/^\s{0,3}#{1,6}\s+(.+?)\s*$/);
|
|
41
|
+
if (!match) {
|
|
42
|
+
return line;
|
|
43
|
+
}
|
|
44
|
+
return `**${match[1]}**`;
|
|
45
|
+
}
|
|
46
|
+
function normalizeChecklistLine(line) {
|
|
47
|
+
const match = line.match(/^(\s*)(?:[-+*]|\d+\.)\s+\[( |x|X)\]\s+(.*)$/);
|
|
48
|
+
if (!match) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const marker = match[2].toLowerCase() === "x" ? "✅" : "🔲";
|
|
52
|
+
return `${match[1]}${marker} ${match[3]}`;
|
|
53
|
+
}
|
|
54
|
+
function preprocessMarkdownForTelegram(text) {
|
|
55
|
+
const lines = text.split("\n");
|
|
56
|
+
const output = [];
|
|
57
|
+
let inCodeFence = false;
|
|
58
|
+
let inQuote = false;
|
|
59
|
+
for (let index = 0; index < lines.length; index++) {
|
|
60
|
+
const line = lines[index];
|
|
61
|
+
if (isCodeFenceLine(line)) {
|
|
62
|
+
inCodeFence = !inCodeFence;
|
|
63
|
+
inQuote = false;
|
|
64
|
+
output.push(line);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inCodeFence) {
|
|
68
|
+
output.push(line);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!line.trim()) {
|
|
72
|
+
inQuote = false;
|
|
73
|
+
output.push(line);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (isHeadingLine(line)) {
|
|
77
|
+
output.push(normalizeHeadingLine(line));
|
|
78
|
+
inQuote = false;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (isHorizontalRuleLine(line)) {
|
|
82
|
+
output.push("──────────");
|
|
83
|
+
inQuote = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const trimmedLeft = line.trimStart();
|
|
87
|
+
if (trimmedLeft.startsWith(">")) {
|
|
88
|
+
inQuote = true;
|
|
89
|
+
const quoteContent = trimmedLeft.replace(/^>\s?/, "");
|
|
90
|
+
const normalizedChecklistInQuote = normalizeChecklistLine(quoteContent);
|
|
91
|
+
output.push(normalizedChecklistInQuote ? `> ${normalizedChecklistInQuote.trimStart()}` : trimmedLeft);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const normalizedChecklist = normalizeChecklistLine(line);
|
|
95
|
+
if (normalizedChecklist) {
|
|
96
|
+
output.push(inQuote ? `> ${normalizedChecklist.trimStart()}` : normalizedChecklist);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (inQuote) {
|
|
100
|
+
output.push(`> ${trimmedLeft}`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
output.push(line);
|
|
104
|
+
}
|
|
105
|
+
return output.join("\n");
|
|
106
|
+
}
|
|
25
107
|
export function normalizePathForDisplay(filePath) {
|
|
26
108
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
27
109
|
const project = getCurrentProject();
|
|
@@ -44,6 +126,25 @@ export function normalizePathForDisplay(filePath) {
|
|
|
44
126
|
return normalizedPath;
|
|
45
127
|
}
|
|
46
128
|
export function formatSummary(text) {
|
|
129
|
+
return formatSummaryWithMode(text, config.bot.messageFormatMode);
|
|
130
|
+
}
|
|
131
|
+
export function getAssistantParseMode() {
|
|
132
|
+
if (config.bot.messageFormatMode === "markdown") {
|
|
133
|
+
return "MarkdownV2";
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
function formatMarkdownForTelegram(text) {
|
|
138
|
+
try {
|
|
139
|
+
const preprocessed = preprocessMarkdownForTelegram(text);
|
|
140
|
+
return convert(preprocessed, "keep");
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
logger.warn("[Formatter] Failed to convert markdown summary, falling back to raw text", error);
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function formatSummaryWithMode(text, mode) {
|
|
47
148
|
if (!text || text.trim().length === 0) {
|
|
48
149
|
return [];
|
|
49
150
|
}
|
|
@@ -54,6 +155,17 @@ export function formatSummary(text) {
|
|
|
54
155
|
if (!trimmed) {
|
|
55
156
|
continue;
|
|
56
157
|
}
|
|
158
|
+
if (mode === "markdown") {
|
|
159
|
+
const converted = formatMarkdownForTelegram(trimmed);
|
|
160
|
+
const convertedParts = splitText(converted, TELEGRAM_MESSAGE_LIMIT);
|
|
161
|
+
for (const convertedPart of convertedParts) {
|
|
162
|
+
const normalizedPart = convertedPart.trim();
|
|
163
|
+
if (normalizedPart) {
|
|
164
|
+
formattedParts.push(normalizedPart);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
57
169
|
if (parts.length > 1) {
|
|
58
170
|
formattedParts.push(`\`\`\`\n${trimmed}\n\`\`\``);
|
|
59
171
|
}
|
|
@@ -142,15 +254,15 @@ function getToolIcon(tool) {
|
|
|
142
254
|
function formatTodos(todos) {
|
|
143
255
|
const MAX_TODOS = 20;
|
|
144
256
|
const statusToMarker = {
|
|
145
|
-
completed: "
|
|
146
|
-
in_progress: "
|
|
147
|
-
pending: "
|
|
257
|
+
completed: "✅",
|
|
258
|
+
in_progress: "🔄",
|
|
259
|
+
pending: "🔲",
|
|
148
260
|
};
|
|
149
261
|
const formattedTodos = [];
|
|
150
262
|
for (let i = 0; i < Math.min(todos.length, MAX_TODOS); i++) {
|
|
151
263
|
const todo = todos[i];
|
|
152
|
-
const marker = statusToMarker[todo.status] ?? "
|
|
153
|
-
formattedTodos.push(
|
|
264
|
+
const marker = statusToMarker[todo.status] ?? "🔲";
|
|
265
|
+
formattedTodos.push(`${marker} ${todo.content}`);
|
|
154
266
|
}
|
|
155
267
|
let result = formattedTodos.join("\n");
|
|
156
268
|
if (todos.length > MAX_TODOS) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grinev/opencode-telegram-bot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Telegram bot client for OpenCode to run and monitor coding tasks from chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"dotenv": "^17.2.3",
|
|
58
58
|
"grammy": "^1.39.2",
|
|
59
59
|
"https-proxy-agent": "^7.0.6",
|
|
60
|
-
"socks-proxy-agent": "^8.0.5"
|
|
60
|
+
"socks-proxy-agent": "^8.0.5",
|
|
61
|
+
"telegram-markdown-v2": "^0.0.4"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@types/better-sqlite3": "^7.6.13",
|