From 2bf710aeb703f5002fb1e741dad5f80a2b96b8cf Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 5 Sep 2020 16:10:26 +0300 Subject: [PATCH] Initial commit. Split bot from myapi as separate application. --- .gitignore | 5 ++ LICENSE | 127 ++++++++++++++++++++++++++ README.md | 51 +++++++++++ bin/aleesa-telegram-bot | 97 ++++++++++++++++++++ bootstrap.sh | 5 ++ confcheck.pl | 34 +++++++ cpanfile | 14 +++ data/sample_config.json | 15 ++++ lib/botlib.pm | 194 ++++++++++++++++++++++++++++++++++++++++ lib/conf.pm | 67 ++++++++++++++ lib/telegrambot.pm | 149 ++++++++++++++++++++++++++++++ 11 files changed, 758 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100755 bin/aleesa-telegram-bot create mode 100644 bootstrap.sh create mode 100755 confcheck.pl create mode 100644 cpanfile create mode 100644 data/sample_config.json create mode 100644 lib/botlib.pm create mode 100644 lib/conf.pm create mode 100644 lib/telegrambot.pm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5390ce2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +vendor_perl +data/config.json +data/cache +data/telegrambot-brains +TODO \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4ff3f42 --- /dev/null +++ b/LICENSE @@ -0,0 +1,127 @@ + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/README.md b/README.md new file mode 100644 index 0000000..ecdc8d9 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Aleesa-Telegram-bot - Simple Telegram chatty bot + +## About + +It is based on Perl modules [Telegram::Bot][1] and [Hailo][2] as conversation +generator. + +Config located in **data/config.json**, sample config provided as +**data/sample_config.json**. + +Bot can be run via **bin/aleesa-telegam-bot** and acts as daemon. + +## Installation + +In order to run this application, you need to "bootstrap" it - download all +dependencies and libraries. + +You'll need "Development Tools" or similar group of packages, perl, perl-devel, +perl-local-lib, perl-app-cpanm, sqlite-devel, zlib-devel, openssl-devel, +libdb4-devel (Berkeley DB devel), make. + +After installng required dependencies it is possible to run: + +```bash +bash bootstrap.sh +``` + +and all libraries should be downloaded and built. + +## N.B. + +If bot is unable to post to channel due to limitated permissions, it will die. +It if possible to change this behavior by patching Telegram/Bot/Brain.pm: + +```diff +--- Brain.pm 2019-07-01 04:04:17.000000000 +0300 ++++ Brain.pm.new 2020-09-05 22:25:20.941373455 +0300 +@@ -237,8 +237,9 @@ + + my $res = $self->ua->post($url, form => $form_args)->result; + if ($res->is_success) { return $res->json->{result}; } +- elsif ($res->is_error) { die "Failed to post: " . $res->message; } +- else { die "Not sure what went wrong"; } ++ elsif ($res->is_error) { warn "Failed to post: " . $res->message; } ++ else { warn "Not sure what went wrong"; } ++ return ""; + } +``` + +[1]: https://metacpan.org/pod/Telegram::Bot +[2]: https://metacpan.org/pod/Hailo diff --git a/bin/aleesa-telegram-bot b/bin/aleesa-telegram-bot new file mode 100755 index 0000000..a6f1de6 --- /dev/null +++ b/bin/aleesa-telegram-bot @@ -0,0 +1,97 @@ +#!/usr/bin/perl + +use strict; +use warnings "all"; +use utf8; +use open qw(:std :utf8); + +my $workdir; + +# before we run, change working dir +BEGIN { + use Cwd qw(chdir abs_path); + my @CWD = split (/\//, abs_path ($0)); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join ('/', @CWD); + chdir ($workdir); +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use POSIX qw(setgid setuid setsid); +use Fcntl qw(O_WRONLY O_TRUNC O_CREAT); +use File::Path qw(make_path); +use File::Basename qw(basename); +use telegrambot; +use conf; + +sub daemonize (); +sub __fork (); +sub __mkpidfile (); + +$| = 1; + +my $piddir = "/var/run/" . basename (abs_path ($0)); +my $C = loadConf (); + +# if we run under root, create pid dir and set correct owner for it +if ($< == 0) { + my $user = $C->{user}; + die "Unable to run as root, please define unprivileged user in config.json\n" unless (defined ($user)); + my ($uid, $gid) = (getpwnam ($user))[2,3]; + + unless (-d $piddir) { + make_path ( + $piddir, { + uid => $uid, + group => $gid + } + ); + } + +# script intended to run as "unit" user, so drop privs to "unit" user + setgid ($gid) or die "Unable to switch to group of $user($!)\n"; + setuid ($uid) or die "Unable to switch to account $user($!)\n"; +} + +# double fork attach to /dev/null all stdio and go background +daemonize (); + +# write pidfile +my $pidfile; + +if ($< == 0) { +# TODO: handle case where we run with unpriv. user + $pidfile = sprintf ("%s/%s.pid", $piddir, basename (abs_path ($0))); + sysopen (PID, $pidfile, O_WRONLY|O_TRUNC|O_CREAT) or die "Error opening $pidfile: $!\n"; + syswrite PID, $$ or die "Error writing to $pidfile: $!\n"; + close PID; +} + +# set correct proctitle +$0 = abs_path ($0); + +# run bot, at last +run_telegrambot (); + +unlink ($pidfile) if (-f $pidfile); +exit 0; + +sub daemonize () { + __fork (); + POSIX::setsid (); + open (STDIN, "<", "/dev/null"); + open (STDOUT, ">", "/dev/null"); + open (STDERR, ">", "/dev/null"); + __fork (); + umask 0; +} + +sub __fork () { + my $pid = fork; + die "Can't fork: $!\n" unless (defined ($pid)); + exit if ($pid != 0); +} + + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..697f373 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cpanm -l vendor_perl --self-contained --force --update --installdeps . + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/confcheck.pl b/confcheck.pl new file mode 100755 index 0000000..daf3c18 --- /dev/null +++ b/confcheck.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl + +use strict; +use warnings "all"; +use utf8; +use open qw(:std :utf8); + +use lib ("./lib", "./vendor_perl", "./vendor_perl/lib/perl5"); + +use JSON::PP; + +my $c = "data/config.json"; +open (C, "<", $c) or die "No conf at $c: $!\n"; +my $len = (stat ($c)) [7]; +my $json; +my $readlen = read (C, $json, $len); + +unless ($readlen) { + close C; + die "Unable to read $c: $!\n"; +} + +if ($readlen != $len) { + close C; + die "File $c is $len bytes on disk, but we read only $readlen bytes\n"; +} + +close C; +$c = decode_json ($json); + +my $j = JSON::PP->new->pretty->canonical->indent_length (4); +print $j->encode ($c); + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..25ebd87 --- /dev/null +++ b/cpanfile @@ -0,0 +1,14 @@ +requires 'local::lib'; +requires 'JSON'; +requires 'Hailo'; +requires 'Telegram::Bot'; +requires 'DB_File'; +requires 'Digest::MD5'; +requires 'Encode'; +requires 'File::Path'; +requires 'File::Basename'; +requires 'HTTP::Tiny'; +requires 'Hailo'; +requires 'JSON::XS'; +requires 'Mojo::Base'; +requires 'URI::URL'; diff --git a/data/sample_config.json b/data/sample_config.json new file mode 100644 index 0000000..e3e5347 --- /dev/null +++ b/data/sample_config.json @@ -0,0 +1,15 @@ +{ + "user" : "nobody", + "telegrambot" : { + "name" : "mybot", + "tname" : "@my_bot", + "csign" : "!", + "token" : "get_it_from_botfather", + "braindir": "data/telegrambot-brains" + }, + "openweathermap" : { + "cachetime": "data/cache/openweathermap.Id-Timestamp.db", + "cachedata": "data/cache/openweathermap.Id-Data.db", + "appid": "get_it_on_openweathermap.org" + } +} diff --git a/lib/botlib.pm b/lib/botlib.pm new file mode 100644 index 0000000..c8ca327 --- /dev/null +++ b/lib/botlib.pm @@ -0,0 +1,194 @@ +package botlib; + +use strict; +use warnings "all"; +use utf8; +use open qw(:std :utf8); + +use HTTP::Tiny; +use URI::URL; +use JSON::XS qw(decode_json encode_json); +use Digest::MD5 qw(md5_base64); +use DB_File; + +use conf; + +use vars qw/$VERSION/; + +use Exporter qw(import); +our @EXPORT = qw(weather); + +$VERSION = "1.0"; + +my $c = loadConf(); + +sub weather($) { + my $city = shift; + while (substr($city, -1) eq "\n") { chomp($city); } + while (substr($city, 0, 1) eq "\n") { $city = substr($city, 1); } + + return "Мне нужно ИМЯ города." if ($city eq ''); + return "Длинновато для названия города." if (length($city) > 80); + + $city = ucfirst($city); + my $w = __weather($city); + my $reply; + + if ($w) { + if ($w->{temperature_min} == $w->{temperature_max}) { + $reply = sprintf ("Погода в городе %s, %s:\n%s, ветер %s %s м/c, температура %s°C, ощущается как %s°C, относительная влажность %s%%, давление %s мм.рт.ст", + $w->{name}, + $w->{country}, + ucfirst($w->{description}), + $w->{wind_direction}, + $w->{wind_speed}, + $w->{temperature_min}, + $w->{temperature_feelslike}, + $w->{humidity}, + $w->{pressure} + ); + } else { + $reply = sprintf ("Погода в городе %s, %s:\n%s, ветер %s %s м/c, температура %s-%s°C, ощущается как %s°C, относительная влажность %s%%, давление %s мм.рт.ст", + $w->{name}, + $w->{country}, + ucfirst($w->{description}), + $w->{wind_direction}, + $w->{wind_speed}, + $w->{temperature_min}, + $w->{temperature_max}, + $w->{temperature_feelslike}, + $w->{humidity}, + $w->{pressure} + ); + } + } else { + $reply = "Я не знаю, какая погода в $city"; + } + + return $reply; +} + +sub __weather($) { + my $city = shift; + $city = __urlencode($city); + my $id = md5_base64($city); + my $appid = $c->{openweathermap}->{appid}; + my $now = time(); + my $fc; + my $w; + +# attach to cache data + tie my %cachetime, 'DB_File', $c->{openweathermap}->{cachetime} or do { + warn "Something nasty happen when cachetime ties to its data: $!"; + return undef; + }; + + tie my %cachedata, 'DB_File', $c->{openweathermap}->{cachedata} or do { + warn "Something nasty happen when cachedata ties to its data: $!"; + return undef; + }; + +# lookup cache + if (defined ($cachetime{$id}) && ($now - $cachetime{$id}) < 10800) { + $fc = decode_json($cachedata{$id}); + } else { + my $r; + +# try 3 times and giveup + for (my $counter = 0; $counter < 3; $counter++) { + next if ($r->{success}); + my $http = HTTP::Tiny->new(timeout => 3); + $r = $http->get(sprintf("http://api.openweathermap.org/data/2.5/weather?q=%s&lang=ru&APPID=%s", $city, $appid)); + sleep 2; + } + +# all 3 times can give error, so check it here + if ($r->{success}) { + $fc = eval { decode_json($r->{content}) }; + + if ($@) { + warn "[WARN] openweathermap returns corrupted json: $@"; + untie %cachetime; + untie %cachedata; + return undef; + }; + + $cachetime{$id} = $now; + $cachedata{$id} = encode_json($fc); + } else { + untie %cachetime; + untie %cachedata; + return undef; + } + } + + untie %cachetime; + untie %cachedata; + +# TODO: check all of this for existance + $w->{'name'} = $fc->{name}; + $w->{'state'} = $fc->{state}; + $w->{'country'} = $fc->{sys}->{country}; + $w->{'longitude'} = $fc->{coord}->{lon}; + $w->{'latitude'} = $fc->{coord}->{lat}; + $w->{'temperature_min'} = int($fc->{main}->{temp_min} - 273.15); + $w->{'temperature_max'} = int($fc->{main}->{temp_max} - 273.15); + $w->{'temperature_feelslike'} = int($fc->{main}->{feels_like} - 273.15); + $w->{'humidity'} = $fc->{main}->{humidity}; + $w->{'pressure'} = int($fc->{main}->{pressure} * 0.75006375541921); + $w->{'description'} = $fc->{weather}->[0]->{description}; + $w->{'wind_speed'} = $fc->{wind}->{speed}; + $w->{'wind_direction'} = 'разный'; + my $dir = int($fc->{wind}->{deg} + 0); + + if ($dir == 0) { + $w->{'wind_direction'} = 'северный'; + } elsif ($dir > 0 && $dir <= 30) { + $w->{'wind_direction'} = 'северо-северо-восточный'; + } elsif ($dir > 30 && $dir <= 60) { + $w->{'wind_direction'} = 'северо-восточный'; + } elsif ($dir > 60 && $dir < 90) { + $w->{'wind_direction'} = 'восточно-северо-восточный'; + } elsif ($dir == 90) { + $w->{'wind_direction'} = 'восточный'; + } elsif ($dir > 90 && $dir <= 120) { + $w->{'wind_direction'} = 'восточно-юго-восточный'; + } elsif ($dir > 120 && $dir <= 150) { + $w->{'wind_direction'} = 'юговосточный'; + } elsif ($dir > 150 && $dir < 180) { + $w->{'wind_direction'} = 'юго-юго-восточный'; + } elsif ($dir == 180) { + $w->{'wind_direction'} = 'южный'; + } elsif ($dir > 180 && $dir <= 210) { + $w->{'wind_direction'} = 'юго-юго-западный'; + } elsif ($dir > 210 && $dir <= 240) { + $w->{'wind_direction'} = 'юго-западный'; + } elsif ($dir > 240 && $dir < 270) { + $w->{'wind_direction'} = 'западно-юго-западный'; + } elsif ($dir == 270) { + $w->{'wind_direction'} = 'западный'; + } elsif ($dir > 270 && $dir <= 300) { + $w->{'wind_direction'} = 'западно-северо-западный'; + } elsif ($dir > 300 && $dir <= 330) { + $w->{'wind_direction'} = 'северо-западный'; + } elsif ($dir > 330 && $dir < 360) { + $w->{'wind_direction'} = 'северо-северо-западный'; + } elsif ($dir == 360) { + $w->{'wind_direction'} = 'северный'; + } + + return $w; +} + +sub __urlencode($) { + my $str = shift; + my $urlobj = url $str; + $str = $urlobj->as_string; + $urlobj = undef; undef $urlobj; + return $str; +} + + +1; + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/conf.pm b/lib/conf.pm new file mode 100644 index 0000000..9098a96 --- /dev/null +++ b/lib/conf.pm @@ -0,0 +1,67 @@ +package conf; + +use strict; +use warnings "all"; +use utf8; +use open qw(:std :utf8); + +use vars qw/$VERSION/; +use Fcntl qw(O_WRONLY O_CREAT O_TRUNC); +use JSON::XS qw(decode_json encode_json); + +use Exporter qw(import); +our @EXPORT = qw(loadConf saveConf); + +$VERSION = "1.0"; +sub loadConf (); +sub saveConf ($); + +sub loadConf () { + my $c = "data/config.json"; + open (C, "<", $c) or die "[FATA] No conf at $c: $!\n"; + my $len = (stat ($c)) [7]; + my $json; + my $readlen = read (C, $json, $len); + + unless ($readlen) { + close C; + die "[FATA] Unable to read $c: $!\n"; + } + + if ($readlen != $len) { + close C; + die "[FATA] File $c is $len bytes on disk, but we read only $readlen bytes\n"; + } + + close C; + return decode_json ($json); +} + +sub saveConf($) { + my $c = shift; + my $file = "data/myapi.json"; + my $j = JSON::XS->new->pretty->canonical->indent (1); + my $json = $j->encode ($c); + $j = undef; undef $j; + use bytes; + my $len = length ($json); + no bytes; + # TODO: make it transactional + sysopen (C, $file, O_WRONLY|O_CREAT|O_TRUNC) or die "[FATA] Unable to open $file: $!\n"; + + my $written = syswrite (C, $json, $len); + + unless (defined ($written)) { + die "[FATA] Unable to write to $file: $!"; + } + + unless ($written != $len) { + die "[FATA] We wrote $written bytes to $file, bu buffer length id $len bytes\n"; + } + + close C; +} + +1; + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/telegrambot.pm b/lib/telegrambot.pm new file mode 100644 index 0000000..e0dc5c7 --- /dev/null +++ b/lib/telegrambot.pm @@ -0,0 +1,149 @@ +package telegrambot; + +use strict; +use warnings "all"; +use vars qw/$VERSION/; +use v5.10.0; +use utf8; +use open qw(:std :utf8); +use File::Path qw( mkpath ); +use Hailo; +use Encode; +use Mojo::Base 'Telegram::Bot::Brain'; + +use conf; +use botlib; + +use Exporter qw(import); +our @EXPORT = qw(run_telegrambot); + +$VERSION = "1.0"; + +my $c = loadConf(); +my $hailo; + +has token => $c->{telegrambot}->{token}; + +sub __cron { + my $self = shift; +# noop + return; +} + +sub __on_msg { + my ($self, $msg) = @_; +# lazy init chat-bot brains + unless (defined($hailo->{$msg->chat->id})) { + $hailo->{$msg->chat->id} = Hailo->new( +# we'll got file like this: data/telegrambot-brains/-1001332512695.brain.sqlite + brain => sprintf("%s/%s.brain.sqlite", $c->{telegrambot}->{braindir}, $msg->chat->id), + order => 3 + ); + } + +# is this a 1-on-1 ? + if ($msg->chat->type eq 'private') { + return unless(defined($msg->text)); + my $text = $msg->text; + my $csign = quotemeta($c->{telegrambot}->{csign}); + my $reply; + + if (substr($text, 0, 1) eq $c->{telegrambot}->{csign}) { + if (substr($text, 1) eq "ping") { + $reply = "Pong."; + } elsif (substr($text, 1) eq "пинг") { + $reply = "Понг."; + } elsif (substr($text, 1, 1) eq 'w' || substr($text, 1, 1) eq 'п') { + my $city = substr($text, 2); + $reply = weather($city); + } else { + $reply = "Чего?"; + } + } else { + $reply = $hailo->{$msg->chat->id}->learn_reply($text); + } + + if (defined($reply) && $reply ne '') { + $msg->reply($reply); + } else { +# if we have no answer, say something default in private chat + $msg->reply("Давайте ещё пообщаемся, а то я ещё не научилась от вас плохому."); + } +# group chat + } elsif (($msg->chat->type eq 'supergroup') or ($msg->chat->type eq 'group')) { + my $reply; + return unless(defined($msg->text)); + my $text = $msg->text; +# sometimes shit happens? + return unless(defined($text)); + + my $qname = quotemeta($c->{telegrambot}->{name}); + my $qtname = quotemeta($c->{telegrambot}->{tname}); + my $csign = quotemeta($c->{telegrambot}->{csign}); + +# simple commands + if (substr($text, 0, 1) eq $c->{telegrambot}->{csign}) { + if (substr($text, 1) eq "ping") { + $reply = "Pong."; + } elsif (substr($text, 1) eq "пинг") { + $reply = "Понг."; + } elsif (substr($text, 1, 1) eq 'w' || substr($text, 1, 1) eq 'п') { + my $city = substr($text, 2); + $reply = weather($city); + } + } elsif ( + ($text eq $qname) or + ($text eq sprintf("%s", $qtname)) or + ($text eq sprintf("@%s_bot", $qname)) or # :( + ($text eq sprintf("%s ", $qtname)) + ) { + $reply = "Чего?"; + } else { +# phrase directed to bot + if ((lc($text) =~ /^${qname}[\,|\:]? (.+)/) or (lc($text) =~ /^${qtname}[\,|\:]? (.+)/)){ + $reply = $hailo->{$msg->chat->id}->learn_reply($1); +# bot mention by name + } elsif ((lc($text) =~ /.+ ${qname}[\,|\!|\?|\.| ]/) or (lc($text) =~ / $qname$/)) { + $reply = $hailo->{$msg->chat->id}->reply($text); +# bot mention by telegram name + } elsif ((lc($text) =~ /.+ ${qtname}[\,|\!|\?|\.| ]/) or (lc($text) =~ / $qtname$/)) { + $reply = $hailo->{$msg->chat->id}->reply($text); +# just message in chat + } else { + $hailo->{$msg->chat->id}->learn($text); + } + } + + if (defined($reply) && $reply ne '') { + $msg->reply($reply); + } +# should be channel, so we can't talk + } else { + return; + } + + return; +} + +# setup our bot +sub init { + unless (-d $c->{telegrambot}->{braindir}) { + mkpath ($c->{telegrambot}->{braindir}, 0, 0755); + } + + my $self = shift; + $self->add_listener(\&__on_msg); +# $self->add_repeating_task(900, \&__cron); +} + +sub run_telegrambot { + while (sleep 3) { + eval { + telegrambot->new->think; + } + } +} + +1; + +# vim: ft=perl noet ai ts=4 sw=4 sts=4: