mnemosyne/bin/mnemosyne-bot

79 lines
2.7 KiB
Perl
Executable File

#!/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;