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