@goodnesshq/opencode-notification 0.1.7 → 0.2.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/.opencode/notify-init.mjs +135 -79
- package/.opencode/oc-notify.schema.json +15 -0
- package/README.md +9 -6
- package/package.json +4 -3
- package/plugins/opencode-notifications.mjs +39 -2
|
@@ -21,10 +21,10 @@ const DEFAULTS = {
|
|
|
21
21
|
enabled: true,
|
|
22
22
|
title: "",
|
|
23
23
|
tier: "focus",
|
|
24
|
-
detailLevel: "
|
|
24
|
+
detailLevel: "title-only",
|
|
25
25
|
responseComplete: {
|
|
26
26
|
enabled: true,
|
|
27
|
-
trigger: "
|
|
27
|
+
trigger: "session.idle",
|
|
28
28
|
},
|
|
29
29
|
channels: {
|
|
30
30
|
mac: {
|
|
@@ -36,6 +36,11 @@ const DEFAULTS = {
|
|
|
36
36
|
server: "https://ntfy.sh",
|
|
37
37
|
topic: "",
|
|
38
38
|
},
|
|
39
|
+
telegram: {
|
|
40
|
+
enabled: false,
|
|
41
|
+
token: "",
|
|
42
|
+
chatId: "",
|
|
43
|
+
},
|
|
39
44
|
},
|
|
40
45
|
overrides: {
|
|
41
46
|
includeGroups: [],
|
|
@@ -128,6 +133,20 @@ async function copySchema(cwd) {
|
|
|
128
133
|
} catch {}
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
async function ensureGitignore(cwd) {
|
|
137
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
138
|
+
const entry = ".opencode/oc-notify.json";
|
|
139
|
+
try {
|
|
140
|
+
const content = await readFile(gitignorePath, "utf8");
|
|
141
|
+
const lines = content.split(/\r?\n/);
|
|
142
|
+
if (lines.some((line) => line.trim() === entry)) return;
|
|
143
|
+
const prefix = content.endsWith("\n") || content.length === 0 ? "" : "\n";
|
|
144
|
+
await writeFile(gitignorePath, content + prefix + entry + "\n", "utf8");
|
|
145
|
+
} catch {
|
|
146
|
+
await writeFile(gitignorePath, entry + "\n", "utf8");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
131
150
|
function isInteractive() {
|
|
132
151
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
133
152
|
if (process.env.CI) return false;
|
|
@@ -263,53 +282,50 @@ async function promptMultiSelect({
|
|
|
263
282
|
|
|
264
283
|
const render = () => {
|
|
265
284
|
const filtered = getFiltered();
|
|
266
|
-
if (cursor
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
`Filter: ${filter || "(type to filter)"} (${selected.size} selected)`,
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
if (filtered.length === 0) {
|
|
275
|
-
lines.push(" No matches");
|
|
276
|
-
renderPrompt(lines, state);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const startIndex = Math.max(
|
|
281
|
-
0,
|
|
282
|
-
Math.min(
|
|
283
|
-
cursor - Math.floor(pageSize / 2),
|
|
284
|
-
Math.max(0, filtered.length - pageSize),
|
|
285
|
-
),
|
|
285
|
+
if (cursor >= filtered.length) cursor = Math.max(0, filtered.length - 1);
|
|
286
|
+
const maxStart = Math.max(0, filtered.length - pageSize);
|
|
287
|
+
const pageStart = Math.min(
|
|
288
|
+
maxStart,
|
|
289
|
+
Math.max(0, cursor - Math.floor(pageSize / 2)),
|
|
286
290
|
);
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
const page = filtered.slice(pageStart, pageStart + pageSize);
|
|
292
|
+
const lines = [];
|
|
293
|
+
const header = filter ? `${message} (filter: ${filter})` : message;
|
|
294
|
+
lines.push(header);
|
|
295
|
+
lines.push("Use \u2191/\u2193 to move, space to toggle, enter to confirm");
|
|
296
|
+
if (!page.length) {
|
|
297
|
+
lines.push(" (no matches)");
|
|
298
|
+
} else {
|
|
299
|
+
for (let index = 0; index < page.length; index += 1) {
|
|
300
|
+
const choice = page[index];
|
|
301
|
+
const absoluteIndex = pageStart + index;
|
|
302
|
+
const pointer = absoluteIndex === cursor ? ">" : " ";
|
|
303
|
+
const mark = selected.has(choice.value) ? "x" : " ";
|
|
304
|
+
lines.push(` ${pointer} [${mark}] ${choice.name}`);
|
|
305
|
+
}
|
|
296
306
|
}
|
|
297
|
-
|
|
298
307
|
renderPrompt(lines, state);
|
|
299
308
|
};
|
|
300
309
|
|
|
310
|
+
const toggle = () => {
|
|
311
|
+
const filtered = getFiltered();
|
|
312
|
+
const choice = filtered[cursor];
|
|
313
|
+
if (!choice) return;
|
|
314
|
+
if (selected.has(choice.value)) selected.delete(choice.value);
|
|
315
|
+
else selected.add(choice.value);
|
|
316
|
+
};
|
|
317
|
+
|
|
301
318
|
const finish = () => {
|
|
302
|
-
const
|
|
319
|
+
const values = choices
|
|
303
320
|
.filter((choice) => selected.has(choice.value))
|
|
304
321
|
.map((choice) => choice.value);
|
|
305
|
-
|
|
306
|
-
renderPrompt([`${message} ${summary}`], state);
|
|
322
|
+
renderPrompt([`${message} ${values.join(", ")}`], state);
|
|
307
323
|
process.stdout.write("\n");
|
|
308
324
|
cleanup();
|
|
309
|
-
resolve(
|
|
325
|
+
resolve(values);
|
|
310
326
|
};
|
|
311
327
|
|
|
312
|
-
const onKey = (
|
|
328
|
+
const onKey = (_char, key) => {
|
|
313
329
|
if (!key) return;
|
|
314
330
|
if (key.ctrl && key.name === "c") {
|
|
315
331
|
cleanup();
|
|
@@ -327,32 +343,24 @@ async function promptMultiSelect({
|
|
|
327
343
|
return;
|
|
328
344
|
}
|
|
329
345
|
if (key.name === "space") {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (choice) {
|
|
333
|
-
if (selected.has(choice.value)) {
|
|
334
|
-
selected.delete(choice.value);
|
|
335
|
-
} else {
|
|
336
|
-
selected.add(choice.value);
|
|
337
|
-
}
|
|
338
|
-
render();
|
|
339
|
-
}
|
|
346
|
+
toggle();
|
|
347
|
+
render();
|
|
340
348
|
return;
|
|
341
349
|
}
|
|
342
|
-
if (key.name === "
|
|
343
|
-
|
|
350
|
+
if (key.name === "return" || key.name === "enter") {
|
|
351
|
+
finish();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (key.name === "backspace" || key.name === "delete") {
|
|
355
|
+
if (filter) {
|
|
344
356
|
filter = filter.slice(0, -1);
|
|
345
357
|
cursor = 0;
|
|
346
358
|
render();
|
|
347
359
|
}
|
|
348
360
|
return;
|
|
349
361
|
}
|
|
350
|
-
if (key.
|
|
351
|
-
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (char && char.length === 1 && !key.ctrl && !key.meta) {
|
|
355
|
-
filter += char;
|
|
362
|
+
if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
|
|
363
|
+
filter += key.sequence;
|
|
356
364
|
cursor = 0;
|
|
357
365
|
render();
|
|
358
366
|
}
|
|
@@ -368,7 +376,6 @@ async function promptMultiSelect({
|
|
|
368
376
|
);
|
|
369
377
|
}
|
|
370
378
|
|
|
371
|
-
|
|
372
379
|
async function sendTestNotification(config, repoName) {
|
|
373
380
|
const title = config.title || `${repoName}@test`;
|
|
374
381
|
const subtitle = "OpenCode Notify";
|
|
@@ -429,6 +436,31 @@ async function sendTestNotification(config, repoName) {
|
|
|
429
436
|
console.log("Test ntfy notification failed.");
|
|
430
437
|
}
|
|
431
438
|
}
|
|
439
|
+
|
|
440
|
+
if (config.channels.telegram.enabled) {
|
|
441
|
+
try {
|
|
442
|
+
const token = config.channels.telegram.token?.trim();
|
|
443
|
+
const chatId = config.channels.telegram.chatId?.trim();
|
|
444
|
+
if (!token || !chatId) return;
|
|
445
|
+
const { execFileSync } = await import("node:child_process");
|
|
446
|
+
const url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
447
|
+
const payload = [title, subtitle, message].filter(Boolean).join("\n");
|
|
448
|
+
execFileSync("curl", [
|
|
449
|
+
"-sS",
|
|
450
|
+
"-X",
|
|
451
|
+
"POST",
|
|
452
|
+
url,
|
|
453
|
+
"--data-urlencode",
|
|
454
|
+
`chat_id=${chatId}`,
|
|
455
|
+
"--data-urlencode",
|
|
456
|
+
`text=${payload}`,
|
|
457
|
+
"--data-urlencode",
|
|
458
|
+
"disable_web_page_preview=true",
|
|
459
|
+
]);
|
|
460
|
+
} catch {
|
|
461
|
+
console.log("Test Telegram notification failed.");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
432
464
|
}
|
|
433
465
|
|
|
434
466
|
async function main() {
|
|
@@ -492,18 +524,18 @@ async function main() {
|
|
|
492
524
|
value: "message.part.updated",
|
|
493
525
|
},
|
|
494
526
|
],
|
|
495
|
-
defaultValue: "
|
|
527
|
+
defaultValue: "session.idle",
|
|
496
528
|
})
|
|
497
529
|
: await (async () => {
|
|
498
530
|
console.log(
|
|
499
531
|
"\nResponse-complete trigger:\n 1) session.idle (coarse, low noise)\n 2) message.updated (precise)\n 3) message.part.updated (very precise, more noise)",
|
|
500
532
|
);
|
|
501
|
-
const triggerInput = await rl.question("> [
|
|
502
|
-
return triggerInput.trim() === "
|
|
503
|
-
? "
|
|
533
|
+
const triggerInput = await rl.question("> [1] ");
|
|
534
|
+
return triggerInput.trim() === "2"
|
|
535
|
+
? "message.updated"
|
|
504
536
|
: triggerInput.trim() === "3"
|
|
505
537
|
? "message.part.updated"
|
|
506
|
-
: "
|
|
538
|
+
: "session.idle";
|
|
507
539
|
})();
|
|
508
540
|
|
|
509
541
|
const macEnabled = interactive
|
|
@@ -519,10 +551,17 @@ async function main() {
|
|
|
519
551
|
? await promptConfirm("Enable ntfy notifications?", true)
|
|
520
552
|
: yesNo(await rl.question("Enable ntfy notifications? (Y/n) [Y] "), true);
|
|
521
553
|
|
|
554
|
+
const telegramEnabled = interactive
|
|
555
|
+
? await promptConfirm("Enable Telegram notifications?", false)
|
|
556
|
+
: yesNo(await rl.question("Enable Telegram notifications? (y/N) [N] "), false);
|
|
557
|
+
|
|
522
558
|
let globalConfig = await readJson(GLOBAL_CONFIG_PATH);
|
|
523
559
|
if (!globalConfig) globalConfig = {};
|
|
524
560
|
if (!globalConfig.ntfy) globalConfig.ntfy = {};
|
|
525
561
|
|
|
562
|
+
let telegramToken = "";
|
|
563
|
+
let telegramChatId = "";
|
|
564
|
+
|
|
526
565
|
if (ntfyEnabled) {
|
|
527
566
|
const serverInput = interactive
|
|
528
567
|
? await promptInput(
|
|
@@ -544,6 +583,17 @@ async function main() {
|
|
|
544
583
|
if (topicInput.trim()) globalConfig.ntfy.topic = topicInput.trim();
|
|
545
584
|
}
|
|
546
585
|
|
|
586
|
+
if (telegramEnabled) {
|
|
587
|
+
const tokenInput = interactive
|
|
588
|
+
? await promptInput("Telegram bot token: ")
|
|
589
|
+
: await rl.question("Telegram bot token: ");
|
|
590
|
+
const chatIdInput = interactive
|
|
591
|
+
? await promptInput("Telegram chat ID (see docs/telegram-chat-id.md): ")
|
|
592
|
+
: await rl.question("Telegram chat ID (see docs/telegram-chat-id.md): ");
|
|
593
|
+
telegramToken = tokenInput.trim();
|
|
594
|
+
telegramChatId = chatIdInput.trim();
|
|
595
|
+
}
|
|
596
|
+
|
|
547
597
|
const detailLevel = interactive
|
|
548
598
|
? await promptSelect({
|
|
549
599
|
message: "Detail level:",
|
|
@@ -551,11 +601,11 @@ async function main() {
|
|
|
551
601
|
{ name: "full", value: "full" },
|
|
552
602
|
{ name: "title-only", value: "title-only" },
|
|
553
603
|
],
|
|
554
|
-
defaultValue: "
|
|
604
|
+
defaultValue: "title-only",
|
|
555
605
|
})
|
|
556
|
-
: (await rl.question("Detail level (1=full, 2=title-only) [
|
|
557
|
-
? "
|
|
558
|
-
: "
|
|
606
|
+
: (await rl.question("Detail level (1=full, 2=title-only) [2] ")).trim() === "1"
|
|
607
|
+
? "full"
|
|
608
|
+
: "title-only";
|
|
559
609
|
|
|
560
610
|
const adjustGroups = interactive
|
|
561
611
|
? await promptConfirm("Adjust event groups?", false)
|
|
@@ -607,20 +657,20 @@ async function main() {
|
|
|
607
657
|
const hasTN = await commandExists("terminal-notifier");
|
|
608
658
|
const hasBrew = await commandExists("brew");
|
|
609
659
|
if (!hasTN && hasBrew) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
660
|
+
const installNow = interactive
|
|
661
|
+
? await promptConfirm(
|
|
662
|
+
"terminal-notifier not found. Install with Homebrew now?",
|
|
663
|
+
false,
|
|
664
|
+
)
|
|
665
|
+
: yesNo(
|
|
666
|
+
await rl.question(
|
|
667
|
+
"terminal-notifier not found. Install with Homebrew now? (y/N) [N] ",
|
|
668
|
+
),
|
|
669
|
+
false,
|
|
670
|
+
);
|
|
671
|
+
if (installNow) {
|
|
672
|
+
try {
|
|
673
|
+
const { execSync } = await import("node:child_process");
|
|
624
674
|
execSync("brew install terminal-notifier", { stdio: "inherit" });
|
|
625
675
|
} catch {
|
|
626
676
|
console.log("Homebrew install failed. Falling back to AppleScript.");
|
|
@@ -649,6 +699,11 @@ async function main() {
|
|
|
649
699
|
server: globalConfig.ntfy?.server || DEFAULTS.channels.ntfy.server,
|
|
650
700
|
topic: globalConfig.ntfy?.topic || DEFAULTS.channels.ntfy.topic,
|
|
651
701
|
},
|
|
702
|
+
telegram: {
|
|
703
|
+
enabled: telegramEnabled,
|
|
704
|
+
token: telegramToken,
|
|
705
|
+
chatId: telegramChatId,
|
|
706
|
+
},
|
|
652
707
|
},
|
|
653
708
|
overrides: {
|
|
654
709
|
includeGroups,
|
|
@@ -668,6 +723,7 @@ async function main() {
|
|
|
668
723
|
await ensureOpencodeDir(cwd);
|
|
669
724
|
await writeJson(join(cwd, REPO_CONFIG_PATH), config);
|
|
670
725
|
await copySchema(cwd);
|
|
726
|
+
await ensureGitignore(cwd);
|
|
671
727
|
}
|
|
672
728
|
|
|
673
729
|
const sendTest = interactive
|
|
@@ -66,6 +66,21 @@
|
|
|
66
66
|
"type": "string"
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
},
|
|
70
|
+
"telegram": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"additionalProperties": false,
|
|
73
|
+
"properties": {
|
|
74
|
+
"enabled": {
|
|
75
|
+
"type": "boolean"
|
|
76
|
+
},
|
|
77
|
+
"token": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
},
|
|
80
|
+
"chatId": {
|
|
81
|
+
"type": "string"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
},
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenCode Notifications
|
|
2
2
|
|
|
3
|
-
This repo implements a per-repo notification plugin for OpenCode. It uses a repo-local config file (`.opencode/oc-notify.json`) and sends notifications to macOS Notification Center and
|
|
3
|
+
This repo implements a per-repo notification plugin for OpenCode. It uses a repo-local config file (`.opencode/oc-notify.json`) and sends notifications to macOS Notification Center, ntfy, and Telegram.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -24,6 +24,7 @@ npx @goodnesshq/opencode-notification install
|
|
|
24
24
|
This only installs repo assets and prints next steps for manual setup.
|
|
25
25
|
|
|
26
26
|
Before running the installer, create a unique ntfy topic and set up mobile notifications. See `docs/ntfy-topic.md`.
|
|
27
|
+
If you plan to use Telegram, see `docs/telegram-chat-id.md` for how to find your chat ID.
|
|
27
28
|
The installer uses an interactive, arrow-key prompt flow (with a non-interactive fallback in non-TTY environments).
|
|
28
29
|
|
|
29
30
|
## Config Schema
|
|
@@ -34,8 +35,8 @@ The config is defined in `.opencode/oc-notify.schema.json` and supports:
|
|
|
34
35
|
- `title`: override or template (supports `{repo}` and `{branch}`)
|
|
35
36
|
- `tier`: `focus` | `full` | `custom`
|
|
36
37
|
- `detailLevel`: `full` | `title-only`
|
|
37
|
-
- `responseComplete`: trigger for completion notifications
|
|
38
|
-
- `channels`: `mac` and `
|
|
38
|
+
- `responseComplete`: trigger for completion notifications (default: `session.idle`)
|
|
39
|
+
- `channels`: `mac`, `ntfy`, and `telegram` settings
|
|
39
40
|
- `overrides`: include/exclude groups
|
|
40
41
|
- `dedupe`: in-memory TTL settings
|
|
41
42
|
|
|
@@ -51,13 +52,15 @@ The config is defined in `.opencode/oc-notify.schema.json` and supports:
|
|
|
51
52
|
{
|
|
52
53
|
"enabled": true,
|
|
53
54
|
"tier": "focus",
|
|
55
|
+
"detailLevel": "title-only",
|
|
54
56
|
"responseComplete": {
|
|
55
57
|
"enabled": true,
|
|
56
58
|
"trigger": "session.idle"
|
|
57
59
|
},
|
|
58
60
|
"channels": {
|
|
59
61
|
"mac": { "enabled": true, "method": "auto" },
|
|
60
|
-
"ntfy": { "enabled": true, "server": "https://ntfy.sh", "topic": "<your-topic>" }
|
|
62
|
+
"ntfy": { "enabled": true, "server": "https://ntfy.sh", "topic": "<your-topic>" },
|
|
63
|
+
"telegram": { "enabled": false, "token": "", "chatId": "" }
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
```
|
|
@@ -67,10 +70,10 @@ The config is defined in `.opencode/oc-notify.schema.json` and supports:
|
|
|
67
70
|
{
|
|
68
71
|
"enabled": true,
|
|
69
72
|
"tier": "full",
|
|
70
|
-
"detailLevel": "
|
|
73
|
+
"detailLevel": "title-only",
|
|
71
74
|
"responseComplete": {
|
|
72
75
|
"enabled": true,
|
|
73
|
-
"trigger": "
|
|
76
|
+
"trigger": "session.idle"
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodnesshq/opencode-notification",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Per-repo notification plugin for OpenCode with macOS and
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Per-repo notification plugin for OpenCode with macOS, ntfy, and Telegram delivery.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ocn": "bin/ocn.mjs"
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"notifications",
|
|
19
19
|
"plugin",
|
|
20
20
|
"ntfy",
|
|
21
|
-
"macos"
|
|
21
|
+
"macos",
|
|
22
|
+
"telegram"
|
|
22
23
|
],
|
|
23
24
|
"license": "MIT"
|
|
24
25
|
}
|
|
@@ -11,10 +11,10 @@ const DEFAULTS = {
|
|
|
11
11
|
enabled: true,
|
|
12
12
|
title: "",
|
|
13
13
|
tier: "focus",
|
|
14
|
-
detailLevel: "
|
|
14
|
+
detailLevel: "title-only",
|
|
15
15
|
responseComplete: {
|
|
16
16
|
enabled: true,
|
|
17
|
-
trigger: "
|
|
17
|
+
trigger: "session.idle",
|
|
18
18
|
},
|
|
19
19
|
channels: {
|
|
20
20
|
mac: {
|
|
@@ -26,6 +26,11 @@ const DEFAULTS = {
|
|
|
26
26
|
server: "https://ntfy.sh",
|
|
27
27
|
topic: "",
|
|
28
28
|
},
|
|
29
|
+
telegram: {
|
|
30
|
+
enabled: false,
|
|
31
|
+
token: "",
|
|
32
|
+
chatId: "",
|
|
33
|
+
},
|
|
29
34
|
},
|
|
30
35
|
overrides: {
|
|
31
36
|
includeGroups: [],
|
|
@@ -170,6 +175,20 @@ function normalizeConfig(raw, globalConfig) {
|
|
|
170
175
|
normalizeString(globalSource.ntfy?.topic, "")
|
|
171
176
|
),
|
|
172
177
|
},
|
|
178
|
+
telegram: {
|
|
179
|
+
enabled: normalizeBool(
|
|
180
|
+
source.channels?.telegram?.enabled,
|
|
181
|
+
DEFAULTS.channels.telegram.enabled
|
|
182
|
+
),
|
|
183
|
+
token: normalizeString(
|
|
184
|
+
source.channels?.telegram?.token,
|
|
185
|
+
DEFAULTS.channels.telegram.token
|
|
186
|
+
),
|
|
187
|
+
chatId: normalizeString(
|
|
188
|
+
source.channels?.telegram?.chatId,
|
|
189
|
+
DEFAULTS.channels.telegram.chatId
|
|
190
|
+
),
|
|
191
|
+
},
|
|
173
192
|
},
|
|
174
193
|
overrides: {
|
|
175
194
|
includeGroups: sanitizeGroups(normalizeArray(source.overrides?.includeGroups)),
|
|
@@ -634,6 +653,23 @@ export default async function opencodeNotify(input) {
|
|
|
634
653
|
} catch {}
|
|
635
654
|
}
|
|
636
655
|
|
|
656
|
+
function buildTelegramMessage(notification) {
|
|
657
|
+
const parts = [notification.title, notification.subtitle, notification.body].filter(Boolean);
|
|
658
|
+
return parts.join("\n");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function notifyTelegram(notification, config) {
|
|
662
|
+
if (!config.channels.telegram.enabled) return;
|
|
663
|
+
const token = config.channels.telegram.token?.trim();
|
|
664
|
+
const chatId = config.channels.telegram.chatId?.trim();
|
|
665
|
+
if (!token || !chatId) return;
|
|
666
|
+
try {
|
|
667
|
+
const url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
668
|
+
const message = buildTelegramMessage(notification);
|
|
669
|
+
await $`curl -sS -X POST ${url} --data-urlencode ${"chat_id=" + chatId} --data-urlencode ${"text=" + message} --data-urlencode ${"disable_web_page_preview=true"}`.quiet();
|
|
670
|
+
} catch {}
|
|
671
|
+
}
|
|
672
|
+
|
|
637
673
|
return {
|
|
638
674
|
event: async ({ event }) => {
|
|
639
675
|
const payload = event?.payload ?? event;
|
|
@@ -678,6 +714,7 @@ export default async function opencodeNotify(input) {
|
|
|
678
714
|
|
|
679
715
|
await notifyMac(notification, config);
|
|
680
716
|
await notifyNtfy(notification, config);
|
|
717
|
+
await notifyTelegram(notification, config);
|
|
681
718
|
},
|
|
682
719
|
};
|
|
683
720
|
}
|