#!/usr/bin/env perl use strict; use warnings; use FindBin qw($RealBin); use lib "$RealBin/../lib"; # Webhook receiver — Mojolicious::Lite app. # Binds to 127.0.0.1 only; nginx terminates TLS and proxies inbound updates. # # Startup: # mnemosyne-bot --config /path/to/mnemosyne.conf # mnemosyne-bot --config /path/to/mnemosyne.conf daemon (same, explicit) # # In production (systemd), the unit sets: # ExecStart=/usr/bin/perl /opt/mnemosyne/bin/mnemosyne-bot --config /etc/mnemosyne/mnemosyne.conf use Mnemosyne::Config; use Mnemosyne::DB; use Mnemosyne::Telegram; use Mnemosyne::Webhook; use Mojolicious::Lite; # ---- parse --config before Mojolicious sees ARGV ---------------------- my $config_path = "$RealBin/../config/mnemosyne.conf"; my @passthrough; while (@ARGV) { my $arg = shift @ARGV; if ($arg eq '--config' && @ARGV) { $config_path = shift @ARGV; } else { push @passthrough, $arg; } } @ARGV = @passthrough; # ---- load config & bootstrap ------------------------------------------ die "Config file not found: $config_path\n" unless -f $config_path; my $config = Mnemosyne::Config->new($config_path); my $db = Mnemosyne::DB->new($config->get('db', 'path')); my $token = $config->get('bot', 'token') or die "bot.token missing in config\n"; my $secret = $config->get('bot', 'webhook_secret') or die "bot.webhook_secret missing in config\n"; my $port = $config->get('bot', 'listen_port') // 8443; my $wh_url = $config->get('bot', 'webhook_url') or die "bot.webhook_url missing in config\n"; my $telegram = Mnemosyne::Telegram->new($token); # Extract the path component from the webhook URL # e.g. https://example.com/tghook/abc123 → /tghook/abc123 (my $wh_path = $wh_url) =~ s{^https?://[^/]+}{}; $wh_path ||= '/webhook'; # ---- webhook endpoint ------------------------------------------------- post $wh_path => sub { my ($c) = @_; # Gate 1: validate Telegram's secret-token header my $incoming_secret = $c->req->headers->header('X-Telegram-Bot-Api-Secret-Token') // ''; unless ($incoming_secret eq $secret) { $c->render(text => 'Forbidden', status => 403); return; } # Respond 200 immediately — Telegram retries on non-2xx or timeouts $c->render(text => 'ok', status => 200); # Process the update (synchronous; fast enough for single-user load) my $update = $c->req->json; eval { Mnemosyne::Webhook->handle_update($update, $db, $config, $telegram); }; warn "Webhook processing error: $@\n" if $@; }; # ---- start ------------------------------------------------------------ # Default to daemon on localhost; let explicit ARGV override for hypnotoad etc. push @ARGV, ('daemon', '-l', "http://127.0.0.1:$port") unless @ARGV; app->start;