0

Remove desktop notifications, move assist-stuff to separate dir

This commit is contained in:
eleksir
2023-12-09 10:38:34 +03:00
parent 556540ddb2
commit acc1a4571e
6 changed files with 30 additions and 381 deletions

View File

@@ -13,7 +13,7 @@
# Список каналов, к которым необходимо заджойниться # Список каналов, к которым необходимо заджойниться
"channels": { "channels": {
"assist": "#a", "assist": "#a",
"notify": "#notify" "notify": "#registry"
}, },
# datadir, куда складываются данные - databases, datafiles, etc # datadir, куда складываются данные - databases, datafiles, etc
@@ -21,29 +21,9 @@
# Базы данных и их относительные пути (относительно datadir) # Базы данных и их относительные пути (относительно datadir)
"db": { "db": {
"notifications": "db/notifications",
"delayed_notifications": "db/delayed_notifications",
"todo": "db/todo" "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", "log" : "meow.log",
"loglevel" : "info", "loglevel" : "info",

View File

@@ -4,20 +4,20 @@ use 5.030; ## no critic (ProhibitImplicitImport)
use strict; use strict;
use warnings; use warnings;
use utf8; use utf8;
use open qw (:std :utf8); use open qw (:std :utf8);
use English qw ( -no_match_vars ); use English qw ( -no_match_vars );
use JSON::XS (); use JSON::XS ();
use Log::Any qw ($log); use Log::Any qw ($log);
use BotLib::Conf qw (LoadConf); use BotLib::Conf qw (LoadConf);
use BotLib::Command qw (Help Notify Todo Md5Str Sha1Str Sha224Str Sha256Str Sha384Str Sha512Str Crc32Str MurmurhashStr use BotLib::Chan::Assist qw (Help Todo Md5Str Sha1Str Sha224Str Sha256Str Sha384Str Sha512Str Crc32Str MurmurhashStr
B64Str UrlencodeStr IrandNum); B64Str UrlencodeStr IrandNum);
use BotLib::Util qw (runcmd); use BotLib::Util qw (runcmd);
use version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.0);
use Exporter qw (import); use Exporter qw (import);
our @EXPORT_OK = qw (Command SigHandler SigIntHandler SigTermHandler SigQuitHandler); our @EXPORT_OK = qw (Command SigHandler SigIntHandler SigTermHandler SigQuitHandler);
local $OUTPUT_AUTOFLUSH = 1; local $OUTPUT_AUTOFLUSH = 1;
@@ -123,16 +123,6 @@ sub Command {
} }
} elsif ($cmd eq 'quit' || $cmd eq 'exit') { } elsif ($cmd eq 'quit' || $cmd eq 'exit') {
SigHandler ('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+.+)$/) { } elsif ($cmd =~ /^(todo\s*|todo\s+.+)$/) {
$reply = Todo ($cmd); $reply = Todo ($cmd);
} elsif ($cmd =~ /^b64\s(.*)/) { } elsif ($cmd =~ /^b64\s(.*)/) {

View File

@@ -1,4 +1,4 @@
package BotLib::Command; package BotLib::Chan::Assist;
# Парсит команды # Парсит команды
use 5.030; ## no critic (ProhibitImplicitImport) use 5.030; ## no critic (ProhibitImplicitImport)
@@ -13,11 +13,11 @@ use Log::Any qw ($log);
use BotLib::Conf qw (LoadConf); use BotLib::Conf qw (LoadConf);
use BotLib::Util qw (storedata rakedata deletedata timems utf2md5hex utf2sha1hex utf2sha224hex utf2sha256hex 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 version; our $VERSION = qw (1.0);
use Exporter qw (import); 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); Sha512Str Crc32Str MurmurhashStr UrlencodeStr IrandNum);
local $OUTPUT_AUTOFLUSH = 1; local $OUTPUT_AUTOFLUSH = 1;
@@ -56,96 +56,6 @@ sub Help {
return $text; 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-шек из базы # Выгребает список todo-шек из базы
# $msg = _todoPrint() # $msg = _todoPrint()

View File

@@ -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:

View File

@@ -26,7 +26,7 @@ use version; our $VERSION = qw (1.0);
use Exporter qw (import); use Exporter qw (import);
our @EXPORT_OK = qw (timems trim urlencode utf2b64 utf2sha1 utf2md5 utf2sha1hex utf2sha224hex utf2sha256hex our @EXPORT_OK = qw (timems trim urlencode utf2b64 utf2sha1 utf2md5 utf2sha1hex utf2sha224hex utf2sha256hex
utf2sha384hex utf2sha512hex utf2md5hex utf2crc32hex utf2murmurhash irandom runcmd storedata utf2sha384hex utf2sha512hex utf2md5hex utf2crc32hex utf2murmurhash irandom runcmd storedata
cleanexpireddata rakedata deletedata); cleanexpireddata rakedata deletedata PrintMsg);
local $OUTPUT_AUTOFLUSH = 1; local $OUTPUT_AUTOFLUSH = 1;
@@ -405,6 +405,18 @@ sub deletedata {
return; 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; 1;
# vim: set ft=perl noet ai ts=4 sw=4 sts=4: # vim: set ft=perl noet ai ts=4 sw=4 sts=4:

View File

@@ -17,10 +17,8 @@ use JSON::XS qw (decode_json encode_json);
use Log::Any qw ($log); use Log::Any qw ($log);
use BotLib qw (Command SigIntHandler SigTermHandler SigQuitHandler); use BotLib qw (Command SigIntHandler SigTermHandler SigQuitHandler);
use BotLib::Command qw (PrintMsg);
use BotLib::Conf qw (LoadConf); use BotLib::Conf qw (LoadConf);
use BotLib::Periodic qw (PollNotifications CleanExpiredEntries); use BotLib::Util qw (rakedata storedata PrintMsg);
use BotLib::Util qw (rakedata storedata deletedata);
use version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.0);
use Exporter qw (import); use Exporter qw (import);
@@ -39,20 +37,6 @@ sub RunIRCBot {
my $sigterm = AnyEvent->signal (signal => 'TERM', cb => \&SigTermHandler); my $sigterm = AnyEvent->signal (signal => 'TERM', cb => \&SigTermHandler);
my $sigquit = AnyEvent->signal (signal => 'QUIT', cb => \&SigQuitHandler); 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 # 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) $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); $log->info (sprintf '[INFO] I joined to %s channel', $channel);
} else { } else {
$log->info( sprintf '[INFO] %s joined to %s channel', $nick, $channel); $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; return;
@@ -326,7 +248,7 @@ sub RunIRCBot {
$MAIN::IRC->ctcp_auto_reply ('SOURCE', ## no critic (Modules::RequireExplicitInclusion) $MAIN::IRC->ctcp_auto_reply ('SOURCE', ## no critic (Modules::RequireExplicitInclusion)
[ [
'SOURCE', 'SOURCE',
'https://github.com/elersir/meow-irc-bot', 'https://github.com/eleksir/meow-irc-bot',
], ],
); );
@@ -352,7 +274,7 @@ sub RunIRCBot {
); );
if ($c->{ssl}) { 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) $MAIN::IRC->connect ( ## no critic (Modules::RequireExplicitInclusion)