1

Merge branch 'master' into update_teapot_bot_lib_to_api_v5.1

This commit is contained in:
eleksir
2021-04-22 19:47:58 +03:00
11 changed files with 317 additions and 200 deletions

103
README_RU.md Normal file
View File

@@ -0,0 +1,103 @@
# Aleesa-Telegram-bot - Болтливый бот для Telegram
## Что это такое
Это бот основанный на перловых модулях [Telegram::Bot][1] для работы в
мессенджере Telegram и [Hailo][2] для генерации "умной" беседы.
Для корректной работы пришлось форкнуть [Telegram::Bot][1] чтобы обновить
интерфейс API до [Telegram Bot API v5][3]. Для избежания неоднозначностей модуль
пришлось переименовать и теперь он идёт в составе бота. Возможно, когда модуль
отлежится и будет похож на стабильный, он стнет самостоятельным и поселится на
metacpan.org
Конфиг бота должен лежать в **data/config.json**, пример конфига расположен в
**data/sample_config.json**.
Бота можно запустить через **bin/aleesa-telegram-bot** и он взлетит как юниксовый
демон (сделает двойной fork() и отцепится от stdio).
## Установка
Чтобы запустить приложение, его надо "забурстрапить" - загрузить и собрать все
необходимые зависимости.
Понадобится perl-5.18 а желательно новее, "Development Tools" или подобная
группа пакетов , perl, perl-devel, perl-local-lib, perl-app-cpanm, sqlite-devel,
zlib-devel, openssl-devel, libdb4-devel (Berkeley DB devel), make.
После установки этих пакетов можно будет запустить:
```bash
bash bootstrap.sh
```
и по идее всё что нужно будет выкачено, собрано и разложено куда надо.
## Запуск и работа
Бот не использует [вебхуки][4], поэтому ничего дополнительного, кроме хорошего
подключения к интернету ему не нужно.
### Дополнительные настройки
Некоторые "плагины" требуют регистрации в сторонних сервисах (API keys, secrets
итд), чтобы бот не сыпал ошибками в логи (возможно, в дальнейшем возможно будет
отключить эти плагины).
#### OpenWeatherMap
Для работы погодного плагина надо [заргистрироваться][5], [залогиниться][6] и
[создать][7] api key. Free plan предлагает всё необхдимое для работы "плагина".
Когда api key будет создан, его нужно вписать в **config.json** "секцию"
openweathermap в качестве appid. На этом всё.
#### Imgur
Этот плагин "просто есть" и его на самом деле ничто и никто не использует. И в
этой связи все настройки плагина можно оставить "как есть" в примере конфига.
Тем не менее, если есть большое желание, то можно [залогиниться][8] и
[зарегистрировать][9] бота как приложение, а потом разрешить приложению
пользоваться API-шкой, как описано в [этом][10] или [этом][11] мануале.
#### Flickr
Этот "плагин" действительно используется для добычи ссылок на картинки в разных
"картиночных" командах бота.
Есть скрипт, который поможет с регистрацией приложения в api Flickr -
**bin/flickr_init.pl** он достанет все нужные ключи и секреты к ним.
До запуска этого скрипта, бот должен быть забутстраплен через **`bootstrap.sh`**.
[Залогиньтесь][11] и создайте [app][12] in своём Flicker-овском аккаунте. Далее,
*разлогиньтесь*, это нужно, чтобы потом проще было достать параметр verifier.
Запишите [app key и secret][13] в **config.json**. Удостоверьтесь, что параметр
"verifier" в конфиге либо закоменчен либо отсутствует.
Запустите **bin/flickr_init.pl** и следуте предлагаемым инструкциям - в итоге
будет дана ссылка, которую надо скопипастить в браузер и через один-два
редиректа вы попадёте на example.com. Из адресной строки браузера надо
скопировать параметр "verifier" и вписать его в **config.json**.
Чтобы сохранить в бд плагина API access token для нормальной работы "плагина",
второй раз запустите **bin/flickr_init.pl**. На этом всё.
[1]: https://metacpan.org/pod/Telegram::Bot
[2]: https://metacpan.org/pod/Hailo
[3]: https://core.telegram.org/bots/api
[4]: https://core.telegram.org/bots/api#getting-updates
[5]: https://home.openweathermap.org/users/sign_up
[6]: https://home.openweathermap.org/users/sign_in
[7]: https://home.openweathermap.org/api_keys
[8]: https://imgur.com/signin
[9]: https://api.imgur.com/oauth2/addclient
[10]: https://apidocs.imgur.com/#authorization-and-oauth
[11]: https://identity.flickr.com/login
[12]: https://www.flickr.com/services/apps/create/apply/
[13]: https://www.flickr.com/services/api/keys/

