@shepai/cli 1.155.0-pr481.af47b59 → 1.155.0-pr482.ac79b36
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/README.md +113 -117
- package/apis/json-schema/Settings.yaml +3 -0
- package/apis/json-schema/TelegramCommandType.yaml +11 -0
- package/apis/json-schema/TelegramConfig.yaml +21 -0
- package/apis/json-schema/TelegramNotifyEvents.yaml +61 -0
- package/dist/packages/core/src/application/ports/output/services/telegram-service.interface.d.ts +92 -0
- package/dist/packages/core/src/application/ports/output/services/telegram-service.interface.d.ts.map +1 -0
- package/dist/packages/core/src/application/ports/output/services/telegram-service.interface.js +37 -0
- package/dist/packages/core/src/application/use-cases/telegram/configure-telegram.use-case.d.ts +42 -0
- package/dist/packages/core/src/application/use-cases/telegram/configure-telegram.use-case.d.ts.map +1 -0
- package/dist/packages/core/src/application/use-cases/telegram/configure-telegram.use-case.js +108 -0
- package/dist/packages/core/src/domain/generated/output.d.ts +82 -0
- package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
- package/dist/packages/core/src/domain/generated/output.js +9 -0
- package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/di/container.js +12 -1
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts +14 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.js +34 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations/049-add-telegram-settings.d.ts +17 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations/049-add-telegram-settings.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations/049-add-telegram-settings.js +40 -0
- package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts +9 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +40 -1
- package/dist/packages/core/src/infrastructure/services/telegram/telegram.service.d.ts +22 -0
- package/dist/packages/core/src/infrastructure/services/telegram/telegram.service.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/telegram/telegram.service.js +128 -0
- package/dist/src/presentation/cli/commands/settings/index.d.ts +1 -0
- package/dist/src/presentation/cli/commands/settings/index.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/settings/index.js +4 -1
- package/dist/src/presentation/cli/commands/settings/telegram.command.d.ts +18 -0
- package/dist/src/presentation/cli/commands/settings/telegram.command.d.ts.map +1 -0
- package/dist/src/presentation/cli/commands/settings/telegram.command.js +158 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/chat/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/@drawer/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +29 -29
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/chat/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +29 -29
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +5 -5
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +10 -10
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +10 -10
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +5 -5
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__beda892a._.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__beda892a._.js.map +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js.map +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__d2c18946._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_feature-drawer-client_tsx_e9755fc8._.js +2 -2
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_feature-drawer-client_tsx_e9755fc8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0b150ddf._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17ed7ed1._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17ed7ed1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__28d0d265._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__28d0d265._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__42bf1807._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__42bf1807._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__56b70465._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__56b70465._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__88f7e8e6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__88f7e8e6._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8b0aac03._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c30f1f82._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c30f1f82._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f80bfc75._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f80bfc75._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f8dd4422._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f8dd4422._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_65f2dbe6._.js → _002d7e8d._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_65f2dbe6._.js.map → _002d7e8d._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_05c23ad9._.js +1 -1
- package/web/.next/server/chunks/ssr/_05c23ad9._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_16eb4fec._.js +1 -1
- package/web/.next/server/chunks/ssr/_16eb4fec._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_3a0b989f._.js +2 -2
- package/web/.next/server/chunks/ssr/_3a0b989f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_56b9d60f._.js +1 -1
- package/web/.next/server/chunks/ssr/_56b9d60f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_5f69c13f._.js +1 -1
- package/web/.next/server/chunks/ssr/_5f69c13f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_7c5b97c6._.js +1 -1
- package/web/.next/server/chunks/ssr/_7c5b97c6._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_82c57f10._.js +1 -1
- package/web/.next/server/chunks/ssr/_82c57f10._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_d1120ef4._.js → _912552dd._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_d1120ef4._.js.map → _912552dd._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_9495d50b._.js +1 -1
- package/web/.next/server/chunks/ssr/_9495d50b._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_4640978c._.js → _97486b47._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_4640978c._.js.map → _97486b47._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_a0e3f7e4._.js +1 -1
- package/web/.next/server/chunks/ssr/_a0e3f7e4._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_ac4a3873._.js +1 -1
- package/web/.next/server/chunks/ssr/_ca0aa7f0._.js +1 -1
- package/web/.next/server/chunks/ssr/_ca0aa7f0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_cb5a021e._.js +1 -1
- package/web/.next/server/chunks/ssr/_cb5a021e._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_d4b20e29._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_d86175ae._.js +1 -1
- package/web/.next/server/chunks/ssr/_d86175ae._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_d8bedf13._.js +1 -1
- package/web/.next/server/chunks/ssr/_d8bedf13._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_fa7efce3._.js +2 -2
- package/web/.next/server/chunks/ssr/_fa7efce3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_cdc632e3.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_cdc632e3.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_39ca0924.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_39ca0924.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_open-ide_ts_baaca5d5._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_357e3eb0._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_357e3eb0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +45 -45
- package/web/.next/static/chunks/{3432338262ea5cfa.js → 13d4e516b0ecf5f0.js} +1 -1
- package/web/.next/static/chunks/{362b815754eed954.js → 6c5e7b36525b3532.js} +2 -2
- package/web/.next/static/chunks/{71e12e354ae78bc5.js → 818689ed2c41772b.js} +1 -1
- package/web/.next/static/chunks/{64b598518cbee0d3.js → 8208e717057eedd0.js} +1 -1
- package/web/.next/static/chunks/a07d5cbafca0277c.js +1 -0
- package/web/.next/static/chunks/{31d74fa1d6acc5e7.js → ad67307f6b0c1490.js} +1 -1
- package/web/.next/static/chunks/{345fcb2e225b77d5.js → b4e5c56e14e63cb0.js} +3 -3
- package/web/.next/static/chunks/{c4651ed82a2fbfc6.js → b6fc216cfa8fa52a.js} +2 -2
- package/web/.next/static/chunks/{b9e6584f428f2e87.js → d1c58b0747b10ae5.js} +1 -1
- package/web/.next/static/chunks/{dd83670b0a141611.js → d5cbc603b8bb904d.js} +1 -1
- package/web/.next/static/chunks/{a6e1c13997e2772b.js → deef7f0b5f7981e8.js} +1 -1
- package/web/.next/static/chunks/{c599446f655681bd.js → e664cd5c9c0f4fd4.js} +1 -1
- package/web/.next/static/chunks/14a6cecd54027e13.js +0 -1
- /package/web/.next/static/{K7WzDNsT2rF466QGhk8x9 → fX8_4OoVzGj6K0VH9X7nb}/_buildManifest.js +0 -0
- /package/web/.next/static/{K7WzDNsT2rF466QGhk8x9 → fX8_4OoVzGj6K0VH9X7nb}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{K7WzDNsT2rF466QGhk8x9 → fX8_4OoVzGj6K0VH9X7nb}/_ssgManifest.js +0 -0
package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* events to enabled channels:
|
|
6
6
|
* - Notification bus (for SSE → in-app toasts and browser notifications)
|
|
7
7
|
* - Desktop notifier (node-notifier for OS-level notifications)
|
|
8
|
+
* - Telegram bot (sends messages via Bot API)
|
|
8
9
|
*
|
|
9
10
|
* Channel enable/disable and event type filters are read from Settings.
|
|
10
11
|
*/
|
|
@@ -23,12 +24,27 @@ const EVENT_TYPE_TO_CONFIG_KEY = {
|
|
|
23
24
|
[NotificationEventType.PrBlocked]: 'prBlocked',
|
|
24
25
|
[NotificationEventType.MergeReviewReady]: 'mergeReviewReady',
|
|
25
26
|
};
|
|
27
|
+
const EVENT_TYPE_TO_TELEGRAM_KEY = {
|
|
28
|
+
[NotificationEventType.AgentStarted]: 'agentStarted',
|
|
29
|
+
[NotificationEventType.PhaseCompleted]: 'phaseCompleted',
|
|
30
|
+
[NotificationEventType.WaitingApproval]: 'waitingApproval',
|
|
31
|
+
[NotificationEventType.AgentCompleted]: 'agentCompleted',
|
|
32
|
+
[NotificationEventType.AgentFailed]: 'agentFailed',
|
|
33
|
+
[NotificationEventType.PrMerged]: 'prMerged',
|
|
34
|
+
[NotificationEventType.PrClosed]: 'prClosed',
|
|
35
|
+
[NotificationEventType.PrChecksPassed]: 'prChecksPassed',
|
|
36
|
+
[NotificationEventType.PrChecksFailed]: 'prChecksFailed',
|
|
37
|
+
[NotificationEventType.PrBlocked]: 'prBlocked',
|
|
38
|
+
[NotificationEventType.MergeReviewReady]: 'mergeReviewReady',
|
|
39
|
+
};
|
|
26
40
|
export class NotificationService {
|
|
27
41
|
bus;
|
|
28
42
|
desktopNotifier;
|
|
29
|
-
|
|
43
|
+
telegramService;
|
|
44
|
+
constructor(bus, desktopNotifier, telegramService) {
|
|
30
45
|
this.bus = bus;
|
|
31
46
|
this.desktopNotifier = desktopNotifier;
|
|
47
|
+
this.telegramService = telegramService;
|
|
32
48
|
}
|
|
33
49
|
notify(event) {
|
|
34
50
|
const { notifications } = getSettings();
|
|
@@ -43,5 +59,28 @@ export class NotificationService {
|
|
|
43
59
|
}
|
|
44
60
|
// Desktop notifications disabled — removed in favour of in-app toasts.
|
|
45
61
|
// The DesktopNotifier dependency is kept for API compatibility but never called.
|
|
62
|
+
// Send to Telegram if enabled and event type is allowed
|
|
63
|
+
this.sendTelegramNotification(event);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Send a notification to Telegram if the channel is enabled and
|
|
67
|
+
* the specific event type is allowed in the Telegram notify events config.
|
|
68
|
+
*/
|
|
69
|
+
sendTelegramNotification(event) {
|
|
70
|
+
const { telegram } = getSettings();
|
|
71
|
+
if (!telegram?.enabled || !telegram.botToken || !telegram.chatId) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Check Telegram-specific event type filter
|
|
75
|
+
const telegramKey = EVENT_TYPE_TO_TELEGRAM_KEY[event.eventType];
|
|
76
|
+
if (telegramKey && !telegram.notifyEvents[telegramKey]) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Fire-and-forget: don't block the notification pipeline on Telegram API calls
|
|
80
|
+
this.telegramService.sendNotification(telegram.botToken, telegram.chatId, event).catch(() => {
|
|
81
|
+
// Swallow errors silently — Telegram is a best-effort channel.
|
|
82
|
+
// Network failures, rate limits, or invalid tokens should not
|
|
83
|
+
// break the core notification flow.
|
|
84
|
+
});
|
|
46
85
|
}
|
|
47
86
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Service Implementation
|
|
3
|
+
*
|
|
4
|
+
* Implements the ITelegramService port using the Telegram Bot HTTP API.
|
|
5
|
+
* Uses native `fetch` (available in Node 18+) — no external SDK required.
|
|
6
|
+
*
|
|
7
|
+
* All methods are stateless; bot token and chat ID are passed as parameters
|
|
8
|
+
* so the service can be used for validation before persisting config.
|
|
9
|
+
*/
|
|
10
|
+
import type { NotificationEvent } from '../../../domain/generated/output.js';
|
|
11
|
+
import type { ITelegramService, TelegramBotInfo, TelegramChatResolution } from '../../../application/ports/output/services/telegram-service.interface.js';
|
|
12
|
+
export declare class TelegramService implements ITelegramService {
|
|
13
|
+
validateBotToken(botToken: string): Promise<TelegramBotInfo>;
|
|
14
|
+
resolveChatId(botToken: string): Promise<TelegramChatResolution>;
|
|
15
|
+
sendMessage(botToken: string, chatId: string, text: string): Promise<void>;
|
|
16
|
+
sendNotification(botToken: string, chatId: string, event: NotificationEvent): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Escape HTML special characters for Telegram's HTML parse mode.
|
|
19
|
+
*/
|
|
20
|
+
private escapeHtml;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=telegram.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/telegram/telegram.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAE7E,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACvB,MAAM,0EAA0E,CAAC;AA2ClF,qBAAa,eAAgB,YAAW,gBAAgB;IAChD,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA6B5D,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA0ChE,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B1E,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC;IAgBhB;;OAEG;IACH,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Service Implementation
|
|
3
|
+
*
|
|
4
|
+
* Implements the ITelegramService port using the Telegram Bot HTTP API.
|
|
5
|
+
* Uses native `fetch` (available in Node 18+) — no external SDK required.
|
|
6
|
+
*
|
|
7
|
+
* All methods are stateless; bot token and chat ID are passed as parameters
|
|
8
|
+
* so the service can be used for validation before persisting config.
|
|
9
|
+
*/
|
|
10
|
+
import { NotificationEventType } from '../../../domain/generated/output.js';
|
|
11
|
+
import { TelegramAuthError, TelegramNoChatError, TelegramSendError, } from '../../../application/ports/output/services/telegram-service.interface.js';
|
|
12
|
+
const TELEGRAM_API_BASE = 'https://api.telegram.org/bot';
|
|
13
|
+
/**
|
|
14
|
+
* Human-readable labels for notification event types.
|
|
15
|
+
*/
|
|
16
|
+
const EVENT_LABELS = {
|
|
17
|
+
[NotificationEventType.AgentStarted]: 'Agent Started',
|
|
18
|
+
[NotificationEventType.PhaseCompleted]: 'Phase Completed',
|
|
19
|
+
[NotificationEventType.WaitingApproval]: 'Waiting for Approval',
|
|
20
|
+
[NotificationEventType.AgentCompleted]: 'Agent Completed',
|
|
21
|
+
[NotificationEventType.AgentFailed]: 'Agent Failed',
|
|
22
|
+
[NotificationEventType.PrMerged]: 'PR Merged',
|
|
23
|
+
[NotificationEventType.PrClosed]: 'PR Closed',
|
|
24
|
+
[NotificationEventType.PrChecksPassed]: 'PR Checks Passed',
|
|
25
|
+
[NotificationEventType.PrChecksFailed]: 'PR Checks Failed',
|
|
26
|
+
[NotificationEventType.PrBlocked]: 'PR Blocked',
|
|
27
|
+
[NotificationEventType.MergeReviewReady]: 'Merge Review Ready',
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Emoji indicators for notification event types.
|
|
31
|
+
*/
|
|
32
|
+
const EVENT_EMOJI = {
|
|
33
|
+
[NotificationEventType.AgentStarted]: '\u{1F680}',
|
|
34
|
+
[NotificationEventType.PhaseCompleted]: '\u2705',
|
|
35
|
+
[NotificationEventType.WaitingApproval]: '\u23F3',
|
|
36
|
+
[NotificationEventType.AgentCompleted]: '\u{1F389}',
|
|
37
|
+
[NotificationEventType.AgentFailed]: '\u274C',
|
|
38
|
+
[NotificationEventType.PrMerged]: '\u{1F7E2}',
|
|
39
|
+
[NotificationEventType.PrClosed]: '\u{1F534}',
|
|
40
|
+
[NotificationEventType.PrChecksPassed]: '\u2705',
|
|
41
|
+
[NotificationEventType.PrChecksFailed]: '\u274C',
|
|
42
|
+
[NotificationEventType.PrBlocked]: '\u26A0\uFE0F',
|
|
43
|
+
[NotificationEventType.MergeReviewReady]: '\u{1F4CB}',
|
|
44
|
+
};
|
|
45
|
+
export class TelegramService {
|
|
46
|
+
async validateBotToken(botToken) {
|
|
47
|
+
const url = `${TELEGRAM_API_BASE}${botToken}/getMe`;
|
|
48
|
+
let response;
|
|
49
|
+
try {
|
|
50
|
+
response = await fetch(url);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new TelegramAuthError(`Failed to connect to Telegram API: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
}
|
|
55
|
+
const body = (await response.json());
|
|
56
|
+
if (!body.ok || !body.result) {
|
|
57
|
+
throw new TelegramAuthError(body.description ?? 'Invalid bot token');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
id: body.result.id,
|
|
61
|
+
firstName: body.result.first_name,
|
|
62
|
+
username: body.result.username,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async resolveChatId(botToken) {
|
|
66
|
+
const url = `${TELEGRAM_API_BASE}${botToken}/getUpdates?limit=10&allowed_updates=["message"]`;
|
|
67
|
+
let response;
|
|
68
|
+
try {
|
|
69
|
+
response = await fetch(url);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new TelegramNoChatError(`Failed to connect to Telegram API: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
+
}
|
|
74
|
+
const body = (await response.json());
|
|
75
|
+
if (!body.ok || !body.result) {
|
|
76
|
+
throw new TelegramNoChatError('Failed to fetch bot updates');
|
|
77
|
+
}
|
|
78
|
+
// Look for a /start message
|
|
79
|
+
const startUpdate = body.result.find((update) => update.message?.text === '/start');
|
|
80
|
+
if (!startUpdate?.message) {
|
|
81
|
+
throw new TelegramNoChatError('No /start message found. Please send /start to your bot on Telegram first, then retry.');
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
chatId: String(startUpdate.message.chat.id),
|
|
85
|
+
firstName: startUpdate.message.from?.first_name ?? 'Unknown',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async sendMessage(botToken, chatId, text) {
|
|
89
|
+
const url = `${TELEGRAM_API_BASE}${botToken}/sendMessage`;
|
|
90
|
+
let response;
|
|
91
|
+
try {
|
|
92
|
+
response = await fetch(url, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: { 'Content-Type': 'application/json' },
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
chat_id: chatId,
|
|
97
|
+
text,
|
|
98
|
+
parse_mode: 'HTML',
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new TelegramSendError(`Failed to send Telegram message: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
|
+
}
|
|
105
|
+
const body = (await response.json());
|
|
106
|
+
if (!body.ok) {
|
|
107
|
+
throw new TelegramSendError(`Telegram API error: ${body.description ?? 'Unknown error'}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async sendNotification(botToken, chatId, event) {
|
|
111
|
+
const emoji = EVENT_EMOJI[event.eventType] ?? '';
|
|
112
|
+
const label = EVENT_LABELS[event.eventType] ?? event.eventType;
|
|
113
|
+
const lines = [
|
|
114
|
+
`${emoji} <b>${label}</b>`,
|
|
115
|
+
`Feature: ${this.escapeHtml(event.featureName)}`,
|
|
116
|
+
];
|
|
117
|
+
if (event.message) {
|
|
118
|
+
lines.push(`\n${this.escapeHtml(event.message)}`);
|
|
119
|
+
}
|
|
120
|
+
await this.sendMessage(botToken, chatId, lines.join('\n'));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Escape HTML special characters for Telegram's HTML parse mode.
|
|
124
|
+
*/
|
|
125
|
+
escapeHtml(text) {
|
|
126
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* shep settings ide # Configure preferred IDE
|
|
13
13
|
* shep settings workflow # Configure workflow defaults
|
|
14
14
|
* shep settings model # Configure default LLM model
|
|
15
|
+
* shep settings telegram # Configure Telegram remote control
|
|
15
16
|
*/
|
|
16
17
|
import { Command } from 'commander';
|
|
17
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/settings/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/settings/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAuB/C"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* shep settings ide # Configure preferred IDE
|
|
13
13
|
* shep settings workflow # Configure workflow defaults
|
|
14
14
|
* shep settings model # Configure default LLM model
|
|
15
|
+
* shep settings telegram # Configure Telegram remote control
|
|
15
16
|
*/
|
|
16
17
|
import { Command } from 'commander';
|
|
17
18
|
import { createShowCommand } from './show.command.js';
|
|
@@ -20,6 +21,7 @@ import { createAgentCommand } from './agent.command.js';
|
|
|
20
21
|
import { createIdeCommand } from './ide.command.js';
|
|
21
22
|
import { createWorkflowCommand } from './workflow.command.js';
|
|
22
23
|
import { createModelCommand } from './model.command.js';
|
|
24
|
+
import { createTelegramCommand } from './telegram.command.js';
|
|
23
25
|
import { onboardingWizard } from '../../../tui/wizards/onboarding/onboarding.wizard.js';
|
|
24
26
|
import { messages } from '../../ui/index.js';
|
|
25
27
|
/**
|
|
@@ -33,7 +35,8 @@ export function createSettingsCommand() {
|
|
|
33
35
|
.addCommand(createAgentCommand())
|
|
34
36
|
.addCommand(createIdeCommand())
|
|
35
37
|
.addCommand(createWorkflowCommand())
|
|
36
|
-
.addCommand(createModelCommand())
|
|
38
|
+
.addCommand(createModelCommand())
|
|
39
|
+
.addCommand(createTelegramCommand());
|
|
37
40
|
// Default action: launch the full setup wizard when no subcommand is given
|
|
38
41
|
cmd.action(async () => {
|
|
39
42
|
try {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Configuration Command
|
|
3
|
+
*
|
|
4
|
+
* Configures the Telegram Bot integration for remote control and notifications.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* shep settings telegram # Interactive setup
|
|
8
|
+
* shep settings telegram --bot-token <token> # Set token + auto-pair
|
|
9
|
+
* shep settings telegram --bot-token <token> --chat-id 123 # Set token + explicit chat
|
|
10
|
+
* shep settings telegram --disable # Disable Telegram
|
|
11
|
+
* shep settings telegram --status # Show current status
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
/**
|
|
15
|
+
* Create the Telegram configuration command.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createTelegramCommand(): Command;
|
|
18
|
+
//# sourceMappingURL=telegram.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/settings/telegram.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAsD/C"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Configuration Command
|
|
3
|
+
*
|
|
4
|
+
* Configures the Telegram Bot integration for remote control and notifications.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* shep settings telegram # Interactive setup
|
|
8
|
+
* shep settings telegram --bot-token <token> # Set token + auto-pair
|
|
9
|
+
* shep settings telegram --bot-token <token> --chat-id 123 # Set token + explicit chat
|
|
10
|
+
* shep settings telegram --disable # Disable Telegram
|
|
11
|
+
* shep settings telegram --status # Show current status
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
import { input, confirm } from '@inquirer/prompts';
|
|
15
|
+
import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
|
|
16
|
+
import { ConfigureTelegramUseCase } from '../../../../../packages/core/src/application/use-cases/telegram/configure-telegram.use-case.js';
|
|
17
|
+
import { getSettings, resetSettings, initializeSettings, } from '../../../../../packages/core/src/infrastructure/services/settings.service.js';
|
|
18
|
+
import { messages, colors, fmt, symbols } from '../../ui/index.js';
|
|
19
|
+
/**
|
|
20
|
+
* Create the Telegram configuration command.
|
|
21
|
+
*/
|
|
22
|
+
export function createTelegramCommand() {
|
|
23
|
+
return new Command('telegram')
|
|
24
|
+
.description('Configure Telegram remote control and notifications')
|
|
25
|
+
.option('--bot-token <token>', 'Bot API token from @BotFather')
|
|
26
|
+
.option('--chat-id <id>', 'Telegram chat ID (auto-resolved if omitted)')
|
|
27
|
+
.option('--disable', 'Disable Telegram integration')
|
|
28
|
+
.option('--status', 'Show current Telegram configuration status')
|
|
29
|
+
.addHelpText('after', `
|
|
30
|
+
Examples:
|
|
31
|
+
$ shep settings telegram Interactive setup wizard
|
|
32
|
+
$ shep settings telegram --bot-token 123456:ABC-DEF... Set token and auto-pair
|
|
33
|
+
$ shep settings telegram --disable Disable Telegram
|
|
34
|
+
$ shep settings telegram --status Show current status
|
|
35
|
+
|
|
36
|
+
Setup guide:
|
|
37
|
+
1. Open Telegram and search for @BotFather
|
|
38
|
+
2. Send /newbot and follow the prompts to create a bot
|
|
39
|
+
3. Copy the bot token (looks like 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11)
|
|
40
|
+
4. Start a chat with your new bot and send /start
|
|
41
|
+
5. Run: shep settings telegram --bot-token <your-token>`)
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
try {
|
|
44
|
+
if (options.status) {
|
|
45
|
+
showTelegramStatus();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (options.disable) {
|
|
49
|
+
await disableTelegram();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const hasFlags = options.botToken !== undefined;
|
|
53
|
+
if (hasFlags) {
|
|
54
|
+
await enableTelegramNonInteractive(options);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await enableTelegramInteractive();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
62
|
+
if (err.message.includes('force closed') || err.message.includes('User force closed')) {
|
|
63
|
+
messages.info('Configuration cancelled.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
messages.error('Failed to configure Telegram', err);
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function showTelegramStatus() {
|
|
72
|
+
const settings = getSettings();
|
|
73
|
+
const telegram = settings.telegram;
|
|
74
|
+
if (!telegram?.enabled) {
|
|
75
|
+
messages.info('Telegram integration is disabled.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(`\n${symbols.success} Telegram integration is ${colors.success('enabled')}`);
|
|
79
|
+
console.log(` Bot token: ${colors.muted(telegram.botToken ? `****${telegram.botToken.slice(-8)}` : 'not set')}`);
|
|
80
|
+
console.log(` Chat ID: ${colors.muted(telegram.chatId ?? 'not set')}`);
|
|
81
|
+
const enabledEvents = Object.entries(telegram.notifyEvents)
|
|
82
|
+
.filter(([, enabled]) => enabled)
|
|
83
|
+
.map(([key]) => key);
|
|
84
|
+
console.log(` Events: ${colors.muted(enabledEvents.length > 0 ? enabledEvents.join(', ') : 'none')}\n`);
|
|
85
|
+
}
|
|
86
|
+
async function disableTelegram() {
|
|
87
|
+
const useCase = container.resolve(ConfigureTelegramUseCase);
|
|
88
|
+
const result = await useCase.execute({ enabled: false });
|
|
89
|
+
resetSettings();
|
|
90
|
+
initializeSettings(result.settings);
|
|
91
|
+
messages.success('Telegram integration disabled.');
|
|
92
|
+
}
|
|
93
|
+
async function enableTelegramNonInteractive(options) {
|
|
94
|
+
if (!options.botToken) {
|
|
95
|
+
messages.error('--bot-token is required when enabling Telegram.');
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const useCase = container.resolve(ConfigureTelegramUseCase);
|
|
100
|
+
messages.info('Validating bot token...');
|
|
101
|
+
const result = await useCase.execute({
|
|
102
|
+
enabled: true,
|
|
103
|
+
botToken: options.botToken,
|
|
104
|
+
chatId: options.chatId,
|
|
105
|
+
});
|
|
106
|
+
resetSettings();
|
|
107
|
+
initializeSettings(result.settings);
|
|
108
|
+
if (result.botInfo) {
|
|
109
|
+
messages.success(`Connected to @${result.botInfo.username}`);
|
|
110
|
+
}
|
|
111
|
+
if (result.chatResolution) {
|
|
112
|
+
messages.success(`Paired with ${result.chatResolution.firstName} (chat ID: ${result.chatResolution.chatId})`);
|
|
113
|
+
}
|
|
114
|
+
messages.success('Telegram integration enabled. Check your Telegram for a confirmation message.');
|
|
115
|
+
}
|
|
116
|
+
async function enableTelegramInteractive() {
|
|
117
|
+
console.log(`\n${fmt.heading('Telegram Remote Control Setup')}\n`);
|
|
118
|
+
console.log(`${colors.muted('This wizard will connect Shep to your personal Telegram bot.')}`);
|
|
119
|
+
console.log(`${colors.muted('You will receive notifications and can send commands via Telegram.\n')}`);
|
|
120
|
+
console.log(`${colors.muted('Prerequisites:')}`);
|
|
121
|
+
console.log(`${colors.muted(' 1. Create a bot via @BotFather on Telegram')}`);
|
|
122
|
+
console.log(`${colors.muted(' 2. Send /start to your new bot')}\n`);
|
|
123
|
+
const botToken = await input({
|
|
124
|
+
message: 'Enter your bot token from @BotFather:',
|
|
125
|
+
validate: (value) => {
|
|
126
|
+
if (!value.trim())
|
|
127
|
+
return 'Bot token is required';
|
|
128
|
+
if (!value.includes(':'))
|
|
129
|
+
return 'Invalid bot token format (should contain ":")';
|
|
130
|
+
return true;
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const useCase = container.resolve(ConfigureTelegramUseCase);
|
|
134
|
+
messages.info('Validating bot token...');
|
|
135
|
+
const result = await useCase.execute({
|
|
136
|
+
enabled: true,
|
|
137
|
+
botToken: botToken.trim(),
|
|
138
|
+
});
|
|
139
|
+
resetSettings();
|
|
140
|
+
initializeSettings(result.settings);
|
|
141
|
+
if (result.botInfo) {
|
|
142
|
+
messages.success(`Connected to @${result.botInfo.username}`);
|
|
143
|
+
}
|
|
144
|
+
if (result.chatResolution) {
|
|
145
|
+
messages.success(`Paired with ${result.chatResolution.firstName} (chat ID: ${result.chatResolution.chatId})`);
|
|
146
|
+
}
|
|
147
|
+
messages.success('Telegram integration enabled! Check your Telegram for a confirmation message.');
|
|
148
|
+
const wantTest = await confirm({
|
|
149
|
+
message: 'Send a test notification?',
|
|
150
|
+
default: false,
|
|
151
|
+
});
|
|
152
|
+
if (wantTest) {
|
|
153
|
+
const telegramSvc = container.resolve('ITelegramService');
|
|
154
|
+
const telegram = result.settings.telegram;
|
|
155
|
+
await telegramSvc.sendMessage(telegram.botToken, telegram.chatId, '\u{1F6CE}\uFE0F <b>Test notification from Shep!</b>\nIf you see this, everything is working.');
|
|
156
|
+
messages.success('Test notification sent!');
|
|
157
|
+
}
|
|
158
|
+
}
|