Skip to content

Commit

Permalink
Implement user list and automatic .tell
Browse files Browse the repository at this point in the history
Previously evalable6 and unicodable6 had to run NAMES command in order
to see if there's a specific user on the channel. Now bots
automatically keep track of join/part/quit/nick events, relying on
NAMES only to get the initial list. They will also use NAMES to
refresh the list from time to time, which is somehow needed in my IRC
client during netsplits and such.

Also, thanks to this tellable6 now implements automatic .tell when
you try to speak to a real user who is no longer on the channel.
  • Loading branch information
AlexDaniel committed Aug 14, 2019
1 parent fc4d257 commit a956d12
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 99 deletions.
85 changes: 0 additions & 85 deletions lib/Whateverable/Replaceable.pm6

This file was deleted.

97 changes: 97 additions & 0 deletions lib/Whateverable/Userlist.pm6
@@ -0,0 +1,97 @@
# Copyright © 2016-2019
# Aleks-Daniel Jakimenko-Aleksejev <alex.jakimenko@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

use Whateverable::Bits;

#| Keep track of users
unit role Whateverable::Userlist;

# This is a temporary solution (aha, sure). See this bug report:
# * https://github.com/zoffixznet/perl6-IRC-Client/issues/29

# We'll need at least one lock no matter what, so let's
# use *just* one to make things simpler
has %!userlist; #= %( channel0 => %(:user0, …), … );
has $!userlist-lock = Lock.new;

method userlist($msg) {
$!userlist-lock.protect: {
%!userlist{$msg.channel} // %()
}
}

#| Impersonate other bots
method make-believe($msg, @nicks, &play) {
my @found-nicks = self.userlist($msg){@nicks}:exists;
if @found-nicks.none {
$_ but Reply($msg) with play;
}
}

#| Nick change event
method irc-nick($event) {
$!userlist-lock.protect: {
for %!userlist.keys -> $channel {
%!userlist{$channel}{$event.nick}:delete;
%!userlist{$channel}{$event.new-nick} = True;
}
}
}
method irc-join($event) {
$!userlist-lock.protect: {
if not %!userlist{$event.channel} or (^30).pick == 0 { # self-healing behavior
$event.irc.send-cmd: NAMES, $event.channel;
}
%!userlist{$event.channel}{$event.nick} = True;
}
}
method irc-part($event) {
$!userlist-lock.protect: {
%!userlist{$event.channel}{$event.nick}:delete;
}
}
method irc-quit($event) {
$!userlist-lock.protect: {
for %!userlist.keys -> $channel {
%!userlist{$channel}{$event.nick}:delete;
}
}
}

has %!userlist-temp; # for storing partial messages

#| Receive a user list (one or more messages)
method irc-n353($event) {
my $channel = $event.args[2];
# Try to filter out privileges ↓
my @nicks = $event.args[3].words.map: { m/ (<.&irc-nick>) $ /[0].Str };
$!userlist-lock.protect: {
%!userlist-temp{$channel}{@nicks} = True xx @nicks
}
}

# XXX What if we receive a `join` right here? Whatever…

#| Receive a user list (final message)
method irc-n366($event) {
my $channel = $event.args[1];
$!userlist-lock.protect: {
%!userlist{$channel} = %!userlist-temp{$channel};
%!userlist-temp{$channel}:delete
}
}

# vim: expandtab shiftwidth=4 ft=perl6
4 changes: 2 additions & 2 deletions xbin/Evalable.p6
Expand Up @@ -22,13 +22,13 @@ use Whateverable::Bits;
use Whateverable::Builds;
use Whateverable::Config;
use Whateverable::Processing;
use Whateverable::Replaceable;
use Whateverable::Running;
use Whateverable::Userlist;

use IRC::Client;
use Terminal::ANSIColor;

unit class Evalable does Whateverable does Whateverable::Replaceable;
unit class Evalable does Whateverable does Whateverable::Userlist;