View File

@@ -1,5 +1,8 @@
requires 'Carp';
requires 'CHI';
requires 'CHI::Driver::BerkeleyDB';
requires 'Cwd::utf8';
requires 'DateTime';
requires 'DB_File';
requires 'SQLite_File';
requires 'Digest::HMAC_SHA1';
@@ -10,7 +13,6 @@ requires 'File::Basename';
requires 'File::Path';
requires 'Hailo';
requires 'HTML::TokeParser';
requires 'HTTP::Tiny';
requires 'IO::Socket::SSL';
requires 'JSON';
requires 'JSON::XS';
@@ -23,6 +25,7 @@ requires 'Math::Random::Secure';
requires 'Mojo::Base';
requires 'Mojo::IOLoop';
requires 'Mojo::UserAgent';
requires 'Mojo::UserAgent::Cached';
requires 'Text::Fuzzy';
requires 'URI::Encode::XS';
requires 'URI::URL';

View File

@@ -653,7 +653,15 @@ sub _process_message {
foreach my $listener (@{ $self->listeners }) {
# call the listener code, supplying ourself and the update
$listener->($self, $update);
my $evalRet = eval {
$listener->($self, $update);
return 1;
};
unless ($evalRet) {
cluck "An error occured during update processing: $EVAL_ERROR";
return;
}
}
return;

View File

@@ -7,8 +7,8 @@ use utf8;
use open qw (:std :utf8);
use English qw ( -no_match_vars );
use Carp qw (cluck);
use HTTP::Tiny;
use HTML::TokeParser;
use Mojo::UserAgent;
use version; our $VERSION = qw (1.0);
use Exporter qw (import);
@@ -19,18 +19,18 @@ sub buni {
my $ret = 'Нету Buni';
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ('http://www.bunicomic.com/?random&nocache=1');
my $ua = Mojo::UserAgent->new->connect_timeout (3);
$r = $ua->get ('http://www.bunicomic.com/?random&nocache=1')->result;
if ($r->{success}) {
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
my $p = HTML::TokeParser->new(\$r->{content});
if ($r->is_success) {
my $p = HTML::TokeParser->new(\$r->body);
my @a;
# additional {} required in order "last" to work properly :)
@@ -47,7 +47,7 @@ sub buni {
} while ($#{$a[0]} > 1);
}
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
}
return $ret;

View File

@@ -5,11 +5,16 @@ use strict;
use warnings;
use utf8;
use open qw (:std :utf8);
use CHI;
use CHI::Driver::BerkeleyDB;
use DateTime;
use English qw ( -no_match_vars );
use Encode;
use Carp qw (cluck);
use HTTP::Tiny;
use HTML::TokeParser;
use Mojo::Log;
use Mojo::UserAgent::Cached;
use POSIX qw (strftime);
use version; our $VERSION = qw (1.0);
use Exporter qw (import);
@@ -23,19 +28,56 @@ sub drink {
my ($dayNum, $monthNum) = (localtime ())[3, 4];
my $url = sprintf 'https://kakoysegodnyaprazdnik.ru/baza/%s/%s', $MONTH[$monthNum], $dayNum;
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ($url, {'Accept-Charset' => 'utf-8', 'Accept-Language' => 'ru-RU'});
# Those POSIX assholes just forgot to add unix timestamps without TZ offset, so...
my ($mday, $mon, $year) = (gmtime ())[3, 4, 5];
my $offset = strftime ('%z', gmtime ());
my $offsetMinutes = (substr $offset, -2) * 60;
my $offsetHours = (substr $offset, 1, 2) * 60 * 60;
my $offsetSign;
if ($r->{success}) {
if ((substr $offset, 0, 1) eq '+') {
$offsetSign = 1;
}
my $expirationDate = DateTime->new (
year => $year + 1900,
month => $mon + 1,
day => $mday,
hour => 0,
minute => 0,
second => 0
)->add (days => 1)->strftime ('%s');
if ((substr $offset, 0, 1) eq '+') {
$expirationDate = $expirationDate - $offsetHours - $offsetMinutes;
} else {
$expirationDate = $expirationDate + $offsetHours + $offsetMinutes;
}
for (1..3) {
my $ua = Mojo::UserAgent::Cached->new->connect_timeout (3);
$ua->cache_agent(
CHI->new (
driver => 'BerkeleyDB',
root_dir => 'data/cache',
namespace => __PACKAGE__,
expires_at => $expirationDate,
expires_on_backend => 1,
)
);
# just to make Mojo::UserAgent::Cached happy
$ua->logger (Mojo::Log->new (path => '/dev/null', level => 'error'));
$r = $ua->get ($url => {'Accept-Language' => 'ru-RU', 'Accept-Charset' => 'utf-8'})->result;
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
my $p = HTML::TokeParser->new(\$r->{content});
if ($r->is_success) {
my $p = HTML::TokeParser->new(\$r->body);
my @a;
my @holyday;
@@ -58,7 +100,7 @@ sub drink {
$ret = join "\n", @holyday;
}
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
}
return $ret;

View File

@@ -9,10 +9,9 @@ use English qw ( -no_match_vars );
use Carp qw (cluck);
use Digest::HMAC_SHA1 qw (hmac_sha1);
use File::Path qw (make_path);
use JSON::XS;
use HTTP::Tiny;
use Math::Random::Secure qw (irand);
use MIME::Base64;
use Mojo::UserAgent;
use SQLite_File;
use URI::Encode::XS qw (uri_encode);
@@ -120,15 +119,15 @@ sub flickrRequestToken {
uri_encode ($flickr_callback_url)
);
my $http = HTTP::Tiny->new (timeout => 5);
my $r = $http->get ($url);
my $ua = Mojo::UserAgent->new->connect_timeout (5);
my $r = $ua->get ($url)->result;
my %params;
if ($r->{success}) {
if ($r->is_success) {
# they ignore format=json, so...
%params = map {split /=/} split /&/, $r->{content};
%params = map {split /=/} split /&/, $r->body;
} else {
say "Something bad returns from flickr api: $r->{status} $r->{content}"; ## no critic (InputOutput::RequireCheckedSyscalls)
say "Something bad returns from flickr api: $r->code $r->message"; ## no critic (InputOutput::RequireCheckedSyscalls)
}
return %params;
@@ -161,15 +160,15 @@ sub flickrAccessToken {
uri_encode ($oauth_signature)
);
my $http = HTTP::Tiny->new (timeout => 5);
my $r = $http->get ($url);
my $ua = Mojo::UserAgent->new->connect_timeout (5);
my $r = $ua->get ($url)->result;
my %params;
if ($r->{success}) {
if ($r->is_success) {
# they ignore format=json
%params = map {split /=/} split /&/, $r->{content};
%params = map {split /=/} split /&/, $r->body;
} else {
say "Something bad returns from flickr api: $r->{status} $r->{content}"; ## no critic (InputOutput::RequireCheckedSyscalls)
say "Something bad returns from flickr api: $r->code $r->message"; ## no critic (InputOutput::RequireCheckedSyscalls)
}
return %params;
@@ -183,13 +182,13 @@ sub flickrAuthorization {
$flickr_request_token,
);
my $http = HTTP::Tiny->new (timeout => 5);
my $r = $http->get ($url);
my $ua = Mojo::UserAgent->new->connect_timeout (5);
my $r = $ua->get ($url)->result;
if ($r->{success}) {
return $r->{url};
if ($r->is_success) {
return $r->url;
} else {
say "Something went wrong: $r->{status} $r->{content}"; ## no critic (InputOutput::RequireCheckedSyscalls)
say "Something went wrong: $r->code $r->message"; ## no critic (InputOutput::RequireCheckedSyscalls)
return;
}
}
@@ -224,24 +223,23 @@ sub flickrTestLogin {
uri_encode ($oauth_signature)
);
my $http = HTTP::Tiny->new (timeout => 5);
my $r = $http->get ($url);
my $ua = Mojo::UserAgent->new->connect_timeout (5);
my $r = $ua->get ($url)->result;
if ($r->{success}) {
if ($r->is_success) {
my $response = eval {
my $j = JSON::XS->new->utf8->relaxed;
return $j->decode ($r->{content});
return $r->json;
};
if (defined $response) {
say Dumper $response; ## no critic (InputOutput::RequireCheckedSyscalls)
return 1;
} else {
say "Flickr api returns incorrect json: $r->{content}"; ## no critic (InputOutput::RequireCheckedSyscalls)
say "Flickr api returns incorrect json: $r->message"; ## no critic (InputOutput::RequireCheckedSyscalls)
return 0;
}
} else {
say "HTTP status code not 200: $r->{status}, $r>{contenr}"; ## no critic (InputOutput::RequireCheckedSyscalls)
say "HTTP status code not 200: $r->code, $r->message"; ## no critic (InputOutput::RequireCheckedSyscalls)
return 0;
}
}
@@ -282,13 +280,12 @@ sub flickrSearchByText {
uri_encode ($text)
);
my $http = HTTP::Tiny->new (timeout => 5);
my $r = $http->get ($url);
my $ua = Mojo::UserAgent->new->connect_timeout (5);
my $r = $ua->get ($url)->result;
if ($r->{success}) {
if ($r->is_success) {
my $response = eval {
my $j = JSON::XS->new->utf8->relaxed;
return $j->decode ($r->{content});
return $r->json;
};
if (defined $response) {
@@ -326,31 +323,31 @@ sub flickrSearchByText {
uri_encode ($text)
);
$http = HTTP::Tiny->new (timeout => 5);
$r = $http->get ($url);
$ua = Mojo::UserAgent->new->connect_timeout (5);
$r = $ua->get ($url)->result;
if ($r->{success}) {
if ($r->is_success) {
$response = eval {
my $j = JSON::XS->new->utf8->relaxed;
return $j->decode ($r->{content});
return $r->json;
};
if (defined $response) {
return $response;
} else {
cluck "Flickr api returns incorrect json: $r->{content}";
cluck "Flickr api returns incorrect json: $r->message";
return undef;
}
} else {
cluck "Flickr api returns incorrect json: $r->{content}";
cluck "Flickr api returns incorrect json: $r->message";
return undef;
}
} else {
cluck "Flickr api returns incorrect json: $r->{content}";
cluck "Flickr api returns incorrect json: $r->message";
return undef;
}
} else {
cluck "HTTP status code not 200: $r->{status}, $r->{content}";
cluck "HTTP status code not 200: $r->code, $r->message";
return undef;
}
}
@@ -391,13 +388,12 @@ sub flickrSearchByTags {
uri_encode ($tags)
);
my $http = HTTP::Tiny->new (timeout => 5);
my $r = $http->get ($url);
my $ua = Mojo::UserAgent->new->connect_timeout (5);
my $r = $ua->get ($url)->result;
if ($r->{success}) {
if ($r->is_success) {
my $response = eval {
my $j = JSON::XS->new->utf8->relaxed;
return $j->decode ($r->{content});
return $r->json;
};
if (defined $response) {
@@ -435,31 +431,30 @@ sub flickrSearchByTags {
uri_encode ($tags)
);
$http = HTTP::Tiny->new (timeout => 5);
$r = $http->get ($url);
$ua = Mojo::UserAgent->new->connect_timeout (5);
$r = $ua->get ($url)->result;
if ($r->{success}) {
if ($r->is_success) {
$response = eval {
my $j = JSON::XS->new->utf8->relaxed;
return $j->decode ($r->{content});
return $r->json;
};
if (defined $response) {
return $response;
} else {
cluck "Flickr api returns incorrect json: $r->{content}";
cluck "Flickr api returns incorrect json: $r->message";
return undef;
}
} else {
cluck "Flickr api returns incorrect json: $r->{content}";
cluck "Flickr api returns incorrect json: $r->message";
return undef;
}
} else {
cluck "Flickr api returns incorrect json: $r->{content}";
cluck "Flickr api returns incorrect json: $r->message";
return undef;
}
} else {
cluck "HTTP status code not 200: $r->{status}, $r>{contenr}";
cluck "HTTP status code not 200: $r->code, $r->message";
return undef;
}
}
@@ -484,8 +479,8 @@ sub flickr_init {
my $flickr_request_token_secret = $secret{flickr_request_token_secret};
unless (defined $flickr_request_token || defined $flickr_request_token_secret) {
say "Looks like there is no request token or request token secret in $backingfile";
say "Please remove verifier settings from config.json and run this script again";
say "Looks like there is no request token or request token secret in $backingfile"; ## no critic (InputOutput::RequireCheckedSyscalls)
say 'Please remove verifier settings from config.json and run this script again'; ## no critic (InputOutput::RequireCheckedSyscalls)
untie %secret;
return 0;
}

View File

@@ -8,9 +8,8 @@ use open qw (:std :utf8);
use English qw ( -no_match_vars );
use Carp qw (cluck);
use File::Path qw (make_path);
use JSON::XS;
use HTTP::Tiny;
use Math::Random::Secure qw (irand);
use Mojo::UserAgent;
use SQLite_File;
use conf qw (loadConf);
use flickr qw (flickr_by_tags);
@@ -29,19 +28,19 @@ sub kitty {
my $ret = 'Нету кошечек, все разбежались.';
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ('https://api.thecatapi.com/v1/images/search');
my $ua = Mojo::UserAgent->new->connect_timeout (3);
$r = $ua->get ('https://api.thecatapi.com/v1/images/search')->result;
if ($r->{success}) {
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
if ($r->is_success) {
my $jcat = eval {
decode_json ($r->{content})
return $r->json;
};
unless (defined $jcat) {
@@ -53,7 +52,7 @@ sub kitty {
}
}
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
}
return $ret;
@@ -64,19 +63,19 @@ sub fox {
my $ret = 'Нету лисичек, все разбежались.';
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ('https://randomfox.ca/floof/');
my $ua = Mojo::UserAgent->new->connect_timeout (3);
$r = $ua->get ('https://randomfox.ca/floof/')->result;
if ($r->{success}) {
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
if ($r->is_success) {
my $jfox = eval {
decode_json ($r->{content})
return $r->json;
};
unless (defined $jfox) {
@@ -88,7 +87,7 @@ sub fox {
}
}
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
}
return $ret;
@@ -99,19 +98,19 @@ sub oboobs {
my $ret = 'Нету cисичек, все разбежались.';
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ('http://api.oboobs.ru/boobs/0/1/random');
my $ua = Mojo::UserAgent->new->connect_timeout (3);
$r = $ua->get ('http://api.oboobs.ru/boobs/0/1/random')->result;
if ($r->{success}) {
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
if ($r->is_success) {
my $joboobs = eval {
decode_json ($r->{content})
return $r->json;
};
unless (defined $joboobs) {
@@ -123,7 +122,7 @@ sub oboobs {
}
}
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
}
return $ret;
@@ -134,19 +133,19 @@ sub obutts {
my $ret = 'Нету попок, все разбежались.';
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ('http://api.obutts.ru/butts/0/1/random');
my $ua = Mojo::UserAgent->new->connect_timeout (3);
$r = $ua->get ('http://api.obutts.ru/butts/0/1/random')->result;
if ($r->{success}) {
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
if ($r->is_success) {
my $jobutts = eval {
decode_json ($r->{content})
return $r->json;
};
unless (defined $jobutts) {
@@ -158,7 +157,7 @@ sub obutts {
}
}
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
}
return $ret;

View File

@@ -8,9 +8,8 @@ use open qw (:std :utf8);
use English qw ( -no_match_vars );
use Carp qw (cluck);
use File::Path qw (make_path);
use JSON::XS;
use HTTP::Tiny;
use Math::Random::Secure qw (irand);
use Mojo::UserAgent;
use SQLite_File;
use conf qw (loadConf);
@@ -30,7 +29,6 @@ sub imgur {
my $refresh_url = 'https://api.imgur.com/oauth2/token';
my $search_url = 'https://api.imgur.com/3/gallery/search/';
my $http = HTTP::Tiny->new (timeout => 5);
# load all tokens from api secrets db
my $backingfile = sprintf '%s/secrets.sqlite', $dir;
@@ -62,20 +60,13 @@ sub imgur {
}
# query api
my $r = $http->get (
$search_url . $tag,
{
headers => {
Authorization => "Bearer $imgur_access_token"
}
}
);
my $ua = Mojo::UserAgent->new->connect_timeout (3);
my $r = $ua->get ($search_url . $tag => {Authorization => "Bearer $imgur_access_token"})->result;
# looks like we need to refresh token
if (($r->{status} == 401) || ($r->{status} == 403)) {
$r = $http->post_form (
$refresh_url,
{
if (($r->code == 401) || ($r->code == 403)) {
$ua->post (
$refresh_url => form {
refresh_token => $imgur_refresh_token,
client_id => $imgur_client_id,
clinent_secret => $imgur_client_secret,
@@ -83,13 +74,13 @@ sub imgur {
}
);
unless ($r->{success}) {
cluck "Unable to refresh imgur token, please re-authorise app! or imgur experiencing api outage: $r->{status} $r->{content}";
unless ($r->is_success) {
cluck "Unable to refresh imgur token, please re-authorise app! or imgur experiencing api outage: $r->code $r->message";
untie %secret;
return undef;
}
my $refresh = eval { decode_json $r->{content} };
my $refresh = eval { return $r->json; };
if (defined $refresh) {
$imgur_refresh_token = $refresh->{refresh_token};
@@ -103,20 +94,13 @@ sub imgur {
}
# query api again
$r = $http->get (
$search_url . $tag,
{
headers => {
Authorization => "Bearer $imgur_access_token"
}
}
);
$r = $ua->get ($search_url . $tag => {Authorization => "Bearer $imgur_access_token"})->result;
}
untie %secret;
if ($r->{success}) {
my $searchResult = eval { decode_json ($r->{content}) };
if ($r->is_success) {
my $searchResult = eval { return $r->json; };
my @urls;
if (defined $searchResult) {
@@ -148,7 +132,7 @@ sub imgur {
return undef;
}
} else {
cluck "Unable to get results from imgur api: $r->{content}";
cluck "Unable to get results from imgur api: $r->message";
return undef;
}
} else {
@@ -156,7 +140,7 @@ sub imgur {
return undef;
}
} else {
cluck "Unable to get results from imgur api: $r->{status}, $r->{content}";
cluck "Unable to get results from imgur api: $r->code, $r->message";
return undef;
}
}

View File

@@ -7,9 +7,9 @@ use utf8;
use open qw (:std :utf8);
use English qw ( -no_match_vars );
use Carp qw (cluck);
use HTTP::Tiny;
use HTML::TokeParser;
use Math::Random::Secure qw (irand);
use Mojo::UserAgent;
use version; our $VERSION = qw (1.0);
use Exporter qw (import);
@@ -21,18 +21,18 @@ sub monkeyuser {
my @link;
for (1..3) {
my $http = HTTP::Tiny->new (timeout => 3);
$r = $http->get ('https://www.monkeyuser.com/toc/');
my $ua = Mojo::UserAgent->new->connect_timeout (3);
$r = $ua->get ('https://www.monkeyuser.com/toc/')->result;
if ($r->{success}) {
if ($r->is_success) {
last;
}
sleep 2;
}
if ($r->{success}) {
my $p = HTML::TokeParser->new(\$r->{content});
if ($r->is_success) {
my $p = HTML::TokeParser->new(\$r->body);
my @a;
do {

View File

@@ -7,11 +7,13 @@ use utf8;
use open qw (:std :utf8);
use English qw ( -no_match_vars );
use Carp qw (carp cluck);
use CHI;
use CHI::Driver::BerkeleyDB;
use DB_File;
use Digest::MD5 qw (md5_base64);
use File::Path qw (make_path);
use HTTP::Tiny;
use JSON::XS;
use Mojo::Log;
use Mojo::UserAgent::Cached;
use conf qw (loadConf);
use util qw (trim urlencode);
@@ -71,77 +73,53 @@ sub weather {
sub __weather {
my $city = shift;
$city = urlencode $city;
my $id = md5_base64 $city;
my $appid = $c->{openweathermap}->{appid};
my $cachedir = $c->{openweathermap}->{cachedir};
my $cachetimefile = $cachedir . '/openweathermap.Id-Timestamp.db';
my $cachedatafile = $cachedir . '/openweathermap.Id-Data.db';
my $now = time ();
my $fc;
my $w;
unless (-d $cachedir) {
make_path ($cachedir) || do {
cluck "[ERROR] Unable to create $cachedir: $OS_ERROR";
return;
my $r;
# try 3 times and giveup
for (1..3) {
my $ua = Mojo::UserAgent::Cached->new;
$ua->local_dir ($cachedir);
$ua->cache_agent(
CHI->new (
driver => 'BerkeleyDB',
root_dir => $cachedir,
namespace => __PACKAGE__,
expires_in => '3 hours',
expires_on_backend => 1,
)
);
# just to make Mojo::UserAgent::Cached happy
$ua->logger (Mojo::Log->new (path => '/dev/null', level => 'error'));
$r = $ua->get (sprintf ('http://api.openweathermap.org/data/2.5/weather?q=%s&lang=ru&APPID=%s', $city, $appid))->result;
if ($r->is_success) {
last;
}
sleep 2;
}
# all 3 times can give error, so check it here
if ($r->is_success) {
$fc = eval {
return $r->json;
};
}
# attach to cache data
tie my %cachetime, 'DB_File', $cachetimefile or do {
carp "[ERROR] Something nasty happen when cachetime ties to its data: $OS_ERROR";
return undef;
};
tie my %cachedata, 'DB_File', $cachedatafile or do {
carp "[ERROR] Something nasty happen when cachedata ties to its data: $OS_ERROR";
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 (1..3) {
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));
if ($r->{success}) {
last;
}
sleep 2;
}
# all 3 times can give error, so check it here
if ($r->{success}) {
$fc = eval {
decode_json $r->{content};
};
if ($EVAL_ERROR) {
cluck "[WARN] openweathermap returns corrupted json: $EVAL_ERROR";
untie %cachetime;
untie %cachedata;
return undef;
};
$cachetime{$id} = $now;
$cachedata{$id} = encode_json $fc;
} else {
cluck sprintf 'Server return status %s with message: %s', $r->{status}, $r->{reason};
untie %cachetime;
untie %cachedata;
unless ($fc) {
cluck "[WARN] openweathermap returns corrupted json: $EVAL_ERROR";
return undef;
}
};
} else {
cluck sprintf 'Server return status %s with message: %s', $r->code, $r->message;
return undef;
}
untie %cachetime;
untie %cachedata;
# TODO: check all of this for existence
$w->{'name'} = $fc->{name};
$w->{'state'} = $fc->{state};

View File

@@ -5,18 +5,23 @@ use strict;
use warnings;
use utf8;
use open qw (:std :utf8);
use HTTP::Tiny;
use Mojo::UserAgent;
use version; our $VERSION = qw (1.0);
use Exporter qw (import);
our @EXPORT_OK = qw (xkcd);
sub xkcd {
my $http = HTTP::Tiny->new (timeout => 5, max_redirect => 0);
my $r = $http->get ('https://xkcd.ru/random/');
my $ua = Mojo::UserAgent->new->connect_timeout (5)->max_redirects (0);
my $r = $ua->get ('https://xkcd.ru/random/')->result;
if (defined $r->{headers} && defined $r->{headers}->{location} && $r->{headers}->{location} ne '') {
return sprintf '[xkcd.ru](https://xkcd.ru/i/%s_v1.png)', substr ($r->{headers}->{location}, 1, -1);
if (
defined $r->content &&
defined $r->content->headers &&
defined $r->content->headers->location &&
$r->content->headers->location ne ''
) {
return sprintf '[xkcd.ru](https://xkcd.ru/i/%s_v1.png)', substr ($r->content->headers->location, 1, -1);
}
return 'Комикс-стрип нарисовать не так-то просто :(';