From acc1a4571e0a093c52d0918e0bf9508afe565fbc Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 9 Dec 2023 10:38:34 +0300 Subject: [PATCH] Remove desktop notifications, move assist-stuff to separate dir --- data/config-sample.json | 22 +-- lib/BotLib.pm | 30 ++-- lib/BotLib/{Command.pm => Chan/Assist.pm} | 96 +------------ lib/BotLib/Periodic.pm | 165 ---------------------- lib/BotLib/Util.pm | 14 +- lib/IRCBot.pm | 84 +---------- 6 files changed, 30 insertions(+), 381 deletions(-) rename lib/BotLib/{Command.pm => Chan/Assist.pm} (59%) delete mode 100644 lib/BotLib/Periodic.pm diff --git a/data/config-sample.json b/data/config-sample.json index cbef88d..2cbbde5 100644 --- a/data/config-sample.json +++ b/data/config-sample.json @@ -13,7 +13,7 @@ # Список каналов, к которым необходимо заджойниться "channels": { "assist": "#a", - "notify": "#notify" + "notify": "#registry" }, # datadir, куда складываются данные - databases, datafiles, etc @@ -21,29 +21,9 @@ # Базы данных и их относительные пути (относительно datadir) "db": { - "notifications": "db/notifications", - "delayed_notifications": "db/delayed_notifications", "todo": "db/todo" }, - # Настройки уведомлений - "notifications": { - # 0 - ничего в канал notify не пишем - "enabled": 1, - # Забываем все нотификашки старее, чем retention_days дней - "retention_days": 2, - # 0 - не показывать Desktop Notification в MacOS/Linux - "desktop_notification": 0, - # Звук уведомлений, желательно абсолютный путь - #"sound": "data/bell.mp3" - }, - - # Отложенные уведомления происходят, джоинится на канал notify - "delayed_notifications": { - "enabled": 1, - "retention_days": 2 - }, - # Настройки логов "log" : "meow.log", "loglevel" : "info", diff --git a/lib/BotLib.pm b/lib/BotLib.pm index 796dc00..8c32293 100644 --- a/lib/BotLib.pm +++ b/lib/BotLib.pm @@ -4,20 +4,20 @@ use 5.030; ## no critic (ProhibitImplicitImport) use strict; use warnings; use utf8; -use open qw (:std :utf8); -use English qw ( -no_match_vars ); +use open qw (:std :utf8); +use English qw ( -no_match_vars ); -use JSON::XS (); -use Log::Any qw ($log); +use JSON::XS (); +use Log::Any qw ($log); -use BotLib::Conf qw (LoadConf); -use BotLib::Command qw (Help Notify Todo Md5Str Sha1Str Sha224Str Sha256Str Sha384Str Sha512Str Crc32Str MurmurhashStr - B64Str UrlencodeStr IrandNum); -use BotLib::Util qw (runcmd); +use BotLib::Conf qw (LoadConf); +use BotLib::Chan::Assist qw (Help Todo Md5Str Sha1Str Sha224Str Sha256Str Sha384Str Sha512Str Crc32Str MurmurhashStr + B64Str UrlencodeStr IrandNum); +use BotLib::Util qw (runcmd); use version; our $VERSION = qw (1.0); -use Exporter qw (import); -our @EXPORT_OK = qw (Command SigHandler SigIntHandler SigTermHandler SigQuitHandler); +use Exporter qw (import); +our @EXPORT_OK = qw (Command SigHandler SigIntHandler SigTermHandler SigQuitHandler); local $OUTPUT_AUTOFLUSH = 1; @@ -123,16 +123,6 @@ sub Command { } } elsif ($cmd eq 'quit' || $cmd eq 'exit') { SigHandler ('exit'); - } elsif ($cmd =~ /^(notify|remind)\s+me\s+after\s+(\d+)\s*(s|seconds|m|minutes|h|hours)\s+(.*)$/) { - my $amount = $2; - my $units = $3; - my $message = $4; - $reply = Notify ($chatid, $amount, $units, $message); - } elsif ($cmd =~ /n\s+(\d+)(s|m|h)\s+(.*)$/) { - my $amount = $1; - my $units = $2; - my $message = $3; - $reply = Notify ($chatid, $amount, $units, $message); } elsif ($cmd =~ /^(todo\s*|todo\s+.+)$/) { $reply = Todo ($cmd); } elsif ($cmd =~ /^b64\s(.*)/) { diff --git a/lib/BotLib/Command.pm b/lib/BotLib/Chan/Assist.pm similarity index 59% rename from lib/BotLib/Command.pm rename to lib/BotLib/Chan/Assist.pm index 5bdf08d..5ee2a2b 100644 --- a/lib/BotLib/Command.pm +++ b/lib/BotLib/Chan/Assist.pm @@ -1,4 +1,4 @@ -package BotLib::Command; +package BotLib::Chan::Assist; # Парсит команды use 5.030; ## no critic (ProhibitImplicitImport) @@ -13,11 +13,11 @@ use Log::Any qw ($log); use BotLib::Conf qw (LoadConf); use BotLib::Util qw (storedata rakedata deletedata timems utf2md5hex utf2sha1hex utf2sha224hex utf2sha256hex - utf2sha384hex utf2sha512hex utf2crc32hex utf2murmurhash utf2b64 urlencode irandom); + utf2sha384hex utf2sha512hex utf2crc32hex utf2murmurhash utf2b64 urlencode irandom PrintMsg); use version; our $VERSION = qw (1.0); use Exporter qw (import); -our @EXPORT_OK = qw (Help Notify DelayedNotify PrintMsg Todo B64Str Md5Str Sha1Str Sha224Str Sha256Str Sha384Str +our @EXPORT_OK = qw (Help PrintMsg Todo B64Str Md5Str Sha1Str Sha224Str Sha256Str Sha384Str Sha512Str Crc32Str MurmurhashStr UrlencodeStr IrandNum); local $OUTPUT_AUTOFLUSH = 1; @@ -56,96 +56,6 @@ sub Help { return $text; } -# Одноразовая напоминалка. Сохраняет сообщение в базу notifications и отправляет его в delayed notifications. -# Возвращает либо строку-подтверждение, либо подсказку как пользоваться командой. -# $string Notify($chatid, $amount, $units, $message) -sub Notify { - my $chatid = shift; - my $amount = shift; - my $units = shift; - my $message = shift; - - if (defined $units) { - if ($units eq 'm' || $units eq 'minute' || $units eq 'minutes') { - $amount = eval { $amount * 60 }; - - unless (defined $units) { - return sprintf ('Usage: %s NUM [s|seconds|m|minutes|h|hours]', $units); - } - } elsif ($units eq 'h' || $units eq 'hour' || $units eq 'hours') { - $amount = eval { $amount * 60 * 60 }; - - unless (defined $units) { - return sprintf ('Usage: %s NUM [s|seconds|m|minutes|h|hours]', $units); - } - } elsif ($units eq 's' || $units eq 'second' || $units eq 'seconds') { - $amount = eval { $amount + 0 }; - - unless (defined $units) { - return sprintf ('Usage: %s NUM [s|seconds|m|minutes|h|hours]', $units); - } - } - } else { - $amount = eval { $amount + 0 }; - - unless (defined $units) { - return sprintf ('Usage: %s NUM [s|seconds|m|minutes|h|hours]', $units); - } - } - - my $timestamp = time () + $amount; - my $msg->{title} = 'Meow-bot notification'; - my $json; - - if (defined $message && $message ne '') { - $msg->{text} = $message; - } else { - $msg->{text} = 'Напоминалка!'; - } - - $json = encode_json $msg; - storedata ('notifications', $timestamp, $json, $c->{notifications}->{retention_days}); - DelayedNotify ($c->{channels}->{notify}, $msg->{text}); - - return sprintf 'Notification will be sent after %d seconds', $amount; -} - -# Пишет сообщение в чятик и складывает в БД, чтобы позже показать юзеру, когда он появится в чятике -# undef PrinfMsgForUser($userid, $chatid, $message) -sub DelayedNotify { - my $channel = shift; - my $message = shift; - - my $timestamp = timems (); - my $data; - $data->{message} = $message; - $data->{channel} = $channel; - $data->{timestamp} = $timestamp; - $data->{expiration_time} = $c->{delayed_notifications}->{retention_days} * 24 * 60 * 60 * 1000 + $timestamp; - $data->{retention_days} = $c->{delayed_notifications}->{retention_days} ; - - # Достанем список тех, кто есть в канальчике notify, им на джоине показывать сообщение не надо - my @users = keys %{$MAIN::IRC->channel_list->{$c->{channels}->{notify}}}; ## no critic (Modules::RequireExplicitInclusion) - $data->{users_shown} = \@users; - - # TODO: Pretty jsonl - my $json = encode_json $data; - storedata ('delayed_notifications', timems (), $json, $c->{delayed_notifications}->{retention_days}); - - return; -} - -# Пишет сообщение в чятик или юзеру -# undef PrintMsg($chatid, $text) -sub PrintMsg { - my $channel = shift; - my $message = shift; - - $MAIN::IRC->send_long_message ('utf8', 0, 'PRIVMSG', $channel, $message); ## no critic (Modules::RequireExplicitInclusion) - $log->debug ("[DEBUG] message '$message' sent to $channel"); - - return; -} # Выгребает список todo-шек из базы # $msg = _todoPrint() diff --git a/lib/BotLib/Periodic.pm b/lib/BotLib/Periodic.pm deleted file mode 100644 index 9ca3981..0000000 --- a/lib/BotLib/Periodic.pm +++ /dev/null @@ -1,165 +0,0 @@ -package BotLib::Periodic; - -use 5.030; ## no critic (ProhibitImplicitImport) -use strict; -use warnings; - -use utf8; -use open qw (:std :utf8); -use English qw ( -no_match_vars ); - -use JSON::XS qw (decode_json); -use Log::Any qw ($log); - -use BotLib::Command qw (PrintMsg); -use BotLib::Conf qw (LoadConf); -use BotLib::Util qw (runcmd cleanexpireddata rakedata deletedata); - -use version; our $VERSION = qw (1.0); -use Exporter qw (import); -our @EXPORT_OK = qw (PollNotifications CleanExpiredEntries); - -local $OUTPUT_AUTOFLUSH = 1; - -my $c = LoadConf (); - -# Регулярно выгребаем нотификашки из базы, запускаем нотификашки и пишем уведомления в канальчик с оповещениями -# undef PollNotifications() -sub PollNotifications { - # Достаём все нотифаи в формате ссылки на хэш - my $notification = rakedata ('notifications'); - $log->debug ('[DEBUG] Raked notifications from ' . $c->{db}->{notifications} . ' db'); - - my $deadline = time (); - my @notifications = sort (keys %{$notification}); - - foreach my $timestamp (@notifications) { - if ($timestamp <= $deadline) { - $log->debug ("[Debug] Notification timestamp $timestamp <= $deadline, parsing"); - - my $alarm = eval { decode_json ($notification->{$timestamp}); }; - deletedata ('notifications', $timestamp); - - next unless ($c->{notifications}->{enabled}); - - # TODO: Показывать нотификации не только для mac os - if (defined $alarm) { - my $res; - my @cmd; - - if (defined ($c->{notifications}->{sound}) && -e $c->{notifications}->{sound}) { - if ($OSNAME eq 'darwin') { - # Mac OS - $log->debug ("[DEBUG] use macos-specific methods of playing sounds"); - @cmd = ('afplay', $c->{notifications}->{sound}, '--volume', '6'); - } elsif ($OSNAME eq 'MSWin32') { - # Windows - # Хрен знает как проиграть штатными средствами, без всплывающих окон звук в windows - # TODO: Научиться проигрывать звук в windows :) - $log->debug ("[DEBUG] windows detected, i don't know how to play sounds there") - } elsif ($OSNAME eq 'linux' || $OSNAME eq 'freebsd') { - # Linux/FreeBSD - $log->debug ("[DEBUG] use linux/freebsd-specific methods of playing sounds"); - - if (-x '/usr/bin/mpg123' || -x '/bin/mpg123' || -x '/usr/local/bin/mpg123') { - @cmd = ('mpg123', '-q', $c->{notifications}->{sound}); - } elsif (-x '/usr/bin/mpg321' || -x '/bin/mpg321' || -x '/usr/local/bin/mpg321') { - @cmd = ('mpg321', '-q', $c->{notifications}->{sound}); - } elsif (-x '/usr/bin/madplay' || -x '/bin/madplay' || -x '/usr/local/bin/madplay') { - @cmd = ('madplay', '-q', '--no-tty-control', $c->{notifications}->{sound}); - } elsif (-x '/usr/bin/mp3blaster' || -x '/bin/mp3blaster' || -x '/usr/local/bin/mp3blaster') { - @cmd = ('mp3blaster', $c->{notifications}->{sound}); - } - } else { - $log->debug ("[DEBUG] don't know how to play sounds on $OSNAME"); - } - - if ($#cmd > 0) { - $res = runcmd (@cmd); - } - - $#cmd = -1; - } - - # Хрен знает, как показывать нотификашки стандартными средствами windows - # TODO: Научиться показывать нотификашки в windows :) - if ($OSNAME ne 'MSWin32') { - if (defined $alarm->{text} && $alarm->{text} ne '') { - if (defined $alarm->{title} && $alarm->{title} ne '') { - if ($OSNAME eq 'darwin') { - $log->debug ("[DEBUG] use macos-specific method of desktop notifications"); - my $applescript = sprintf ( - 'display notification "%s" with title "%s"', - $alarm->{text}, $alarm->{title}, - ); - - @cmd = ('osascript', '-e', "'$applescript'"); - } else { - $log->debug ("[DEBUG] use linux/freebsd-specific method of desktop notifications"); - @cmd = ('notify-send', '--expire-time=5000', $alarm->{title}, $alarm->{text}); - } - } else { - if ($OSNAME eq 'darwin') { - $log->debug ("[DEBUG] use macos-specific method of desktop notifications"); - my $applescript = sprintf ( - 'display notification "%s" with title "%s"', - $alarm->{text}, $alarm->{text}, - ); - - @cmd = ('osascript', '-e', "'$applescript'"); - } else { - $log->debug ("[DEBUG] use linux/freebsd-specific method of desktop notifications"); - @cmd = ('notify-send', '--expire-time=5000', 'Напоминание от meow-бота', $alarm->{text}); - } - } - - PrintMsg($c->{channels}->{notify}, $alarm->{text}); - } else { - if ($OSNAME eq 'darwin') { - $log->debug ("[DEBUG] use macos-specific method of desktop notifications"); - my $applescript = 'display notification "Настало время придти времени." with title "Напоминание от meow-бота"'; - @cmd = ('osascript', '-e', "'$applescript'"); - } else { - $log->debug ("[DEBUG] use linux/freebsd-specific method of desktop notifications"); - @cmd = ('notify-send', '--expire-time=5000', 'Напоминание от meow-бота', 'Настало время придти времени.'); - } - - PrintMsg ($c->{channels}->{notify}, 'Настало время придти времени.'); - } - - $res = runcmd (@cmd); - } else { - $log->debug ('[DEBUG] skip desktopn notification on windows'); - } - } else { - $log->error (sprintf ( - '[ERROR] Bad notification data in db %s: %s', - $c->{db}->{notifications}, $EVAL_ERROR, - )); - } - } else { - $log->debug ("[Debug] Notification timestamp $timestamp > $deadline, skipping"); - } - - # Не выплёвываем все нотификации сразу, если их более одной штуки - if ($#notifications > -1) { - sleep 1; - } - } - - return; -} - -# Вычищает протухшие записи из всех баз -# undef CleanExpiredEntries() -sub CleanExpiredEntries { - foreach my $db (keys %{$c->{db}}) { - cleanexpireddata ($db); - } - - return; -} - -1; - -# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/BotLib/Util.pm b/lib/BotLib/Util.pm index f09fc7b..c32c22e 100644 --- a/lib/BotLib/Util.pm +++ b/lib/BotLib/Util.pm @@ -26,7 +26,7 @@ use version; our $VERSION = qw (1.0); use Exporter qw (import); our @EXPORT_OK = qw (timems trim urlencode utf2b64 utf2sha1 utf2md5 utf2sha1hex utf2sha224hex utf2sha256hex utf2sha384hex utf2sha512hex utf2md5hex utf2crc32hex utf2murmurhash irandom runcmd storedata - cleanexpireddata rakedata deletedata); + cleanexpireddata rakedata deletedata PrintMsg); local $OUTPUT_AUTOFLUSH = 1; @@ -405,6 +405,18 @@ sub deletedata { return; } +# Пишет сообщение в чятик или юзеру +# undef PrintMsg($chatid, $text) +sub PrintMsg { + my $channel = shift; + my $message = shift; + + $MAIN::IRC->send_long_message ('utf8', 0, 'PRIVMSG', $channel, $message); ## no critic (Modules::RequireExplicitInclusion) + $log->debug ("[DEBUG] message '$message' sent to $channel"); + + return; +} + 1; # vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/IRCBot.pm b/lib/IRCBot.pm index c065124..90d9c1e 100644 --- a/lib/IRCBot.pm +++ b/lib/IRCBot.pm @@ -17,10 +17,8 @@ use JSON::XS qw (decode_json encode_json); use Log::Any qw ($log); use BotLib qw (Command SigIntHandler SigTermHandler SigQuitHandler); -use BotLib::Command qw (PrintMsg); use BotLib::Conf qw (LoadConf); -use BotLib::Periodic qw (PollNotifications CleanExpiredEntries); -use BotLib::Util qw (rakedata storedata deletedata); +use BotLib::Util qw (rakedata storedata PrintMsg); use version; our $VERSION = qw (1.0); use Exporter qw (import); @@ -39,20 +37,6 @@ sub RunIRCBot { my $sigterm = AnyEvent->signal (signal => 'TERM', cb => \&SigTermHandler); my $sigquit = AnyEvent->signal (signal => 'QUIT', cb => \&SigQuitHandler); - # Раз в секунду опрашиваем базу с нотификашками - my $pollnotifications = AnyEvent->timer ( - after => 3, # seconds - interval => 1, # second - cb => \&PollNotifications, - ); - - # Раз в 15 минут чистим базы от просроченных записей - my $cleandatabeses = AnyEvent->timer ( - after => 900, # seconds - interval => 900, # seconds - cb => \&CleanExpiredEntries, - ); - # Looks butt-ugly, but we have to make global var calls... err calls to MAIN namespace var $MAIN::IRC = AnyEvent::IRC::Client->new (send_initial_whois => 1); ## no critic (Modules::RequireExplicitInclusion) @@ -225,68 +209,6 @@ sub RunIRCBot { $log->info (sprintf '[INFO] I joined to %s channel', $channel); } else { $log->info( sprintf '[INFO] %s joined to %s channel', $nick, $channel); - - # Покажем отложенные уведомления пользователю в личку - if ($channel eq $c->{channels}->{notify}) { - # Юзер заджойнился в канал с нотификашками, надо ему в приват вывалить все нотификашки, которые - # были, пока его не было, либо то, что было за последние retention_days - my $notifications = rakedata ('delayed_notifications'); - my $title_shown = 0; - - foreach my $timestamp (sort keys (%{$notifications})) { - my $show = 1; - my $item = decode_json ($notifications->{$timestamp}); - - unless (defined $item) { - $log->error ("[ERROR] Unable to decode json from $c->{db}->{delayed_notifications} db: $EVAL_ERROR"); - } - - foreach my $user (@{$item->{users_shown}}) { - if ($user eq $nick) { - $show = 0; - last; - } - } - - if ($show) { - push @{$item->{users_shown}}, $nick; - - unless ($title_shown) { - PrintMsg ($nick, 'Missing notifications since your last visit or for 2 days:'); - $title_shown = 1; - } - - my $msg = sprintf ( - 'Channel %s: %s', - $item->{channel}, - $item->{message}, - ); - - if ($c->{delayed_notifications}->{enabled}) { - PrintMsg ($nick, $msg); - } - - deletedata ('delayed_notifications', $item->{timestamp}); - # TODO: pretty jsonl - my $json = encode_json $item; - - storedata ( - $c->{db}->{delayed_notifications}, - $item->{timestamp}, - $json, - $item->{expiration_time}, - ); - } - } - } - - # Заинвайтим пользователя в канал notify - if ($channel eq $c->{channels}->{assist}) { - if ($c->{notifications}->{enabled}) { - $log->debug ("[DEBUG] Inviting $nick to $c->{channels}->{notify}"); - $MAIN::IRC->send_srv ('INVITE', $nick, $c->{channels}->{notify}); - } - } } return; @@ -326,7 +248,7 @@ sub RunIRCBot { $MAIN::IRC->ctcp_auto_reply ('SOURCE', ## no critic (Modules::RequireExplicitInclusion) [ 'SOURCE', - 'https://github.com/elersir/meow-irc-bot', + 'https://github.com/eleksir/meow-irc-bot', ], ); @@ -352,7 +274,7 @@ sub RunIRCBot { ); if ($c->{ssl}) { - $MAIN::IRC->enable_ssl (); ## no critic (Modules::RequireExplicitInclusion) + $MAIN::IRC->enable_ssl (); ## no critic (Modules::RequireExplicitInclusion) } $MAIN::IRC->connect ( ## no critic (Modules::RequireExplicitInclusion)