method help($msg) {
Like this: {$msg.server.current-nick}: say ‘hello’; say ‘world’
Expand Down
21 changes: 11 additions & 10 deletions xbin/Tellable.p6
Expand Up @@ -18,11 +18,12 @@
use Whateverable;
use Whateverable::Bits;
use Whateverable::FootgunDB;
use Whateverable::Userlist;

use IRC::Client;
use JSON::Fast;

unit class Tellable does Whateverable;
unit class Tellable does Whateverable does Whateverable::Userlist;

my $db-seen = FootgunDB.new: name => tellable-seen;
my $db-tell = FootgunDB.new: name => tellable-tell;
Expand Down Expand Up @@ -71,19 +72,19 @@ multi method irc-privmsg-channel($msg) {
$.NEXT
}

#`「 TODO implement proper user tracking first

#| automatic tell
multi method irc-privmsg-channel($msg where { m:r/^ \s* $<who>=<.&irc-nick> ‘:’+ \s+ (.*) $/ }) {
my $who = $<who>;
# TODO use `does Replaceable`
return $.NEXT if $who ~~ list-users.any; # still on the channel
return $.NEXT if self.userlist($msg){$who}; # still on the channel
my $normalized = normalize-weirdly $who;
my %seen := $db-seen.read;
return $.NEXT unless %seen{$who}:exists; # haven't seen them talk ever
my $last-seen-duration = DateTime.now(:0timezone) - DateTime.new(%seen{$who}<timestamp>);
return $.NEXT if $last-seen-duration ≥ 60×60×24 × 3; # haven't seen for months
return $.NEXT unless %seen{$normalized}:exists; # haven't seen them talk ever
my $last-seen-duration = DateTime.now(:0timezone) - DateTime.new(%seen{$normalized}<timestamp>);
return $.NEXT if $last-seen-duration60×60×24 × 28 × 3; # haven't seen for months
$msg.text = tell ~ $msg.text;
self.irc-to-me: $msg;
$.NEXT
}」
}

#| .seen
multi method irc-privmsg-channel($msg where .args[1] ~~ /^ ‘.seen’ \s+ (.*) /) {
Expand Down Expand Up @@ -141,6 +142,6 @@ multi method irc-to-me($msg where { m:r/^ \s* [[to|tell|ask] \s+]?
my %*BOT-ENV = %();

Tellable.new.selfrun: tellable6, [/ [to|tell|ask|seen] 6? <before ‘:’> /,
fuzzy-nick(tellable6, 3)];
fuzzy-nick(tellable6, 1)];

# vim: expandtab shiftwidth=4 ft=perl6
4 changes: 2 additions & 2 deletions xbin/Unicodable.p6
Expand Up @@ -21,13 +21,13 @@ use Whateverable;
use Whateverable::Bits;
use Whateverable::Builds;
use Whateverable::Processing;
use Whateverable::Replaceable;
use Whateverable::Running;
use Whateverable::Uniprops;
use Whateverable::Userlist;

use IRC::Client;

unit class Unicodable does Whateverable does Whateverable::Replaceable;
unit class Unicodable does Whateverable does Whateverable::Userlist;

constant MESSAGE-LIMIT = 3;
constant $LIMIT = 5_000;
Expand Down
50 changes: 50 additions & 0 deletions xt/tellable.t
Expand Up @@ -5,6 +5,7 @@ BEGIN %*ENV<PERL6_TEST_DIE_ON_FAIL> = 1;
use lib <lib xt/lib>;
use Test;
use Testable;
use IRC::Client;

my $t = Testable.new: bot => Tellable;

Expand Down Expand Up @@ -217,6 +218,55 @@ $t.test(‘receiving all messages (normalization)’,
# “{$t.our-nick}, I haven't seen x{$t.our-nick} around, did you mean {$t.our-nick}?”);


# Making sure that user tracking works

my $jnthn = IRC::Client.new(:nick(jnthn)
:host<127.0.0.1> :port(%*ENV<TESTABLE_PORT>)
:channels<#whateverable_tellable6>);
start $jnthn.run;
sleep 1;

$jnthn.send: where => #whateverable_tellable6, text => hello;

$t.test(autosend doesn't mistrigger (join),
jnthn: hello,);

$t.test(.seen is still working,
.seen jnthn,
/^ <me($t)>‘, I saw jnthn 2’\S+‘Z in #whateverable_tellable6: <jnthn> hello’ $/
);

$jnthn.nick: notjnthn;

$t.test(autosend works (nick change),
jnthn: where are you,
{$t.our-nick}, I'll pass your message to jnthn);

$jnthn.send: where => #whateverable_tellable6, text => right here;

$t.test(autosend doesn't mistrigger (nick change),
notjnthn: hello,);

$t.test(.seen is still working (again),
.seen notjnthn,
/^ <me($t)>‘, I saw notjnthn 2’\S+‘Z in #whateverable_tellable6: <notjnthn> right here’ $/
);

$jnthn.part: #whateverable_tellable6;
$t.test(autosend works (part),
notjnthn: where???,
{$t.our-nick}, I'll pass your message to notjnthn);

$jnthn.join: #whateverable_tellable6;
$t.test(autosend doesn't mistrigger (join),
notjnthn: hello,);

$jnthn.quit;
$t.test(autosend works (quit),
notjnthn: you've got to be kidding me,
{$t.our-nick}, I'll pass your message to notjnthn);


$t.last-test;
done-testing;
END $t.end;
Expand Down

0 comments on commit a956d12

Please sign in to comment.