From ac89f7cdc6370d5e001e83406df1dfe387175612 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:47:02 +0200 Subject: [PATCH 1/2] Accept /start as a subscribe alias (#103) * Accept /start as a subscribe alias Telegram's first-contact button on a fresh chat sends /start, which the bot previously fell through silently. Mapping it to the same handler as /subscribe gives users one-click onboarding without losing the explicit /subscribe command for users that already know it. * Align subscribe/unsubscribe replies with monitoring bots Match the explicit-feedback style used by the new /subscribe self- service in the monitoring/rangekeeper/lds bots: - /subscribe when already subscribed: 'You are already subscribed.' (was: silent return) - /subscribe when new: 'You are now subscribed. Use /unsubscribe to stop.' (was: 'You are now subscribed.') - /unsubscribe when not subscribed: 'You are not subscribed.' (was: silent return) - /unsubscribe when subscribed: unchanged Plus add /start to the telegramHandles list so HelpMessage advertises it consistently after the alias commit. --- socialmedia/telegram/telegram.service.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/socialmedia/telegram/telegram.service.ts b/socialmedia/telegram/telegram.service.ts index da0a4d7..a113def 100644 --- a/socialmedia/telegram/telegram.service.ts +++ b/socialmedia/telegram/telegram.service.ts @@ -38,7 +38,7 @@ const TELEGRAM_THROTTLE_MS = 100; export class TelegramService implements OnModuleInit, SocialMediaFct { private readonly logger = new Logger(this.constructor.name); private readonly bot = new TelegramBot(CONFIG.telegram.botToken, { polling: true }); - private readonly telegramHandles: string[] = ['/subscribe', '/unsubscribe', '/help']; + private readonly telegramHandles: string[] = ['/start', '/subscribe', '/unsubscribe', '/help']; private readonly telegramState: TelegramState; private telegramGroupState: TelegramGroupState; @@ -373,6 +373,10 @@ export class TelegramService implements OnModuleInit, SocialMediaFct { this.sendHelpMessage(m); break; + // Telegram's default first-contact button maps to /start, so accepting it as + // a subscribe alias gives users a one-click onboarding path. /subscribe stays + // as an explicit alias for users that already know the command. + case '/start': case '/subscribe': this.sendSubscribeMessage(m); break; @@ -391,10 +395,13 @@ export class TelegramService implements OnModuleInit, SocialMediaFct { private async sendSubscribeMessage(msg: TelegramBot.Message): Promise { const group = msg.chat.id.toString(); const isSubscribed = this.telegramGroupState.groups.find((g) => g === group); - if (isSubscribed) return; + if (isSubscribed) { + this.sendMessage(group, `You are already subscribed.`); + return; + } this.telegramGroupState.groups.push(group); - this.sendMessage(group, `You are now subscribed.`); + this.sendMessage(group, `You are now subscribed. Use /unsubscribe to stop.`); this.writeBackupGroups(); } @@ -402,7 +409,10 @@ export class TelegramService implements OnModuleInit, SocialMediaFct { private async sendUnsubscribeMessage(msg: TelegramBot.Message): Promise { const group = msg.chat.id.toString(); const isSubscribed = this.telegramGroupState.groups.find((g) => g === group); - if (!isSubscribed) return; + if (!isSubscribed) { + this.sendMessage(group, `You are not subscribed.`); + return; + } const newGroups = this.telegramGroupState.groups.filter((g) => g != group); this.telegramGroupState.groups = newGroups; From 1203a5ac8b949f4e92ea16325b442896c6d4d492 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:47:21 +0200 Subject: [PATCH 2/2] Initialise /app/.api with node:node ownership (#102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The api container runs as the `node` user but the .api persistence directory (telegram.groups.json, twitter.token.json) was never explicitly created in the image — when bind-mounted as a Docker named volume the runtime path is initialised as root:root, and writeBackupGroups() fails with EACCES on every /subscribe. Pre-create /app/.api in the image with the correct ownership so fresh volumes inherit it. Existing volumes still need a one-off chown (handled separately via the deploy stack). --- Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3066c9d..bb280b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,11 @@ FROM node:lts-alpine -RUN mkdir /app && chown -R node:node /app +# Pre-create /app/.api so the telegram/twitter persistence dir inherits node:node +# ownership when bind-mounted as a Docker volume. Without this, Docker initialises +# the volume as root:root and the container — running as `node` — cannot persist +# subscriber state via writeBackupGroups() (manifests as `Telegram group backup +# failed` on every /subscribe, with no telegram.groups.json ever written). +RUN mkdir -p /app/.api && chown -R node:node /app WORKDIR /app USER node