#!/usr/bin/env rakudo use v6.d; use Config::TOML; use IRC::Client; my $config = from-toml( file => (%*ENV // $?FILE.IO.sibling('boha.toml').Str) ); role TracksOps does IRC::Client::Plugin { has %!nick-cache; has $!waiting-for-names = False; method !check-op($e) { return Promise.broken('not a channel message') unless $e.?channel; # if we already know whether that's an op, the cache will # contain a kept promise; if we don't, we have to wait for the # next 353, so we set and return a planned promise return %!nick-cache{$e.channel}{$e.nick} //= Promise.new(); } # response to /NAMES method irc-n353($e) { my ($my-nick,$equal,$channel,$names) = $e.args(); for $names.split(/\s+/) -> $name-str { my $user = $name-str ~~ / ^ $ = [ '@' | '+' ]? $ = [ .+ ] $ /; # I'm not sure what `+` means my $is-op = $user eq '@'; my $value := %!nick-cache{$channel}{$user}; if $value ~~ Promise && $value.status ~~ Planned { # someone called check-op before we saw this nick: # keep the promise $value.keep($is-op); } else { # either this nick changed state, or nobody asked # about it before: set a kept promise $value = Promise.kept($is-op); } } $!waiting-for-names = False; return $.NEXT; } method irc-join($e) { # someone (maybe us) joined the channel: update our ops map # # maybe we should do this every few seconds anyway… Promise.in(5).then: { unless ($!waiting-for-names) { $e.irc.send-cmd('NAMES', $e.channel, :server($e.server)); $!waiting-for-names = True; } }; return $.NEXT; } } class Boha1 does TracksOps { # irc-addressed for in-channel messages # irc-privmsg-me for direct messages method irc-addressed($e) { return self!check-op($e).then: sub ($promise) { if ($promise.status ~~ Broken) { # not a channel message? return Nil; } if (so $promise.result) { return "you're an op"; } else { return "you're a normal user"; } }; } } .run with IRC::Client.new( |($config), channels => $config.map(*.), :debug, :plugins( Boha1.new, ), );