3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-22 20:09:43 +01:00

Move Plugins directory to root, out of PBot source tree

This commit is contained in:
Pragmatic Software 2019-09-01 11:01:18 -07:00
parent 60cf77d538
commit fc8a1057c9
26 changed files with 377 additions and 47 deletions

View File

@ -45,10 +45,14 @@ sub initialize {
sub autoload {
my ($self, %conf) = @_;
return if $self->{pbot}->{registry}->get_value('plugins', 'noautoload');
my $path = $self->{pbot}->{registry}->get_value('plugins', 'path') // 'PBot/Plugins';
$self->{pbot}->{logger}->log("Loading plugins ...\n");
my $plugin_count = 0;
my @plugins = glob 'PBot/Plugins/*.pm';
my @plugins = glob "$path/*.pm";
foreach my $plugin (sort @plugins) {
$plugin = basename $plugin;
@ -68,9 +72,18 @@ sub load {
$self->unload($plugin);
my $class = "PBot::Plugins::$plugin";
return if $self->{pbot}->{registry}->get_value('plugins', 'disabled');
$self->{pbot}->{refresher}->{refresher}->refresh_module("PBot/Plugins/$plugin.pm");
my $path = $self->{pbot}->{registry}->get_value('plugins', 'path') // 'PBot/Plugins';
if (not grep { $_ eq $path } @INC) {
unshift @INC, $path;
}
my $class = $path . "/$plugin";
$class =~ s,[/\\],::,g;
$self->{pbot}->{refresher}->{refresher}->refresh_module("$path/$plugin.pm");
my $ret = eval {
eval "require $class";
@ -84,7 +97,7 @@ sub load {
$self->{pbot}->{logger}->log("Loading $plugin\n");
my $mod = $class->new(pbot => $self->{pbot}, %conf);
$self->{plugins}->{$plugin} = $mod;
$self->{pbot}->{refresher}->{refresher}->update_cache("PBot/Plugins/$plugin.pm");
$self->{pbot}->{refresher}->{refresher}->update_cache("$path/$plugin.pm");
return 1;
};
@ -110,8 +123,12 @@ sub unload {
$self->{pbot}->{logger}->log("Warning: got error unloading plugin $plugin: $@\n");
}
$self->{pbot}->{refresher}->{refresher}->unload_module("PBot::Plugins::$plugin");
$self->{pbot}->{refresher}->{refresher}->unload_subs("PBot/Plugins/$plugin.pm");
my $path = $self->{pbot}->{registry}->get_value('plugins', 'path') // 'PBot/Plugins';
my $class = $path;
$class =~ s,[/\\],::,g;
$self->{pbot}->{refresher}->{refresher}->unload_module($class . '::' . $plugin);
$self->{pbot}->{refresher}->{refresher}->unload_subs("$path/$plugin.pm");
$self->{pbot}->{logger}->log("Plugin $plugin unloaded.\n");
return 1;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::ActionTrigger;
package Plugins::ActionTrigger;
use warnings;
use strict;

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::AntiAway;
package Plugins::AntiAway;
use warnings;
use strict;

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::AntiKickAutoRejoin;
package Plugins::AntiKickAutoRejoin;
use warnings;
use strict;

View File

@ -8,7 +8,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::AntiNickSpam;
package Plugins::AntiNickSpam;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::AntiRepeat;
package Plugins::AntiRepeat;
use warnings;
use strict;

201
Plugins/AntiRepeat.pm.bak Normal file
View File

@ -0,0 +1,201 @@
package PBot::Plugins::AntiRepeat;
use warnings;
use strict;
use feature 'switch';
no if $] >= 5.018, warnings => "experimental::smartmatch";
use Carp ();
use String::LCSS qw/lcss/;
use Time::HiRes qw/gettimeofday/;
use POSIX qw/strftime/;
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat', $conf{antirepeat} // 1);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_threshold', $conf{antirepeat_threshold} // 2.5);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_match', $conf{antirepeat_match} // 0.5);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_allow_bot', $conf{antirepeat_allow_bot} // 1);
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{timer}->register(sub { $self->adjust_offenses }, 60 * 60 * 1, 'antirepeat');
$self->{offenses} = {};
}
sub unload {
my $self = shift;
# perform plugin clean-up here
# normally we'd unregister the 'irc.public' event handler; however, the
# event dispatcher will do this automatically for us when it sees there
# is no longer an existing sub.
$self->{pbot}->{timer}->unregister('antirepeat');
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if not $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat');
my $antirepeat = $self->{pbot}->{registry}->get_value($channel, 'antirepeat');
return 0 if defined $antirepeat and not $antirepeat;
return 0 if $channel !~ m/^#/;
return 0 if $event->{interpreted};
return 0 if $self->{pbot}->{antiflood}->whitelisted($channel, "$nick!$user\@$host", 'antiflood');
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages($account, $channel, 6, $self->{pbot}->{messagehistory}->{MSG_CHAT});
my %matches;
my $log = '';
my $now = gettimeofday;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $bot_trigger = $self->{pbot}->{registry}->get_value($channel, 'trigger')
// $self->{pbot}->{registry}->get_value('general', 'trigger');
my $allow_bot = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_allow_bot')
// $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_allow_bot');
my $match = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_match')
// $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_match');
foreach my $string1 (@$messages) {
next if length $string1->{msg} < 10;
next if $now - $string1->{timestamp} > 60 * 60 * 2;
next if $allow_bot and $string1->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
if (exists $self->{offenses}->{$account} and exists $self->{offenses}->{$account}->{$channel}) {
next if $self->{offenses}->{$account}->{$channel}->{last_offense} >= $string1->{timestamp};
}
foreach my $string2 (@$messages) {
next if length $string2->{msg} < 10;
next if $now - $string2->{timestamp} > 60 * 60 * 2;
next if $allow_bot and $string2->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
if (exists $self->{offenses}->{$account} and exists $self->{offenses}->{$account}->{$channel}) {
next if $self->{offenses}->{$account}->{$channel}->{last_offense} >= $string2->{timestamp};
}
my $string = lcss($string1->{msg}, $string2->{msg});
if (defined $string) {
my $length = length $string;
my $length1 = $length / length $string1->{msg};
my $length2 = $length / length $string2->{msg};
$log .= " $string -- ";
$log .= " 1: [$string1->{msg}] -- ";
$log .= " 2: [$string2->{msg}]";
$log .= " $length1 - $length2\n";
if ($length1 >= $match && $length2 >= $match) {
$matches{$string}++;
}
}
}
}
my $threshold = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_threshold')
// $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_threshold');
my $ban = 0;
foreach my $match (keys %matches) {
my $sqrt = sqrt $matches{$match};
$log .= "- $matches{$match} (sqrt: $sqrt) matches: $match\n";
if (sqrt $matches{$match} > $threshold) {
$log .= " +++ BAN!\n";
$ban = 1;
}
}
if ($ban) {
print "------------\n";
print $log;
open my $fh, '>> banlog.txt';
print $fh "-------------\n";
print $fh "$channel <$nick!$user\@$host> $msg\n";
foreach my $string (@$messages) {
my $time = strftime "%a %b %e %H:%M:%S %Z %Y", localtime $string->{timestamp};
print $fh "-> $time $string->{msg}\n";
}
print $fh $log;
close $fh;
if ($ban && ($channel eq '##c')) {
$self->{offenses}->{$account}->{$channel}->{last_offense} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{last_adjustment} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{offenses}++;
$self->{pbot}->{logger}->log("antirepeat: offenses for $account/$channel = $self->{offenses}->{$account}->{$channel}->{offenses}\n");
given ($self->{offenses}->{$account}->{$channel}->{offenses}) {
when (1) {
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick Stop repeating yourself");
$self->{pbot}->{chanops}->gain_ops($channel);
}
when (2) {
$self->{pbot}->{chanops}->ban_user_timed("*!*\@$host", $channel, 60 * 15);
}
when (3) {
$self->{pbot}->{chanops}->ban_user_timed("*!*\@$host", $channel, 60 * 60 * 24 * 7);
}
default {
$self->{pbot}->{chanops}->ban_user_timed("*!*\@$host", $channel, 60 * 60 * 24 * 31);
}
}
}
}
return 0;
}
sub adjust_offenses {
my $self = shift;
my $now = gettimeofday;
foreach my $account (keys %{ $self->{offenses} }) {
foreach my $channel (keys %{ $self->{offenses}->{$account} }) {
if ($self->{offenses}->{$account}->{$channel}->{offenses} > 0 and $now - $self->{offenses}->{$account}->{$channel}->{last_adjustment} > 60 * 60 * 3) {
$self->{offenses}->{$account}->{$channel}->{offenses}--;
if ($self->{offenses}->{$account}->{$channel}->{offenses} <= 0) {
delete $self->{offenses}->{$account}->{$channel};
if (keys $self->{offenses}->{$account} == 0) {
delete $self->{offenses}->{$account};
}
} else {
$self->{offenses}->{$account}->{$channel}->{last_adjustment} = $now;
}
}
}
}
}
1;

View File

@ -8,7 +8,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::AntiTwitter;
package Plugins::AntiTwitter;
use warnings;
use strict;

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::AutoRejoin;
package Plugins::AutoRejoin;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Battleship;
package Plugins::Battleship;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Connect4;
package Plugins::Connect4;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Counter;
package Plugins::Counter;
use warnings;
use strict;

View File

@ -7,7 +7,7 @@
# module and refreshing loaded modules before running the magical
# command).
package PBot::Plugins::MagicCommand;
package Plugins::MagicCommand;
use warnings;
use strict;

View File

@ -4,7 +4,7 @@
# Just a quick interface to test/play with PBot::Utils::ParseDate
package PBot::Plugins::ParseDate;
package Plugins::ParseDate;
use warnings;
use strict;

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Quotegrabs;
package Plugins::Quotegrabs;
use warnings;
use strict;
@ -19,8 +19,8 @@ use Time::Duration;
use Time::HiRes qw(gettimeofday);
use Getopt::Long qw(GetOptionsFromArray);
use PBot::Plugins::Quotegrabs::Quotegrabs_SQLite; # use SQLite backend for quotegrabs database
#use PBot::Plugins::Quotegrabs::Quotegrabs_Hashtable; # use Perl hashtable backend for quotegrabs database
use Plugins::Quotegrabs::Quotegrabs_SQLite; # use SQLite backend for quotegrabs database
#use Plugins::Quotegrabs::Quotegrabs_Hashtable; # use Perl hashtable backend for quotegrabs database
use PBot::Utils::ValidateString;
use POSIX qw(strftime);
@ -40,21 +40,29 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in Quotegrabs");
$self->{filename} = delete $conf{quotegrabs_file};
$self->{export_path} = delete $conf{export_quotegrabs_path};
$self->{export_site} = delete $conf{export_quotegrabs_site};
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference in Quotegrabs");
$self->{filename} = $conf{quotegrabs_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quotegrabs.sqlite3';
$self->{export_path} = $conf{export_quotegrabs_path} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quotegrabs.html';
$self->{export_site} = $conf{export_quotegrabs_site} // 'http://www.iso-9899.info/candide/quotegrabs.html';
$self->{database} = PBot::Plugins::Quotegrabs::Quotegrabs_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
#$self->{database} = PBot::Plugins::Quotegrabs::Quotegrabs_Hashtable->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database} = Plugins::Quotegrabs::Quotegrabs_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
#$self->{database} = Plugins::Quotegrabs::Quotegrabs_Hashtable->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();
$self->{pbot}->{atexit}->register(sub { $self->{database}->end(); return; });
$self->{pbot}->{commands}->register(sub { $self->grab_quotegrab(@_) }, "grab", 0);
$self->{pbot}->{commands}->register(sub { $self->show_quotegrab(@_) }, "getq", 0);
$self->{pbot}->{commands}->register(sub { $self->delete_quotegrab(@_) }, "delq", 0);
$self->{pbot}->{commands}->register(sub { $self->show_random_quotegrab(@_) }, "rq", 0);
$self->{pbot}->{commands}->register(sub { $self->grab_quotegrab(@_) }, 'grab', 0);
$self->{pbot}->{commands}->register(sub { $self->show_quotegrab(@_) }, 'getq', 0);
$self->{pbot}->{commands}->register(sub { $self->delete_quotegrab(@_) }, 'delq', 0);
$self->{pbot}->{commands}->register(sub { $self->show_random_quotegrab(@_) }, 'rq', 0);
}
sub unload {
my ($self) = @_;
$self->{pbot}->{commands}->unregister('grab');
$self->{pbot}->{commands}->unregister('getq');
$self->{pbot}->{commands}->unregister('delq');
$self->{pbot}->{commands}->unregister('rq');
}
sub uniq { my %seen; grep !$seen{$_}++, @_ }
@ -332,7 +340,7 @@ sub show_random_quotegrab {
};
my @opt_args = $self->{pbot}->{interpreter}->split_line($arguments, preserve_escapes => 1, strip_quotes => 1);
my ($ret, $rest) = GetOptionsFromArray(\@opt_args,
GetOptionsFromArray(\@opt_args,
'channel|c=s' => \$channel_search,
'text|t=s' => \$text_search);

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Quotegrabs::Quotegrabs_Hashtable;
package Plugins::Quotegrabs::Quotegrabs_Hashtable;
use warnings;
use strict;

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Quotegrabs::Quotegrabs_SQLite;
package Plugins::Quotegrabs::Quotegrabs_SQLite;
use warnings;
use strict;

View File

@ -1,5 +1,5 @@
package PBot::Plugins::RelayUnreg;
package Plugins::RelayUnreg;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::RemindMe;
package Plugins::RemindMe;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Spinach;
package Plugins::Spinach;
use warnings;
use strict;
@ -35,8 +35,8 @@ $Data::Dumper::Useqq = 1;
use PBot::HashObject;
use PBot::Plugins::Spinach::Stats;
use PBot::Plugins::Spinach::Rank;
use Plugins::Spinach::Stats;
use Plugins::Spinach::Rank;
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
@ -69,8 +69,8 @@ sub initialize {
$self->{metadata} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Spinach Metadata', filename => $self->{metadata_filename});
$self->load_metadata;
$self->{stats} = PBot::Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{stats_filename});
$self->{rankcmd} = PBot::Plugins::Spinach::Rank->new(pbot => $self->{pbot}, channel => $self->{channel}, filename => $self->{stats_filename});
$self->{stats} = Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{stats_filename});
$self->{rankcmd} = Plugins::Spinach::Rank->new(pbot => $self->{pbot}, channel => $self->{channel}, filename => $self->{stats_filename});
$self->create_states;
$self->load_questions;

View File

@ -4,7 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Spinach::Rank;
package Plugins::Spinach::Rank;
use warnings;
use strict;
@ -14,7 +14,7 @@ use feature 'unicode_strings';
use FindBin;
use lib "$FindBin::RealBin/../../..";
use PBot::Plugins::Spinach::Stats;
use Plugins::Spinach::Stats;
use Math::Expression::Evaluator;
sub new {
@ -30,7 +30,7 @@ sub initialize {
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{channel} = $conf{channel} // Carp::croak("Missing channel reference to " . __FILE__);
$self->{filename} = $conf{filename} // 'stats.sqlite';
$self->{stats} = PBot::Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{stats} = Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{filename});
}
sub sort_generic {

View File

@ -4,7 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::Spinach::Stats;
package Plugins::Spinach::Stats;
use warnings;
use strict;

View File

@ -0,0 +1,104 @@
#!/usr/bin/perl
use warnings;
use strict;
use JSON;
use Time::Piece;
my $self = {};
sub load_questions {
my ($filename) = @_;
if (not defined $filename) {
$filename = $ENV{HOME} . "/pbot/data/spinach/trivia.json";
}
$self->{loaded_filename} = $filename;
my $contents = do {
open my $fh, '<', $filename or do {
print "Spinach: Failed to open $filename: $!\n";
return "Failed to load $filename";
};
local $/;
<$fh>;
};
$self->{questions} = decode_json $contents;
$self->{categories} = ();
my $questions;
foreach my $key (keys %{$self->{questions}}) {
foreach my $question (@{$self->{questions}->{$key}}) {
$question->{category} = uc $question->{category};
$self->{categories}{$question->{category}}{$question->{id}} = $question;
if (not exists $question->{seen_timestamp}) {
$question->{seen_timestamp} = 0;
}
$questions++;
}
}
my $categories;
foreach my $category (sort { keys %{$self->{categories}{$b}} <=> keys %{$self->{categories}{$a}} } keys %{$self->{categories}}) {
my $count = keys %{$self->{categories}{$category}};
print "Category [$category]: $count\n";
$categories++;
}
print "Spinach: Loaded $questions questions in $categories categories.\n";
return "Loaded $questions questions in $categories categories.";
}
sub save_questions {
my $json = encode_json $self->{questions};
my $filename = exists $self->{loaded_filename} ? $self->{loaded_filename} : $self->{questions_filename};
open my $fh, '>', $filename or do {
print "Failed to open Spinach file $filename: $!\n";
return;
};
print $fh "$json\n";
close $fh;
}
load_questions;
open my $fh, '<', 'seent' or do {
print "Failed to open seent file: $!\n";
die;
};
my $nr = 0;
foreach my $line (<$fh>) {
++$nr;
my ($date, $id) = $line =~ m/^(.*?) :: .*? question:.*?\s(\d+,?\d*)\)/;
if (not defined $date or not defined $id) {
print "Parse error at line $nr\n";
die;
}
$id =~ s/,//g;
print "matched [$date] and [$id]\n";
my $time = Time::Piece->strptime($date, "%a %b %e %H:%M:%S %Y");
print "epoch: ", $time->epoch, "\n";
foreach my $q (@{$self->{questions}->{questions}}) {
if ($q->{id} == $id) {
print "question: $q->{question}\n";
$q->{seen_timestamp} = $time->epoch;
last;
}
}
}
close $fh;
save_questions;

View File

@ -7,7 +7,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::UrlTitles;
package Plugins::UrlTitles;
use warnings;
use strict;

View File

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Plugins::_Example;
package Plugins::_Example;
use warnings;
use strict;