Initial commit. Split bot from myapi as separate application.
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
vendor_perl
|
||||
data/config.json
|
||||
data/cache
|
||||
data/telegrambot-brains
|
||||
TODO
|
||||
127
LICENSE
Normal file
127
LICENSE
Normal file
@@ -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
|
||||
51
README.md
Normal file
51
README.md
Normal file
@@ -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
|
||||
97
bin/aleesa-telegram-bot
Executable file
97
bin/aleesa-telegram-bot
Executable file
@@ -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:
|
||||
5
bootstrap.sh
Normal file
5
bootstrap.sh
Normal file
@@ -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:
|
||||
34
confcheck.pl
Executable file
34
confcheck.pl
Executable file
@@ -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:
|
||||
14
cpanfile
Normal file
14
cpanfile
Normal file
@@ -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';
|
||||
15
data/sample_config.json
Normal file
15
data/sample_config.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
194
lib/botlib.pm
Normal file
194
lib/botlib.pm
Normal file
@@ -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:
|
||||
67
lib/conf.pm
Normal file
67
lib/conf.pm
Normal file
@@ -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:
|
||||
149
lib/telegrambot.pm
Normal file
149
lib/telegrambot.pm
Normal file
@@ -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:
|
||||
Reference in New Issue
Block a user