Compare commits

...

92 Commits

Author SHA1 Message Date
eleksir
65a3382bb0 Do not manipulate lib path directly.
Perl is a bit fucky about addng paths to @INC directly or via use lib;
Also update .gitignore.
2026-01-02 16:16:22 +03:00
eleksir
8f6ccfba04 Add export_settings.pl to save chat settings in json
Add missing functions to lib/BotLib/Admin.pm to be able to expoprt
chat settings for for particular chats.
Add extract_chatlist.txt.pl to extract chat list from debug.log
2026-01-02 15:56:08 +03:00
eleksir
2d0ad8560d Enhance logging, disable autoban spammers 2025-05-19 22:08:26 +03:00
eleksir
c24644b10e Ban spammers in slackware_ru chat automatically 2025-05-10 00:44:31 +03:00
eleksir
f97557868b User sendMessage directly via global object, without sugar 2025-05-09 16:19:49 +03:00
eleksir
b024d5f13a Do not send "Typing" as it cause errors, replace it with sleep. 2025-05-09 11:59:18 +03:00
eleksir
bc270457a4 Use correct fields for last_name and user id in updated member event (when new user joins) 2025-05-08 21:16:29 +03:00
eleksir
ba4ff36daf Solve myterious riddle with users some sneaking unnoted by bot, which is a bummer! 2025-05-08 17:51:41 +03:00
eleksir
ec59703000 Fix reaction username in logs 2025-05-08 17:27:52 +03:00
eleksir
59a1a3538b I have nothing to do with chat reactions, so log it and skip 2025-05-08 17:20:50 +03:00
eleksir
6e76d85899 Add quirk for api incosistency in reactions 2025-05-08 17:11:16 +03:00
eleksir
d21bab68ef Make proper use of Reaction stuff in Teapot lib 2025-05-08 15:49:25 +03:00
eleksir
408dccd1b5 Fix variable name in Brain.pm that explodes bot 2025-05-08 15:06:12 +03:00
eleksir
a568c6f146 Do not include in repo data/Admin directory 2025-05-08 15:03:45 +03:00
eleksir
5448c9bfdb Add type Reaction 2025-05-08 14:56:25 +03:00
eleksir
da182ae62f Do not call logOut() since we have no webhooks and for faster restarts. 2025-04-27 13:07:15 +03:00
eleksir
82df563f40 Add list of allowed updates explicitly, add Close() method for telegram connection 2025-04-27 13:05:31 +03:00
eleksir
7bd0c3428f Set explicitly ["chat_member","message_reaction","message_reaction_count"] as additional updates i want to receive 2025-04-24 22:01:29 +03:00
eleksir
7904957aaa Implement commands w п weather погода погодка погадка without arguments 2025-02-09 14:51:10 +03:00
eleksir
132ebeffa4 Fix crash on condition check in !admin {obutts,oboobs} # command 2024-10-25 09:44:37 +03:00
eleksir
4f9675f1d2 Import JSON::PP explicitly in lib/BotLib.pm 2024-08-22 21:44:09 +03:00
eleksir
1667772268 Make use of logOut() method 2024-08-22 21:42:58 +03:00
eleksir
ebaf2c9b38 Add logOut() method to Teapot lib 2024-08-22 21:29:49 +03:00
eleksir
b9c88ad4e7 Dump SendMessage() error only if we not handle it properly 2023-08-29 17:28:35 -07:00
eleksir
6a4c2bd076 Add command пословиться 2023-05-19 00:16:14 +03:00
eleksir
2f9b79d77d Add missing semicolon in Teapot/Bot/Object/Chat.pm 2023-04-16 17:06:33 +03:00
eleksir
8eb83ac2b8 Update internal version number of Teapot lib 2023-04-16 17:03:39 +03:00
eleksir
df3c198bce Add general support for BotAPI v6.6 2023-04-16 16:58:59 +03:00
eleksir
f019b98b1f Add partial BotAPI v6.5 support 2023-04-16 16:31:12 +03:00
eleksir
ecf4f94dca Add general support for BotAPI v6.4 2023-04-16 16:03:38 +03:00
eleksir
cea6920d78 Change loglevel for known errors to notice while handling sendMessage errors 2023-01-14 20:52:03 +03:00
eleksir
d4d830715b Add comments fo couple of fields in banChatMember method 2023-01-14 20:50:02 +03:00
eleksir
be5c654045 Rework BotAPI error handling, this time it looks kinda working, still wip, though 2023-01-07 17:19:16 +03:00
eleksir
5edf5a7e0c Fine tune GET polling requests timings, update Teapot lib pod-docs, bump Teapot lib version where missed last time 2023-01-07 16:56:56 +03:00
eleksir
780ec4fe29 Correctly handle successful response from banChatMember and restrictChatMember in BotLib.pm 2023-01-07 16:53:47 +03:00
eleksir
fc56f99433 Try to catch and log as most Bot API errors as we can in BotLib.pm 2023-01-02 21:27:34 +03:00
eleksir
5b93e43280 Only creator can toggle admin mute 2023-01-02 19:54:22 +03:00
eleksir
c5b4d1c866 Try to catch and log as most Bot API errors as we can in TelegramBot.pm 2023-01-02 19:34:38 +03:00
eleksir
5a5e4b5ead 5/5 Fix formatting in TelegramBot.pm 2023-01-02 19:21:19 +03:00
eleksir
e8765c0e88 4/5 Try to handle errors on sending message in RedisLib.pm 2023-01-02 19:20:31 +03:00
eleksir
3dc75936ea 3/5 Allow to enable mute-by-command not only to creator of chat 2023-01-02 19:08:02 +03:00
eleksir
1f3675de8f 2/5 Lift up error to caller in Teapot lib, introduce can_talk() in Brain.pm 2023-01-02 19:06:02 +03:00
eleksir
3668620bd8 1/5 Fix formatting in bin/aleesa-telegram 2023-01-02 19:04:28 +03:00
eleksir
5411d6df7f Fix fetching error message 2022-12-15 02:21:17 +03:00
eleksir
f6a5a05e58 Disable chat in morning greet list if chat id is not found 2022-12-15 02:19:01 +03:00
eleksir
6e084995a7 Use proper functions to check state of services in migration 2022-12-15 02:03:02 +03:00
eleksir
43c08e156f Return only if migration performed 2022-12-12 17:31:18 +03:00
eleksir
b84f8bb245 Move IsCensored to Admin.pm 2022-12-12 17:26:50 +03:00
eleksir
d43cb5995b Add settings migration when group -> supergroup promotion event discovered 2022-12-12 17:10:02 +03:00
eleksir
7d74a86dad In channel context Hightlight() is pointless 2022-12-12 15:19:41 +03:00
eleksir
d539c8c9cf DeeJay utility functions to Util.pm 2022-12-12 13:32:44 +03:00
eleksir
bd9f66930b In _post_request() try to catch error message as raw and return it to upper layer 2022-12-12 12:41:54 +03:00
eleksir
9618369a45 Add debug for api errors 2022-11-26 14:22:11 +03:00
eleksir
770fce4cfe Update dependencies 2022-11-26 14:16:19 +03:00
eleksir
80d9aeca20 Add fuck command 2022-11-26 14:02:52 +03:00
eleksir
548c0b3655 Fix logic in GreetMsgToggle() 2022-11-07 00:57:09 +03:00
eleksir
b4e6685213 Add support for Bot API 6.3 2022-11-06 22:08:02 +03:00
eleksir
f9658ac5e8 Actually add admin mute command 2022-10-09 02:06:54 +03:00
eleksir
3a75f4c947 Rename admin mute command because it actually bans chat prticipants 2022-10-09 00:47:38 +03:00
eleksir
58128bdbdb Add utility script 2022-05-13 18:06:21 +03:00
eleksir
bec963c7b3 Allow to parse any json-file as loosely formatted (not only config.json) 2022-05-13 18:05:15 +03:00
eleksir
b2af26d4e6 Ratelimit good morning greet 2022-05-13 11:50:41 +03:00
eleksir
2708d84d8a Properly set loglevel if it omitted in config 2022-05-13 11:49:22 +03:00
eleksir
508b749dff Fix 'open' module version, it is hardwired to perl distribution 2022-05-13 11:47:02 +03:00
eleksir
cefdec95c4 Forgot to sed /voice/video/g in message.pm 2022-05-01 11:50:00 +03:00
eleksir
35104f883c Take into account possible api errors 2022-04-24 01:13:36 +03:00
eleksir
281a5e6b3c Remove excessive debug, fix message format
* Remove duplicate debug for outgoing redis message
* Add log category for outgoing debug redis message
* Add missing field in outgoing redis message
2022-04-24 01:00:06 +03:00
eleksir
060244a84d Cleanup and update sample config 2022-04-24 00:54:23 +03:00
eleksir
1958f77e9c Also dump redis messages on debug loglevel 2022-04-23 14:48:34 +03:00
eleksir
93032dd35e Bump version of Mojolicious 9.22 -> 9.24 2022-04-23 14:35:59 +03:00
eleksir
8fd6fc1c10 Implement command !admin mute 2022-04-23 14:13:22 +03:00
eleksir
cb069ace15 Chatid must be non zero value 2022-04-23 14:12:28 +03:00
eleksir
33ab2a368d More strict check of redis message json format 2022-04-18 23:19:19 +03:00
eleksir
391b9889cf Add a bit of debug to RedisLib.pm 2022-04-16 20:03:12 +03:00
eleksir
216a139c18 Update Teapot lib to comply with api v5.6, v5.7, v6.0 2022-04-16 19:51:26 +03:00
eleksir
305550f6cf Set json types in more obvious fashion 2022-04-10 00:35:45 +03:00
eleksir
22cb7c9372 Restore good morning greet head part of message 2022-04-04 23:23:08 +03:00
eleksir
5022cb1f06 Add forgotten cerlcritic policy 2022-04-03 18:34:31 +03:00
eleksir
2209872a7b Fix admin command, use regex for complex command detection 2022-04-03 17:34:26 +03:00
eleksir
e140176c99 Check text length while detecting command 2022-04-03 16:53:07 +03:00
eleksir
3c8f79a19a Use regex while omittimg response to response on our new member greet 2022-04-03 16:40:10 +03:00
eleksir
c4c21127d8 more careful work with substr in BotLib.pm 2022-04-03 16:38:14 +03:00
eleksir
8d4bf1abd9 Use utf8-friendly space symbols chop operation in trim() 2022-04-03 16:36:56 +03:00
eleksir
5e5613ff82 Add list of used perlcritic modules 2022-04-03 16:06:11 +03:00
eleksir
8ce226f755 Более корректно работаем с substr() и задаём misc->answer явно 2022-03-27 20:08:57 +03:00
eleksir
3bbe325fc0 Also add !karma and !карма without trailing space 2022-03-27 18:10:23 +03:00
eleksir
e35e7065ad Set chatid explicitly to num for tgm msg 2022-03-27 16:15:18 +03:00
eleksir
6cd7c4091b Set userid and chatid explicitly to num for redis json msg 2022-03-26 09:49:30 +03:00
eleksir
6d8097d060 Hide !goodbye as bot api work not reliably 2022-03-08 21:27:52 +03:00
eleksir
b2d5512c84 Fix grammar, on simple toggle disable greet 2022-03-06 00:59:25 +03:00
eleksir
03a4a51a86 Add !admin goodbye command 2022-03-06 00:48:16 +03:00
eleksir
de2106b16b Add !admin greet command 2022-03-06 00:08:34 +03:00
71 changed files with 3373 additions and 874 deletions

2
.gitignore vendored
View File

@@ -4,5 +4,7 @@ _Deparsed_XSubs.pm
.vstags .vstags
aleesa-telegram.pid aleesa-telegram.pid
data/cache/** data/cache/**
data/admin/**
data/config.json data/config.json
data/chatlist.txt
vendor_perl/** vendor_perl/**

View File

@@ -1,4 +1,19 @@
#!/usr/bin/env perlcritic #!/usr/bin/env perlcritic
# Задействованные модули критика:
#
# Perl::Critic::More
# Perl::Critic::Pulp
# Perl::Critic::TooMuchCode
# Perl::Critic::Lax
# Perl::Critic::CognitiveComplexity
# Perl::Critic::Policy::CompileTime
# Perl::Critic::Policy::ProhibitImplicitImport
# Perl::Critic::Policy::Dynamic::NoIndirect
# Perl::Critic::StricterSubs
# Perl::Critic::Policy::ProhibitImplicitImport
# Perl::Critic::Policy::CodeLayout::RequireUseUTF8
severity = 5 severity = 5
verbose = 8 verbose = 8
@@ -130,3 +145,6 @@ max_length = 200
# This is problematic # This is problematic
[-Miscellanea::ProhibitUnrestrictedNoCritic] [-Miscellanea::ProhibitUnrestrictedNoCritic]
[-RegularExpressions::ProhibitFixedStringMatches]
# no, just no. I'm too lazy to everytime check string length before substr

View File

@@ -4,7 +4,7 @@ use 5.018; ## 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 version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.0);
@@ -24,29 +24,33 @@ BEGIN {
chdir $workdir; chdir $workdir;
} }
use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5");
no Cwd; no Cwd;
use Cwd::utf8 qw (chdir abs_path); use Cwd::utf8 qw (chdir abs_path);
use File::Basename qw (basename dirname); use File::Basename qw (basename dirname);
use File::Path qw (make_path); use File::Path qw (make_path);
use File::Spec qw (); use File::Spec qw ();
use Log::Any qw ($log); use Log::Any qw ($log);
use Log::Any::Adapter (); use Log::Any::Adapter ();
use Log::Any::For::Std (); use Log::Any::For::Std ();
use POSIX qw (setgid setuid setsid); use POSIX qw (setgid setuid setsid);
use BotLib::Conf qw (LoadConf); use BotLib::Conf qw (LoadConf);
use TelegramBot qw (RunTelegramBot); use TelegramBot qw (RunTelegramBot);
sub daemonize; sub daemonize;
sub __fork; sub __fork;
sub __signal_handler; sub __signal_handler;
local $OUTPUT_AUTOFLUSH = 1; local $OUTPUT_AUTOFLUSH = 1;
my $runAsUid = $UID;
my $piddir = '/var/run/' . basename (abs_path ($PROGRAM_NAME)); my $piddir = '/var/run/' . basename (abs_path ($PROGRAM_NAME));
my $C = LoadConf (); my $C = LoadConf ();
my $loglevel = $C->{'loglevel'} // 'notice'; my $loglevel = 'notice';
if (defined $C->{'loglevel'}) {
$loglevel = $C->{'loglevel'};
}
my $logfile; my $logfile;
if (defined $C->{'log'}) { if (defined $C->{'log'}) {

View File

@@ -7,7 +7,7 @@ requires 'Cwd', '==3.75';
requires 'Cwd::utf8', '==0.011'; requires 'Cwd::utf8', '==0.011';
requires 'Data::Dumper', '==2.183'; requires 'Data::Dumper', '==2.183';
requires 'Digest::SHA', '==6.02'; requires 'Digest::SHA', '==6.02';
requires 'Encode', '==3.16'; requires 'Encode', '==3.19';
requires 'English', '0'; requires 'English', '0';
requires 'Exporter', '==5.74'; requires 'Exporter', '==5.74';
# part of perl distribution # part of perl distribution
@@ -15,34 +15,33 @@ requires 'IPC::Open2', '0';
requires 'File::Basename', '==2.85'; requires 'File::Basename', '==2.85';
requires 'File::Path', '==2.18'; requires 'File::Path', '==2.18';
requires 'File::Spec', '==3.75'; requires 'File::Spec', '==3.75';
requires 'IO::Socket::SSL', '==2.074'; requires 'IO::Socket::SSL', '==2.077';
requires 'JSON', '==4.05'; requires 'JSON', '==4.10';
requires 'JSON::PP', '==4.07'; requires 'JSON::PP', '==4.12';
requires 'JSON::XS', '==4.03'; requires 'JSON::XS', '==4.03';
requires 'List::Util', '==1.60'; requires 'List::Util', '==1.60';
requires 'local::lib', '==2.000028'; requires 'local::lib', '==2.000029';
requires 'Log::Any', '==1.710'; requires 'Log::Any', '==1.711';
requires 'Log::Any::Adapter', '==1.710'; requires 'Log::Any::Adapter', '==1.711';
requires 'Log::Any::For::Std', '==0.05'; requires 'Log::Any::For::Std', '==0.05';
requires 'Math::Random::Secure', '==0.080001'; requires 'Math::Random::Secure', '==0.080001';
requires 'MIME::Base64', '==3.16'; requires 'MIME::Base64', '==3.16';
requires 'Mojolicious', '==9.22'; requires 'Mojolicious', '==9.30';
# part of Mojolicious # part of Mojolicious
requires 'Mojo::Base', '0'; requires 'Mojo::Base', '0';
# part of Mojolicious # part of Mojolicious
requires 'Mojo::IOLoop', '0'; requires 'Mojo::IOLoop', '0';
requires 'Mojo::Redis', '==3.27'; requires 'Mojo::Redis', '==3.29';
# part of Mojo::Redis # part of Mojo::Redis
requires 'Mojo::Redis::Connection', '0'; requires 'Mojo::Redis::Connection', '0';
# part of Mojolicious # part of Mojolicious
requires 'Mojo::UserAgent', '0'; requires 'Mojo::UserAgent', '0';
requires 'open', '==1.12'; # part of perl distribution
requires 'open', '0';
# part of perl distribution # part of perl distribution
requires 'POSIX', '0'; requires 'POSIX', '0';
# v0.07 does not pass tests if linked with hiredis >= 1.0, but as pub-sub works ok
requires 'Protocol::Redis::XS', '0';
requires 'Text::Fuzzy', '==0.29'; requires 'Text::Fuzzy', '==0.29';
requires 'URI::URL', '==5.10'; requires 'URI::URL', '==5.17';
# part of perl distribution # part of perl distribution
requires 'utf8', '0'; requires 'utf8', '0';
requires 'version', '==0.9929'; requires 'version', '==0.9929';

View File

@@ -2,13 +2,8 @@
# Cachedir used not only for caches but for some settings and stats too. # Cachedir used not only for caches but for some settings and stats too.
"cachedir" : "data/cache", "cachedir" : "data/cache",
"gamestat" : {
"dir" : "data",
"gamestatdir" : "data/gamestat"
},
# Required for debugging reasons, if commented out or not exist, no debug logging appears at all. # Required for debugging reasons, if commented out or not exist, no debug logging appears at all.
"log" : "/var/log/aleesa-telegram-bot/aleesa.log", "log" : "/var/log/aleesa-telegram/debug.log",
# Verbosity can be trace, debug, info (inform), notice, warning (warn), error (err), critical (crit, fatal), alert, emergency. # Verbosity can be trace, debug, info (inform), notice, warning (warn), error (err), critical (crit, fatal), alert, emergency.
# Currently implemented debug, info, notice, warn, error, fatal. Notice is default logging level. # Currently implemented debug, info, notice, warn, error, fatal. Notice is default logging level.
@@ -16,10 +11,6 @@
# Stuff for telegram itself. # Stuff for telegram itself.
"telegrambot" : { "telegrambot" : {
# Directory with per-conversation "brains".
"braindir" : "data/telegrambot-brains",
# Prefix-sign for all commands. In many irc-bots (eggdrop/windrop) it is "." (dot), in sulci it was "!". # Prefix-sign for all commands. In many irc-bots (eggdrop/windrop) it is "." (dot), in sulci it was "!".
# In telegram commands begin with "/" on the start of first (and only) line. In case of this bot you can define what to use. # In telegram commands begin with "/" on the start of first (and only) line. In case of this bot you can define what to use.
"csign" : "!", "csign" : "!",
@@ -30,10 +21,11 @@
# Unprivileged user to run this bot from. # Unprivileged user to run this bot from.
"user" : "nobody", "user" : "nobody",
"redis_server": "127.0.0.1", "redis_server": "redis",
"redis_port" : "6379", "redis_port" : "6379",
"redis_channels" : [ "redis_channels" : [
"telegram" "telegram"
], ],
"redis_router_channel" : "misc" "redis_router_channel" : "misc"
} }

168
export_settings.pl Normal file
View File

@@ -0,0 +1,168 @@
#!/usr/bin/perl
# Плагин для сохранения настроек чатов. Экспортирует настройки в json, который можно использовать для импорта в другого
# бота.
#
# Файлик data/chatlist.txt придётся добывать грепаньем дебаг-логов (через скрипт extract_chatlist.txt.pl).
# Формат 1 chatid на строку.
use 5.018; ## no critic (ProhibitImplicitImport)
use strict;
use warnings;
use utf8;
use open qw (:std :utf8);
use English qw ( -no_match_vars );
use version; our $VERSION = qw (1.0);
my $workdir;
# before we run, change working dir
BEGIN {
use Cwd qw (chdir abs_path);
my @CWD = split /\//xms, abs_path ($PROGRAM_NAME);
if ($#CWD > 1) {
$#CWD = $#CWD - 1;
}
$workdir = join '/', @CWD;
chdir $workdir;
}
use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5");
no Cwd;
use Data::Dumper qw(Dumper);
use JSON::XS ();
use BotLib::Admin qw(
FortuneToggleList FortuneEnabled
ChanMsgToggleList ChanMsgEnabled
GreetMsgToggleList GreetMsgEnabled
GoodbyeMsgToggleList GoodbyeMsgEnabled
MuteByAdminToggleList MuteByAdminEnabled
PluginEnabled);
use BotLib::Conf qw(LoadConf);
my $settings;
my $chatlistfile = 'data/chatlist.txt';
my $c = LoadConf('data/config.json');
my $admin_dir = $c->{admin}->{dir};
my $cachedir = $c->{cachedir};
# Достаём список чатов, где настроена fortune-ка по утрам.
my @fortune_chats = FortuneToggleList();
foreach my $chatid (@fortune_chats) {
$settings->{$chatid}->{fortune} = FortuneEnabled($chatid);
}
# Достаём список чатов, где настроена возможность удаления сообщений, отправленных от имени другого калана.
my @chanmsg_chats = ChanMsgToggleList();
foreach my $chatid (@chanmsg_chats) {
$settings->{$chatid}->{chanmsg} = ChanMsgEnabled($chatid);
}
# Достаём список чатов, где настроены приветственные сообщения.
my @greetmsg_chats = GreetMsgToggleList();
foreach my $chatid (@greetmsg_chats) {
$settings->{$chatid}->{greetmsg} = GreetMsgEnabled($chatid);
}
# Достаём список чатов, где настроены прощальные сообщения.
my @goodbyemsg_chats = GoodbyeMsgToggleList();
foreach my $chatid (@goodbyemsg_chats) {
$settings->{$chatid}->{goodbyemsg} = GoodbyeMsgEnabled($chatid);
}
# Достаём список чатов, где настроена возможность мута администратором.
my @adminmute_chats = MuteByAdminToggleList();
foreach my $chatid (@adminmute_chats) {
$settings->{$chatid}->{adminmute} = MuteByAdminEnabled($chatid);
}
open my $F, '<', $chatlistfile or die "Unable to open $chatlistfile\n"; ## no critic (ErrorHandling::RequireUseOfExceptions,InputOutput::RequireBriefOpen)
foreach my $chatid (<$F>) { ## no critic (InputOutput::ProhibitReadlineInForLoop)
chomp $chatid;
next if $chatid eq '';
if (PluginEnabled($chatid, 'oboobs')) {
$settings->{$chatid}->{plugins}->{oboobs} = 1;
}
if (PluginEnabled($chatid, 'obutts')) {
$settings->{$chatid}->{plugins}->{obutts} = 1;
}
}
close $F;
my $data;
foreach my $chatid (keys %$settings) {
my $item->{id} = $chatid;
if ($settings->{$chatid}->{fortune}) {
$item->{fortune} = 1;
} else {
$item->{fortune} = 0;
}
if ($settings->{$chatid}->{chanmsg}) {
$item->{chanmsg} = 1;
} else {
$item->{chanmsg} = 0;
}
if ($settings->{$chatid}->{greetmsg}) {
$item->{greetmsg} = 1;
} else {
$item->{greetmsg} = 0;
}
if ($settings->{$chatid}->{goodbyemsg}) {
$item->{goodbyemsg} = 1;
} else {
$item->{goodbyemsg} = 0;
}
if ($settings->{$chatid}->{adminmute}) {
$item->{adminmute} = 1;
} else {
$item->{adminmute} = 0;
}
if ($settings->{$chatid}->{plugins}->{obutts}) {
$item->{plugins}->{obutts} = 1;
} else {
$item->{plugins}->{obutts} = 0;
}
if ($settings->{$chatid}->{plugins}->{oboobs}) {
$item->{plugins}->{oboobs} = 1;
} else {
$item->{plugins}->{oboobs} = 0;
}
push @{$data}, $item;
}
my $json = eval { JSON::XS->new->utf8(1)->pretty(1)->encode($data) };
if (defined $json) {
print $json . "\n";
} else {
die "Unable to generate json, something went wrong: $EVAL_ERROR\n"; ## no critic (ErrorHandling::RequireUseOfExceptions)
}
# vim: set ft=perl noet ai ts=4 sw=4 sts=4:

28
extract_chatlist.txt.pl Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/perl
#
use strict;
use warnings;
my %chats;
open my $F, '<', 'debug.log' or die "Unable to open debug.log: $!\n"; ## no critic (ErrorHandling::RequireUseOfExceptions, InputOutput::RequireBriefOpen)
foreach my $line (<$F>) { ## no critic (InputOutput::ProhibitReadlineInForLoop)
if ($line =~ /.DEBUG. In public chat .* \(\-(\d+)\) .* say:/) {
next unless defined $1;
next if $1 eq '';
$chats{$1} = 1;
}
}
close $F;
open $F, '>', 'chatlist.txt' or die "Unable to open chatlist.txt: $!\n"; ## no critic (ErrorHandling::RequireUseOfExceptions)
foreach my $chat (keys %chats) {
print $F "$chat\n";
}
close $F;
# vim: set ft=perl noet ai ts=4 sw=4 sts=4:

View File

@@ -9,39 +9,48 @@ use open qw (:std :utf8);
use English qw ( -no_match_vars ); use English qw ( -no_match_vars );
use Clone qw (clone); use Clone qw (clone);
use Math::Random::Secure qw (irand);
use Data::Dumper qw (Dumper); use Data::Dumper qw (Dumper);
use JSON::PP ();
use Log::Any qw ($log);
use Math::Random::Secure qw (irand);
use BotLib::Conf qw (LoadConf); use BotLib::Conf qw (LoadConf);
use BotLib::Admin qw (@ForbiddenMessageTypes GetForbiddenTypes AddForbiddenType use BotLib::Admin qw (@ForbiddenMessageTypes GetForbiddenTypes AddForbiddenType
DelForbiddenType ListForbidden FortuneToggle FortuneStatus DelForbiddenType ListForbidden FortuneToggle FortuneStatus
PluginToggle PluginStatus PluginEnabled ChanMsgToggle ChanMsgStatus); PluginToggle PluginStatus PluginEnabled ChanMsgToggle ChanMsgStatus
use BotLib::Util qw (trim); GreetMsgToggle GreetMsgStatus GoodbyeMsgToggle GoodbyeMsgStatus MuteByAdminToggle
MuteByAdminStatus MuteByAdminEnabled);
use BotLib::Util qw (trim Highlight);
use version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.1);
use Exporter qw (import); use Exporter qw (import);
our @EXPORT_OK = qw (Command Highlight BotSleep IsCensored); our @EXPORT_OK = qw (Command);
my $c = LoadConf (); my $c = LoadConf ();
my $csign = $c->{telegrambot}->{csign}; my $csign = $c->{telegrambot}->{csign};
my $redismsg->{from} = 'telegram';
$redismsg->{plugin} = 'telegram'; my $redismsg->{from} = 'telegram';
$redismsg->{misc}->{answer} = 1; $redismsg->{threadid} = '';
$redismsg->{misc}->{csign} = $c->{telegrambot}->{csign}; $redismsg->{plugin} = 'telegram';
$redismsg->{misc}->{msg_format} = 0; $redismsg->{misc}->{answer} = 1;
$redismsg->{misc}->{fwd_cnt} = 1; $redismsg->{misc}->{csign} = "$c->{telegrambot}->{csign}";
$redismsg->{misc}->{msg_format} = 0;
$redismsg->{misc}->{fwd_cnt} = 1;
$redismsg->{misc}->{good_morning} = 0;
sub Command { sub Command {
my $self = shift; my $self = shift;
my $msg = shift; my $msg = shift;
my $text = shift; my $text = shift;
my $chatid = shift; my $chatid = shift;
my $reply; my $reply;
my ($userid, $username, $fullname, $highlight, $visavi) = Highlight ($msg); my ($userid, $username, $fullname, $highlight, $visavi) = Highlight ($msg);
my $rmsg = clone ($redismsg);
$rmsg->{userid} = $userid; my $rmsg = clone ($redismsg);
$rmsg->{chatid} = $chatid; $rmsg->{userid} = "$userid";
$rmsg->{chatid} = "$chatid";
$rmsg->{misc}->{answer} = 1;
if ($chatid >= 0) { if ($chatid >= 0) {
$rmsg->{mode} = 'private'; $rmsg->{mode} = 'private';
@@ -49,7 +58,7 @@ sub Command {
$rmsg->{mode} = 'public'; $rmsg->{mode} = 'public';
} }
my $cmd = substr $text, 1; my $cmd = substr $text, length ($csign);
# Проверка #1 на админское разрешение некоторых команд # Проверка #1 на админское разрешение некоторых команд
my @cmds = qw (tits boobs tities boobies сиси сисечки); my @cmds = qw (tits boobs tities boobies сиси сисечки);
@@ -77,9 +86,10 @@ sub Command {
# Полноценный поиск простой команды во входящем сообщении # Полноценный поиск простой команды во входящем сообщении
$#cmds = -1; $#cmds = -1;
@cmds = qw (ping пинг пинх pong понг понх coin монетка roll dice кости ver version версия хэлп halp kde кде lat @cmds = qw (ping пинг пинх pong понг понх coin монетка roll dice кости ver version версия хэлп halp kde кде lat
лат friday пятница proverb пословица fortune фортунка f ф anek анек анекдот buni cat кис drink праздник fox лис лат friday пятница proverb пословица пословиться fortune фортунка f ф anek анек анекдот buni cat кис
frog лягушка horse лошадь лошадка monkeyuser owl сова сыч rabbit bunny кролик snail улитка tits boobs tities drink праздник fox лис frog лягушка horse лошадь лошадка monkeyuser owl сова сыч rabbit bunny кролик
boobies сиси сисечки butt booty ass попа попка xkcd dig копать fish fishing рыба рыбка рыбалка); snail улитка tits boobs tities boobies сиси сисечки butt booty ass попа попка xkcd dig копать fish
fishing рыба рыбка рыбалка karma карма fuck w п weather погода погодка погадка);
my $bingo = 0; my $bingo = 0;
@@ -91,15 +101,13 @@ sub Command {
} }
# Поиск "сложных" команд во входящем сообщении # Поиск "сложных" команд во входящем сообщении
if (($cmd =~ /^w\s/u) || ($cmd =~ /^п\s/u) || ($cmd =~ /^weather\s/u) || ($cmd =~ /^погода\s/u) || if ($cmd =~ /^(w|п|weather|погода|погодка|погадка|karma|карма)\s+/) { ## no critic (RegularExpressions::ProhibitComplexRegexes)
($cmd =~ /^погодка\s/u) || ($cmd =~ /^погадка\s/u) || ($cmd =~ /^karma\s/u) || ($cmd =~ /карма\s/u)) {
$bingo = 1; $bingo = 1;
} }
# Если команда найдена... # Если команда найдена...
if ($bingo) { if ($bingo) {
$rmsg->{message} = $text; $rmsg->{message} = "$text";
$self->log->debug ('[DEBUG] Sending message to redis ' . Dumper ($rmsg));
# Для некоторых команд мы хотим получать форматированный вывод # Для некоторых команд мы хотим получать форматированный вывод
$#cmds = -1; $#cmds = -1;
@@ -121,11 +129,20 @@ sub Command {
while (my $check = pop @cmds) { while (my $check = pop @cmds) {
if ($cmd eq $check) { if ($cmd eq $check) {
$rmsg->{misc}->{msg_format} = 1; $rmsg->{misc}->{msg_format} = 1;
$rmsg->{misc}->{username} = $highlight; $rmsg->{misc}->{username} = "$highlight";
last; last;
} }
} }
# Если тип группы supergroup, в ней могут быть треды, попробуем найти message_thread_id, если таковой есть
if ($msg->chat->type eq 'supergroup') {
if ($msg->can ('is_topic_message') && $msg->is_topic_message) {
if ($msg->can ('message_thread_id') && $msg->message_thread_id ne '') {
$rmsg->{threadid} = "$msg->message_thread_id";
}
}
}
# Если соединения с редиской нету, ждём, пока оно появится. Своего рода костыль на всякий случай. # Если соединения с редиской нету, ждём, пока оно появится. Своего рода костыль на всякий случай.
{ {
do { do {
@@ -134,13 +151,15 @@ sub Command {
} while (sleep 1); } while (sleep 1);
} }
$log->debug ('[DEBUG] Outgoing redis message: ' . Dumper ($rmsg));
my $pubsub = $main::REDIS->pubsub; my $pubsub = $main::REDIS->pubsub;
$pubsub->json ($c->{'redis_router_channel'})->notify ( $pubsub->json ($c->{'redis_router_channel'})->notify (
$c->{'redis_router_channel'} => $rmsg, $c->{'redis_router_channel'} => $rmsg,
); );
# Если команда не найдена, но строка подозрительно напоминает команду help # Если команда не найдена, но строка подозрительно напоминает команду help
} elsif (substr ($text, 1) eq 'help' || substr ($text, 1) eq 'помощь') { } elsif ($cmd =~ /^(help|помощь)$/u) {
$reply = << "MYHELP"; $reply = << "MYHELP";
``` ```
${csign}help | ${csign}помощь - список команд ${csign}help | ${csign}помощь - список команд
@@ -167,11 +186,11 @@ ${csign}ping | ${csign}пинг - попинговать бота
${csign}roll | ${csign}dice | ${csign}кости - бросить кости ${csign}roll | ${csign}dice | ${csign}кости - бросить кости
${csign}snail | ${csign}улитка - улитка ${csign}snail | ${csign}улитка - улитка
${csign}ver | ${csign}version | ${csign}версия - что-то про версию ПО ${csign}ver | ${csign}version | ${csign}версия - что-то про версию ПО
${csign}w город | ${csign}п город - погода в указанном городе ${csign}w <город> | ${csign}п <город> - погода в указанном городе
${csign}weather город - погода в указанном городе ${csign}weather <город> - погода в указанном городе
${csign}погода город - погода в указанном городе ${csign}погода <город> - погода в указанном городе
${csign}погодка город - погода в указанном городе ${csign}погодка <город> - погода в указанном городе
${csign}погадка город - погода в указанном городе ${csign}погадка <город> - погода в указанном городе
${csign}xkcd - рандомный стрип с сайта xkcd.ru ${csign}xkcd - рандомный стрип с сайта xkcd.ru
${csign}karma фраза | ${csign}карма фраза - посмотреть карму фразы ${csign}karma фраза | ${csign}карма фраза - посмотреть карму фразы
фраза-- | фраза++ - убавить или добавить карму фразе фраза-- | фраза++ - убавить или добавить карму фразе
@@ -179,121 +198,293 @@ ${csign}karma фраза | ${csign}карма фраза - посмотреть
Но на самом деле я бот больше для общения, чем для исполнения команд. Но на самом деле я бот больше для общения, чем для исполнения команд.
Поговоришь со мной? Поговоришь со мной?
MYHELP MYHELP
$msg->replyMd ($reply); my $res = $msg->replyMd ("$reply");
if ($res->{error}) {
$log->error ('[ERROR] Unable to call sendMessage BotAPI method: ' . Dumper ($res));
}
return; return;
# Если строка не найдена, но это команда admin # Если команда не найдена, но это команда admin :)
} elsif (substr ($text, 1) eq 'admin' || substr ($text, 1) eq 'админ') { } elsif ($cmd =~ /^(admin|админ)(\s+|\s+.*)?$/u) {
my $member = $self->getChatMember ({ 'chat_id' => $msg->chat->id, 'user_id' => $msg->from->id }); my $send_args->{chat_id} = 0 + $chatid;
$send_args->{user_id} = 0 + $msg->from->id;
my $member = $self->getChatMember ($send_args);
if ($member->{error}) {
$log->error ('[ERROR] Unable to call getChatMember BotAPI method: ' . Dumper ($member));
return;
}
# Это должно показываться только админам чата # Это должно показываться только админам чата
if (($member->status eq 'administrator') || ($member->status eq 'creator')) { if (($member->status ne 'administrator') && ($member->status ne 'creator')) {
$log->debug ("[DEBUG] Non admin member called ${csign}admin command, " . $member->status);
return;
}
if ($cmd =~ /^(admin|админ)\s*$/u) {
$reply = << "MYADMIN"; $reply = << "MYADMIN";
``` ```
${csign}admin censor type # - где 1 - вкл, 0 - выкл цензуры для означенного типа сообщений ${csign}admin censor type # - где 1 - вкл, 0 - выкл цензуры для означенного типа сообщений
${csign}админ ценз тип # - где 1 - вкл, 0 - выкл цензуры для означенного типа сообщений ${csign}админ ценз тип # - где 1 - вкл, 0 - выкл цензуры для означенного типа сообщений
${csign}admin censor - показать список состояния типов сообщений ${csign}admin censor - показать список состояния типов сообщений
${csign}админ ценз - показать список состояния типов сообщений ${csign}админ ценз - показать список состояния типов сообщений
${csign}admin fortune # - где 1 - вкл, 0 - выкл фортунку с утра ${csign}admin fortune # - где 1 - вкл, 0 - выкл фортунку с утра
${csign}admin фортунка # - где 1 - вкл, 0 - выкл фортунку с утра ${csign}admin фортунка # - где 1 - вкл, 0 - выкл фортунку с утра
${csign}admin fortune - показываем ли с утра фортунку для чата ${csign}admin fortune - показываем ли с утра фортунку для чата
${csign}admin фортунка - показываем ли с утра фортунку для чата ${csign}admin фортунка - показываем ли с утра фортунку для чата
${csign}admin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs ${csign}admin greet # - где 1 - вкл, 0 - выкл приветствия новых участников чата
${csign}admin oboobs - показываем ли сисечки по просьбе участников чата (команды ${csign}tits, ${csign}tities, ${csign}boobs, ${csign}boobies, ${csign}сиси, ${csign}сисечки) ${csign}admin приветствие # - где 1 - вкл, 0 - выкл приветствия новых участников чата
${csign}admin obutts # - где 1 - вкл, 0 - выкл плагина obutts ${csign}admin greet - приветствуем ли новых участников чата
${csign}admin obutts - показываем ли попки по просьбе участников чата (команды ${csign}ass, ${csign}butt, ${csign}booty, ${csign}попа, ${csign}попка) ${csign}admin приветствие - приветствуем ли новых участников чата
${csign}admin chan_msg - оставляем ли сообщения присланные от имени (других) каналов ${csign}admin oboobs # - где 1 - вкл, 0 - выкл плагина oboobs
${csign}admin chan_msg # - где 1 - оставляем, 0 - удаляем ${csign}admin oboobs - показываем ли сисечки по просьбе участников чата (команды ${csign}tits, ${csign}tities, ${csign}boobs, ${csign}boobies, ${csign}сиси, ${csign}сисечки)
${csign}admin obutts # - где 1 - вкл, 0 - выкл плагина obutts
${csign}admin obutts - показываем ли попки по просьбе участников чата (команды ${csign}ass, ${csign}butt, ${csign}booty, ${csign}попа, ${csign}попка)
${csign}admin chan_msg - оставляем ли сообщения присланные от имени (других) каналов
${csign}admin chan_msg # - где 1 - оставляем, 0 - удаляем
${csign}admin ban userid sec - выдаём ban указанному user-у на указанное количество секунд (от 30 сек до 1 года), доступно только создателю чата
${csign}admin mute userid sec - выдаём mute указанному user-у на указанное количество секунд (от 30 сек до 1 года), доступно только создателю чата
${csign}admin admin mute - разрешено ли обычным админам мьютить участников чата через бота (если бот - админ), (создатель чата всегда может попросить бота-админа замьютить обычного участника чата)
${csign}admin admin mute # - где 1 - разрешено, 0 - не разрешено
``` ```
Типы сообщений: Типы сообщений:
audio voice photo video animation sticker dice game poll document audio voice photo video animation sticker dice game poll document
MYADMIN MYADMIN
$msg->replyMd ($reply); my $res = $msg->replyMd ($reply);
}
return; if ($res->{error}) {
# Если строка не найдена, но это команда admin с параметрами $log->error('[ERROR] Unable to call sendMessage BotAPI method: ' . Dumper($res));
} elsif ((substr ($text, 1, 5) eq 'admin' || substr ($text, 1, 5) eq 'админ') && (length ($text) >= 8)) { }
my $member = $self->getChatMember ({ 'chat_id' => $msg->chat->id, 'user_id' => $msg->from->id });
# Это должно показываться только админам чата return;
if (($member->status eq 'administrator') || ($member->status eq 'creator')) { } elsif ($cmd =~ /^admin\s+(admin\s+mute)(\s*|\s+0|\s+1)$/gu) {
# Вынем субкоманду, это первый аргумент команды admin my $arg;
my $command = trim (substr $text, 7); $arg = trim $2 if (defined $2);
$cmd = undef;
my $args;
($cmd, $args) = split /\s+/, $command, 2;
# Субкоманды не нашлось, ну и какбэ досвидонья if (defined $arg && $arg !~ /^\s*$/ && ($member->status eq 'creator')) {
if ($cmd eq '') { if ($arg == 1) {
$reply = MuteByAdminToggle ($chatid, 1);
} elsif ($arg == 0) {
$reply = MuteByAdminToggle ($chatid, 0);
}
} else {
$reply = MuteByAdminStatus ($chatid);
}
} elsif ($cmd =~ /^(admin|админ)\s+(ban|mute)\s+(\d+)\s+(\d+)$/gu) {
if ($member->status eq 'admin' && (! MuteByAdminEnabled ($chatid))) {
return 'Не буду я для тебя никого мьтить.';
}
my (undef, $action, $user_id_to_prosecute, $time) = split /\s+/, $cmd;
# Бот должен быть админом, чтобы банить юзеров
my $me = $self->getMe ();
if ($me->{error}) {
$log->error ('[ERROR] Unable to call getMe BotAPI method: ' . Dumper ($member));
return; return;
# Субкоманда censor... }
} elsif ($cmd eq 'censor' || $cmd eq 'ценз') {
# Censor с аргументами
if (defined ($args) && ($args ne '')) {
my ($msgType, $toggle) = split /\s/, $args, 2;
if (defined $toggle) { my $myId = $me->id;
foreach (@ForbiddenMessageTypes) {
if ($msgType eq $_) { if ($myId == $user_id_to_prosecute) {
if ($toggle == 1) { if ($action eq 'mute') {
AddForbiddenType ($chatid, $msgType); $reply = 'Я не буду себя мьютить.';
$reply = "Теперь сообщения с $msgType будут автоматически удаляться"; } else {
} elsif ($toggle == 0) { $reply = 'Я не буду себя банить.';
DelForbiddenType ($chatid, $msgType); }
$reply = "Теперь сообщения с $msgType будут оставаться"; } else {
$send_args = undef;
$send_args->{chat_id} = 0 + $chatid;
$send_args->{user_id} = 0 + $me->id;
$me = $self->getChatMember ($send_args);
if ($me->{error}) {
$log->error ('[ERROR] Unable to call getChatMember BotAPI method: ' . Dumper ($me));
return;
}
if ($me->status eq 'administrator') {
# Проверим, что такой юзер есть в чятике
$send_args = undef;
$send_args->{chat_id} = 0 + $chatid;
$send_args->{user_id} = 0 + $user_id_to_prosecute;
my $chatMember = $self->getChatMember ($send_args);
if ($chatMember->{error}) {
$log->error ('[ERROR] Unable to call getChatMember BotAPI method: ' . Dumper ($chatMember));
return;
}
# Предположительно, юзер нашёлся
my $memberStatus = $chatMember->status;
if ($memberStatus eq 'creator') {
if ($action eq 'mute') {
$reply = 'Ты поехал себя мьютить?';
} else {
$reply = 'Ты поехал себя банить?';
}
} elsif ($memberStatus eq 'administrator') {
if ($action eq 'mute') {
$reply = 'Админов мьютить не буду.';
} else {
$reply = 'Админов банить не буду.';
}
} else {
if ($time < 30) {
$reply = 'Ты слишком добр.';
} elsif ($time > (60 * 60 * 24 * 366)) {
if ($action eq 'mute') {
$reply = 'Мьют более чем на 366 дней - перманентный, ты слишком жесток, я так не играю.';
} else {
$reply = 'Бан более чем на 366 дней - перманентный, ты слишком жесток, я так не играю.';
}
} else {
my $result;
$send_args = undef;
$send_args->{chat_id} = 0 + $chatid;
$send_args->{user_id} = 0 + $user_id_to_prosecute;
$send_args->{until_date} = $time + time ();
if ($action eq 'mute') {
$result = $self->muteChatMember ($send_args);
} else {
$result = $self->banChatMember ($send_args);
}
# Если всё хорошо, то возвращается true в формате JSON::PP::Boolean
if ((ref ($result) eq 'JSON::PP::Boolean') && ($result == JSON::PP::true)) {
$reply = 'Готово.';
} elsif (defined ($result->{error}) && $result->{error}) {
if ($action eq 'mute') {
$log->error (
'[ERROR] Unable to call muteChatMember BotAPI method: ' .
Dumper ($result)
);
$reply = 'Что-то пошло не так, не получается замьютить.';
} else {
$log->error (
'[ERROR] Unable to call banChatMember BotAPI method: ' .
Dumper ($result)
);
$reply = 'Что-то пошло не так, не получается забанить.';
} }
} else {
$log->error (
'[ERROR] Unable to call banChatMember/muteChatMember BotAPI method: ' .
Dumper ($result)
);
$reply = 'Что-то пошло совсем не так.';
} }
} }
} }
# Censor без аргументов, выдаёт список типов сообщений и будут ли они автоматически удаляться
} else { } else {
$reply = ListForbidden ($chatid); $reply = 'Я так пока не умею, я здесь не админ.'
} }
# Хотим ли мы показывать фортунку с утра }
} elsif ($cmd eq 'fortune' || $cmd eq 'фортунка') { } elsif ($cmd =~ /^admin\s+(censor|ценз)\s*$/gu) {
if (defined $args) { $reply = ListForbidden ($chatid);
if ($args == 1) { } elsif ($cmd =~ /^admin\s+(censor|ценз)\s+(type|тип)\s+(.+)$/gu) {
$reply = FortuneToggle ($chatid, 1); my $arg;
} elsif ($args == 0) { $arg = trim $3 if (defined $3);
$reply = FortuneToggle ($chatid, 0);
if (defined $arg && $arg ne '') {
my ($msgType, $toggle) = split /\s/, $arg, 2;
if (defined $toggle) {
foreach (@ForbiddenMessageTypes) {
if ($msgType eq $_) {
if ($toggle == 1) {
AddForbiddenType ($chatid, $msgType);
$reply = "Теперь сообщения с $msgType будут автоматически удаляться";
} elsif ($toggle == 0) {
DelForbiddenType ($chatid, $msgType);
$reply = "Теперь сообщения с $msgType будут оставаться";
}
}
} }
} else {
$reply = FortuneStatus ($chatid);
} }
# Хотим ли мы удалять "сообщения от каналов" }
} elsif ($cmd eq 'chan_msg') { } elsif ($cmd =~ /^admin\s+(fortune|фортунка)(\s*|\s+0|\s+1)$/gu) {
if (defined $args) { my $arg;
if ($args == 1) { $arg = trim $2 if (defined $2);
$reply = ChanMsgToggle ($chatid, 1);
} elsif ($args == 0) { if (defined $arg && $arg !~ /^\s*$/) {
$reply = ChanMsgToggle ($chatid, 0); if ($arg == 1) {
} $reply = FortuneToggle ($chatid, 1);
} else { } elsif ($arg == 0) {
$reply = ChanMsgStatus ($chatid); $reply = FortuneToggle ($chatid, 0);
} }
# Работает ли плагин oboobs в чатике } else {
} elsif ($cmd eq 'oboobs') { $reply = FortuneStatus ($chatid);
if (defined $args) { }
if ($args == 1) { } elsif ($cmd =~ /^admin\s+chan_msg(\s*|\s+0|\s+1)$/gu) {
$reply = PluginToggle ($chatid, 'oboobs', 1); my $arg = $1;
} elsif ($args == 0) { $arg = trim $1 if (defined $1);
$reply = PluginToggle ($chatid, 'oboobs', 0);
} if (defined ($arg) && $arg !~ /^\s*$/) {
} else { if ($arg == 1) {
$reply = PluginStatus ($chatid, 'oboobs'); $reply = ChanMsgToggle ($chatid, 1);
} elsif ($arg == 0) {
$reply = ChanMsgToggle ($chatid, 0);
} }
# Работает ли плагин obutts в чатике } else {
} elsif ($cmd eq 'obutts') { $reply = ChanMsgStatus ($chatid);
if (defined $args) { }
if ($args == 1) { } elsif ($cmd =~ /^admin\s+(greet|приветствие)(\s*|\s+0|\s+1)$/gu) {
$reply = PluginToggle ($chatid, 'obutts', 1); my $arg = $2;
} elsif ($args == 0) { $arg = trim $2 if (defined $2);
$reply = PluginToggle ($chatid, 'obutts', 0);
} if (defined ($arg) && $arg !~ /^\s*$/) {
} else { if ($arg == 1) {
$reply = PluginStatus ($chatid, 'obutts'); $reply = GreetMsgToggle ($chatid, 1);
} elsif ($arg == 0) {
$reply = GreetMsgToggle ($chatid, 0);
} }
} else {
$reply = GreetMsgStatus ($chatid);
}
} elsif ($cmd =~ /^admin\s+(goodbye|прощание)(\s*|\s+0|\s+1)$/gu) {
my $arg = $2;
$arg = trim $2 if (defined $2);
if (defined ($arg) && $arg !~ /^\s*$/) {
if ($arg == 1) {
$reply = GoodbyeMsgToggle ($chatid, 1);
} elsif ($arg == 0) {
$reply = GoodbyeMsgToggle ($chatid, 0);
}
} else {
$reply = GoodbyeMsgStatus ($chatid);
}
} elsif ($cmd =~ /^admin\s+oboobs(\s*|\s+0|\s+1)$/gu) {
my $arg = $1;
$arg = trim $1 if (defined $1);
if (defined ($arg) && $arg !~ /^\s*$/) {
if ($arg == 1) {
$reply = PluginToggle ($chatid, 'oboobs', 1);
} elsif ($arg == 0) {
$reply = PluginToggle ($chatid, 'oboobs', 0);
}
} else {
$reply = PluginStatus ($chatid, 'oboobs');
}
} elsif ($cmd =~ /^admin\s+obutts(\s*|\s+0|\s+1)$/gu) {
my $arg = $1;
$arg = trim $1 if (defined $1);
if (defined ($arg) && $arg !~ /^\s*$/) {
if ($arg == 1) {
$reply = PluginToggle ($chatid, 'obutts', 1);
} elsif ($arg == 0) {
$reply = PluginToggle ($chatid, 'obutts', 0);
}
} else {
$reply = PluginStatus ($chatid, 'obutts');
} }
} }
} }
@@ -301,85 +492,6 @@ MYADMIN
return $reply; return $reply;
} }
sub Highlight {
my $msg = shift;
my $fullname;
my $highlight;
my $username;
my $visavi = '';
my $userid = $msg->from->id;
if ($msg->from->can ('username') && defined $msg->from->username ) {
$username = $msg->from->username;
}
if ($msg->from->can ('first_name') && defined $msg->from->first_name) {
$fullname = $msg->from->first_name;
if ($msg->from->can ('last_name') && defined $msg->from->last_name) {
$fullname .= ' ' . $msg->from->last_name;
}
} elsif ($msg->from->can ('last_name') && defined $msg->from->last_name) {
$fullname .= $msg->from->last_name;
}
if (defined $username) {
$visavi .= '@' . $username;
if (defined $fullname) {
$highlight = "[$fullname](tg://user?id=$userid)";
$visavi .= ', ' . $fullname;
} else {
$highlight = "[$username](tg://user?id=$userid)";
}
} else {
$highlight = "[$fullname](tg://user?id=$userid)";
$visavi .= $fullname;
}
$visavi .= " ($userid)";
return ($userid, $username, $fullname, $highlight, $visavi);
}
sub BotSleep {
# TODO: Parametrise this with fuzzy sleep time in seconds
my $msg = shift;
# let's emulate real human and delay answer
sleep (irand (2) + 1);
for (0..(4 + irand (3))) {
$msg->typing ();
sleep 3;
sleep 3 unless ($_);
}
sleep ( 3 + irand (2));
return;
}
sub IsCensored {
my $msg = shift;
my $forbidden = GetForbiddenTypes ($msg->chat->id);
# voice messages are special
if (defined ($msg->voice) && defined ($msg->voice->duration) && ($msg->voice->duration > 0)) {
if ($forbidden->{'voice'}) {
return 1;
}
}
foreach (keys %{$forbidden}) {
if ($forbidden->{$_} && (defined $msg->{$_})) {
return 1;
}
}
return 0;
}
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

@@ -11,13 +11,23 @@ use CHI::Driver::BerkeleyDB ();
use BotLib::Conf qw (LoadConf); use BotLib::Conf qw (LoadConf);
use BotLib::Util qw (utf2sha1); use BotLib::Util qw (utf2sha1);
use version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.1);
use Exporter qw (import); use Exporter qw (import);
# to export array we need @ISA here # to export array we need @ISA here
our @ISA = qw / Exporter /; ## no critic (ClassHierarchies::ProhibitExplicitISA) our @ISA = qw / Exporter /; ## no critic (ClassHierarchies::ProhibitExplicitISA)
our @EXPORT_OK = qw (@ForbiddenMessageTypes @PluginList GetForbiddenTypes AddForbiddenType DelForbiddenType our @EXPORT_OK = qw (@ForbiddenMessageTypes @PluginList
ListForbidden FortuneToggle FortuneToggleList FortuneStatus PluginToggle PluginStatus GetForbiddenTypes AddForbiddenType DelForbiddenType ListForbidden EnabledForbidden
PluginEnabled ChanMsgToggle ChanMsgStatus ChanMsgEnabled); FortuneToggle FortuneStatus FortuneEnabled FortuneToggleList
ChanMsgToggle ChanMsgStatus ChanMsgEnabled ChanMsgToggleList
GreetMsgToggle GreetMsgStatus GreetMsgEnabled GreetMsgToggleList
GoodbyeMsgToggle GoodbyeMsgStatus GoodbyeMsgEnabled GoodbyeMsgToggleList
PluginToggle PluginStatus PluginEnabled
MuteByAdminToggle MuteByAdminStatus MuteByAdminEnabled MuteByAdminToggleList
MigrateSettingsToNewChatID
IsCensored
GetSlackGreetRememberId
SetSlackGreetRememberId
);
my $c = LoadConf (); my $c = LoadConf ();
my $cachedir = $c->{cachedir}; my $cachedir = $c->{cachedir};
@@ -107,6 +117,29 @@ sub ListForbidden {
return join "\n", @list; return join "\n", @list;
} }
sub EnabledForbidden {
my $chatid = shift;
my $ftype = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'censor' . '_' . utf2sha1 ($chatid),
);
my $type = $cache->get ($ftype);
if ($type) {
return 1;
} else {
unless (defined $type) {
$cache->set ($ftype, 0, 'never');
}
return 0;
}
}
sub FortuneToggle (@) { sub FortuneToggle (@) {
my $chatid = shift; my $chatid = shift;
my $action = shift // undef; my $action = shift // undef;
@@ -165,6 +198,24 @@ sub FortuneStatus ($) {
return $phrase; return $phrase;
} }
sub FortuneEnabled ($) {
my $chatid = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'fortune',
);
my $state = $cache->get ($chatid);
if (defined $state && $state) {
return 1;
} else {
return 0;
}
}
sub FortuneToggleList () { sub FortuneToggleList () {
my $cache = CHI->new ( my $cache = CHI->new (
driver => 'BerkeleyDB', driver => 'BerkeleyDB',
@@ -276,6 +327,198 @@ sub ChanMsgToggleList () {
return $cache->get_keys (); return $cache->get_keys ();
} }
sub GreetMsgToggle (@) {
my $chatid = shift;
my $action = shift // undef;
my $phrase = 'Приветствия новых участников чата ';
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'greet_msg',
);
if (defined $action) {
if ($action == 0) {
$cache->set ($chatid, 0, 'never');
$phrase .= 'выключены.';
} else {
$cache->set ($chatid, 1, 'never');
$phrase .= 'включены.';
}
} else {
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state != 0) {
$cache->set ($chatid, 0, 'never');
$phrase .= 'выключены.';
} else {
$cache->set ($chatid, 1, 'never');
$phrase .= 'включены.';
}
} else {
$cache->set ($chatid, 0, 'never');
$phrase .= 'выключены.';
}
}
return $phrase;
}
sub GreetMsgStatus ($) {
my $chatid = shift;
my $phrase = 'Приветствия новых участников чата ';
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'greet_msg',
);
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state) {
$phrase .= 'включено.';
} else {
$phrase .= 'выключено.';
}
} else {
$phrase .= 'включено.';
}
return $phrase;
}
sub GreetMsgEnabled ($) {
my $chatid = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'greet_msg',
);
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state) {
return 1;
} else {
return 0;
}
} else {
return 1;
}
}
sub GreetMsgToggleList () {
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'greet_msg',
);
return $cache->get_keys ();
}
sub GoodbyeMsgToggle (@) {
my $chatid = shift;
my $action = shift // undef;
my $phrase = 'Прощание с ушедшими участниками чата ';
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'goodbye_msg',
);
my $state = $cache->get ($chatid);
if (defined $action) {
if ($action) {
$cache->set ($chatid, 1, 'never');
$phrase .= 'включено.';
} else {
$cache->set ($chatid, 0, 'never');
$phrase .= 'выключено.';
}
} else {
if (defined $state){
if ($state) {
$cache->set ($chatid, 0, 'never');
$phrase .= 'выключено.';
} else {
$cache->set ($chatid, 1, 'never');
$phrase .= 'включено.';
}
} else {
$cache->set ($chatid, 1, 'never');
$phrase .= 'включено.';
}
}
return $phrase;
}
sub GoodbyeMsgStatus ($) {
my $chatid = shift;
my $phrase = 'Прощание с ушедшими участниками чата ';
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'goodbye_msg',
);
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state) {
$phrase .= 'включено.';
} else {
$phrase .= 'выключено.';
}
} else {
$phrase .= 'выключено.';
}
return $phrase;
}
sub GoodbyeMsgEnabled ($) {
my $chatid = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'goodbye_msg',
);
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
sub GoodbyeMsgToggleList () {
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'goodbye_msg',
);
return $cache->get_keys ();
}
sub PluginStatus (@) { sub PluginStatus (@) {
my $chatid = shift; my $chatid = shift;
my $plugin = shift; my $plugin = shift;
@@ -361,6 +604,209 @@ sub PluginToggle (@) {
return $phrase; return $phrase;
} }
sub MuteByAdminToggle (@) {
my $chatid = shift;
my $action = shift // undef;
my $phrase = 'Мьют участников чата админами ';
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'admin_mute',
);
my $state = $cache->get ($chatid);
if (defined $action) {
if ($action) {
$cache->set ($chatid, 1, 'never');
$phrase .= 'разрешён.';
} else {
$cache->set ($chatid, 0, 'never');
$phrase .= 'запрещён.';
}
} else {
if (defined $state){
if ($state) {
$cache->set ($chatid, 0, 'never');
$phrase .= 'запрещён.';
} else {
$cache->set ($chatid, 1, 'never');
$phrase .= 'разрешён.';
}
} else {
$cache->set ($chatid, 1, 'never');
$phrase .= 'разрешён.';
}
}
return $phrase;
}
sub MuteByAdminStatus ($) {
my $chatid = shift;
my $phrase = 'Мьют админами участников чата ';
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'admin_mute',
);
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state) {
$phrase .= 'разрешён.';
} else {
$phrase .= 'запрещён.';
}
} else {
$phrase .= 'запрещён.';
}
return $phrase;
}
sub MuteByAdminEnabled ($) {
my $chatid = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'admin_mute',
);
my $state = $cache->get ($chatid);
if (defined $state) {
if ($state) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
sub MuteByAdminToggleList () {
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'admin_mute',
);
return $cache->get_keys ();
}
sub MigrateSettingsToNewChatID {
my $old_id = shift;
my $new_id = shift;
# TODO: Catch errors, if error==1, than noop
if (FortuneEnabled ($old_id)) {
FortuneToggle ($new_id, 1);
FortuneToggle ($old_id, 0);
} else {
FortuneToggle ($new_id, 0);
}
if (ChanMsgEnabled ($old_id)) {
ChanMsgToggle ($new_id, 1);
} else {
ChanMsgToggle ($new_id, 0);
}
if (GreetMsgEnabled ($old_id)) {
GreetMsgToggle ($new_id, 1);
} else {
GreetMsgToggle ($new_id, 0);
}
if (GoodbyeMsgEnabled ($old_id)) {
GoodbyeMsgToggle ($new_id, 1);
} else {
GoodbyeMsgToggle ($new_id, 0);
}
foreach my $plugin (@PluginList) {
if (PluginEnabled ($old_id, $plugin)) {
PluginToggle ($new_id, $plugin, 1);
} else {
PluginToggle ($new_id, $plugin, 0);
}
}
foreach my $ftype (@ForbiddenMessageTypes) {
if (EnabledForbidden($old_id, $ftype)) {
AddForbiddenType($new_id, $ftype);
}
# Looks like we don't need to add forbidden type for allowed types... right?
}
return;
}
sub IsCensored {
my $msg = shift;
my $forbidden = GetForbiddenTypes ($msg->chat->id);
# voice messages are special
if (defined ($msg->voice) && defined ($msg->voice->duration) && ($msg->voice->duration > 0)) {
if ($forbidden->{'voice'}) {
return 1;
}
}
foreach (keys %{$forbidden}) {
if ($forbidden->{$_} && (defined $msg->{$_})) {
return 1;
}
}
return 0;
}
sub GetSlackGreetRememberId ($) {
my $userid = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'slack_greet_remember_id',
);
my $state = $cache->get ($userid);
if (defined $state) {
if ($state) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
sub SetSlackGreetRememberId ($) {
my $userid = shift;
my $cache = CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__ . '_' . 'slack_greet_remember_id',
);
$cache->set ($userid, 1, 'never');
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

@@ -14,7 +14,7 @@ use Exporter qw (import);
our @EXPORT_OK = qw (LoadConf); our @EXPORT_OK = qw (LoadConf);
sub LoadConf { sub LoadConf {
my $c = 'data/config.json'; my $c = shift // 'data/config.json';
my $CH; my $CH;
unless (open $CH, '<', $c) { unless (open $CH, '<', $c) {

View File

@@ -8,13 +8,14 @@ use open qw (:std :utf8);
use English qw ( -no_match_vars ); use English qw ( -no_match_vars );
use Digest::SHA qw (sha1_base64); use Digest::SHA qw (sha1_base64);
use Encode qw (encode_utf8); use Encode qw (encode_utf8);
use Math::Random::Secure qw (irand);
use MIME::Base64 qw (encode_base64); use MIME::Base64 qw (encode_base64);
use Text::Fuzzy qw (distance_edits); use Text::Fuzzy qw (distance_edits);
use URI::URL qw (url); use URI::URL qw (url);
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 (trim urlencode fmatch utf2b64 utf2sha1); our @EXPORT_OK = qw (trim urlencode fmatch utf2b64 utf2sha1 BotSleep Highlight RandomCommonPhrase);
sub trim { sub trim {
my $str = shift; my $str = shift;
@@ -23,13 +24,8 @@ sub trim {
return $str; return $str;
} }
while (substr ($str, 0, 1) =~ /^\s$/xms) { $str =~ s/^\s+//;
$str = substr $str, 1; $str =~ s/\s+$//;
}
while (substr ($str, -1, 1) =~ /^\s$/xms) {
chop $str;
}
return $str; return $str;
} }
@@ -77,6 +73,110 @@ sub utf2sha1 {
return sha1_base64 $bytes; return sha1_base64 $bytes;
} }
sub Highlight {
my $msg = shift;
my $fullname;
my $highlight;
my $username;
my $visavi = '';
my $userid;
# In channel message $msg->from->id is omitted
# In channel context Highlight is pointless
if ($msg->chat->type eq 'channel') {
return ($userid, $username, $fullname, $highlight, undef);
}
if ($msg->can ('from')) {
$userid = $msg->from->id;
if ($msg->from->can ('username') && defined $msg->from->username ) {
$username = $msg->from->username;
}
if ($msg->from->can ('first_name') && defined $msg->from->first_name) {
$fullname = $msg->from->first_name;
if ($msg->from->can ('last_name') && defined $msg->from->last_name) {
$fullname .= ' ' . $msg->from->last_name;
}
} elsif ($msg->from->can ('last_name') && defined $msg->from->last_name) {
$fullname .= $msg->from->last_name;
}
} else {
# Reacions case.
$userid = $msg->user->id;
if ($msg->user->can ('username') && defined $msg->user->username ) {
$username = $msg->user->username;
}
if ($msg->user->can ('first_name') && defined $msg->user->first_name) {
$fullname = $msg->user->first_name;
if ($msg->user->can ('last_name') && defined $msg->user->last_name) {
$fullname .= ' ' . $msg->user->last_name;
}
} elsif ($msg->user->can ('last_name') && defined $msg->user->last_name) {
$fullname .= $msg->user->last_name;
}
}
if (defined $username) {
$visavi .= '@' . $username;
if (defined $fullname) {
$highlight = "[$fullname](tg://user?id=$userid)";
$visavi .= ', ' . $fullname;
} else {
$highlight = "[$username](tg://user?id=$userid)";
}
} else {
$highlight = "[$fullname](tg://user?id=$userid)";
$visavi .= $fullname;
}
$visavi .= " ($userid)";
return ($userid, $username, $fullname, $highlight, $visavi);
}
sub RandomCommonPhrase {
my @myphrase = (
'Так, блядь...',
'*Закатывает рукава* И ради этого ты меня позвал?',
'Ну чего ты начинаешь, нормально же общались',
'Повтори свой вопрос, не поняла',
'Выйди и зайди нормально',
'Я подумаю',
'Даже не знаю, что на это ответить',
'Ты упал такие вопросы девочке задавать?',
'Можно и так, но не уверена',
'А как ты думаешь?',
'А ви, таки, почему интересуетесь?',
);
return $myphrase[irand ($#myphrase + 1)];
}
sub BotSleep {
# TODO: Parametrise this with fuzzy sleep time in seconds
my $msg = shift;
# let's emulate real human and delay answer
sleep (irand (2) + 1);
for (0..(4 + irand (3))) {
$msg->typing ();
sleep 3;
sleep 3 unless ($_);
}
sleep ( 3 + irand (2));
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

@@ -4,16 +4,17 @@ use 5.018; ## no critic (ProhibitImplicitImport)
use strict; use strict;
use warnings; use warnings;
use utf8; use utf8;
use open qw (:std :utf8); use English qw ( -no_match_vars );
use open qw (:std :utf8);
use Log::Any qw ($log); use Data::Dumper qw (Dumper);
# Чтобы "уж точно" использовать hiredis-биндинги, загрузим этот модуль перед Mojo::Redis use JSON::XS qw (decode_json);
use Protocol::Redis::XS (); use Log::Any qw ($log);
use Mojo::Redis (); use Mojo::Redis ();
use Mojo::Redis::Connection (); use Mojo::Redis::Connection ();
use BotLib::Conf qw (LoadConf); use BotLib::Admin qw (MigrateSettingsToNewChatID FortuneToggle);
use Teapot::Bot::Object::ChatPermissions (); use BotLib::Conf qw (LoadConf);
use version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.0);
use Exporter qw (import); use Exporter qw (import);
@@ -26,6 +27,8 @@ sub redis_parse_message {
my $m = shift; my $m = shift;
my $message; my $message;
$log->debug ('Incoming redis message: ' . Dumper $m);
# Мы "не можем" отправлять сообщения в телеграм, если мы к нему не подключены # Мы "не можем" отправлять сообщения в телеграм, если мы к нему не подключены
# (конечно, можем, но Teapot-lib предполагает, что объектик с чатом - это его собственность, а городить свой что-то # (конечно, можем, но Teapot-lib предполагает, что объектик с чатом - это его собственность, а городить свой что-то
# не очень хочется) # не очень хочется)
@@ -37,14 +40,80 @@ sub redis_parse_message {
$message->{parse_mode} = 'Markdown'; $message->{parse_mode} = 'Markdown';
} }
$message->{chat_id} = $m->{chatid}; $message->{chat_id} = eval { 0 + $m->{chatid}; };
$message->{text} = $m->{message};
my $can_talk = Teapot::Bot::Object::ChatPermissions->canTalk ($main::TGM, $m->{chatid}); unless (defined $message->{chat_id}) {
$log->error ('[ERROR] Incoming redis message is incorrect: chat id must be numeric value!');
return;
}
if ($can_talk) { if ($message->{chat_id} == 0) {
# Результат этого действия нас не сильно волнует, т.к. если будет ошибка, то в лог попадёт трейс $log->error ('[ERROR] Incoming redis message is incorrect: chat id must not be equal 0!');
$main::TGM->sendMessage ($message); return;
}
$message->{text} = "$m->{message}";
if (defined ($m->{threadid}) && $m->{threadid} ne '') {
$message->{message_thread_id} = 0 + $m->{threadid};
}
# we cannot use $self->_brain->can_talk here because of $self pointing to redis object, not telegram
my $can_talk = $main::TGM->can_talk ({ chat_id => 0 + $m->{chatid}});
if ($can_talk->{error}) {
$log->error ('[ERROR] Unable to guess if i can talk in this chat: ' . Dumper ($can_talk));
return;
}
if ($can_talk->{can_talk}) {
my $r = $main::TGM->sendMessage ($message);
if ($r->{error}) {
# TODO: handle 50x errors? They occur when api servers being updated.
my $resp = eval { $r->{debug}->json };
if (defined $resp) {
if (defined ($resp->{error_code}) && $resp->{error_code} == 400) {
if (defined $resp->{description}) {
if ($resp->{description} eq 'Bad Request: not enough rights to send text messages to the chat') {
$log->notice ("[NOTICE] Have no rights to send message to $m->{chatid}");
# TODO: disable fortune if message is good morning fortune
} elsif ($resp->{description} eq 'Bad Request: chat not found') {
$log->notice (
"[NOTICE] Chat not found toggling good morning fortune off for $m->{chatid}"
);
FortuneToggle($m->{chatid}, 0);
return;
} elsif ($resp->description eq 'Bad Request: group chat was upgraded to a supergroup chat') {
if (defined ($resp->{parameters}) &&
defined ($resp->{parameters}->{migrate_to_chat_id})) {
my $tpl = '[NOTICE] group upgraded to supergroup, chat_id changed, ';
$tpl .= 'migrating settings from %s to %s';
$log->notice (
sprintf($tpl, $m->{chatid}, $resp->{parameters}->{migrate_to_chat_id})
);
MigrateSettingsToNewChatID (
$m->{chatid},
$resp->{parameters}->{migrate_to_chat_id}
);
}
} else {
$log->error ('[ERROR] Unable to call sendMessage() BotAPI method: ' . Dumper ($r));
}
} else {
$log->error ('[ERROR] BotAPI returns parsable error: ' . Dumper ($resp));
}
} else {
$log->error ('[ERROR] Unable to handle BotAPI error: ' . Dumper ($resp));
}
} else {
$log->error ('[ERROR] Unable to call sendMessage() BotAPI method: ' . Dumper ($r));
}
}
} }
return; return;

View File

@@ -4,7 +4,7 @@ use strict;
use warnings; use warnings;
use 5.018; ## no critic (ProhibitImplicitImport) use 5.018; ## no critic (ProhibitImplicitImport)
use utf8; use utf8;
$Teapot::Bot::VERSION = '0.022'; $Teapot::Bot::VERSION = '0.026';
# ABSTRACT: A base class to make your very own Telegram bot # ABSTRACT: A base class to make your very own Telegram bot
1; 1;
@@ -25,7 +25,7 @@ A base class to make your very own Telegram bot
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 SYNOPSIS =head1 SYNOPSIS

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,14 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PhotoSize (); use Teapot::Bot::Object::PhotoSize ();
$Teapot::Bot::Object::Animation::VERSION = '0.022'; $Teapot::Bot::Object::Animation::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
has 'width'; has 'width';
has 'height'; has 'height';
has 'duration'; has 'duration';
has 'thumb'; #PhotoSize has 'thumbnail'; #PhotoSize
has 'file_name'; has 'file_name';
has 'mime_type'; has 'mime_type';
has 'file_size'; has 'file_size';
@@ -24,7 +24,7 @@ has 'file_size';
sub fields { sub fields {
return { scalar => [qw/file_id file_unique_id width height duration file_name mime_type return { scalar => [qw/file_id file_unique_id width height duration file_name mime_type
file_size/], file_size/],
'Teapot::Bot::Object::PhotoSize' => [qw/thumb/], 'Teapot::Bot::Object::PhotoSize' => [qw/thumbnail/],
}; };
} }
@@ -43,7 +43,7 @@ Teapot::Bot::Object::Animation - The base class for Telegram message 'Animation'
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'Animation' type. The base class for Telegram message 'Animation' type.

View File

@@ -7,8 +7,8 @@ use 5.018; ## no critic (ProhibitImplicitImport)
use utf8; use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PhotoSize ();
$Teapot::Bot::Object::Audio::VERSION = '0.022'; $Teapot::Bot::Object::Audio::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
@@ -18,12 +18,12 @@ has 'title'; # Optional. Title of the audio as defined by sender or by audio
has 'file_name'; # Optional. Original filename as defined by sender has 'file_name'; # Optional. Original filename as defined by sender
has 'mime_type'; # Optional. MIME type of the file as defined by sender has 'mime_type'; # Optional. MIME type of the file as defined by sender
has 'file_size'; # Optional. File size has 'file_size'; # Optional. File size
has 'thumb'; # Optional. Thumbnail of the album cover to which the music file belongs has 'thumbnail'; # Optional. Thumbnail of the album cover to which the music file belongs
sub fields { sub fields {
return { scalar => [qw/file_id file_unique_id duration performer title file_name mime_type return { scalar => [qw/file_id file_unique_id duration performer title file_name mime_type
file_size/], file_size/],
'Teapot::Bot::Object::PhotoSize' => [qw/thumb/], 'Teapot::Bot::Object::PhotoSize' => [qw/thumbnail/],
}; };
} }
@@ -41,7 +41,7 @@ Teapot::Bot::Object::Audio - The base class for Telegram 'Audio' type objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Audio' type objects. The base class for Telegram 'Audio' type objects.

View File

@@ -13,7 +13,7 @@ use List::Util qw/any/;
use Mojo::Base -base; use Mojo::Base -base;
$Teapot::Bot::Object::Base::VERSION = '0.022'; $Teapot::Bot::Object::Base::VERSION = '0.026';
has '_brain'; # a reference to our brain has '_brain'; # a reference to our brain
@@ -187,7 +187,7 @@ Teapot::Bot::Object::Base - The base class for all Telegram::Bot::Object objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION

View File

@@ -7,7 +7,7 @@ use 5.018; ## no critic (ProhibitImplicitImport)
use utf8; use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::CallbackGame::VERSION = '0.022'; $Teapot::Bot::Object::CallbackGame::VERSION = '0.026';
# https://core.telegram.org/bots/api#callbackgame # https://core.telegram.org/bots/api#callbackgame
# "A placeholder, currently holds no information. Use BotFather to set up your game" # "A placeholder, currently holds no information. Use BotFather to set up your game"
@@ -30,7 +30,7 @@ Teapot::Bot::Object::CallbackGame - The base class for Telegram message 'Callbac
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'CallbackGame' type. The base class for Telegram message 'CallbackGame' type.

View File

@@ -12,7 +12,7 @@ use Teapot::Bot::Object::Message ();
use Teapot::Bot::Object::ChatPermissions (); use Teapot::Bot::Object::ChatPermissions ();
use Teapot::Bot::Object::ChatLocation (); use Teapot::Bot::Object::ChatLocation ();
$Teapot::Bot::Object::Chat::VERSION = '0.022'; $Teapot::Bot::Object::Chat::VERSION = '0.026';
has 'id'; has 'id';
has 'type'; has 'type';
@@ -20,11 +20,23 @@ has 'title'; # Optional.
has 'username'; # Optional. has 'username'; # Optional.
has 'first_name'; # Optional. has 'first_name'; # Optional.
has 'last_name'; # Optional. has 'last_name'; # Optional.
has 'is_forum'; # Optional. True, if the supergroup chat is a forum (has topics enabled)
has 'photo'; # Teapot::Bot::Object::ChatPhoto. Optional. Returned only in getChat. has 'photo'; # Teapot::Bot::Object::ChatPhoto. Optional. Returned only in getChat.
has 'active_usernames'; # Optional. If non-empty, the list of all active chat usernames; for private chats,
# supergroups and channels. Returned only in getChat.
has 'emoji_status_custom_emoji_id'; # Optional. Custom emoji identifier of emoji status of the other party in a private
# chat. Returned only in getChat.
has 'bio'; # Optional. Bio of the other party in a private chat. Returned only in getChat. has 'bio'; # Optional. Bio of the other party in a private chat. Returned only in getChat.
has 'has_private_forwards'; # Optional. True, if privacy settings of the other party in the private chat allows to has 'has_private_forwards'; # Optional. True, if privacy settings of the other party in the private chat allows to
# use tg://user?id=<user_id> links only in chats with the user. # use tg://user?id=<user_id> links only in chats with the user.
# Returned only in getChat. # Returned only in getChat.
has 'has_restricted_voice_and_video_messages'; # Optional. True, if the privacy settings of the other party restrict
# sending voice and video note messages in the private chat.
# Returned only in getChat.
has 'join_to_send_messages'; # Optional. True, if users need to join the supergroup before they can send messages.
# Returned only in getChat.
has 'join_by_request'; # Optional. True, if all users directly joining the supergroup need to be approved by
# supergroup administrators. Returned only in getChat.
has 'description'; # Optional. Returned only in getChat. has 'description'; # Optional. Returned only in getChat.
has 'invite_link'; # Optional. Returned only in getChat. has 'invite_link'; # Optional. Returned only in getChat.
has 'pinned_message'; # Teapot::Bot::Object::Message has 'pinned_message'; # Teapot::Bot::Object::Message
@@ -33,6 +45,10 @@ has 'permissions'; # Teapot::Bot::Object::ChatPermissions. Optional
has 'slow_mode_delay'; # Optional. Returned only in getChat. has 'slow_mode_delay'; # Optional. Returned only in getChat.
has 'message_auto_delete_time'; # Optional. The time after which all messages sent to the chat will be automatically has 'message_auto_delete_time'; # Optional. The time after which all messages sent to the chat will be automatically
# deleted; in seconds. Returned only in getChat. # deleted; in seconds. Returned only in getChat.
has 'has_aggressive_anti_spam_enabled'; # Optional. True, if aggressive anti-spam checks are enabled in the supergroup.
# The field is only available to chat administrators. Returned only in getChat.
has 'has_hidden_members'; # Optional. True, if non-administrators can only get the list of bots and administrators
# in the chat. Returned only in getChat.
has 'has_protected_content'; # Optional. True, if messages from the chat can't be forwarded to other chats. has 'has_protected_content'; # Optional. True, if messages from the chat can't be forwarded to other chats.
# Returned only in getChat. # Returned only in getChat.
has 'sticker_set_name'; # Optional. Returned only in getChat. has 'sticker_set_name'; # Optional. Returned only in getChat.
@@ -42,10 +58,14 @@ has 'location'; # Optional. Returned only in getChat.
sub fields { sub fields {
return { return {
'scalar' => [qw/id type title username first_name last_name bio 'scalar' => [qw/id type title username first_name last_name is_forum
has_private_forwards description invite_link slow_mode_delay emoji_status_custom_emoji_id bio
message_auto_delete_time has_protected_content has_private_forwards has_restricted_voice_and_video_messages
sticker_set_name can_set_sticker_set linked_chat_id/], join_to_send_messages join_by_request description invite_link
slow_mode_delay message_auto_delete_time
has_aggressive_anti_spam_enabled has_hidden_members
has_protected_content sticker_set_name can_set_sticker_set
linked_chat_id/],
'Teapot::Bot::Object::ChatPhoto' => [qw/photo/], 'Teapot::Bot::Object::ChatPhoto' => [qw/photo/],
'Teapot::Bot::Object::Message' => [qw/pinned_message/], 'Teapot::Bot::Object::Message' => [qw/pinned_message/],
'Teapot::Bot::Object::ChatPermissions' => [qw/permissions/], 'Teapot::Bot::Object::ChatPermissions' => [qw/permissions/],
@@ -53,6 +73,9 @@ sub fields {
}; };
} }
sub arrays {
return qw/active_usernames/;
}
sub is_user { sub is_user {
return shift->id > 0; return shift->id > 0;
@@ -77,7 +100,7 @@ Teapot::Bot::Object::Chat - The base class for Telegram 'Chat' type objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Chat' type objects. The base class for Telegram 'Chat' type objects.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::User (); use Teapot::Bot::Object::User ();
$Teapot::Bot::Object::ChatInviteLink::VERSION = '0.022'; $Teapot::Bot::Object::ChatInviteLink::VERSION = '0.026';
has 'invite_link'; has 'invite_link';
has 'creator'; has 'creator';
@@ -44,7 +44,7 @@ Teapot::Bot::Object::ChatInviteLink - The base class for Telegram message 'ChatI
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'ChatInviteLink' type. The base class for Telegram message 'ChatInviteLink' type.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ChatLocation::VERSION = '0.022'; $Teapot::Bot::Object::ChatLocation::VERSION = '0.026';
has 'location'; has 'location';
has 'address'; has 'address';
@@ -34,7 +34,7 @@ Teapot::Bot::Object::ChatLocation - The base class for Telegram message 'ChatLoc
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'ChatLocation' type. The base class for Telegram message 'ChatLocation' type.

View File

@@ -12,7 +12,7 @@ use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::User (); use Teapot::Bot::Object::User ();
$Teapot::Bot::Object::ChatMember::VERSION = '0.022'; $Teapot::Bot::Object::ChatMember::VERSION = '0.026';
# ChatMemberOwner # ChatMemberOwner
has 'user'; has 'user';
@@ -39,7 +39,8 @@ has 'can_promote_members'; # optional. Administrators only.
has 'can_change_info'; # optional. Administrators and restricted only. has 'can_change_info'; # optional. Administrators and restricted only.
has 'can_invite_users'; # optional. Administrators and restricted only. has 'can_invite_users'; # optional. Administrators and restricted only.
has 'can_pin_messages'; # optional. Administrators and restricted only. has 'can_pin_messages'; # optional. Administrators and restricted only.
has 'can_manage_topics'; # Optional. True, if the user is allowed to create, rename, close, and reopen forum
# topics; supergroups only
# ChatMemberMember # ChatMemberMember
# has 'status'; # has 'status';
# has 'user'; # has 'user';
@@ -52,11 +53,17 @@ has 'is_member'; # optional. Restricted only.
# has 'can_invite_users'; # optional. Administrators and restricted only. # has 'can_invite_users'; # optional. Administrators and restricted only.
# has 'can_pin_messages'; # optional. Administrators and restricted only. # has 'can_pin_messages'; # optional. Administrators and restricted only.
has 'can_send_messages'; # optional. Restricted only. has 'can_send_messages'; # optional. Restricted only.
has 'can_send_media_messages'; # optional. Restricted only. has 'can_send_audios';
has 'can_send_documents';
has 'can_send_photos';
has 'can_send_videos';
has 'can_send_video_notes';
has 'can_send_voice_notes';
has 'can_send_polls'; # optional. Restricted only. has 'can_send_polls'; # optional. Restricted only.
has 'can_send_other_messages'; # optional. Restricted only. has 'can_send_other_messages'; # optional. Restricted only.
has 'can_add_web_page_previews'; # optional. Restricted only. has 'can_add_web_page_previews'; # optional. Restricted only.
has 'until_date'; # optional. Restricted and kicked only. has 'until_date'; # optional. Restricted and kicked only.
# has 'can_manage_topics';
# ChatMemberLeft # ChatMemberLeft
# has 'status'; # has 'status';
@@ -69,11 +76,14 @@ has 'until_date'; # optional. Restricted and kicked only.
sub fields { sub fields {
return { return {
'scalar' => [qw/status custom_title is_anonymous can_be_edited can_manage_chat can_post_messages 'scalar' => [qw/status custom_title is_anonymous can_be_edited can_manage_chat
can_edit_messages can_delete_messages can_manage_voice_chats can_restrict_members can_post_messages can_edit_messages can_delete_messages
can_promote_members can_change_info can_invite_users can_pin_messages is_member can_manage_voice_chats can_restrict_members can_promote_members
can_send_messages can_send_media_messages can_send_polls can_send_other_messages can_change_info can_invite_users can_pin_messages is_member
can_add_web_page_previews until_date/], can_send_messages can_send_audios can_send_documents can_send_photos
can_send_videos can_send_video_notes can_send_voice_notes can_send_polls
can_send_other_messagescan_add_web_page_previews until_date
can_manage_topics/],
'Teapot::Bot::Object::User' => [qw/user/], 'Teapot::Bot::Object::User' => [qw/user/],
}; };
} }
@@ -145,7 +155,7 @@ Teapot::Bot::Object::ChatMember - The base class for Telegram 'ChatMember' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'ChatMember' type objects. The base class for Telegram 'ChatMember' type objects.

View File

@@ -13,7 +13,7 @@ use Teapot::Bot::Object::ChatMember ();
use Teapot::Bot::Object::ChatInviteLink (); use Teapot::Bot::Object::ChatInviteLink ();
use Teapot::Bot::Object::Voice (); use Teapot::Bot::Object::Voice ();
$Teapot::Bot::Object::ChatMemberUpdated::VERSION = '0.022'; $Teapot::Bot::Object::ChatMemberUpdated::VERSION = '0.026';
has 'chat'; has 'chat';
has 'from'; has 'from';
@@ -50,7 +50,7 @@ Teapot::Bot::Object::ChatMemberUpdated - The base class for Telegram 'ChatMember
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'ChatMemberUpdated' type objects. The base class for Telegram 'ChatMemberUpdated' type objects.

View File

@@ -10,13 +10,17 @@ use Carp qw(carp);
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Brain (); use Teapot::Bot::Brain ();
$Teapot::Bot::Object::ChatPermissions::VERSION = '0.022'; $Teapot::Bot::Object::ChatPermissions::VERSION = '0.026';
# all fields are optional # all fields are optional
# N.B. Some of the fields "overlaps" other, so in order to guess valid set of permissions you should check all of them # N.B. Some of the fields "overlaps" other, so in order to guess valid set of permissions you should check all of them
has 'can_send_messages'; # True, if the user is allowed to send text messages, contacts, locations and venues has 'can_send_messages'; # True, if the user is allowed to send text messages, contacts, locations and venues
has 'can_send_media_messages'; # True, if the user is allowed to send audios, documents, photos, videos, video notes has 'can_send_audios'; # True, if the user is allowed to send audios
# and voice notes, implies can_send_messages has 'can_send_documents'; # True, if the user is allowed to send documents
has 'can_send_photos'; # True, if the user is allowed to send photos
has 'can_send_videos'; # True, if the user is allowed to send videos
has 'can_send_video_notes'; # True, if the user is allowed to send video_notes
has 'can_send_voice_notes'; # True, if the user is allowed to send voice_notes
has 'can_send_polls'; # True, if the user is allowed to send polls, implies can_send_messages has 'can_send_polls'; # True, if the user is allowed to send polls, implies can_send_messages
has 'can_send_other_messages'; # True, if the user is allowed to send animations, games, stickers and use inline bots, has 'can_send_other_messages'; # True, if the user is allowed to send animations, games, stickers and use inline bots,
# implies can_send_media_messages # implies can_send_media_messages
@@ -26,21 +30,52 @@ has 'can_change_info'; # True, if the user is allowed to change the ch
# Ignored in public supergroups # Ignored in public supergroups
has 'can_invite_users'; # True, if the user is allowed to invite new users to the chat has 'can_invite_users'; # True, if the user is allowed to invite new users to the chat
has 'can_pin_messages'; # True, if the user is allowed to pin messages. Ignored in public supergroups has 'can_pin_messages'; # True, if the user is allowed to pin messages. Ignored in public supergroups
has 'can_manage_topics'; # Optional. True, if the user is allowed to create forum topics. If omitted defaults to
# the value of can_pin_messages
sub fields { sub fields {
return { return {
'scalar' => [qw/can_send_messages can_send_media_messages can_send_polls 'scalar' => [qw/can_send_messages can_send_audios can_send_documents
can_send_other_messages can_add_web_page_previews can_change_info can_send_photos can_send_videos can_send_video_notes
can_invite_users can_pin_messages/], can_send_voice_notes can_send_polls can_send_other_messages
can_add_web_page_previews can_change_info can_invite_users
can_pin_messages can_manage_topics/],
}; };
} }
sub canTalk { sub canTalk {
my $self = shift; my $self;
my $args = {};
# There is some magic happen if we call function as object, as we usually do here
# But if we access function from usual-style wrapper that calls it directly from namespace it supply noy $self object
# but string with its name, which is a bummer.
while (my $o = shift @_) {
if (ref ($o) ne '') {
$self = $o;
last;
}
}
my $chatid = shift; my $chatid = shift;
my $args->{chat_id} = $chatid; $args->{chat_id} = $chatid;
my $can_talk = 0; my $can_talk = 0;
my $ret;
if (! defined($self->token) || $self->token eq '') {
$ret->{error} = 1;
$ret->{message} = 'No token supplied to canTalk()';
return $ret;
}
unless ($args->{chat_id}) {
$ret->{error} = 1;
$ret->{message} = 'No chat_id supplied to canTalk()';
return $ret;
}
if ($chatid < 0) { if ($chatid < 0) {
# group chat # group chat
@@ -48,16 +83,23 @@ sub canTalk {
# on api error, keep silence # on api error, keep silence
if ($chatobj == 0 || $chatobj->{error}) { if ($chatobj == 0 || $chatobj->{error}) {
carp "Unable to get chat info for $chatid from telegram API"; my $emesg = "Unable to get chat info for $chatid from telegram API";
return $can_talk; $ret->{message} = $emesg;
$ret->{error} = 1;
$ret->{can_talk} = 0;
return $ret;
} }
my $myObj = Teapot::Bot::Brain->getMe ($self); my $myObj = Teapot::Bot::Brain->getMe ($self);
# on api error, keep silence # on api error, keep silence
if ($myObj == 0 || $myObj->{error}) { if ($myObj == 0 || $myObj->{error}) {
carp "Unable to get chat info for $chatid from telegram API"; my $emesg = "Unable to get chat info for $chatid from telegram API";
return $can_talk; $ret->{message} = $emesg;
$ret->{error} = 1;
$ret->{can_talk} = 0;
return $ret;
} }
my $myid = $myObj->id; my $myid = $myObj->id;
@@ -66,8 +108,12 @@ sub canTalk {
# on api error, keep silence # on api error, keep silence
if ($me == 0 || $me->{error}) { if ($me == 0 || $me->{error}) {
carp 'Unable to get chat info for bot itself from telegram API'; my $emesg = 'Unable to get chat info for bot itself from telegram API';
return $can_talk; $ret->{message} = $emesg;
$ret->{error} = 1;
$ret->{can_talk} = 0;
return $ret;
} }
if ($me->{'status'} eq 'administrator') { if ($me->{'status'} eq 'administrator') {
@@ -81,9 +127,13 @@ sub canTalk {
$can_talk = 1; $can_talk = 1;
} }
return $can_talk; $ret->{error} = 0;
$ret->{can_talk} = $can_talk;
return $ret;
} }
1; 1;
__END__ __END__
@@ -98,7 +148,7 @@ Teapot::Bot::Object::ChatPermissions - The base class for Telegram 'ChatPermissi
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'ChatPermissions' type objects. The base class for Telegram 'ChatPermissions' type objects.
@@ -112,7 +162,9 @@ attributes available for C<Teapot::Bot::Object::ChatPermissions> objects.
A convenience method to check if bot can talk in given conversation. A convenience method to check if bot can talk in given conversation.
Returns true if the bot can talk in given conversation, otherwise false. Returns hash reference with can_talk true if the bot can talk in given conversation, otherwise false.
Also set field error 1 if an error occurs during api interaction.
And if error set to true message field also set plus can_talk set to false.
=head1 AUTHOR =head1 AUTHOR

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ChatPhoto::VERSION = '0.022'; $Teapot::Bot::Object::ChatPhoto::VERSION = '0.026';
has 'small_file_id'; has 'small_file_id';
has 'small_file_unique_id'; has 'small_file_unique_id';
@@ -35,7 +35,7 @@ Teapot::Bot::Object::ChatPhoto - The base class for Telegram 'ChatPhoto' type ob
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'ChatPhoto' type objects. The base class for Telegram 'ChatPhoto' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::Contact::VERSION = '0.022'; $Teapot::Bot::Object::Contact::VERSION = '0.026';
has 'phone_number'; has 'phone_number';
has 'first_name'; has 'first_name';
@@ -36,7 +36,7 @@ Teapot::Bot::Object::Contact - The base class for Telegram 'Contact' objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Contact' objects. The base class for Telegram 'Contact' objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::Dice::VERSION = '0.022'; $Teapot::Bot::Object::Dice::VERSION = '0.026';
has 'emoji'; has 'emoji';
has 'value'; has 'value';
@@ -31,7 +31,7 @@ Teapot::Bot::Object::Dice - The base class for Telegram message 'Dice' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'Dice' type. The base class for Telegram message 'Dice' type.

View File

@@ -9,11 +9,11 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PhotoSize (); use Teapot::Bot::Object::PhotoSize ();
$Teapot::Bot::Object::Document::VERSION = '0.022'; $Teapot::Bot::Object::Document::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
has 'thumb'; #PhotoSize has 'thumbnail'; #PhotoSize
has 'file_name'; has 'file_name';
has 'mime_type'; has 'mime_type';
has 'file_size'; has 'file_size';
@@ -21,7 +21,7 @@ has 'file_size';
sub fields { sub fields {
return { return {
scalar => [qw/file_id file_unique_id file_name mime_type file_size/], scalar => [qw/file_id file_unique_id file_name mime_type file_size/],
'Teapot::Bot::Object::PhotoSize' => [qw/thumb/], 'Teapot::Bot::Object::PhotoSize' => [qw/thumbnail/],
}; };
} }
@@ -39,7 +39,7 @@ Teapot::Bot::Object::Document - The base class for Telegram 'Document' objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Document' objects. The base class for Telegram 'Document' objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::EncryptedCredentials::VERSION = '0.022'; $Teapot::Bot::Object::EncryptedCredentials::VERSION = '0.026';
has 'data'; has 'data';
has 'hash'; has 'hash';
@@ -32,7 +32,7 @@ Teapot::Bot::Object::EncryptedCredentials - The base class for Telegram 'Encrypt
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'EncryptedCredentials' type objects. The base class for Telegram 'EncryptedCredentials' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::EncryptedPassportElement::VERSION = '0.022'; $Teapot::Bot::Object::EncryptedPassportElement::VERSION = '0.026';
# XXX Implement rest of this # XXX Implement rest of this
# https://core.telegram.org/bots/api#encryptedpassportelement # https://core.telegram.org/bots/api#encryptedpassportelement
@@ -38,7 +38,7 @@ Teapot::Bot::Object::EncryptedPassportElement - The base class for Telegram 'Enc
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'EncryptedPassportElement' type objects. The base class for Telegram 'EncryptedPassportElement' type objects.

View File

@@ -0,0 +1,51 @@
package Teapot::Bot::Object::ForumTopicClosed;
# ABSTRACT: The base class for Telegram message 'ForumTopicClosed' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ForumTopicClosed::VERSION = '0.026';
sub fields {
return { };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::ForumTopicClosed - The base class for Telegram message 'ForumTopicClosed' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'ForumTopicClosed' type.
See L<https://core.telegram.org/bots/api#forumtopicclosed> for details of the
attributes available for C<Teapot::Bot::Object::ForumTopicClosed> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,54 @@
package Teapot::Bot::Object::ForumTopicCreated;
# ABSTRACT: The base class for Telegram message 'ForumTopicCreated' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ForumTopicCreated::VERSION = '0.026';
has 'name';
has 'icon_color';
has 'icon_custom_emoji_id';
sub fields {
return { scalar => [qw/name icon_color icon_custom_emoji_id/] };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::ForumTopicCreated - The base class for Telegram message 'ForumTopicCreated' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'ForumTopicCreated' type.
See L<https://core.telegram.org/bots/api#forumtopiccreated> for details of the
attributes available for C<Teapot::Bot::Object::ForumTopicCreated> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,55 @@
package Teapot::Bot::Object::ForumTopicEdited;
# ABSTRACT: The base class for Telegram message 'ForumTopicEdited' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ForumTopicEdited::VERSION = '0.026';
has 'name';
has 'icon_custom_emoji_id';
sub fields {
return {
'scalar' => [qw/name icon_custom_emoji_id/],
};
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::ForumTopicEdited - The base class for Telegram message 'ForumTopicEdited' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'ForumTopicEdited' type.
See L<https://core.telegram.org/bots/api#forumtopicedited> for details of the
attributes available for C<Teapot::Bot::Object::ForumTopicEdited> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2023 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,51 @@
package Teapot::Bot::Object::ForumTopicReopened;
# ABSTRACT: The base class for Telegram message 'ForumTopicReopened' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ForumTopicReopened::VERSION = '0.026';
sub fields {
return { };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::ForumTopicReopened - The base class for Telegram message 'ForumTopicReopened' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'ForumTopicReopened' type.
See L<https://core.telegram.org/bots/api#forumtopicreopened> for details of the
attributes available for C<Teapot::Bot::Object::ForumTopicReopened> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -11,7 +11,7 @@ use Teapot::Bot::Object::PhotoSize ();
use Teapot::Bot::Object::Animation (); use Teapot::Bot::Object::Animation ();
use Teapot::Bot::Object::MessageEntity (); use Teapot::Bot::Object::MessageEntity ();
$Teapot::Bot::Object::Game::VERSION = '0.022'; $Teapot::Bot::Object::Game::VERSION = '0.026';
has 'title'; has 'title';
has 'description'; has 'description';
@@ -45,7 +45,7 @@ Teapot::Bot::Object::Game - The base class for Telegram message 'Game' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'Game' type. The base class for Telegram message 'Game' type.

View File

@@ -0,0 +1,51 @@
package Teapot::Bot::Object::GeneralForumTopicHidden;
# ABSTRACT: The base class for Telegram message 'GeneralForumTopicHidden' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::GeneralForumTopicHidden::VERSION = '0.026';
sub fields {
return { };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::GeneralForumTopicHidden - The base class for Telegram message 'GeneralForumTopicHidden' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'GeneralForumTopicHidden' type.
See L<https://core.telegram.org/bots/api#generalforumtopichidden> for details of the
attributes available for C<Teapot::Bot::Object::GeneralForumTopicHidden> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2023 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,51 @@
package Teapot::Bot::Object::GeneralForumTopicUnhidden;
# ABSTRACT: The base class for Telegram message 'GeneralForumTopicUnhidden' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::GeneralForumTopicUnhidden::VERSION = '0.026';
sub fields {
return { };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::GeneralForumTopicUnhidden - The base class for Telegram message 'GeneralForumTopicUnhidden' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'GeneralForumTopicUnhidden' type.
See L<https://core.telegram.org/bots/api#generalforumtopicunhidden> for details of the
attributes available for C<Teapot::Bot::Object::GeneralForumTopicUnhidden> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2023 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -10,7 +10,7 @@ use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::LoginUrl (); use Teapot::Bot::Object::LoginUrl ();
use Teapot::Bot::Object::CallbackGame (); use Teapot::Bot::Object::CallbackGame ();
$Teapot::Bot::Object::InlineKeyboardButton::VERSION = '0.022'; $Teapot::Bot::Object::InlineKeyboardButton::VERSION = '0.026';
has 'text'; has 'text';
has 'url'; has 'url';
@@ -43,7 +43,7 @@ Teapot::Bot::Object::InlineKeyboardButton - The base class for Telegram 'InlineK
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'InlineKeyboardButton' type objects. The base class for Telegram 'InlineKeyboardButton' type objects.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::InlineKeyboardButton (); use Teapot::Bot::Object::InlineKeyboardButton ();
$Teapot::Bot::Object::InlineKeyboardMarkup::VERSION = '0.022'; $Teapot::Bot::Object::InlineKeyboardMarkup::VERSION = '0.026';
has 'inline_keyboard'; has 'inline_keyboard';
@@ -35,7 +35,7 @@ Teapot::Bot::Object::InlineKeyboardMarkup - The base class for Telegram 'InlineK
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'InlineKeyboardMarkup' type objects. The base class for Telegram 'InlineKeyboardMarkup' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::Invoice::VERSION = '0.022'; $Teapot::Bot::Object::Invoice::VERSION = '0.026';
has 'title'; has 'title';
has 'description'; has 'description';
@@ -34,7 +34,7 @@ Teapot::Bot::Object::Invoice - The base class for Telegram 'Invoice' type object
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Invoice' type objects. The base class for Telegram 'Invoice' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::Location::VERSION = '0.022'; $Teapot::Bot::Object::Location::VERSION = '0.026';
has 'longitude'; has 'longitude';
has 'latitude'; has 'latitude';
@@ -35,7 +35,7 @@ Teapot::Bot::Object::Location - The base class for Telegram message 'Location' t
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'Location' type. The base class for Telegram message 'Location' type.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::LoginUrl::VERSION = '0.022'; $Teapot::Bot::Object::LoginUrl::VERSION = '0.026';
has 'url'; has 'url';
has 'forward_text'; has 'forward_text';
@@ -33,7 +33,7 @@ Teapot::Bot::Object::LoginUrl - The base class for Telegram 'LoginUrl' type obje
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'LoginUrl' type objects. The base class for Telegram 'LoginUrl' type objects.

View File

@@ -0,0 +1,55 @@
package Teapot::Bot::Object::MaskPosition;
# ABSTRACT: The base class for Telegram 'MaskPosition' type objects
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::MaskPosition::VERSION = '0.026';
has 'point';
has 'x_shift';
has 'y_shift';
has 'scale';
sub fields {
return { scalar => [qw/point x_shift y_shift scale/] };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::MaskPosition - The base class for Telegram 'MaskPosition' type objects
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram 'MaskPosition' type objects.
See L<https://core.telegram.org/bots/api#MaskPosition> for details of the
attributes available for C<Teapot::Bot::Object::MaskPosition> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -30,20 +30,29 @@ use Teapot::Bot::Object::PhotoSize ();
use Teapot::Bot::Object::Invoice (); use Teapot::Bot::Object::Invoice ();
use Teapot::Bot::Object::Venue (); use Teapot::Bot::Object::Venue ();
use Teapot::Bot::Object::SuccessfulPayment (); use Teapot::Bot::Object::SuccessfulPayment ();
use Teapot::Bot::Object::WriteAccessAllowed ();
use Teapot::Bot::Object::PassportData (); use Teapot::Bot::Object::PassportData ();
use Teapot::Bot::Object::ForumTopicCreated ();
use Teapot::Bot::Object::ForumTopicEdited ();
use Teapot::Bot::Object::ForumTopicClosed ();
use Teapot::Bot::Object::ForumTopicReopened ();
use Teapot::Bot::Object::GeneralForumTopicHidden ();
use Teapot::Bot::Object::GeneralForumTopicUnhidden ();
use Teapot::Bot::Object::InlineKeyboardMarkup (); use Teapot::Bot::Object::InlineKeyboardMarkup ();
use Teapot::Bot::Object::ProximityAlertTriggered (); use Teapot::Bot::Object::ProximityAlertTriggered ();
use Teapot::Bot::Object::Dice (); use Teapot::Bot::Object::Dice ();
use Teapot::Bot::Object::MessageAutoDeleteTimerChanged (); use Teapot::Bot::Object::MessageAutoDeleteTimerChanged ();
use Teapot::Bot::Object::VoiceChatScheduled (); use Teapot::Bot::Object::VideoChatScheduled ();
use Teapot::Bot::Object::VoiceChatStarted (); use Teapot::Bot::Object::VideoChatStarted ();
use Teapot::Bot::Object::VoiceChatEnded (); use Teapot::Bot::Object::VideoChatEnded ();
use Teapot::Bot::Object::VoiceChatParticipantsInvited (); use Teapot::Bot::Object::VideoChatParticipantsInvited ();
$Teapot::Bot::Object::Message::VERSION = '0.022'; $Teapot::Bot::Object::Message::VERSION = '0.026';
# basic message stuff # basic message stuff
has 'message_id'; has 'message_id';
has 'message_thread_id'; # Optional. Unique identifier of a message thread to which the message belongs;
# for supergroups only
has 'from'; # User has 'from'; # User
has 'sender_chat'; # Chat has 'sender_chat'; # Chat
has 'date'; has 'date';
@@ -54,6 +63,7 @@ has 'forward_from_message_id';
has 'forward_signature'; has 'forward_signature';
has 'forward_sender_name'; has 'forward_sender_name';
has 'forward_date'; has 'forward_date';
has 'is_topic_message'; # Optional. True, if the message is sent to a forum topic
has 'is_automatic_forward'; has 'is_automatic_forward';
has 'reply_to_message'; # Message has 'reply_to_message'; # Message
has 'via_bot'; # User has 'via_bot'; # User
@@ -73,6 +83,7 @@ has 'video_note'; # VideoNote
has 'voice'; # Voice has 'voice'; # Voice
has 'caption'; has 'caption';
has 'caption_entities'; # Array of MessageEntity has 'caption_entities'; # Array of MessageEntity
has 'has_media_spoiler';
has 'contact'; # Contact has 'contact'; # Contact
has 'dice'; has 'dice';
has 'game'; has 'game';
@@ -94,57 +105,73 @@ has 'pinned_message'; # Message
has 'invoice'; # Invoice has 'invoice'; # Invoice
has 'successful_payment'; # SuccessfulPayment has 'successful_payment'; # SuccessfulPayment
has 'connected_website'; has 'connected_website';
has 'write_access_allowed'; # WriteAccessAllowed
has 'passport_data'; # PassportData has 'passport_data'; # PassportData
has 'proximity_alert_triggered'; has 'proximity_alert_triggered';
has 'voice_chat_scheduled'; # Optional. Service message: voice chat scheduled has 'forum_topic_created'; # ForumTopicCreated
has 'voice_chat_started'; # Optional. Service message: voice chat started has 'forum_topic_edited'; # ForumTopicEdited
has 'voice_chat_ended'; # Optional. Service message: voice chat ended has 'forum_topic_closed'; # ForumTopicClosed
has 'voice_chat_participants_invited'; # Optional. Service message: new participants invited to a voice chat has 'forum_topic_reopened'; # ForumTopicReopened
has 'general_forum_topic_hidden'; # GeneralForumTopicHidden
has 'general_forum_topic_unhidden'; # GeneralForumTopicUnhidden
has 'video_chat_scheduled';
has 'video_chat_started';
has 'video_chat_ended';
has 'video_chat_participants_invited';
has 'web_app_data';
has 'reply_markup'; # Array of InlineKeyboardMarkup has 'reply_markup'; # Array of InlineKeyboardMarkup
sub fields { sub fields {
return { return {
'scalar' => [qw/message_id date forward_from_message_id 'scalar' => [qw/message_id message_thread_id date forward_from_message_id
forward_signature forward_sender_name forward_signature forward_sender_name forward_date
forward_date is_automatic_forward edit_date has_protected_content is_topic_message is_automatic_forward edit_date
media_group_id author_signature text caption has_protected_content media_group_id author_signature text
new_chat_title delete_chat_photo caption has_media_spoiler new_chat_title delete_chat_photo
group_chat_created supergroup_chat_created group_chat_created supergroup_chat_created
channel_chat_created migrate_to_chat_id channel_chat_created migrate_to_chat_id
migrate_from_chat_id connected_website/], migrate_from_chat_id connected_website/],
'Teapot::Bot::Object::User' => [qw/from forward_from via_bot new_chat_members left_chat_member /], 'Teapot::Bot::Object::User' => [qw/from forward_from via_bot new_chat_members left_chat_member /],
'Teapot::Bot::Object::Chat' => [qw/sender_chat chat forward_from_chat/], 'Teapot::Bot::Object::Chat' => [qw/sender_chat chat forward_from_chat/],
'Teapot::Bot::Object::Message' => [qw/reply_to_message pinned_message/], 'Teapot::Bot::Object::Message' => [qw/reply_to_message pinned_message/],
'Teapot::Bot::Object::MessageEntity' => [qw/entities caption_entities /], 'Teapot::Bot::Object::MessageEntity' => [qw/entities caption_entities /],
'Teapot::Bot::Object::Audio' => [qw/audio/], 'Teapot::Bot::Object::Audio' => [qw/audio/],
'Teapot::Bot::Object::Document' => [qw/document/], 'Teapot::Bot::Object::Document' => [qw/document/],
'Teapot::Bot::Object::Animation' => [qw/animation/], 'Teapot::Bot::Object::Animation' => [qw/animation/],
'Teapot::Bot::Object::Game' => [qw/game/], 'Teapot::Bot::Object::Game' => [qw/game/],
'Teapot::Bot::Object::PhotoSize' => [qw/photo new_chat_photo/], 'Teapot::Bot::Object::PhotoSize' => [qw/photo new_chat_photo/],
'Teapot::Bot::Object::Sticker' => [qw/sticker/], 'Teapot::Bot::Object::Sticker' => [qw/sticker/],
'Teapot::Bot::Object::Video' => [qw/video/], 'Teapot::Bot::Object::Video' => [qw/video/],
'Teapot::Bot::Object::Voice' => [qw/voice/], 'Teapot::Bot::Object::Voice' => [qw/voice/],
'Teapot::Bot::Object::VideoNote' => [qw/video_note/], 'Teapot::Bot::Object::VideoNote' => [qw/video_note/],
'Teapot::Bot::Object::Contact' => [qw/contact/], 'Teapot::Bot::Object::Contact' => [qw/contact/],
'Teapot::Bot::Object::Location' => [qw/location/], 'Teapot::Bot::Object::Location' => [qw/location/],
'Teapot::Bot::Object::Venue' => [qw/venue/], 'Teapot::Bot::Object::Venue' => [qw/venue/],
'Teapot::Bot::Object::Poll' => [qw/poll/], 'Teapot::Bot::Object::Poll' => [qw/poll/],
'Teapot::Bot::Object::Invoice' => [qw/invoice/], 'Teapot::Bot::Object::Invoice' => [qw/invoice/],
'Teapot::Bot::Object::SuccessfulPayment' => [qw/successful_payment/], 'Teapot::Bot::Object::SuccessfulPayment' => [qw/successful_payment/],
'Teapot::Bot::Object::PassportData' => [qw/passport_data/], 'Teapot::Bot::Object::WriteAccessAllowed' => [qw/write_access_allowed/],
'Teapot::Bot::Object::InlineKeyboardMarkup' => [qw/reply_markup/], 'Teapot::Bot::Object::PassportData' => [qw/passport_data/],
'Teapot::Bot::Object::ProximityAlertTriggered' => [qw/proximity_alert_triggered/], 'Teapot::Bot::Object::ForumTopicCreated' => [qw/forum_topic_created/],
'Teapot::Bot::Object::Dice' => [qw/dice/], 'Teapot::Bot::Object::ForumTopicEdited' => [qw/forum_topic_edited/],
'Teapot::Bot::Object::ForumTopicClosed' => [qw/forum_topic_closed/],
'Teapot::Bot::Object::ForumTopicReopened' => [qw/forum_topic_reopened/],
'Teapot::Bot::Object::GeneralForumTopicHidden' => [qw/general_forum_topic_unhidden/],
'Teapot::Bot::Object::GeneralForumTopicUnhidden' => [qw/general_forum_topic_unhidden/],
'Teapot::Bot::Object::InlineKeyboardMarkup' => [qw/reply_markup/],
'Teapot::Bot::Object::ProximityAlertTriggered' => [qw/proximity_alert_triggered/],
'Teapot::Bot::Object::Dice' => [qw/dice/],
'Teapot::Bot::Object::MessageAutoDeleteTimerChanged' => [qw/message_auto_delete_timer_changed/], 'Teapot::Bot::Object::MessageAutoDeleteTimerChanged' => [qw/message_auto_delete_timer_changed/],
'Teapot::Bot::Object::VoiceChatScheduled' => [qw/voice_chat_scheduled/], 'Teapot::Bot::Object::VideoChatScheduled' => [qw/video_chat_scheduled/],
'Teapot::Bot::Object::VoiceChatStarted' => [qw/voice_chat_started/], 'Teapot::Bot::Object::VideoChatStarted' => [qw/video_chat_started/],
'Teapot::Bot::Object::VoiceChatEnded' => [qw/voice_chat_ended/], 'Teapot::Bot::Object::VideoChatEnded' => [qw/video_chat_ended/],
'Teapot::Bot::Object::VoiceChatParticipantsInvited' => [qw/voice_chat_participants_invited/], 'Teapot::Bot::Object::VideoChatParticipantsInvited' => [qw/video_chat_participants_invited/],
'Teapot::Bot::Object::WebAppData' => [qw/web_app_data/],
}; };
} }
@@ -152,48 +179,76 @@ sub arrays {
return qw/photo entities caption_entities new_chat_members new_chat_photo/; return qw/photo entities caption_entities new_chat_members new_chat_photo/;
} }
sub reply { sub reply {
my $self = shift; my $self = shift;
my $text = shift; my $text = shift;
my $chatid = $self->chat->id; my $ret = '';
if (Teapot::Bot::Object::ChatPermissions->canTalk($self, $chatid)) { my $send_args->{chat_id} = $self->chat->id;
return $self->_brain->sendMessage({chat_id => $chatid, text => $text}); my $message_thread_id = eval {$self->message_thread_id};
my $can_talk = $self->_brain->can_talk($send_args);
if ($can_talk->{error}) {
$ret = $can_talk;
} else {
if ($can_talk->{can_talk}) {
$send_args->{text} = $text;
if (defined $message_thread_id && $message_thread_id ne '') {
$send_args->{$message_thread_id} = $message_thread_id;
}
$ret = $self->_brain->sendMessage($send_args);
}
} }
return {'error' => 1}; return $ret;
} }
sub replyMd { sub replyMd {
my $self = shift; my $self = shift;
my $text = shift; my $text = shift;
my $chatid = $self->chat->id; my $ret = '';
if (Teapot::Bot::Object::ChatPermissions->canTalk ($self, $chatid)) { my $send_args->{chat_id} = $self->chat->id;
my $send_args; my $message_thread_id = eval {$self->message_thread_id};
$send_args->{text} = $text;
$send_args->{parse_mode} = 'Markdown';
$send_args->{chat_id} = $chatid;
return $self->_brain->sendMessage($send_args); my $can_talk = $self->_brain->can_talk($send_args);
if ($can_talk->{error}) {
$ret = $can_talk;
} else {
if ($can_talk->{can_talk}) {
$send_args->{text} = $text;
$send_args->{parse_mode} = 'Markdown';
$send_args->{message_thread_id} = $message_thread_id if (defined $message_thread_id);
$ret = $self->_brain->sendMessage($send_args);
}
} }
return {'error' => 1}; return $ret;
} }
sub typing { sub typing {
my $self = shift; my $self = shift;
my $chatid = $self->chat->id; my $ret = '';
my $send_args->{chat_id} = $self->chat->id;
my $can_talk = $self->_brain->can_talk($send_args);
if (Teapot::Bot::Object::ChatPermissions->canTalk ($self, $chatid)) { if ($can_talk->{error}) {
return $self->_brain->sendChatAction({chat_id => $chatid}); $ret = $can_talk;
} else {
if ($can_talk->{can_talk}) {
$ret = $self->_brain->sendChatAction($send_args);
}
} }
return {'error' => 1}; return $ret;
} }
1; 1;
@@ -210,7 +265,7 @@ Teapot::Bot::Object::Message - The base class for the Telegram type "Message"
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for the Telegram type "Message". The base class for the Telegram type "Message".
@@ -226,12 +281,25 @@ A convenience method to reply to a message with text.
Will return the C<Teapot::Bot::Object::Message> object representing the message Will return the C<Teapot::Bot::Object::Message> object representing the message
sent. sent.
On error returns hash reference with error field set to 1, also set fields
param, url and debug (which is result of Mojo::UserAgent->post->result->json)
=head2 replyMd
A convenience method to reply to a message with markdown formatted text.
Will return the C<Teapot::Bot::Object::Message> object representing the message
sent.
On error returns hash reference with error field set to 1, also set fields
param, url and debug (which is result of Mojo::UserAgent->post->result->json)
=head2 typing =head2 typing
Sends notification to chat that bot is "typing" something. Sends notification to chat that bot is "typing" something.
Returns nothing that should be checked. On error returns hash reference with error field set to 1, also set fields
param, url and debug (which is result of Mojo::UserAgent->post->result->json)
=head1 AUTHOR =head1 AUTHOR

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::MessageAutoDeleteTimerChanged::VERSION = '0.022'; $Teapot::Bot::Object::MessageAutoDeleteTimerChanged::VERSION = '0.026';
has 'message_auto_delete_time'; has 'message_auto_delete_time';
@@ -32,7 +32,7 @@ Teapot::Bot::Object::MessageAutoDeleteTimerChanged - The base class for Telegram
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'MessageAutoDeleteTimerChanged' type objects. The base class for Telegram 'MessageAutoDeleteTimerChanged' type objects.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::User (); use Teapot::Bot::Object::User ();
$Teapot::Bot::Object::MessageEntity::VERSION = '0.022'; $Teapot::Bot::Object::MessageEntity::VERSION = '0.026';
has 'type'; has 'type';
has 'offset'; has 'offset';
@@ -39,7 +39,7 @@ Teapot::Bot::Object::MessageEntity - The base class for Telegram 'MessageEntity'
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'MessageEntity' type objects. The base class for Telegram 'MessageEntity' type objects.

View File

@@ -0,0 +1,60 @@
package Teapot::Bot::Object::MessageReaction;
# ABSTRACT: The base class for Telegram message 'Reaction' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::MessageReaction::VERSION = '0.026';
has 'type'; # Type of the reaction, always “emoji”
has 'emoji'; # Reaction emoji. Currently, it can be one of "👍", "👎", "❤", "🔥", "🥰", "👏", "😁", "🤔",
# "🤯", "😱", "🤬", "😢", "🎉", "🤩", "🤮", "💩", "🙏", "👌", "🕊", "🤡", "🥱", "🥴", "😍", "🐳", "❤‍🔥", "🌚", "🌭",
# "💯", "🤣", "⚡", "🍌", "🏆", "💔", "🤨", "😐", "🍓", "🍾", "💋", "🖕", "😈", "😴", "😭", "🤓", "👻", "👨‍💻", "👀",
# "🎃", "🙈", "😇", "😨", "🤝", "✍", "🤗", "🫡", "🎅", "🎄", "☃", "💅", "🤪", "🗿", "🆒", "💘", "🙉", "🦄", "😘",
# "💊", "🙊", "😎", "👾", "🤷‍♂", "🤷", "🤷‍♀", "😡"
has 'custom_emoji_id'; # Only for ReactionTypeCustomEmoji. Custom emoji identifier.
sub fields {
return {
'scalar' => [qw/type emoji custom_emoji_id total_count/],
};
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::MessageReaction - The base class for Telegram message 'Reaction' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'Reaction' type.
See L<https://core.telegram.org/bots/api#reactiontype> for details of the
attributes available for C<Teapot::Bot::Object::MessageReaction> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2025 Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,58 @@
package Teapot::Bot::Object::MessageReactionCount;
# ABSTRACT: The base class for Telegram message 'Reaction' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::MessageReaction ();
$Teapot::Bot::Object::MessageReactionCount::VERSION = '0.026';
has 'type'; # Type of the reaction
has 'total_count'; # Number of times the reaction was added
sub fields {
return {
'Teapot::Bot::Object::MessageReaction' => [qw/type/],
'scalar' => [qw/total_count/],
};
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::MessageReactionCount - The base class for Telegram message 'Reaction' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'Reaction' type.
See L<https://core.telegram.org/bots/api#reactiontype> for details of the
attributes available for C<Teapot::Bot::Object::MessageReactionCount> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2025 Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,61 @@
package Teapot::Bot::Object::MessageReactionCountUpdated;
# ABSTRACT: The base class for Telegram message 'Reaction' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::MessageReactionCount ();
$Teapot::Bot::Object::MessageReactionCountUpdated::VERSION = '0.026';
has 'chat';
has 'message_id';
has 'date';
has 'reactions';
sub fields {
return {
'Teapot::Bot::Object::Chat' => [qw/chat actor_chat/],
'Teapot::Bot::Object::MessageReactionCount' => [qw/reactions/],
'scalar' => [qw/message_id date/],
};
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::MessageReactionCountUpdated - The base class for Telegram message 'Reaction' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'Reaction' type.
See L<https://core.telegram.org/bots/api#reactiontype> for details of the
attributes available for C<Teapot::Bot::Object::MessageReactionCountUpdated> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2025 Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,67 @@
package Teapot::Bot::Object::MessageReactionUpdated;
# ABSTRACT: The base class for Telegram message 'ReactionUpdated' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::Chat ();
use Teapot::Bot::Object::Chat ();
use Teapot::Bot::Object::MessageReaction ();
$Teapot::Bot::Object::MessageReactionUpdated::VERSION = '0.026';
has 'chat'; # The chat containing the message the user reacted to
has 'message_id'; # Unique identifier of the message inside the chat
has 'user'; # Optional. The user that changed the reaction, if the user isn't anonymous
has 'actor_chat'; # Optional. The chat on behalf of which the reaction was changed, if the user is anonymous
has 'date'; # Date of the change in Unix time
has 'old_reaction'; # Previous list of reaction types that were set by the user
has 'new_reaction'; # New list of reaction types that have been set by the user
sub fields {
return {
'Teapot::Bot::Object::Chat' => [qw/chat actor_chat/],
'Teapot::Bot::Object::User' => [qw/user/],
'Teapot::Bot::Object::MessageReaction' => [qw/old_reaction new_reaction/],
'scalar' => [qw/message_id date/],
};
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::MessageReaction - The base class for Telegram message 'ReactionUpdated' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'ReactionUpdated' type.
See L<https://core.telegram.org/bots/api#messagereactionupdated> for details of the
attributes available for C<Teapot::Bot::Object::MessageReactionUpdated> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2025 Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -10,7 +10,7 @@ use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::EncryptedPassportElement (); use Teapot::Bot::Object::EncryptedPassportElement ();
use Teapot::Bot::Object::EncryptedCredentials (); use Teapot::Bot::Object::EncryptedCredentials ();
$Teapot::Bot::Object::PassportData::VERSION = '0.022'; $Teapot::Bot::Object::PassportData::VERSION = '0.026';
has 'data'; # Array of EncryptedPassportElement has 'data'; # Array of EncryptedPassportElement
has 'credentials'; # EncryptedCredentials has 'credentials'; # EncryptedCredentials
@@ -40,7 +40,7 @@ Teapot::Bot::Object::PassportData - The base class for Telegram 'PassportData' t
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'PassportData' type objects. The base class for Telegram 'PassportData' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::PhotoSize::VERSION = '0.022'; $Teapot::Bot::Object::PhotoSize::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
@@ -34,7 +34,7 @@ Teapot::Bot::Object::PhotoSize - The base class for Telegram message 'PhotoSize'
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'PhotoSize' type. The base class for Telegram message 'PhotoSize' type.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PollOption (); use Teapot::Bot::Object::PollOption ();
$Teapot::Bot::Object::Poll::VERSION = '0.022'; $Teapot::Bot::Object::Poll::VERSION = '0.026';
has 'id'; has 'id';
has 'question'; has 'question';
@@ -52,7 +52,7 @@ Teapot::Bot::Object::Poll - The base class for Telegram 'Poll' type objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Poll' type objects The base class for Telegram 'Poll' type objects

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::PollAnswer::VERSION = '0.022'; $Teapot::Bot::Object::PollAnswer::VERSION = '0.026';
has 'poll_id'; has 'poll_id';
has 'user'; has 'user';
@@ -39,7 +39,7 @@ Teapot::Bot::Object::PollAnswer - The base class for Telegram 'PollAnswer' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'PollAnswer' type objects. The base class for Telegram 'PollAnswer' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::PollOption::VERSION = '0.022'; $Teapot::Bot::Object::PollOption::VERSION = '0.026';
has 'text'; has 'text';
has 'voter_count'; has 'voter_count';
@@ -31,7 +31,7 @@ Teapot::Bot::Object::PollOption - The base class for Telegram 'PollOption' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'PollOption' type objects. The base class for Telegram 'PollOption' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::ProximityAlertTriggered::VERSION = '0.022'; $Teapot::Bot::Object::ProximityAlertTriggered::VERSION = '0.026';
has 'traveler'; # User has 'traveler'; # User
has 'watcher'; # User has 'watcher'; # User
@@ -35,7 +35,7 @@ Teapot::Bot::Object::ProximityAlertTriggered - The base class for Telegram messa
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'ProximityAlertTriggered' type. The base class for Telegram message 'ProximityAlertTriggered' type.

View File

@@ -8,22 +8,33 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PhotoSize (); use Teapot::Bot::Object::PhotoSize ();
use Teapot::Bot::Object::MaskPosition ();
$Teapot::Bot::Object::Sticker::VERSION = '0.022'; $Teapot::Bot::Object::Sticker::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id';
has 'type';
has 'width'; has 'width';
has 'height'; has 'height';
has 'thumb'; # PhotoSize has 'is_animated';
has 'is_video';
has 'thumbnail'; # PhotoSize
has 'emoji'; has 'emoji';
has 'set_name'; has 'set_name';
# has 'mask_position'; # XXX TODO has 'premium_animation';
has 'mask_position';
has 'custom_emoji_id';
has 'needs_repainting';
has 'file_size'; has 'file_size';
sub fields { sub fields {
return { return {
scalar => [ qw/file_id width height emoji set_name file_size/ ], scalar => [ qw/file_id file_unique_id type width height is_animated is_video emoji
'Teapot::Bot::Object::PhotoSize' => [ qw/thumb/ ], set_name premium_animation custom_emoji_id needs_repainting
file_size/ ],
'Teapot::Bot::Object::PhotoSize' => [ qw/thumbnail/ ],
'Teapot::Bot::Object::MaskPosition' => [qw/mask_position/],
}; };
} }
@@ -41,7 +52,7 @@ Teapot::Bot::Object::Sticker - The base class for Telegram message 'Sticker' typ
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'Sticker' type. The base class for Telegram message 'Sticker' type.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
# use Teapot::Bot::Object::OrderInfo; # use Teapot::Bot::Object::OrderInfo;
$Teapot::Bot::Object::SuccessfulPayment::VERSION = '0.022'; $Teapot::Bot::Object::SuccessfulPayment::VERSION = '0.026';
has 'currency'; has 'currency';
has 'total_amount'; has 'total_amount';
@@ -41,7 +41,7 @@ Teapot::Bot::Object::SuccessfulPayment - The base class for Telegram 'Successful
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'SuccessfulPayment' type objects. The base class for Telegram 'SuccessfulPayment' type objects.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::User::VERSION = '0.022'; $Teapot::Bot::Object::User::VERSION = '0.026';
has 'id'; has 'id';
has 'is_bot'; has 'is_bot';
@@ -41,7 +41,7 @@ Teapot::Bot::Object::User - The base class for Telegram message 'User' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'User' type. The base class for Telegram message 'User' type.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::Location (); use Teapot::Bot::Object::Location ();
$Teapot::Bot::Object::Venue::VERSION = '0.022'; $Teapot::Bot::Object::Venue::VERSION = '0.026';
has 'location'; #Location has 'location'; #Location
has 'title'; has 'title';
@@ -41,7 +41,7 @@ Teapot::Bot::Object::Venue - The base class for Telegram 'LoginUrl' type objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'LoginUrl' type objects. The base class for Telegram 'LoginUrl' type objects.

View File

@@ -9,14 +9,14 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PhotoSize (); use Teapot::Bot::Object::PhotoSize ();
$Teapot::Bot::Object::Video::VERSION = '0.022'; $Teapot::Bot::Object::Video::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
has 'width'; has 'width';
has 'height'; has 'height';
has 'duration'; has 'duration';
has 'thumb'; # Optional. Video thumbnail has 'thumbnail'; # Optional. Video thumbnail
has 'file_name'; # Optional. Original filename as defined by sender has 'file_name'; # Optional. Original filename as defined by sender
has 'mime_type'; # Optional. Mime type of a file as defined by sender has 'mime_type'; # Optional. Mime type of a file as defined by sender
has 'file_size'; # Optional. File size has 'file_size'; # Optional. File size
@@ -25,7 +25,7 @@ sub fields {
return { return {
scalar => [qw/file_id file_unique_id width height duration file_name mime_type scalar => [qw/file_id file_unique_id width height duration file_name mime_type
file_size/], file_size/],
'Teapot::Bot::Object::PhotoSize' => [qw/thumb/], 'Teapot::Bot::Object::PhotoSize' => [qw/thumbnail/],
}; };
} }
@@ -43,7 +43,7 @@ Teapot::Bot::Object::Video - The base class for Telegram 'Video' object
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Video' object. The base class for Telegram 'Video' object.

View File

@@ -1,5 +1,5 @@
package Teapot::Bot::Object::VoiceChatEnded; package Teapot::Bot::Object::VideoChatEnded;
# ABSTRACT: The base class for Telegram message 'VoiceChatEnded' type. # ABSTRACT: The base class for Telegram message 'VideoChatEnded' type.
use strict; use strict;
use warnings; use warnings;
@@ -8,9 +8,9 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::VoiceChatEnded::VERSION = '0.022'; $Teapot::Bot::Object::VideoChatEnded::VERSION = '0.026';
# This object represents a service message about a voice chat started in the chat. Currently holds no information. # This object represents a service message about a Video chat started in the chat. Currently holds no information.
has 'duration'; has 'duration';
sub fields { sub fields {
@@ -27,17 +27,17 @@ __END__
=head1 NAME =head1 NAME
Teapot::Bot::Object::VoiceChatEnded - The base class for Telegram message 'VoiceChatEnded' type Teapot::Bot::Object::VideoChatEnded - The base class for Telegram message 'VideoChatEnded' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'VoiceChatEnded' type. The base class for Telegram message 'VideoChatEnded' type.
See L<https://core.telegram.org/bots/api#voicechatended> for details of the See L<https://core.telegram.org/bots/api#Videochatended> for details of the
attributes available for C<Teapot::Bot::Object::VoiceChatEnded> objects. attributes available for C<Teapot::Bot::Object::VideoChatEnded> objects.
=head1 AUTHOR =head1 AUTHOR
@@ -45,7 +45,7 @@ Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT =head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2021 by Sergei Fedosov <eleksir@gmail.com>. This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself. the same terms as the Perl 5 programming language system itself.

View File

@@ -1,5 +1,5 @@
package Teapot::Bot::Object::VoiceChatParticipantsInvited; package Teapot::Bot::Object::VideoChatParticipantsInvited;
# ABSTRACT: The base class for Telegram message 'VoiceChatParticipantsInvited' type. # ABSTRACT: The base class for Telegram message 'VideoChatParticipantsInvited' type.
use strict; use strict;
use warnings; use warnings;
@@ -9,9 +9,9 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::User (); use Teapot::Bot::Object::User ();
$Teapot::Bot::Object::VoiceChatParticipantsInvited::VERSION = '0.022'; $Teapot::Bot::Object::VideoChatParticipantsInvited::VERSION = '0.026';
# This object represents a service message about a voice chat started in the chat. Currently holds no information. # This object represents a service message about a Video chat started in the chat. Currently holds no information.
has 'users'; has 'users';
sub fields { sub fields {
@@ -32,17 +32,17 @@ __END__
=head1 NAME =head1 NAME
Teapot::Bot::Object::VoiceChatParticipantsInvited - The base class for Telegram message 'VoiceChatParticipantsInvited' type Teapot::Bot::Object::VideoChatParticipantsInvited - The base class for Telegram message 'VideoChatParticipantsInvited' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'VoiceChatParticipantsInvited' type. The base class for Telegram message 'VideoChatParticipantsInvited' type.
See L<https://core.telegram.org/bots/api#voicechatparticipantsinvited> for details of the See L<https://core.telegram.org/bots/api#Videochatparticipantsinvited> for details of the
attributes available for C<Teapot::Bot::Object::VoiceChatParticipantsInvited> objects. attributes available for C<Teapot::Bot::Object::VideoChatParticipantsInvited> objects.
=head1 AUTHOR =head1 AUTHOR
@@ -50,7 +50,7 @@ Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT =head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2021 by Sergei Fedosov <eleksir@gmail.com>. This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself. the same terms as the Perl 5 programming language system itself.

View File

@@ -1,4 +1,4 @@
package Teapot::Bot::Object::VoiceChatScheduled; package Teapot::Bot::Object::VideoChatScheduled;
# ABSTRACT: The base class for Telegram message 'VoiceChatEnded' type. # ABSTRACT: The base class for Telegram message 'VoiceChatEnded' type.
use strict; use strict;
@@ -8,9 +8,9 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::VoiceChatScheduled::VERSION = '0.022'; $Teapot::Bot::Object::VideoChatScheduled::VERSION = '0.026';
# This object represents a service message about a voice chat started in the chat. Currently holds no information. # This object represents a service message about a video chat started in the chat. Currently holds no information.
has 'start_date'; has 'start_date';
sub fields { sub fields {
@@ -27,17 +27,17 @@ __END__
=head1 NAME =head1 NAME
Teapot::Bot::Object::VoiceChatScheduled - The base class for Telegram message 'VoiceChatScheduled' type Teapot::Bot::Object::VideoChatScheduled - The base class for Telegram message 'VideoChatScheduled' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'VoiceChatScheduled' type. The base class for Telegram message 'VideoChatScheduled' type.
See L<https://core.telegram.org/bots/api#voicechatscheduled> for details of the See L<https://core.telegram.org/bots/api#Videochatscheduled> for details of the
attributes available for C<Teapot::Bot::Object::VoiceChatScheduled> objects. attributes available for C<Teapot::Bot::Object::VideoChatScheduled> objects.
=head1 AUTHOR =head1 AUTHOR
@@ -45,7 +45,7 @@ Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT =head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2021 by Sergei Fedosov <eleksir@gmail.com>. This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself. the same terms as the Perl 5 programming language system itself.

View File

@@ -1,5 +1,5 @@
package Teapot::Bot::Object::VoiceChatStarted; package Teapot::Bot::Object::VideoChatStarted;
# ABSTRACT: The base class for Telegram message 'VoiceChatStarted' type. # ABSTRACT: The base class for Telegram message 'VideoChatStarted' type.
use strict; use strict;
use warnings; use warnings;
@@ -8,9 +8,9 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::VoiceChatStarted::VERSION = '0.022'; $Teapot::Bot::Object::VideoChatStarted::VERSION = '0.026';
# This object represents a service message about a voice chat started in the chat. Currently holds no information. # This object represents a service message about a Video chat started in the chat. Currently holds no information.
has 'stub'; has 'stub';
sub fields { sub fields {
@@ -27,17 +27,17 @@ __END__
=head1 NAME =head1 NAME
Teapot::Bot::Object::VoiceChatStarted - The base class for Telegram message 'VoiceChatStarted' type Teapot::Bot::Object::VideoChatStarted - The base class for Telegram message 'VideoChatStarted' type
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram message 'VoiceChatStarted' type. The base class for Telegram message 'VideoChatStarted' type.
See L<https://core.telegram.org/bots/api#voicechatstarted> for details of the See L<https://core.telegram.org/bots/api#Videochatstarted> for details of the
attributes available for C<Teapot::Bot::Object::VoiceChatStarted> objects. attributes available for C<Teapot::Bot::Object::VideoChatStarted> objects.
=head1 AUTHOR =head1 AUTHOR
@@ -45,7 +45,7 @@ Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT =head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2021 by Sergei Fedosov <eleksir@gmail.com>. This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself. the same terms as the Perl 5 programming language system itself.

View File

@@ -9,7 +9,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
use Teapot::Bot::Object::PhotoSize (); use Teapot::Bot::Object::PhotoSize ();
$Teapot::Bot::Object::VideoNote::VERSION = '0.022'; $Teapot::Bot::Object::VideoNote::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
@@ -40,7 +40,7 @@ Teapot::Bot::Object::VideoNote - The base class for Telegram 'VideoNote' type ob
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'VideoNote' type object. The base class for Telegram 'VideoNote' type object.

View File

@@ -8,7 +8,7 @@ use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base'; use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::Voice::VERSION = '0.022'; $Teapot::Bot::Object::Voice::VERSION = '0.026';
has 'file_id'; has 'file_id';
has 'file_unique_id'; has 'file_unique_id';
@@ -34,7 +34,7 @@ Teapot::Bot::Object::Voice - The base class for Telegram 'Voice' type objects
=head1 VERSION =head1 VERSION
version 0.022 version 0.026
=head1 DESCRIPTION =head1 DESCRIPTION
The base class for Telegram 'Voice' type objects. The base class for Telegram 'Voice' type objects.

View File

@@ -0,0 +1,53 @@
package Teapot::Bot::Object::WebAppData;
# ABSTRACT: The base class for Telegram 'WebAppData' type objects
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::WebAppData::VERSION = '0.026';
has 'data';
has 'button_text';
sub fields {
return { scalar => [qw/data button_text/] };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::WebAppData - The base class for Telegram 'WebAppData' type objects
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram 'WebAppData' type objects.
See L<https://core.telegram.org/bots/api#webappdata> for details of the
attributes available for C<Teapot::Bot::Object::WebAppData> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2022 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,51 @@
package Teapot::Bot::Object::WriteAccessAllowed;
# ABSTRACT: The base class for Telegram message 'WriteAccessAllowed' type.
use strict;
use warnings;
use 5.018; ## no critic (ProhibitImplicitImport)
use utf8;
use Mojo::Base 'Teapot::Bot::Object::Base';
$Teapot::Bot::Object::WriteAccessAllowed::VERSION = '0.026';
sub fields {
return { };
}
1;
__END__
=pod
=encoding utf8
=head1 NAME
Teapot::Bot::Object::WriteAccessAllowed - The base class for Telegram message 'WriteAccessAllowed' type
=head1 VERSION
version 0.026
=head1 DESCRIPTION
The base class for Telegram message 'WriteAccessAllowed' type.
See L<https://core.telegram.org/bots/api#writeaccessallowed> for details of the
attributes available for C<Teapot::Bot::Object::WriteAccessAllowed> objects.
=head1 AUTHOR
Sergei Fedosov <eleksir@gmail.com>
=head1 LICENSE AND COPYRIGHT
This software is copyright (c) 2023 by Sergei Fedosov <eleksir@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -5,18 +5,21 @@ use 5.018; ## 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 Clone qw (clone);
use Log::Any qw ($log);
use Math::Random::Secure qw (irand);
use Mojo::Base 'Teapot::Bot::Brain';
use BotLib::Admin qw (FortuneToggleList ChanMsgEnabled); use Clone qw (clone);
use BotLib qw (Command Highlight BotSleep IsCensored); use Data::Dumper qw (Dumper);
use BotLib::Conf qw (LoadConf); use Log::Any qw ($log);
use BotLib::Util qw (trim fmatch); use Math::Random::Secure qw (irand);
use RedisLib qw (redis_events_listener); use Mojo::Base 'Teapot::Bot::Brain';
use BotLib::Admin qw (FortuneToggleList ChanMsgEnabled GreetMsgEnabled GoodbyeMsgEnabled
MigrateSettingsToNewChatID IsCensored GetSlackGreetRememberId SetSlackGreetRememberId);
use BotLib qw (Command);
use BotLib::Conf qw (LoadConf);
use BotLib::Util qw (trim fmatch Highlight RandomCommonPhrase);
use RedisLib qw (redis_events_listener);
use version; our $VERSION = qw (1.0); use version; our $VERSION = qw (1.0);
use Exporter qw (import); use Exporter qw (import);
@@ -31,22 +34,32 @@ my $myfullname;
has token => $c->{telegrambot}->{token}; has token => $c->{telegrambot}->{token};
my $redismsg->{from} = 'telegram'; my @introduce_greet = (
$redismsg->{plugin} = 'telegram'; 'Дратути',
$redismsg->{misc}->{answer} = 1; 'Дарована',
$redismsg->{misc}->{csign} = $c->{telegrambot}->{csign}; 'Доброе утро, день или вечер',
$redismsg->{misc}->{msg_format} = 0; 'Добро пожаловать в наше скромное коммунити',
$redismsg->{misc}->{fwd_cnt} = 1; 'Наше вам с кисточкой тут, на канальчике',
);
my $redismsg->{from} = 'telegram';
$redismsg->{threadid} = '';
$redismsg->{plugin} = 'telegram';
$redismsg->{misc}->{answer} = 1;
$redismsg->{misc}->{csign} = "$c->{telegrambot}->{csign}";
$redismsg->{misc}->{msg_format} = 0;
$redismsg->{misc}->{fwd_cnt} = 1;
$redismsg->{misc}->{good_morning} = 0;
sub __cron { sub __cron {
my $self = shift; my $self = shift;
my $rmsg = clone ($redismsg); my $rmsg = clone ($redismsg);
$rmsg->{mode} = 'public'; $rmsg->{mode} = 'public';
# Press 'F' to pay respect # Press 'F' to pay respect
$rmsg->{message} = sprintf '%sf', $c->{telegrambot}->{csign}; $rmsg->{message} = sprintf '%sf', $c->{telegrambot}->{csign};
$rmsg->{misc}->{msg_format} = 1;
$rmsg->{misc}->{good_morning} = 1; $rmsg->{misc}->{good_morning} = 1;
$rmsg->{misc}->{msg_format} = 1;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime (time); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime (time);
@@ -61,12 +74,22 @@ sub __cron {
if ($hour == 8 && ($min >= 0 && $min <= 14)) { if ($hour == 8 && ($min >= 0 && $min <= 14)) {
foreach my $enabledfortunechat (FortuneToggleList ()) { foreach my $enabledfortunechat (FortuneToggleList ()) {
$rmsg->{userid} = $enabledfortunechat; # Set it to strings, explicitly
$rmsg->{chatid} = $enabledfortunechat; next unless (defined $enabledfortunechat);
next if ("$enabledfortunechat" eq '');
next if ("$enabledfortunechat" eq '0');
$rmsg->{userid} = "$enabledfortunechat";
$rmsg->{chatid} = "$enabledfortunechat";
$log->debug ('[DEBUG] Outgoing redis message: ' . Dumper ($rmsg));
$pubsub->json ($c->{'redis_router_channel'})->notify ( $pubsub->json ($c->{'redis_router_channel'})->notify (
$c->{'redis_router_channel'} => $rmsg, $c->{'redis_router_channel'} => $rmsg,
); );
# Ratelimit this, just in case
sleep (1);
} }
} }
@@ -77,6 +100,15 @@ sub __on_msg {
my ($self, $msg) = @_; my ($self, $msg) = @_;
# chat info # chat info
my $chatid; my $chatid;
# Ловим сообщения о миграции.
if ($msg->can ('migrate_to_chat_id') && $msg->can ('migrate_from_chat_id')) {
if (defined ($msg->migrate_from_chat_id) && defined ($msg->migrate_from_chat_id)) {
MigrateSettingsToNewChatID ($msg->migrate_from_chat_id, $msg->migrate_to_chat_id);
return;
}
}
my $chatname = 'Noname chat'; my $chatname = 'Noname chat';
# user sending message info # user sending message info
my ($userid, $username, $fullname, $highlight, $vis_a_vi) = Highlight ($msg); my ($userid, $username, $fullname, $highlight, $vis_a_vi) = Highlight ($msg);
@@ -85,15 +117,15 @@ sub __on_msg {
unless ($myid) { unless ($myid) {
my $myObj = $self->getMe (); my $myObj = $self->getMe ();
unless ($myObj) { if ($myObj->{error}) {
$log->error ('[ERROR] Unable to get chatid, API Error?'); $log->error ("[ERROR] Unable to call getMe BotAPI method: " . Dumper ($myObj));
return; return;
} }
$myid = $myObj->id; $myid = $myObj->id;
$myusername = $myObj->username; $myusername = $myObj->username;
$myfirst_name = $myObj->first_name; $myfirst_name = $myObj->first_name;
$mylast_name = $myObj->last_name; $mylast_name = $myObj->last_name;
if (defined ($myfirst_name) && ($myfirst_name ne '') && defined ($mylast_name) && ($mylast_name ne '')) { if (defined ($myfirst_name) && ($myfirst_name ne '') && defined ($mylast_name) && ($mylast_name ne '')) {
$myfullname = $myfirst_name . ' ' . $mylast_name; $myfullname = $myfirst_name . ' ' . $mylast_name;
@@ -127,19 +159,17 @@ sub __on_msg {
# Newcommer event, greet our new member and suggest to introduce themself. # Newcommer event, greet our new member and suggest to introduce themself.
if ($msg->can ('new_chat_members') && defined ($msg->new_chat_members)) { if ($msg->can ('new_chat_members') && defined ($msg->new_chat_members)) {
unless (GreetMsgEnabled ($chatid)) {
return;
}
# Новые юзеры пачками появляются, только если их кто-то добавил через клиента
my @members; my @members;
my @introduce = (
'Дратути',
'Дарована',
'Доброе утро, день или вечер',
'Добро пожаловать в наше скромное коммунити',
'Наше вам с кисточкой тут, на канальчике',
);
foreach my $member (@{$msg->new_chat_members}) { foreach my $member (@{$msg->new_chat_members}) {
my $member_str = ''; my $member_str = '';
# avoid naming people by spacing symbols, or empty names # Избегаем именования по пробельному символу
if ($member->can ('first_name') && defined ($member->first_name) && $member->first_name !~ /^\s+$/ui) { if ($member->can ('first_name') && defined ($member->first_name) && $member->first_name !~ /^\s+$/ui) {
$member_str .= $member->first_name; $member_str .= $member->first_name;
@@ -149,31 +179,144 @@ sub __on_msg {
} else { } else {
if ($member->can ('last_name') && defined ($member->last_name) && $member->last_name !~ /^\s+$/ui) { if ($member->can ('last_name') && defined ($member->last_name) && $member->last_name !~ /^\s+$/ui) {
$member_str .= ' ' . $member->last_name; $member_str .= ' ' . $member->last_name;
# username must be uniq and should contain only english letters and numbers, so... # Username - это у нас валидная строка из только английских символов
} elsif ($member->can ('username') && defined ($member->username)) { } elsif ($member->can ('username') && defined ($member->username)) {
$member_str .= '@' . $member->username; $member_str .= '@' . $member->username;
# fallback to id if uose trying to be smartass and use only spacing chars in it's firstname and/or lastname # Если у юзера нету подходящих имени, фамилии или username, будем пользовать его id
} else { } else {
$member_str .= $member->id; $member_str .= $member->id;
} }
} }
# Remember user id, to check it later, in next event.
SetSlackGreetRememberId ($member->id);
$log->info (sprintf ("[INFO] New chat member %s (%s)", $member_str, $member->id));
push @members, sprintf '[%s](tg://user?id=%s)', $member_str, $member->id; push @members, sprintf '[%s](tg://user?id=%s)', $member_str, $member->id;
} }
if ($#members > 1) { if ($#members > 1) {
my $lastone = pop @members; my $lastone = pop @members;
$phrase = sprintf '%s, %s и %s. Представьтес, пожалуйста, и расскажите, что вас сюда привело.', $introduce[irand ($#introduce + 1)], join (', ', @members), $lastone; $phrase = sprintf (
'%s, %s и %s. Представьтес, пожалуйста, и расскажите, что вас сюда привело.',
$introduce_greet[irand ($#introduce_greet + 1)],
join (', ', @members),
$lastone,
);
} else { } else {
$phrase = sprintf '%s, %s. Представьтес, пожалуйста, и расскажите, что вас сюда привело.', $introduce[irand ($#introduce + 1)], $members[0]; $phrase = sprintf (
'%s, %s. Представьтес, пожалуйста, и расскажите, что вас сюда привело.',
$introduce_greet[irand ($#introduce_greet + 1)],
$members[0],
);
}
sleep 2;
my $m->{text} = $phrase;
$m->{chat_id} = $chatid;
$m->{disable_notification} = 1;
$m->{parse_mode} = 'Markdown';
my $res = $main::TGM->sendMessage ($m);
if ($res->{error}) {
$log->error ("[ERROR] Unable to call sendMessage BotAPI method: " . Dumper ($res));
} }
BotSleep $msg;
$msg->replyMd ($phrase);
return; return;
} }
# is this a 1-on-1 ? # Bots have non-0% tendency to appear with this kind of updates.
if ($msg->can ('new_chat_member') && defined ($msg->new_chat_member)) {
return;
# slackware_ru
unless ($chatid == -1001332512695) {
return;
}
sleep 2;
# Can be racy, but hope for best.
unless (GetSlackGreetRememberId($msg->new_chat_member->user->id)) {
$log->info ("[INFO] $vis_a_vi entered to chat without prior notice event");
# if earlier we did not get new chat member event, it looks like we have spammer here. Ban it.
my $send_args = undef;
$send_args->{chat_id} = 0 + $chatid;
$send_args->{user_id} = 0 + $msg->new_chat_member->user->id;
# According to docs if user banned for <30 seonds || >366 days it banned forever.
# Some bots have annoying behavior to constantly try to join chat.
$send_args->{until_date} = 10 + time ();
$send_args->{until_date} = 10 + time ();
my $result = $self->banChatMember ($send_args);
if ((ref ($result) eq 'JSON::PP::Boolean') && ($result == JSON::PP::true)) {
$log->info ("[INFO] $vis_a_vi banned");
return;
} else {
$log->error ("[ERROR] Unable to ban user: " . Dumper ($result));
}
}
return;
}
# Leaving event, say goodbye to member just left chat
if ($msg->can ('left_chat_member') && defined ($msg->left_chat_member)) {
unless (GoodbyeMsgEnabled ($chatid)) {
return;
}
my @goodbye = (
'Чат покинул(а) %s',
'Из чата вышел(ла) %s',
'До свидания, %s, приходите ещё',
'Покеда, %s',
'Удачи тебе, %s',
'Бывай, %s',
);
my $member = $msg->left_chat_member;
my $member_str = '';
# avoid naming people by spacing symbols, or empty names
if ($member->can ('first_name') && defined ($member->first_name) && $member->first_name !~ /^\s+$/ui) {
$member_str .= $member->first_name;
if ($member->can ('last_name') && defined ($member->last_name) && $member->last_name !~ /^\s+$/ui) {
$member_str .= ' ' . $member->last_name;
}
} else {
if ($member->can ('last_name') && defined ($member->last_name) && $member->last_name !~ /^\s+$/ui) {
$member_str .= ' ' . $member->last_name;
# username must be uniq and should contain only english letters and numbers, so...
} elsif ($member->can ('username') && defined ($member->username)) {
$member_str .= '@' . $member->username;
# fallback to id if uose trying to be smartass and use only spacing chars in it's firstname and/or lastname
} else {
$member_str .= $member->id;
}
}
my $leftuser = sprintf '[%s](tg://user?id=%s)', $member_str, $member->id;
$phrase = sprintf $goodbye[irand ($#goodbye + 1)], $leftuser;
my $res = $msg->replyMd ($phrase);
if ($res->{error}) {
$log->error ("[ERROR] Unable to call sendMessage BotAPI method: " . Dumper ($res));
}
return;
}
if ($msg->can ('old_reaction') || $msg->can ('new_reaction')) {
$log->debug (sprintf ('[DEBUG] user %s in %s posts reaction to message', $vis_a_vi, $chatname));
return;
}
# is this a 1-on-1 ?
if ($msg->chat->type eq 'private') { if ($msg->chat->type eq 'private') {
unless (defined $msg->text) { unless (defined $msg->text) {
return; return;
@@ -184,16 +327,29 @@ sub __on_msg {
# Ответ по-умолчанию # Ответ по-умолчанию
my $reply = 'Давайте ещё пообщаемся, а то я ещё не научилась от вас плохому.'; my $reply = 'Давайте ещё пообщаемся, а то я ещё не научилась от вас плохому.';
if (substr ($text, 0, 1) eq $csign) { if ((length $text > length $csign) && substr ($text, 0, length ($csign)) eq $csign) {
# Если текст похож на команду, усылаем его в BotLib.pm # Если текст похож на команду, усылаем его в BotLib.pm
$reply = Command ($self, $msg, $text, $userid); $reply = Command ($self, $msg, $text, $userid);
} else { } else {
my $rmsg = clone ($redismsg); my $rmsg = clone ($redismsg);
$rmsg->{mode} = 'private'; $rmsg->{mode} = 'private';
$rmsg->{message} = $text; $rmsg->{message} = "$text";
$rmsg->{userid} = $userid; # Set it to strings, explicitly
$rmsg->{chatid} = $userid; $rmsg->{userid} = "$userid";
$rmsg->{chatid} = "$userid";
$rmsg->{misc}->{answer} = 1;
{
do {
my $ready = eval { $main::RCONN->is_connected; };
last if ($ready);
} while (sleep 1);
}
$log->debug ('[DEBUG] Outgoing redis message: ' . Dumper ($rmsg));
my $pubsub = $main::REDIS->pubsub; my $pubsub = $main::REDIS->pubsub;
$pubsub->json ($c->{'redis_router_channel'})->notify ( $pubsub->json ($c->{'redis_router_channel'})->notify (
$c->{'redis_router_channel'} => $rmsg, $c->{'redis_router_channel'} => $rmsg,
); );
@@ -208,7 +364,7 @@ sub __on_msg {
$log->debug (sprintf ('[DEBUG] Private chat bot reply to %s: %s', $vis_a_vi, $reply)); $log->debug (sprintf ('[DEBUG] Private chat bot reply to %s: %s', $vis_a_vi, $reply));
$msg->reply ($reply); $msg->reply ($reply);
} }
# group chat # group chat
} elsif (($msg->chat->type eq 'supergroup') or ($msg->chat->type eq 'group')) { } elsif (($msg->chat->type eq 'supergroup') or ($msg->chat->type eq 'group')) {
my $reply; my $reply;
@@ -216,19 +372,27 @@ sub __on_msg {
# И вот тут как раз такая проверка и происходит # И вот тут как раз такая проверка и происходит
if (IsCensored $msg) { if (IsCensored $msg) {
$log->info (sprintf '[INFO] In public chat %s (%s) message from %s was censored', $chatname, $chatid, $vis_a_vi); $log->info (sprintf '[INFO] In public chat %s (%s) message from %s was censored', $chatname, $chatid, $vis_a_vi);
$self->deleteMessage ({chat_id => $chatid, message_id => $msg->{message_id}}); my $res = $self->deleteMessage ({chat_id => $chatid, message_id => $msg->{message_id}});
if ($res->{error}) {
$log->error ("[ERROR] Unable to call sendMessage BotAPI method: " . Dumper ($res));
}
} }
# Некоторые рекламные товарищи пытаются срать своими каналами в чятик это тоже можно зацензурить ботом и это # Некоторые рекламные товарищи пытаются срать своими каналами в чятик это тоже можно зацензурить ботом и это
# пидорство он будет удалять asap # пидорство он будет удалять asap
# 136817688 - это специальный id пользователя, который принимает облик канала, на него можно нажать и попасть # 136817688 - это специальный id пользователя, который принимает облик канала, на него можно нажать и попасть
# на рекламируемый канал # на рекламируемый канал
if ($msg->from->id == 136817688) { if ($msg->from->id == 136817688) {
my $sender_chat = eval { $msg->sender_chat->id }; my $sender_chat = eval { $msg->sender_chat->id };
if (defined $sender_chat) { if (defined $sender_chat) {
unless (ChanMsgEnabled ($chatid)) { unless (ChanMsgEnabled ($chatid)) {
$self->deleteMessage ({chat_id => $chatid, message_id => $msg->{message_id}}); my $res = $self->deleteMessage ({chat_id => $chatid, message_id => $msg->{message_id}});
if ($res->{error}) {
$log->error ("[ERROR] Unable to call sendMessage BotAPI method: " . Dumper ($res));
}
} }
} }
} }
@@ -253,18 +417,11 @@ sub __on_msg {
# Если новый участник чата ответил на наше приветствие, проигнорируем его ответ (и попробуем сойти за # Если новый участник чата ответил на наше приветствие, проигнорируем его ответ (и попробуем сойти за
# человека :) # человека :)
if (substr ($msg->reply_to_message->text, -61) eq 'Представьтес, пожалуйста, и расскажите, что вас сюда привело.') { my $greetphrase = 'Представьтес, пожалуйста, и расскажите, что вас сюда привело.';
my $match = 0;
my @hello = (
'Дратути, ',
'Дарована, ',
'Доброе утро, день или вечер, ',
'Добро пожаловать в наше скромное коммунити, ',
'Наше вам с кисточкой тут, на канальчике, ',
);
foreach my $hi (@hello) { if ($msg->reply_to_message->text =~ /\Q$greetphrase\E$/u) {
if (substr ($msg->reply_to_message->text, 0, length $hi) eq $hi) { foreach my $hi (@introduce_greet) {
if ($msg->reply_to_message->text =~ /^\Q$hi\E\, /u) {
return; return;
} }
} }
@@ -278,11 +435,31 @@ sub __on_msg {
$phrase =~ s/$pat2//g; $phrase =~ s/$pat2//g;
# Попробуем сгенерить ответ # Попробуем сгенерить ответ
my $rmsg = clone ($redismsg); my $rmsg = clone ($redismsg);
$rmsg->{mode} = 'public'; $rmsg->{mode} = 'public';
$rmsg->{message} = $phrase; $rmsg->{message} = "$phrase";
$rmsg->{userid} = $userid; # Set it to strings, explicitly
$rmsg->{chatid} = $chatid; $rmsg->{userid} = "$userid";
$rmsg->{chatid} = "$chatid";
$rmsg->{misc}->{answer} = 1;
# Если тип группы supergroup, в ней могут быть треды, попробуем найти message_thread_id, если таковой есть
if ($msg->chat->type eq 'supergroup') {
if ($msg->can ('is_topic_message') && $msg->is_topic_message) {
if ($msg->can ('message_thread_id') && $msg->message_thread_id ne '') {
$rmsg->{threadid} = "$msg->message_thread_id";
}
}
}
{
do {
my $ready = eval { $main::RCONN->is_connected; };
last if ($ready);
} while (sleep 1);
}
$log->debug ('[DEBUG] Outgoing redis message: ' . Dumper ($rmsg));
my $pubsub = $main::REDIS->pubsub; my $pubsub = $main::REDIS->pubsub;
@@ -292,7 +469,7 @@ sub __on_msg {
return; return;
# Если текст похож на команду, усылаем его в BotLib.pm # Если текст похож на команду, усылаем его в BotLib.pm
} elsif (substr ($text, 0, 1) eq $csign) { } elsif ((length $text > length $csign) && substr ($text, 0, length ($csign)) eq $csign) {
$reply = Command ($self, $msg, $text, $chatid); $reply = Command ($self, $msg, $text, $chatid);
# Похоже кто-то написал наше имя в чятике, но ничего не захотел дописывать к нему # Похоже кто-то написал наше имя в чятике, но ничего не захотел дописывать к нему
} elsif ( } elsif (
@@ -308,30 +485,49 @@ sub __on_msg {
my $qname = quotemeta ('@' . $myusername); my $qname = quotemeta ('@' . $myusername);
my $qtname = quotemeta $myfullname; my $qtname = quotemeta $myfullname;
my $rmsg = clone ($redismsg); my $rmsg = clone ($redismsg);
$rmsg->{mode} = 'public'; $rmsg->{mode} = 'public';
$rmsg->{userid} = $userid; # Set it to strings, explicitly
$rmsg->{chatid} = $chatid; $rmsg->{userid} = "$userid";
$rmsg->{chatid} = "$chatid";
$rmsg->{misc}->{answer} = 1;
# Если тип группы supergroup, в ней могут быть треды, попробуем найти message_thread_id, если таковой есть
if ($msg->chat->type eq 'supergroup') {
if ($msg->can ('is_topic_message') && $msg->is_topic_message) {
if ($msg->can ('message_thread_id') && $msg->message_thread_id ne '') {
$rmsg->{threadid} = "$msg->message_thread_id";
}
}
}
# Сообщение обращено к боту # Сообщение обращено к боту
if ((lc ($text) =~ /^${qname}[\,|\:]? (.+)/) or (lc ($text) =~ /^${qtname}[\,|\:]? (.+)/)){ if ((lc ($text) =~ /^${qname}[\,|\:]? (.+)/) or (lc ($text) =~ /^${qtname}[\,|\:]? (.+)/)){
$phrase = $1; $phrase = $1;
$rmsg->{message} = $phrase; $rmsg->{message} = "$phrase";
# Бота упомянули по имени # Бота упомянули по имени
} elsif ((lc ($text) =~ /.+ ${qname}[\,|\!|\?|\.| ]/) or (lc ($text) =~ / $qname$/)) { } elsif ((lc ($text) =~ /.+ ${qname}[\,|\!|\?|\.| ]/) or (lc ($text) =~ / $qname$/)) {
$phrase = $text; $phrase = $text;
$rmsg->{message} = $phrase; $rmsg->{message} = "$phrase";
# Бота упомянули по телеграммному имени # Бота упомянули по телеграммному имени
} elsif ((lc ($text) =~ /.+ ${qtname}[\,|\!|\?|\.| ]/) or (lc ($text) =~ / $qtname$/)) { } elsif ((lc ($text) =~ /.+ ${qtname}[\,|\!|\?|\.| ]/) or (lc ($text) =~ / $qtname$/)) {
$phrase = $text; $phrase = $text;
$rmsg->{message} = $phrase; $rmsg->{message} = "$phrase";
} else { } else {
$rmsg->{message} = $text; $rmsg->{message} = "$text";
$rmsg->{misc}->{answer} = 0; $rmsg->{misc}->{answer} = 0;
} }
{
do {
my $ready = eval { $main::RCONN->is_connected; };
last if ($ready);
} while (sleep 1);
}
$log->debug ('[DEBUG] Outgoing redis message: ' . Dumper ($rmsg));
my $pubsub = $main::REDIS->pubsub; my $pubsub = $main::REDIS->pubsub;
$pubsub->json ($c->{'redis_router_channel'})->notify ( $pubsub->json ($c->{'redis_router_channel'})->notify (
@@ -357,13 +553,28 @@ sub __on_msg {
$msg->typing (); $msg->typing ();
sleep (irand 2); sleep (irand 2);
$log->debug (sprintf ('[DEBUG] In public chat %s (%s) bot reply to %s: %s', $chatname, $chatid, $vis_a_vi, $reply)); $log->debug (
$msg->reply ($reply); sprintf (
'[DEBUG] In public chat %s (%s) bot reply to %s: %s',
$chatname, $chatid, $vis_a_vi, $reply,
)
);
my $res = $msg->reply ($reply);
if ($res->{error}) {
$log->error ("[ERROR] Unable to call sendMessage BotAPI method: " . Dumper ($res));
}
} else { } else {
$log->debug (sprintf ('[DEBUG] In public chat %s (%s) bot is not required to reply to %s', $chatname, $chatid, $vis_a_vi)); $log->debug (
sprintf (
'[DEBUG] In public chat %s (%s) bot is not required to reply to %s',
$chatname, $chatid, $vis_a_vi,
)
);
} }
# should be channel, so we can't talk # should be channel, so we can't talk
} else { } else {
return; return;
} }
@@ -371,23 +582,6 @@ sub __on_msg {
return; return;
} }
sub RandomCommonPhrase {
my @myphrase = (
'Так, блядь...',
'*Закатывает рукава* И ради этого ты меня позвал?',
'Ну чего ты начинаешь, нормально же общались',
'Повтори свой вопрос, не поняла',
'Выйди и зайди нормально',
'Я подумаю',
'Даже не знаю, что на это ответить',
'Ты упал такие вопросы девочке задавать?',
'Можно и так, но не уверена',
'А как ты думаешь?',
'А ви, таки, почему интересуетесь?',
);
return $myphrase[irand ($#myphrase + 1)];
}
# setup our bot # setup our bot
sub init { sub init {
@@ -407,6 +601,8 @@ sub RunTelegramBot {
eval { ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval) eval { ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
TelegramBot->new->think; TelegramBot->new->think;
}; };
$log->error ("[ERROR] $EVAL_ERROR");
} }
return; return;

View File

@@ -0,0 +1,101 @@
#!/usr/bin/perl
# По соглашению, рабочим каталогом является каталог с приложением, то есть запускать этот скрипт надо как
# util/get_chat_users_by_id.pl
# Для запуска этого скрипта нам понадобится список id пользователей и id чата, для которого мы дампим информацию о
# пользователях. Входные данные мы складываем в файл get_chat_users_by_id.json.
# На выходе в каталоге c->{cachedir} мы получим каталог get_chat_users_by_id/chat_id с кучей json-чиков, по одному на
# каждого пользователя.
use 5.018; ## no critic (ProhibitImplicitImport)
use strict;
use warnings;
use utf8;
use open qw (:std :utf8);
use English qw ( -no_match_vars );
use version; our $VERSION = qw (1.0);
my $workdir;
# before we run, change working dir
BEGIN {
use Cwd qw (chdir abs_path);
my @CWD = split /\//xms, abs_path ($PROGRAM_NAME);
if ($#CWD > 1) {
$#CWD = $#CWD - 2;
}
$workdir = join '/', @CWD;
chdir $workdir;
}
use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5");
no Cwd;
use Cwd::utf8 qw (chdir abs_path);
use Data::Dumper qw (Dumper);
use File::Path qw(make_path);
use JSON::XS qw (decode_json);
# Грех не воспользоваться, если у нас оно есть под рукой, хотя по мне тут достаточно Http::Tiny
use Mojo::UserAgent ();
use BotLib::Conf qw (LoadConf);
# Поехали!
my $c = LoadConf ();
# Пример содержимого файлика: {"chatid": -номер, "users": ["id1", "id2"]}
my $data = LoadConf ("$workdir/get_chat_users_by_id.json");
unless (defined $c->{cachedir}) {
die "Please define cachedir in data/config.jason\n";
}
unless (defined $data->{chatid}) {
die "Please define chatid in get_chat_users_by_id.json\n";
}
my $targetDir = sprintf ("%s/get_chat_users_by_id/%s", $c->{cachedir}, $data->{chatid});
make_path ($targetDir) or die "Unable to create $targetDir: $OS_ERROR\n";
my $url = sprintf (
"https://api.telegram.org/bot%s/getChatMember",
$c->{telegrambot}->{token},
);
while (my $userid = pop @{$data->{users}}) {
my $ua = Mojo::UserAgent->new->connect_timeout (3);
my $r = $ua->post ($url => form => {chat_id => $data->{chatid}, user_id => $userid} )->result;
# Do not flood api with requests!
sleep 1;
unless ($r->is_success) {
warn sprintf ("Unable to get info about user: http status %s\n%s\n", $r->{code}, $r->body);
next;
}
my $j = eval { decode_json ($r->body) };
if (defined $j) {
my $jobj = JSON::XS->new ();
$jobj = $jobj->pretty (1);
$jobj = $jobj->canonical (1);
my $json = $jobj->encode ($j);
my $file = sprintf "%s/%s.json", $targetDir, $userid;
open (my $FH, '>', $file) or do {
warn "Unable to write to $file: $OS_ERROR\n";
next;
};
print $FH $json . "\n";
close $FH;
} else {
warn sprintf ("Telegram bot api returns corrupted json: %s,\n%s\n", $r->body, $@);
}
}