diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..914e21d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,41 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ perl-job:
+ runs-on: [self-hosted, "${{ matrix.architecture }}" ]
+ container:
+ image: perl:${{ matrix.perl-version }}
+ strategy:
+ fail-fast: false
+ matrix:
+ architecture:
+ - X64
+ - ARM
+ perl-version:
+ - '5.32'
+ - 'latest'
+ name: Perl ${{ matrix.perl-version }}:${{ matrix.architecture }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install libraries and cpm
+ run: |
+ apt update && apt -y install libprotobuf-dev libprotoc-dev
+ cpanm local::lib
+ eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"
+ cpanm --mirror http://cpanproxy/ --mirror-only --notest App::cpm
+ - name: Install depedencies
+ run: |
+ eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"
+ cpm install -g -v --no-test --resolver 02packages,http://cpanproxy/ --configure-timeout 180 --build-timeout 600 --cpanfile=./cpanfile
+ - name: Run tests
+ run: |
+ eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"
+ prove
diff --git a/README.pod b/README.pod
index 7a2b2e4..84fc767 100644
--- a/README.pod
+++ b/README.pod
@@ -2,6 +2,12 @@
=head1 ABOUT
+=begin html
+
+
+
+=end html
+
This is the source code for perlbot, the resident infobot on Freenode’s
#perl channel. See:
@@ -24,6 +30,6 @@ This is a semi-complicated situation as while the code is obstensibly open sourc
buu has agreed to put his code under the GPL version 3 ( L ) or at your option any later version.
-Shlomi Fish licences his changes under any and all of the Expat license, the
+Shlomi Fish licenses his changes under any and all of the Expat license, the
CC0, the same terms as perl 5, and the Artistic 2.0 license.
=cut
diff --git a/bin/run-tests b/bin/run-tests
new file mode 100755
index 0000000..40e9e3e
--- /dev/null
+++ b/bin/run-tests
@@ -0,0 +1,45 @@
+#! /usr/bin/env perl
+#
+# Author Shlomi Fish
+# Version 0.0.1
+#
+use strict;
+use warnings;
+use autodie;
+
+# We'd rather avoid exec() here because from our experience, it sometimes
+# does not work too reliably on MSWin32.
+exit( ( system( "prove", glob("t/*.t") ) == 0 ) ? 0 : 1 );
+
+__END__
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2019 by Shlomi Fish
+
+This program is distributed under the MIT / Expat License:
+L
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
diff --git a/cpanfile b/cpanfile
index 3b5388f..fc23cae 100644
--- a/cpanfile
+++ b/cpanfile
@@ -1,165 +1,82 @@
-requires 'Sys::Linux::Namespace' => 'v0.13.0';
-requires 'POE';
-requires 'Parse::RecDescent';
-requires 'Config::General';
-requires 'Cache::FastMmap';
-requires 'POE::Component::IRC::Common';
-requires 'POE::Component::IRC';
-requires 'Text::Handlebars';
-
-requires 'Geo::IP';
-requires 'XML::RSS::Parser';
-requires 'WWW::Shorten';
-requires 'WWW::Mechanize';
-requires 'URI::Encode';
-requires 'Text::Glob';
-
-requires 'DBD::SQLite';
-requires 'DBI';
-requires 'Net::DNS';
-requires 'HTML::TreeBuilder';
-requires 'Net::INET6Glue::INET_is_INET6';
-
-requires 'Net::Dict';
-requires 'HTML::TreeBuilder::XPath';
-
-requires 'Data::Dumper';
-requires 'Scalar::Util'; #Required by Data::Dumper
-requires 'BSD::Resource';
-requires 'File::Glob';
-requires 'POSIX';
-requires 'POSIX::strptime';
-
-requires 'List::Util';
-requires 'List::MoreUtils';
-requires 'List::UtilsBy';
-requires 'Data::Munge';
-requires 'Scalar::MoreUtils';
-requires 'Regexp::Common';
-requires 'Encode';
-requires 'Digest::MD5';
-requires 'Digest::SHA';
-requires 'DateTime';
-requires 'DateTimeX::Easy';
-requires 'Date::Parse';
-
-requires 'Moose';
-requires 'MooseX::Declare';
-
-requires 'Function::Parameters';
-
-requires 'Rand::MersenneTwister';
-# requires 'arybase'; # removed in 5.29
-requires 'Errno';
-requires 'JSON';
-requires 'JSON::PP';
-requires 'JSON::XS';
-requires 'JSON::MaybeXS';
-requires 'Cpanel::JSON::XS';
-
-requires 'LWP::Protocol::https';
-requires 'Mojo::DOM';
-requires 'Mojo::DOM::CSS';
-requires 'Mojo::Collection';
-requires 'YAPE::Regex::Explain';
-requires 'bigint';
-requires 'Math::BigInt';
-requires 'Math::BigFloat';
-requires 'Math::BigRat';
-requires 'indirect';
-requires 'Moo';
-requires 'autovivification';
-
-requires 'Linux::Seccomp';
-requires 'Cwd';
-# requires 'Algorithm::Permute';
-requires 'File::Slurper';
-requires 'Path::Tiny';
-requires 'Time::Moment';
-requires 'Switch::Plain';
-requires 'Quote::Code';
-# requires 'JSON::Tiny'; # broken in the canary
-
-requires 'List::SomeUtils';
-requires 'IO::Async';
-requires 'Future';
-requires 'Class::Tiny';
-requires 'Capture::Tiny';
-
-requires 'Return::MultiLevel';
-requires 'Try::Tiny::ByClass';
-requires 'IPC::Run';
-requires 'Text::Metaphone';
-
-requires 'DBD::SQLite::BundledExtensions';
-requires 'Text::Levenshtein';
-requires 'Text::Metaphone';
-requires 'Math::Round';
-requires 'Twitter::API';
-requires 'Types::Standard';
-requires 'Perl::Tidy';
-requires 'File::Temp';
-requires 'Permute::Named::Iter';
-requires 'Marpa::R2';
-requires 'Syntax::Keyword::Try';
-requires 'File::Open';
-requires 'App::EvalServerAdvanced';
-requires 'Dir::ls';
-requires 'Object::Tap';
-requires 'XML::LibXML';
-# requires 'Sereal'; # comment out temporarily
-
-requires 'Email::Sender::Transport::Test';
-requires 'Task::Kensho::Async';
-requires 'Task::Kensho::Config';
-#requires 'Task::Kensho::Date';
-#requires 'Task::Kensho::DBDev';
-requires 'Task::Kensho::Email';
-requires 'Task::Kensho::Logging';
-requires 'Task::Kensho::ModuleDev';
-requires 'Task::Kensho::OOP';
-#requires 'Task::Kensho::Testing';
-#requires 'Task::Kensho::XML';
-requires 'Text::Unidecode';
-requires 'experimental';
-
-requires 'Math::Calc::Parser';
-
-requires 'ReadonlyX';
-requires 'Const::Fast';
-requires 'DateTime::Event::Holiday::US';
-requires 'App::EvalServerAdvanced::ConstantCalc';
-
-requires 'Crypt::OpenSSL::X509';
-
-requires 'Math::Random::Secure'; # undeclared dep of Data::Random::Flexible
-requires 'Data::Random::Flexible';
-requires 'Acme::AsciiEmoji';
-requires 'PadWalker';
-requires 'Encode::Simple';
-requires 'PPR';
-requires 'Keyword::Simple';
-requires 'Unicode::UTF8';
-requires 'List::Gather';
-requires 'Lingua::EN::Inflexion';
-requires 'local::lib';
-requires 'Array::Utils';
-requires 'DBD::SQLite';
-requires 'Mojo::SQLite';
-requires 'FFI::Platypus';
-requires 'Perl6::Take';
-requires 'List::AllUtils';
-requires 'IRC::FromANSI::Tiny';
-requires 'Unicode::GCString';
-requires 'Unicode::Util';
-requires 'Unicode::Collate';
-requires 'more';
-requires 'Data::Dumper::Compact';
-requires 'Carp::Always';
-requires 'V';
-requires 'Path::Tiny';
-requires 'CryptX';
-requires 'MIME::Base64';
-requires 'DateTime::Event::Cron';
-requires 'Regexp::Assemble';
-requires 'Regexp::Optimizer';
+requires "App::EvalServerAdvanced::Protocol";
+requires "autodie";
+requires "CGI";
+requires "Config::General";
+requires "Crypt::Mode::CBC";
+requires "CryptX";
+requires "Data::Dumper";
+requires "DateTime::Event::Cron";
+requires "DateTime::Event::Holiday::US";
+requires "DBD::SQLite";
+requires "DBI";
+requires "Encode";
+requires "Encode";
+requires "Encode";
+requires "Encode";
+requires "experimental";
+requires "Exporter";
+requires "feature";
+requires "FindBin";
+requires "GeoIP2::Database::Reader";
+requires "HTML::Entities";
+requires "HTML::TreeBuilder";
+requires "HTML::TreeBuilder::XPath";
+requires "HTTP::Status";
+requires "IO::Socket::INET";
+requires "IRC::FromANSI::Tiny";
+requires "IRC::Utils";
+requires "JSON";
+requires "JSON::MaybeXS";
+requires "JSON::MaybeXS";
+requires "JSON::MaybeXS";
+requires "JSON::MaybeXS";
+requires "lib";
+requires "lib::relative";
+requires "List::Util";
+requires "List::Util";
+requires "LWP::Simple";
+requires "LWP::UserAgent";
+requires "Memoize";
+requires "Memoize";
+requires "MIME::Base64";
+requires "Module::ScanDeps";
+requires "Net::CIDR";
+requires "Net::Dict";
+requires "Net::DNS";
+requires "Net::INET6Glue::INET_is_INET6";
+requires "Parse::RecDescent";
+requires "Path::Tiny";
+requires "POE";
+requires "POE::Component::IRC";
+requires "POE::Component::IRC::Common";
+requires "POE::Component::IRC::Common";
+requires "POE::Component::IRC::Plugin::AutoJoin";
+requires "POE::Component::IRC::Plugin::Connector";
+requires "POE::Component::IRC::Plugin::NickReclaim";
+requires "POE::Component::IRC::State";
+requires "POE::Component::Server::SimpleHTTP";
+requires "POE::Component::Server::TCP";
+requires "POE::Filter::Reference";
+requires "POE::Session";
+requires "POE::Wheel::ReadWrite";
+requires "POE::Wheel::Run";
+requires "POE::Wheel::SocketFactory";
+requires "PPI";
+requires "Regexp::Assemble";
+requires "Regexp::Optimizer";
+requires "Socket";
+requires "strict";
+requires "Term::ANSIColor";
+requires "Text::Glob";
+requires "Text::Metaphone";
+requires "Twitter::API";
+requires "Unicode::UCD";
+requires "URI::Encode";
+requires "URI::Escape";
+requires "utf8";
+requires "warnings";
+requires "WWW::Mechanize";
+requires "WWW::Shorten";
+requires "WWW::Shorten::TinyURL";
+requires "XML::RSS::Parser";
+requires "Test::Differences";
diff --git a/cpanfile2 b/cpanfile2
deleted file mode 100644
index 5b1864f..0000000
--- a/cpanfile2
+++ /dev/null
@@ -1,7 +0,0 @@
-Usage:
- % scan-prereqs-cpanfile
-
- --diff=META.json # Generate diff from META.json
- --diff=cpanfile # Generate diff from cpanfile
- --ignore=extlib/
-
diff --git a/cpanfile_diffed b/cpanfile_diffed
deleted file mode 100644
index ab50b1e..0000000
--- a/cpanfile_diffed
+++ /dev/null
@@ -1,164 +0,0 @@
---- cpanfile_scanned 2017-06-21 17:40:10.000000000 -0400
-+++ cpanfile_sorted 2017-06-21 17:41:53.000000000 -0400
-@@ -1,80 +1,91 @@
-
-requires 'autodie';
-requires 'Cache::FastMmap';
-requires 'Cache::File';
-requires 'Cache::Mmap';
-requires 'Config::General';
-requires 'Cpanel::JSON::XS';
-requires 'Data::Dumper';
-requires 'DBD::SQLite';
-requires 'DBI';
-requires 'Encode';
-requires 'HTTP::Status';
-requires 'IO::String';
-requires 'JSON::MaybeXS';
-requires 'perl', '5.24';
-requires 'POE';
-requires 'POE::Component::IRC';
-requires 'POE::Component::IRC::Common';
-requires 'POE::Component::IRC::Plugin::AutoJoin';
-requires 'POE::Component::IRC::Plugin::Connector';
-requires 'POE::Component::IRC::Plugin::NickReclaim';
-requires 'POE::Component::IRC::State';
-requires 'POE::Component::Server::SimpleHTTP';
-requires 'POE::Component::Server::TCP';
-requires 'POE::Filter::Line';
-requires 'POE::Filter::Reference';
-requires 'POE::Filter::Stream';
-requires 'POE::Session';
-requires 'POE::Wheel::ReadWrite';
-requires 'POE::Wheel::Run';
-requires 'POE::Wheel::SocketFactory';
-requires 'Scalar::Util';
-requires 'Socket';
-requires 'Sys::Linux::Mount';
-requires 'Sys::Linux::Namespace';
-requires 'Template';
-requires 'Term::ANSIColor';
-requires 'Text::ParseWords';
-requires 'Tie::Hash::NamedCapture';
-requires 'Data::Dumper';
- requires 'BSD::Resource';
- requires 'Cache::FastMmap';
- requires 'Parse::RecDescent';
-
- requires 'Text::Glob';
-
--requires 'Memoize';
-
-feature 'perlbot_plugins' => sub {
- requires 'App::EvalServerAdvanced::Protocol';
- requires 'Text::Metaphone';
- requires 'Geo::IP';
- requires 'WWW::Mechanize';
- requires 'WWW::Shorten';
--requires 'WWW::Shorten::TinyURL';
- requires 'DBD::SQLite::BundledExtensions';
-};
-
-feature 'eval_needed' => {
--requires 'Linux::Clone';
- requires 'Moo';
- requires 'Linux::Seccomp';
-+requires 'Mojo::Collection';
-+requires 'Mojo::DOM';
-+requires 'Mojo::DOM::CSS';
- requires 'Moo';
-+requires 'Marpa::R2';
- requires 'JSON';
-+requires 'JSON::PP';
-+requires 'JSON::Tiny';
-+requires 'JSON::XS';
-+requires 'arybase';
-+requires 'autovivification';
-+requires 'bigint';
-+requires 'Capture::Tiny';
-+requires 'Class::Tiny';
- requires 'BSD::Resource';
-+requires 'Syntax::Keyword::Try';
-+requires 'Capture::Tiny';
-+requires 'Class::Tiny';
-+requires 'Cpanel::JSON::XS';
-+requires 'Cwd';
-+requires 'Data::Munge';
-+requires 'Date::Parse';
-+requires 'DateTime';
-+requires 'DateTimeX::Easy';
-+requires 'Digest::MD5';
-+requires 'Digest::SHA';
-+requires 'Errno';
-+requires 'File::Glob';
-+requires 'File::Open';
- requires 'File::Slurper';
- requires 'File::Temp';
-}
-
-feature 'eval_optional' => {
-}
-
---- cpanfile_scanned 2017-06-21 18:24:13.000000000 -0400
-+++ cpanfile_sorted 2017-06-21 17:41:53.000000000 -0400
-@@ -1,50 +1,91 @@
- requires 'Encode';
-+requires 'Function::Parameters';
-+requires 'Future';
-+requires 'Geo::IP';
-+requires 'HTML::TreeBuilder';
-+requires 'HTML::TreeBuilder::XPath';
-+requires 'indirect';
-+requires 'IO::Async';
-+requires 'IPC::Run';
-+requires 'JSON';
- requires 'JSON::MaybeXS';
-+requires 'JSON::PP';
-+requires 'JSON::Tiny';
-+requires 'JSON::XS';
--requires 'Linux::Clone';
- requires 'Linux::Seccomp';
-+requires 'List::MoreUtils';
-+requires 'List::SomeUtils';
- requires 'List::Util';
-+requires 'List::UtilsBy';
-+requires 'LWP::Protocol::https';
-+requires 'Marpa::R2';
-+requires 'Math::BigFloat';
-+requires 'Math::BigInt';
-+requires 'Math::BigRat';
-+requires 'Math::Round';
-+requires 'Mojo::Collection';
-+requires 'Mojo::DOM';
-+requires 'Mojo::DOM::CSS';
-+requires 'Moose';
-+requires 'MooseX::Declare';
-+requires 'Net::Dict';
-+requires 'Net::DNS';
-+requires 'Net::INET6Glue::INET_is_INET6';
-+requires 'Path::Tiny';
- requires 'Perl::Tidy';
- requires 'Permute::Named::Iter';
-+requires 'POE';
-+requires 'POE::Component::IRC';
-+requires 'POE::Component::IRC::Common';
-+requires 'POSIX';
-+requires 'Quote::Code';
-+requires 'Rand::MersenneTwister';
-+requires 'Regexp::Common';
-+requires 'Return::MultiLevel';
-+requires 'Scalar::MoreUtils';
-+requires 'Scalar::Util'; #Required by Data::Dumper
-+requires 'Switch::Plain';
-+requires 'Syntax::Keyword::Try';
-+requires 'Sys::Linux::Namespace'.013;
-+requires 'Text::Levenshtein';
-+requires 'Text::Metaphone';
-+requires 'Time::Moment';
-+requires 'Try::Tiny::ByClass';
-+requires 'Types::Standard';
-+requires 'URI::Encode';
-+requires 'WWW::Mechanize';
-+requires 'WWW::Shorten';
-+requires 'XML::RSS::Parser';
-+requires 'YAPE::Regex::Explain';
diff --git a/cpanfile_scanned b/cpanfile_scanned
deleted file mode 100644
index fe2d6bb..0000000
--- a/cpanfile_scanned
+++ /dev/null
@@ -1,50 +0,0 @@
-requires 'BSD::Resource';
-requires 'Cache::FastMmap';
-requires 'Cache::File';
-requires 'Cache::Mmap';
-requires 'Config::General';
-requires 'DBD::SQLite';
-requires 'DBI';
-requires 'Encode';
-requires 'File::Slurper';
-requires 'File::Temp';
-requires 'HTTP::Status';
-requires 'IO::String';
-requires 'JSON::MaybeXS';
-requires 'Linux::Clone';
-requires 'Linux::Seccomp';
-requires 'List::Util';
-requires 'Memoize';
-requires 'Moo';
-requires 'POE';
-requires 'POE::Component::IRC';
-requires 'POE::Component::IRC::Common';
-requires 'POE::Component::IRC::Plugin::AutoJoin';
-requires 'POE::Component::IRC::Plugin::Connector';
-requires 'POE::Component::IRC::Plugin::NickReclaim';
-requires 'POE::Component::IRC::State';
-requires 'POE::Component::Server::SimpleHTTP';
-requires 'POE::Component::Server::TCP';
-requires 'POE::Filter::Line';
-requires 'POE::Filter::Reference';
-requires 'POE::Filter::Stream';
-requires 'POE::Session';
-requires 'POE::Wheel::ReadWrite';
-requires 'POE::Wheel::Run';
-requires 'POE::Wheel::SocketFactory';
-requires 'Parse::RecDescent';
-requires 'Perl::Tidy';
-requires 'Permute::Named::Iter';
-requires 'Scalar::Util';
-requires 'Socket';
-requires 'Sys::Linux::Mount';
-requires 'Sys::Linux::Namespace';
-requires 'Template';
-requires 'Term::ANSIColor';
-requires 'Text::Glob';
-requires 'Text::Handlebars';
-requires 'Text::Metaphone';
-requires 'Text::ParseWords';
-requires 'Tie::Hash::NamedCapture';
-requires 'autodie';
-requires 'perl', '5.24';
diff --git a/cpanfiletest.pl b/cpanfiletest.pl
deleted file mode 100644
index 9c694ed..0000000
--- a/cpanfiletest.pl
+++ /dev/null
@@ -1,23 +0,0 @@
-use strict;
-use warnings;
-
-use Module::CPANfile;
-use Data::Dumper;
-
-my $file = Module::CPANfile->load("/home/ryan/bots/perlbuut/cpanfile");
-
-my $prereqs = $file->prereqs;
-
-my @phases = $prereqs->phases;
-my @prereqs;
-
-for my $phase (@phases) {
- # TODO try/catch and check other types
- for my $type (qw/requires recommends/) {
- push @prereqs, $prereqs->requirements_for($phase, $type)->required_modules;
- }
-}
-
-# TODO uniq
-
-print Dumper(\@prereqs);
diff --git a/cpanfile_sorted b/evalserver_cpanfile
similarity index 55%
rename from cpanfile_sorted
rename to evalserver_cpanfile
index f544932..3b5388f 100644
--- a/cpanfile_sorted
+++ b/evalserver_cpanfile
@@ -1,91 +1,165 @@
-requires 'App::EvalServerAdvanced';
-requires 'arybase';
-requires 'autovivification';
-requires 'bigint';
-requires 'BSD::Resource';
-requires 'Cache::FastMmap';
-requires 'Capture::Tiny';
-requires 'Class::Tiny';
+requires 'Sys::Linux::Namespace' => 'v0.13.0';
+requires 'POE';
+requires 'Parse::RecDescent';
requires 'Config::General';
-requires 'Cpanel::JSON::XS';
-requires 'Cwd';
-requires 'Data::Dumper';
-requires 'Data::Munge';
-requires 'Date::Parse';
-requires 'DateTime';
-requires 'DateTimeX::Easy';
+requires 'Cache::FastMmap';
+requires 'POE::Component::IRC::Common';
+requires 'POE::Component::IRC';
+requires 'Text::Handlebars';
+
+requires 'Geo::IP';
+requires 'XML::RSS::Parser';
+requires 'WWW::Shorten';
+requires 'WWW::Mechanize';
+requires 'URI::Encode';
+requires 'Text::Glob';
+
requires 'DBD::SQLite';
-requires 'DBD::SQLite::BundledExtensions';
requires 'DBI';
+requires 'Net::DNS';
+requires 'HTML::TreeBuilder';
+requires 'Net::INET6Glue::INET_is_INET6';
+
+requires 'Net::Dict';
+requires 'HTML::TreeBuilder::XPath';
+
+requires 'Data::Dumper';
+requires 'Scalar::Util'; #Required by Data::Dumper
+requires 'BSD::Resource';
+requires 'File::Glob';
+requires 'POSIX';
+requires 'POSIX::strptime';
+
+requires 'List::Util';
+requires 'List::MoreUtils';
+requires 'List::UtilsBy';
+requires 'Data::Munge';
+requires 'Scalar::MoreUtils';
+requires 'Regexp::Common';
+requires 'Encode';
requires 'Digest::MD5';
requires 'Digest::SHA';
-requires 'Encode';
-requires 'Errno';
-requires 'File::Glob';
-requires 'File::Open';
-requires 'File::Slurper';
-requires 'File::Temp';
-requires 'Function::Parameters';
-requires 'Future';
-requires 'Geo::IP';
-requires 'HTML::TreeBuilder';
-requires 'HTML::TreeBuilder::XPath';
-requires 'indirect';
-requires 'IO::Async';
-requires 'IPC::Run';
-requires 'JSON';
-requires 'JSON::MaybeXS';
-requires 'JSON::PP';
-requires 'JSON::Tiny';
-requires 'JSON::XS';
-requires 'Linux::Seccomp';
-requires 'List::MoreUtils';
-requires 'List::SomeUtils';
-requires 'List::Util';
-requires 'List::UtilsBy';
-requires 'LWP::Protocol::https';
-requires 'Marpa::R2';
-requires 'Math::BigFloat';
-requires 'Math::BigInt';
-requires 'Math::BigRat';
-requires 'Math::Round';
-requires 'Mojo::Collection';
-requires 'Mojo::DOM';
-requires 'Mojo::DOM::CSS';
-requires 'Moo';
+requires 'DateTime';
+requires 'DateTimeX::Easy';
+requires 'Date::Parse';
+
requires 'Moose';
requires 'MooseX::Declare';
-requires 'Net::Dict';
-requires 'Net::DNS';
-requires 'Net::INET6Glue::INET_is_INET6';
-requires 'Parse::RecDescent';
-requires 'Path::Tiny';
-requires 'Perl::Tidy';
-requires 'Permute::Named::Iter';
-requires 'POE';
-requires 'POE::Component::IRC';
-requires 'POE::Component::IRC::Common';
-requires 'POSIX';
-requires 'Quote::Code';
+
+requires 'Function::Parameters';
+
requires 'Rand::MersenneTwister';
-requires 'Regexp::Common';
-requires 'Return::MultiLevel';
-requires 'Scalar::MoreUtils';
-requires 'Scalar::Util'; #Required by Data::Dumper
+# requires 'arybase'; # removed in 5.29
+requires 'Errno';
+requires 'JSON';
+requires 'JSON::PP';
+requires 'JSON::XS';
+requires 'JSON::MaybeXS';
+requires 'Cpanel::JSON::XS';
+
+requires 'LWP::Protocol::https';
+requires 'Mojo::DOM';
+requires 'Mojo::DOM::CSS';
+requires 'Mojo::Collection';
+requires 'YAPE::Regex::Explain';
+requires 'bigint';
+requires 'Math::BigInt';
+requires 'Math::BigFloat';
+requires 'Math::BigRat';
+requires 'indirect';
+requires 'Moo';
+requires 'autovivification';
+
+requires 'Linux::Seccomp';
+requires 'Cwd';
+# requires 'Algorithm::Permute';
+requires 'File::Slurper';
+requires 'Path::Tiny';
+requires 'Time::Moment';
requires 'Switch::Plain';
-requires 'Syntax::Keyword::Try';
-requires 'Sys::Linux::Namespace'.013;
-requires 'Text::Glob';
-requires 'Text::Handlebars';
+requires 'Quote::Code';
+# requires 'JSON::Tiny'; # broken in the canary
+
+requires 'List::SomeUtils';
+requires 'IO::Async';
+requires 'Future';
+requires 'Class::Tiny';
+requires 'Capture::Tiny';
+
+requires 'Return::MultiLevel';
+requires 'Try::Tiny::ByClass';
+requires 'IPC::Run';
+requires 'Text::Metaphone';
+
+requires 'DBD::SQLite::BundledExtensions';
requires 'Text::Levenshtein';
requires 'Text::Metaphone';
-requires 'Text::Metaphone';
-requires 'Time::Moment';
-requires 'Try::Tiny::ByClass';
+requires 'Math::Round';
requires 'Twitter::API';
requires 'Types::Standard';
-requires 'URI::Encode';
-requires 'WWW::Mechanize';
-requires 'WWW::Shorten';
-requires 'XML::RSS::Parser';
-requires 'YAPE::Regex::Explain';
+requires 'Perl::Tidy';
+requires 'File::Temp';
+requires 'Permute::Named::Iter';
+requires 'Marpa::R2';
+requires 'Syntax::Keyword::Try';
+requires 'File::Open';
+requires 'App::EvalServerAdvanced';
+requires 'Dir::ls';
+requires 'Object::Tap';
+requires 'XML::LibXML';
+# requires 'Sereal'; # comment out temporarily
+
+requires 'Email::Sender::Transport::Test';
+requires 'Task::Kensho::Async';
+requires 'Task::Kensho::Config';
+#requires 'Task::Kensho::Date';
+#requires 'Task::Kensho::DBDev';
+requires 'Task::Kensho::Email';
+requires 'Task::Kensho::Logging';
+requires 'Task::Kensho::ModuleDev';
+requires 'Task::Kensho::OOP';
+#requires 'Task::Kensho::Testing';
+#requires 'Task::Kensho::XML';
+requires 'Text::Unidecode';
+requires 'experimental';
+
+requires 'Math::Calc::Parser';
+
+requires 'ReadonlyX';
+requires 'Const::Fast';
+requires 'DateTime::Event::Holiday::US';
+requires 'App::EvalServerAdvanced::ConstantCalc';
+
+requires 'Crypt::OpenSSL::X509';
+
+requires 'Math::Random::Secure'; # undeclared dep of Data::Random::Flexible
+requires 'Data::Random::Flexible';
+requires 'Acme::AsciiEmoji';
+requires 'PadWalker';
+requires 'Encode::Simple';
+requires 'PPR';
+requires 'Keyword::Simple';
+requires 'Unicode::UTF8';
+requires 'List::Gather';
+requires 'Lingua::EN::Inflexion';
+requires 'local::lib';
+requires 'Array::Utils';
+requires 'DBD::SQLite';
+requires 'Mojo::SQLite';
+requires 'FFI::Platypus';
+requires 'Perl6::Take';
+requires 'List::AllUtils';
+requires 'IRC::FromANSI::Tiny';
+requires 'Unicode::GCString';
+requires 'Unicode::Util';
+requires 'Unicode::Collate';
+requires 'more';
+requires 'Data::Dumper::Compact';
+requires 'Carp::Always';
+requires 'V';
+requires 'Path::Tiny';
+requires 'CryptX';
+requires 'MIME::Base64';
+requires 'DateTime::Event::Cron';
+requires 'Regexp::Assemble';
+requires 'Regexp::Optimizer';
diff --git a/myscandeps.pl b/myscandeps.pl
new file mode 100644
index 0000000..7ded9a5
--- /dev/null
+++ b/myscandeps.pl
@@ -0,0 +1,22 @@
+use Module::ScanDeps;
+use Data::Dumper;
+
+my @files = ('./bin/cpan_fetch.pl', './bin/generate_metaphones.pl', './bin/test_eval.pl', './asndb/mkasn.pl', './plugins/head.pm', './plugins/echo.pm', './plugins/packages.pm', './plugins/translate.pm', './plugins/save_config.pm', './plugins/nick_lookup.pm', './plugins/tell.pm', './plugins/conf.pm', './plugins/oeis.pm', './plugins/reload_plugins.pm', './plugins/seen.pm', './plugins/part.pm', './plugins/utf8.pm', './plugins/cache_check.pm', './plugins/title.pm', './plugins/geoip.pm', './plugins/twitter.pm', './plugins/8ball.pm', './plugins/conf_dump.pm', './plugins/unicode.pm', './plugins/shorten.pm', './plugins/karma.pm', './plugins/join.pm', './plugins/perldoc.pm', './plugins/allowpaste.pm', './plugins/help.pm', './plugins/rss_title.pm', './plugins/get.pm', './plugins/plugins.pm', './plugins/define.pm', './plugins/default.pm', './plugins/host.pm', './plugins/arg.pm', './plugins/quote.pm', './plugins/pastebinadmin.pm', './plugins/null.pm', './plugins/host_lookup.pm', './plugins/zippit.pm', './plugins/talktome.pm', './plugins/factoids.pm', './plugins/karma_modify.pm', './plugins/google.pm', './plugins/compose.pm', './plugins/more.pm', './plugins/core.pm', './plugins/chatbot.pm', './plugins/rss.pm', './plugins/supereval.pm', './plugins/restart.pm', './plugins/karmatop.pm', './package_lists/generate_list_debian.pl', './lib/Bot/BB3.pm', './lib/Bot/BB3/Logger.pm', './lib/Bot/BB3/PluginManager.pm', './lib/Bot/BB3/ConfigParser.pm', './lib/Bot/BB3/DebugCrypt.pm', './lib/Bot/BB3/MacroQuote.pm', './lib/Bot/BB3/Roles/Console.pm', './lib/Bot/BB3/Roles/RestAPI.pm', './lib/Bot/BB3/Roles/Evalpastebin.pm', './lib/Bot/BB3/Roles/SocketMessageIRC.pm', './lib/Bot/BB3/Roles/IRC.pm', './lib/Bot/BB3/Roles/PasteBot.pm', './lib/Bot/BB3/PluginConfigParser.pm', './lib/Bot/BB3/PluginWrapper.pm');
+
+my $hash_ref = scan_deps(
+ # files => \@files,
+ files => ["plugins/geoip.pm"],
+ recurse => 0,
+);
+
+my @keys = keys %$hash_ref;
+
+my @used = sort {$a cmp $b} grep {!m|Bot/BB3|} grep {exists $hash_ref->{$_}{used_by} } @keys;
+
+my @mods = map {s|/|::|g; s|.pm$||r} @used;
+
+#print Dumper(\@mods);
+
+for my $mod (@mods) {
+ printf "requires '%s';\n", $mod;
+}
diff --git a/plugins/core.pm b/plugins/core.pm
index b425fb6..d6f63f0 100644
--- a/plugins/core.pm
+++ b/plugins/core.pm
@@ -29,10 +29,12 @@ sub {
if ( Module::CoreList->can('deprecated_in') ) {
my $dep = Module::CoreList->deprecated_in($module);
print " and deprecated in $dep" if $dep;
+ return 'handled';
}
if ( Module::CoreList->can('removed_from') ) {
my $rem = Module::CoreList->removed_from($module);
print " and removed from $rem" if $rem;
+ return 'handled';
}
}
else {
@@ -42,10 +44,12 @@ sub {
print 'Found', scalar @modules, ':', join ',',
map { $_ . ' in ' . Module::CoreList->first_release($_) }
@modules;
+ return 'handled';
}
else {
print "Module $module does not appear to be in core. Perhaps capitalization matters or try using the 'cpan' command to search for it.";
+ return 'handled';
}
}
}
diff --git a/plugins/factoids.pm b/plugins/factoids.pm
index 65a1e09..db51c3c 100644
--- a/plugins/factoids.pm
+++ b/plugins/factoids.pm
@@ -10,6 +10,8 @@ use Text::Metaphone;
use strict;
use Encode qw/decode/;
use JSON::MaybeXS qw/encode_json/;
+use PPI;
+use PPI::Dumper;
use Data::Dumper;
use List::Util qw/min max/;
@@ -553,13 +555,20 @@ sub get_fact_substitute ($self, $subject, $name, $said) {
my ($aliasserver, $aliasnamespace) = $self->get_alias_namespace($said);
my ($server, $namespace) = $self->get_namespace($said);
- if ( ($said->{body} =~ m{^(?:\s*substitute)?\s*(.*?)\s*=~\s*s /([^/]+ ) /([^/]* )/([gi]*)\s*$}ix)
- || ($said->{body} =~ m{^(?:\s*substitute)?\s*(.*?)\s*=~\s*s\|([^|]+ ) \|([^|]* )\|([gi]*)\s*$}ix)
- || ($said->{body} =~ m{^(?:\s*substitute)?\s*(.*?)\s*=~\s*s\{([^\}]+)\}\{([^\}]*?)\}([gi]*)\s*$}ix)
- || ($said->{body} =~ m{^(?:\s*substitute)?\s*(.*?)\s*=~\s*s\(([^)]+ )\)\(([^)]*? )\)([gi]*)\s*$}ix)
- || ($said->{body} =~ m{^(?:\s*substitute)?\s*(.*?)\s*=~\s*s <([^>]+ ) > <([^>]*? ) >([gi]*)\s*$}ix))
+ if ($said->{body} =~ m{^(?:\s*substitute)?\s*(.*?)\s*=~\s*s(.*)$}ix)
{
- my ($subject, $match, $subst, $flags) = ($1, $2, $3, $4);
+ my ($subject, $regex) = ($1, $2);
+ my $pdoc = PPI::Document->new(\$regex);
+ return "Failed to parse $regex" unless $pdoc;
+
+ # TODO handle tr|y///
+ my $token = $pdoc->find(sub {$_[1]->isa('PPI::Token::Regexp::Substitute')})->[0];
+
+ return "Couldn't find s/// in $regex" unless $token;
+
+ my $match = $token->get_match_string;
+ my $subst = $token->get_substitute_string;
+ my $flags = join '', keys +{$token->get_modifiers()}->%*;
# TODO does this need to be done via the ->get_fact() instead now?
my $fact = $self->_db_get_fact(_clean_subject($subject), 0, $server, $namespace);
@@ -572,11 +581,6 @@ sub get_fact_substitute ($self, $subject, $name, $said) {
#moving this to its own function for cleanliness
$result = $self->_fact_substitute($pred, $match, $subst, $flags);
- # my( $self, $body, $name, $said ) = @_;
-
- # $body =~ s/^\s*learn\s+//;
- # my( $subject, $predicate ) = split /\s+as\s+/, $body, 2;
-
# TODO why is this calling there?
# let this fail for now
my $ret = $self->get_fact_learn("learn $subject as $result", $name, $said, $subject, $result);
diff --git a/plugins/oeis.pm b/plugins/oeis.pm
index 67d753c..dedf201 100644
--- a/plugins/oeis.pm
+++ b/plugins/oeis.pm
@@ -6,15 +6,13 @@ use CGI;
use LWP::Simple;
use WWW::Shorten 'TinyURL';
-sub {
- my($said) = @_;
- my $q = $said->{"body"};
+sub query_oeis {
+ my ($q)=@_;
warn 1;
if( $q =~ /^\s*(?:(?:help|wtf|\?|\:)\s*)?$/i )
{
- print "see http://oeis.org/";
- return;
+ return([], ["see http://oeis.org/"]);
}
warn 2;
my $uri = "http://oeis.org/search?q=" . CGI::escape($q)."&fmt=text";
@@ -24,8 +22,7 @@ sub {
my $nrfound = $1;
unless( /^%N (\S+) (.*)/m )
{
- print "Reply from OEIS in unknown format 2";
- return;
+ return([ "Reply from OEIS in unknown format 2"],[]);
}
warn 3;
my($anum, $title) = ($1, $2);
@@ -35,21 +32,28 @@ sub {
warn 3.5;
if (1 == $nrfound) {
my $outuri = sprintf "http://oeis.org/%s", $anum;
- print sprintf "%s %.256s: %.512s", $outuri, $title, $elts;
+ return ([],[sprintf( "%s %.256s: %.512s", $outuri, $title, $elts)]);
} else {
my $outuri1 = "http://oeis.org/searchs?q=" . CGI::escape($q);
warn 3.6;
# my $outuri = makeashorterlink($outuri1) || $outuri1;
- print sprintf "%s %.10s(1/%d) %.256s: %.512s", $outuri1, $anum, $nrfound, $title, $elts;
+ return([], [sprintf( "%s %.10s(1/%d) %.256s: %.512s", $outuri1, $anum, $nrfound, $title, $elts)]);
}
} elsif (/^no matches/mi) {
- print "No matches found";
+ return([], ["No matches found"]);
warn 4
} else {
warn 5;
- print "Reply from OEIS in unknown format: $_";
+ return([ "Reply from OEIS in unknown format: $_"], []);
}
}
+sub {
+ my($said) = @_;
+ my $q = $said->{"body"};
+ my ($err, $out) = query_oeis($q);
+ print "Error: @$err\n" if @$err;
+ print "@$out\n";
+}
__DATA__
Search for a sequence in the On-Line Encyclopedia of Integer Sequences (http://tinyurl.com/2blo2w) Syntax, oeis 1,1,2,3,5
diff --git a/t/arg-plugin.t b/t/arg-plugin.t
new file mode 100644
index 0000000..2344c7b
--- /dev/null
+++ b/t/arg-plugin.t
@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use lib::relative './lib', '../lib', '..';
+use t::simple_plugin;
+use Encode qw/encode/;
+
+load_plugin("arg");
+
+check("&n", "perlbot", ['FOO'], "empty but valid");
+
+check("&a", "", ['FOO'], "macro args"); # this one is difficult to test for, it really gets the arguments to the parent macro
+check("", "", ['FOO'], "empty arguments, needs better check");
+
+check("&h", "irc.client.example.com", ['FOO'], "host");
+check("&c", "##NULL", ['FOO'], "channel");
+check("&o", 0, ['FOO'], "is op?");
+check("&s", "irc.server.example.com", ['FOO'], "server");
+
+done_testing();
diff --git a/t/core-plugin.t b/t/core-plugin.t
new file mode 100644
index 0000000..e69efa2
--- /dev/null
+++ b/t/core-plugin.t
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use lib::relative './lib', '../lib', '..';
+use t::simple_plugin;
+
+load_plugin("core");
+
+check("", "usage: core Module::Here", ["handled"], "usage help");
+check("CGI", "CGI Added to perl core as of 5.004 and deprecated in 5.019007", ["handled"], "deprecated");
+check("Data::Dumper", "Data::Dumper Added to perl core as of 5.005", ["handled"], "never gonna give it up");
+done_testing();
diff --git a/t/echo-plugin.t b/t/echo-plugin.t
new file mode 100644
index 0000000..fabc036
--- /dev/null
+++ b/t/echo-plugin.t
@@ -0,0 +1,16 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use lib::relative './lib', '../lib', '..';
+use t::simple_plugin;
+use Encode qw/encode/;
+
+load_plugin("echo");
+
+check("", "", [1], "empty but valid");
+check("Hello World", "Hello World", [1], "HW");
+check("\N{SNOWMAN}", encode("utf8", "\N{SNOWMAN}"), [1], "Encoding correctly");
+done_testing();
diff --git a/t/lib/t/common.pm b/t/lib/t/common.pm
new file mode 100644
index 0000000..9663356
--- /dev/null
+++ b/t/lib/t/common.pm
@@ -0,0 +1,48 @@
+package t::common;
+
+use strict;
+use warnings;
+use utf8;
+use parent 'Exporter';
+
+our @EXPORT=qw/load_plugin make_said/;
+
+# This doesn't let us test multiple plugins at a time, which might be needed for the compose plugin
+# This can be fixed later
+our $plugin;
+
+sub load_plugin {
+ my $name = shift;
+
+ my $fullname = "plugins/$name.pm";
+
+ $plugin = require $fullname;
+}
+
+sub make_said
+{
+ my ($body, $who, $server, $channel) = @_;
+
+ # TODO make this fill out a lot more of the said object
+
+ $who //= "perlbot";
+
+ my @args = split /\s+/, $body;
+ my $said = {
+ body => $body,
+ recommended_args => \@args,
+ macro_args => $body,
+ name => $who,
+ ircname => $who."irc",
+ host => "irc.client.example.com",
+ sender_raw => "", # this never gets filled out
+ channel => $channel // "##NULL",
+ server => $server // "irc.server.example.com",
+ by_chan_op => 0,
+ captured => "",
+ };
+
+ return $said;
+}
+
+1;
diff --git a/t/lib/t/simple_plugin.pm b/t/lib/t/simple_plugin.pm
new file mode 100644
index 0000000..f9f0368
--- /dev/null
+++ b/t/lib/t/simple_plugin.pm
@@ -0,0 +1,29 @@
+package t::simple_plugin;
+
+use strict;
+use warnings;
+use utf8;
+use parent 'Exporter';
+use t::common;
+
+our @EXPORT=qw/load_plugin make_said check/;
+
+use Test::Differences qw/ eq_or_diff /;
+use Capture::Tiny qw/capture/;
+
+sub check
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ( $body, $want, $res, $blurb ) = @_;
+
+ my $said = make_said($body);
+ my ($out, $err, @result) = capture {
+ $t::common::plugin->( $said );
+ };
+
+ return eq_or_diff( $err, "", "no errors" )
+ && eq_or_diff(\@result, $res, "Result is correct")
+ && eq_or_diff( $out, $want, $blurb );
+}
+
+1;
diff --git a/t/oeis-plugin.t b/t/oeis-plugin.t
new file mode 100644
index 0000000..bcb17da
--- /dev/null
+++ b/t/oeis-plugin.t
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use Test::More tests => 1;
+use lib '.';
+use plugins::oeis;
+
+# TEST
+like(
+ (query_oeis("1,2,6,24"))[1][0],
+ qr#https?://oeis\.org/.*?Factorial numbers: n!#ms,
+ "factorials",
+);
diff --git a/t/quote-plugin.t b/t/quote-plugin.t
new file mode 100644
index 0000000..788c9cc
--- /dev/null
+++ b/t/quote-plugin.t
@@ -0,0 +1,20 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use lib::relative './lib', '../lib', '..';
+use t::simple_plugin;
+
+load_plugin("quote");
+
+check("", "", [], "do nothing");
+check('d TESTING HERE', q{"TESTING HERE"}, [1], 'quote d simple');
+check('c TESTING HERE', q{TESTING HERE}, [1], 'quote d simple');
+check(qq{d "TESTING \nHERE"}, q{"\\x22TESTING \\x0aHERE\\x22"}, [1], 'quote d complex');
+check(qq{c "TESTING \nHERE"}, q{\\x22TESTING \\x0aHERE\\x22}, [1], 'quote c complex');
+check(qq{e "TESTING \nHERE"}, q{\\x22TESTING\\x20\\x0aHERE\\x22}, [1], 'quote e complex');
+check(qq{f "TESTING \nHERE"}, q{"\\x22TESTING\\x20\\x0aHERE\\x22"}, [1], 'quote f complex');
+check('h TESTING HERE', q{54455354494e472048455245}, [1], 'quote h');
+done_testing();
diff --git a/t/unicode-plugin.t b/t/unicode-plugin.t
new file mode 100644
index 0000000..290678b
--- /dev/null
+++ b/t/unicode-plugin.t
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use lib::relative './lib', '../lib', '..';
+use t::simple_plugin;
+use Encode qw/encode/;
+
+load_plugin("unicode");
+
+# TEST*2
+check(
+ "perl",
+ "U+0070 (70): LATIN SMALL LETTER P [p] ".
+ "U+0065 (65): LATIN SMALL LETTER E [e] ".
+ "U+0072 (72): LATIN SMALL LETTER R [r] ".
+ "U+006C (6c): LATIN SMALL LETTER L [l]\n",
+ [1],
+ "ascii"
+);
+
+# TEST*2
+check(
+ "💟",
+ encode("utf8", "U+1F49F (f0 9f 92 9f): HEART DECORATION [💟]\n"),
+ [1],
+ "emoji" );
+
+done_testing();