Greatly improve channel handling

This also does some more bits of rudimentary user handling, but most of it still remains to be done.

Note: This adds DEV_PREFIX to botconfig and LOG_PREFIX to settings (var), make sure to properly update your bot!
This commit is contained in:
Vgr E. Barry 2016-11-02 22:31:54 -04:00
parent 49208db148
commit d090e573b7
10 changed files with 217 additions and 281 deletions

View File

@ -35,7 +35,8 @@ DISABLE_DEBUG_MODE_TIME_LORD = False
ALT_CHANNELS = "" ALT_CHANNELS = ""
ALLOWED_ALT_CHANNELS_COMMANDS = [] ALLOWED_ALT_CHANNELS_COMMANDS = []
DEV_CHANNEL = "" DEV_CHANNEL = "" # Important: Do *not* include the message prefix!
DEV_PREFIX = "" # The prefix to send to the dev channel (e.g. "+" will send to "+#dev-chan")
PASTEBIN_ERRORS = False # If DEV_CHANNEL is set, errors will be posted there. PASTEBIN_ERRORS = False # If DEV_CHANNEL is set, errors will be posted there.
LOG_CHANNEL = "" # Log !fwarns to this channel, if set LOG_CHANNEL = "" # Log !fwarns to this channel, if set

View File

@ -136,7 +136,6 @@
"ping_player": "PING! {0} player{1}! ", "ping_player": "PING! {0} player{1}! ",
"already_voted_game": "You have already voted for the {0} game mode.", "already_voted_game": "You have already voted for the {0} game mode.",
"vote_game_mode": "\u0002{0}\u0002 votes for the \u0002{1}\u0002 game mode.", "vote_game_mode": "\u0002{0}\u0002 votes for the \u0002{1}\u0002 game mode.",
"bot_not_opped": "Sorry, I'm not opped in {0}.",
"already_playing": "{0}'re already playing!", "already_playing": "{0}'re already playing!",
"too_many_players": "Too many players! Try again next time.", "too_many_players": "Too many players! Try again next time.",
"game_already_running": "Sorry, but the game is already running. Try again next time.", "game_already_running": "Sorry, but the game is already running. Try again next time.",

View File

@ -7,6 +7,8 @@ from src.logger import debuglog
from src import users from src import users
Main = None # main channel Main = None # main channel
Dummy = None # fake channel
Dev = None # dev channel
_channels = {} _channels = {}
@ -175,7 +177,7 @@ class Channel(IRCContext):
if c in status_modes: # op/voice status; keep it here and update the user's registry too if c in status_modes: # op/voice status; keep it here and update the user's registry too
if c not in self.modes: if c not in self.modes:
self.modes[c] = set() self.modes[c] = set()
user = users.get(targets[i], allow_bot=True) user = users._get(targets[i], allow_bot=True) # FIXME
self.modes[c].add(user) self.modes[c].add(user)
user.channels[self].add(c) user.channels[self].add(c)
i += 1 i += 1
@ -199,7 +201,7 @@ class Channel(IRCContext):
else: else:
if c in status_modes: if c in status_modes:
if c in self.modes: if c in self.modes:
user = users.get(targets[i], allow_bot=True) user = users._get(targets[i], allow_bot=True) # FIXME
self.modes[c].discard(user) self.modes[c].discard(user)
user.channels[self].discard(c) user.channels[self].discard(c)
if not self.modes[c]: if not self.modes[c]:
@ -241,10 +243,10 @@ class FakeChannel(Channel):
is_fake = True is_fake = True
def join(self, key=""): def join(self, key=""):
pass # don't actually do anything self.state = _States.Joined
def part(self, message=""): def part(self, message=""):
pass self.state = _States.Left
def send(self, data, *, notice=False, privmsg=False): def send(self, data, *, notice=False, privmsg=False):
debuglog("Would message fake channel {0}: {1!r}".format(self.name, data)) debuglog("Would message fake channel {0}: {1!r}".format(self.name, data))

View File

@ -40,6 +40,42 @@ class IRCContext:
return "NOTICE" return "NOTICE"
return "PRIVMSG" return "PRIVMSG"
@staticmethod
def _who(cli, target, data=b""):
"""Handle WHO requests."""
if isinstance(data, str):
data = data.encode(Features["CHARSET"])
elif isinstance(data, int):
if data > 0xFFFFFF:
data = b""
else:
data = data.to_bytes(3, "little")
if len(data) > 3:
data = b""
if "WHOX" in Features:
cli.send("WHO", target, b"%tcuihsnfdlar," + data)
else:
cli.send("WHO", target)
return int.from_bytes(data, "little")
def who(self, data=b""):
"""Send a WHO request with respect to the server's capabilities.
To get the WHO replies, add an event listener for "who_result",
and an event listener for "who_end" for the end of WHO replies.
The return value of this function is an integer equal to the data
given. If the server supports WHOX, the same integer will be in the
event.params.data attribute. Otherwise, this attribute will be 0.
"""
return self._who(self.client, self.name, data)
@staticmethod @staticmethod
def _send(data, client, send_type, name): def _send(data, client, send_type, name):
full_address = "{cli.nickname}!{cli.ident}@{cli.hostmask}".format(cli=client) full_address = "{cli.nickname}!{cli.ident}@{cli.hostmask}".format(cli=client)

View File

@ -5,50 +5,32 @@ import socket
import sys import sys
import traceback import traceback
from oyoyo.parse import parse_nick
import botconfig import botconfig
import src.settings as var import src.settings as var
from src import decorators, wolfgame, errlog as log, stream_handler as alog from src import decorators, wolfgame, channels, hooks, users, errlog as log, stream_handler as alog
from src.utilities import irc_equals
hook = decorators.hook hook = decorators.hook
def on_privmsg(cli, rawnick, chan, msg, *, notice=False): def on_privmsg(cli, rawnick, chan, msg, *, notice=False):
try: if notice and "!" not in rawnick or not rawnick: # server notice; we don't care about those
prefixes = getattr(var, "STATUSMSG_PREFIXES")
except AttributeError:
pass
else:
if botconfig.IGNORE_HIDDEN_COMMANDS and chan[0] in prefixes:
return return
try: if chan != botconfig.NICK and botconfig.IGNORE_HIDDEN_COMMANDS and not chan.startswith(tuple(hooks.Features["CHANTYPES"])):
getattr(var, "CASEMAPPING")
except AttributeError:
# some kind of hack for strange networks which don't put server name in some of the NOTICEs on startup
if not rawnick:
return
if notice and "!" not in rawnick and chan in ("*", "AUTH"):
# On-connect message before RPL_ISUPPORT is sent.
return return
log("Server did not send a case mapping; falling back to rfc1459.") if (notice and ((not users.equals(chan, botconfig.NICK) and not botconfig.ALLOW_NOTICE_COMMANDS) or
var.CASEMAPPING = "rfc1459" (users.equals(chan, botconfig.NICK) and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
if (notice and ((not irc_equals(chan, botconfig.NICK) and not botconfig.ALLOW_NOTICE_COMMANDS) or
(irc_equals(chan, botconfig.NICK) and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
return # not allowed in settings return # not allowed in settings
if irc_equals(chan, botconfig.NICK): if users.equals(chan, botconfig.NICK):
chan = parse_nick(rawnick)[0] chan = users.parse_rawnick_as_dict(rawnick)["nick"]
for fn in decorators.COMMANDS[""]: for fn in decorators.COMMANDS[""]:
fn.caller(cli, rawnick, chan, msg) fn.caller(cli, rawnick, chan, msg)
phase = var.PHASE phase = var.PHASE
for x in list(decorators.COMMANDS.keys()): for x in list(decorators.COMMANDS.keys()):
if chan != parse_nick(rawnick)[0] and not msg.lower().startswith(botconfig.CMD_CHAR): if chan != users.parse_rawnick_as_dict(rawnick)["nick"] and not msg.lower().startswith(botconfig.CMD_CHAR):
break # channel message but no prefix; ignore break # channel message but no prefix; ignore
if msg.lower().startswith(botconfig.CMD_CHAR+x): if msg.lower().startswith(botconfig.CMD_CHAR+x):
h = msg[len(x)+len(botconfig.CMD_CHAR):] h = msg[len(x)+len(botconfig.CMD_CHAR):]
@ -61,14 +43,9 @@ def on_privmsg(cli, rawnick, chan, msg, *, notice=False):
if phase == var.PHASE: if phase == var.PHASE:
fn.caller(cli, rawnick, chan, h.lstrip()) fn.caller(cli, rawnick, chan, h.lstrip())
def unhandled(cli, prefix, cmd, *args): def unhandled(cli, prefix, cmd, *args):
if cmd in decorators.HOOKS:
largs = list(args)
for i,arg in enumerate(largs):
if isinstance(arg, bytes): largs[i] = arg.decode('ascii')
for fn in decorators.HOOKS.get(cmd, []): for fn in decorators.HOOKS.get(cmd, []):
fn.caller(cli, prefix, *largs) fn.caller(cli, prefix, *args)
def connect_callback(cli): def connect_callback(cli):
@hook("endofmotd", hookid=294) @hook("endofmotd", hookid=294)
@ -76,6 +53,12 @@ def connect_callback(cli):
def prepare_stuff(cli, prefix, *args): def prepare_stuff(cli, prefix, *args):
alog("Received end of MOTD from {0}".format(prefix)) alog("Received end of MOTD from {0}".format(prefix))
# This callback only sets up event listeners
wolfgame.connect_callback()
users.Bot = users.User(cli, botconfig.NICK, None, None, None, None, {})
users.Bot.modes = set() # only for the bot (user modes)
# just in case we haven't managed to successfully auth yet # just in case we haven't managed to successfully auth yet
if not botconfig.SASL_AUTHENTICATION: if not botconfig.SASL_AUTHENTICATION:
cli.ns_identify(botconfig.USERNAME or botconfig.NICK, cli.ns_identify(botconfig.USERNAME or botconfig.NICK,
@ -83,26 +66,24 @@ def connect_callback(cli):
nickserv=var.NICKSERV, nickserv=var.NICKSERV,
command=var.NICKSERV_IDENTIFY_COMMAND) command=var.NICKSERV_IDENTIFY_COMMAND)
channels = {botconfig.CHANNEL} channels.Main = channels.add(botconfig.CHANNEL, cli)
channels.Dummy = channels.add("*", cli)
if botconfig.ALT_CHANNELS: if botconfig.ALT_CHANNELS:
channels.update(botconfig.ALT_CHANNELS.split(",")) for chan in botconfig.ALT_CHANNELS.split(","):
channels.add(chan, cli)
if botconfig.DEV_CHANNEL: if botconfig.DEV_CHANNEL:
channels.update(chan.lstrip("".join(var.STATUSMSG_PREFIXES)) for chan in botconfig.DEV_CHANNEL.split(",")) channels.Dev = channels.add(botconfig.DEV_CHANNEL, cli)
if var.LOG_CHANNEL: if var.LOG_CHANNEL:
channels.add(var.LOG_CHANNEL.lstrip("".join(var.STATUSMSG_PREFIXES))) channels.add(var.LOG_CHANNEL, cli)
cli.join(",".join(channels)) #if var.CHANSERV_OP_COMMAND: # TODO: Add somewhere else if needed
# cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL))
if var.CHANSERV_OP_COMMAND:
cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL))
cli.nick(botconfig.NICK) # very important (for regain/release) cli.nick(botconfig.NICK) # very important (for regain/release)
wolfgame.connect_callback(cli)
def mustregain(cli, *blah): def mustregain(cli, *blah):
if not botconfig.PASS: if not botconfig.PASS:
return return
@ -181,10 +162,4 @@ def connect_callback(cli):
"in botconfig.USERNAME if it's different from the bot nick?") "in botconfig.USERNAME if it's different from the bot nick?")
cli.quit() cli.quit()
@hook("ping")
def on_ping(cli, prefix, server):
cli.send('PONG', server)
# vim: set sw=4 expandtab: # vim: set sw=4 expandtab:

View File

@ -13,46 +13,9 @@ from src.logger import plog
from src import channels, users, settings as var from src import channels, users, settings as var
### WHO/WHOX requests and responses handling ### WHO/WHOX responses handling
def bare_who(cli, target, data=b""): @hook("whoreply")
"""Handle WHO requests."""
if isinstance(data, str):
data = data.encode(Features["CHARSET"])
elif isinstance(data, int):
if data > 0xFFFFFF:
data = b""
else:
data = data.to_bytes(3, "little")
if len(data) > 3:
data = b""
if "WHOX" in Features:
cli.send("WHO", target, b"%tcuihsnfdlar," + data)
else:
cli.send("WHO", target)
return int.from_bytes(data, "little")
# Always use this function whenever sending out a WHO request!
def who(target, data=b""):
"""Send a WHO request with respect to the server's capabilities.
To get the WHO replies, add an event listener for "who_result", and
an event listener for "who_end" for the end of WHO replies.
The return value of this function is an integer equal to the data
given. If the server supports WHOX, the same integer will be in the
event.params.data attribute. Otherwise, this attribute will be 0.
"""
return bare_who(target.client, target.name, data)
#@hook("whoreply")
def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status, hopcount_gecos): def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status, hopcount_gecos):
"""Handle WHO replies for servers without WHOX support. """Handle WHO replies for servers without WHOX support.
@ -95,6 +58,7 @@ def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname) user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname)
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
if ch not in user.channels:
user.channels[ch] = modes user.channels[ch] = modes
ch.users.add(user) ch.users.add(user)
for mode in modes: for mode in modes:
@ -105,7 +69,10 @@ def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status
event = Event("who_result", {}, away=is_away, data=0, ip_address=None, server=server, hop_count=hop, idle_time=None, extended_who=False) event = Event("who_result", {}, away=is_away, data=0, ip_address=None, server=server, hop_count=hop, idle_time=None, extended_who=False)
event.dispatch(var, ch, user) event.dispatch(var, ch, user)
#@hook("whospcrpl") if ch is channels.Main and not users.exists(nick): # FIXME
users.add(nick, ident=ident,host=host,account="*",inchan=True,modes=modes,moded=set())
@hook("whospcrpl")
def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address, host, server, nick, status, hop, idle, account, realname): def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address, host, server, nick, status, hop, idle, account, realname):
"""Handle WHOX responses for servers that support it. """Handle WHOX responses for servers that support it.
@ -164,6 +131,7 @@ def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address,
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname, account=account) user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname, account=account)
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
if ch not in user.channels:
user.channels[ch] = modes user.channels[ch] = modes
ch.users.add(user) ch.users.add(user)
for mode in modes: for mode in modes:
@ -174,7 +142,10 @@ def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address,
event = Event("who_result", {}, away=is_away, data=data, ip_address=ip_address, server=server, hop_count=hop, idle_time=idle, extended_who=True) event = Event("who_result", {}, away=is_away, data=data, ip_address=ip_address, server=server, hop_count=hop, idle_time=idle, extended_who=True)
event.dispatch(var, ch, user) event.dispatch(var, ch, user)
#@hook("endofwho") if ch is channels.Main and not users.exists(nick): # FIXME
users.add(nick, ident=ident,host=host,account=account,inchan=True,modes=modes,moded=set())
@hook("endofwho")
def end_who(cli, bot_server, bot_nick, target, rest): def end_who(cli, bot_server, bot_nick, target, rest):
"""Handle the end of WHO/WHOX responses from the server. """Handle the end of WHO/WHOX responses from the server.
@ -196,7 +167,7 @@ def end_who(cli, bot_server, bot_nick, target, rest):
### Server PING handling ### Server PING handling
#@hook("ping") @hook("ping")
def on_ping(cli, prefix, server): def on_ping(cli, prefix, server):
"""Send out PONG replies to the server's PING requests. """Send out PONG replies to the server's PING requests.
@ -208,7 +179,7 @@ def on_ping(cli, prefix, server):
""" """
with cli: # TODO: Make the client a context manager for this to work with cli:
cli.send("PONG", server) cli.send("PONG", server)
### Fetch and store server information ### Fetch and store server information
@ -266,7 +237,7 @@ def get_features(cli, rawnick, *features):
### Channel and user MODE handling ### Channel and user MODE handling
#@hook("channelmodeis") @hook("channelmodeis")
def current_modes(cli, server, bot_nick, chan, mode, *targets): def current_modes(cli, server, bot_nick, chan, mode, *targets):
"""Update the channel modes with the existing ones. """Update the channel modes with the existing ones.
@ -284,7 +255,7 @@ def current_modes(cli, server, bot_nick, chan, mode, *targets):
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
ch.update_modes(server, mode, targets) ch.update_modes(server, mode, targets)
#@hook("channelcreate") @hook("channelcreate")
def chan_created(cli, server, bot_nick, chan, timestamp): def chan_created(cli, server, bot_nick, chan, timestamp):
"""Update the channel timestamp with the server's information. """Update the channel timestamp with the server's information.
@ -303,7 +274,7 @@ def chan_created(cli, server, bot_nick, chan, timestamp):
channels.add(chan, cli).timestamp = int(timestamp) channels.add(chan, cli).timestamp = int(timestamp)
#@hook("mode") @hook("mode")
def mode_change(cli, rawnick, chan, mode, *targets): def mode_change(cli, rawnick, chan, mode, *targets):
"""Update the channel and user modes whenever a mode change occurs. """Update the channel and user modes whenever a mode change occurs.
@ -320,7 +291,7 @@ def mode_change(cli, rawnick, chan, mode, *targets):
""" """
actor = users._get(rawnick, allow_none=True, raw_nick=True) # FIXME actor = users._get(rawnick, allow_none=True) # FIXME
if chan == users.Bot.nick: # we only see user modes set to ourselves if chan == users.Bot.nick: # we only see user modes set to ourselves
users.Bot.modes.update(mode) users.Bot.modes.update(mode)
return return
@ -335,7 +306,6 @@ def mode_change(cli, rawnick, chan, mode, *targets):
def handle_listmode(cli, chan, mode, target, setter, timestamp): def handle_listmode(cli, chan, mode, target, setter, timestamp):
"""Handle and store list modes.""" """Handle and store list modes."""
return # FIXME
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
if mode not in ch.modes: if mode not in ch.modes:
ch.modes[mode] = {} ch.modes[mode] = {}
@ -417,7 +387,6 @@ def check_inviteexemptlist(cli, server, bot_nick, chan, target, setter, timestam
def handle_endlistmode(cli, chan, mode): def handle_endlistmode(cli, chan, mode):
"""Handle the end of a list mode listing.""" """Handle the end of a list mode listing."""
return # FIXME
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
Event("end_listmode", {}).dispatch(var, ch, mode) Event("end_listmode", {}).dispatch(var, ch, mode)
@ -488,7 +457,7 @@ def end_inviteexemptlist(cli, server, bot_nick, chan, message):
### NICK handling ### NICK handling
#@hook("nick") @hook("nick")
def on_nick_change(cli, old_nick, nick): def on_nick_change(cli, old_nick, nick):
"""Handle a user changing nicks, which may be the bot itself. """Handle a user changing nicks, which may be the bot itself.
@ -507,7 +476,7 @@ def on_nick_change(cli, old_nick, nick):
### JOIN handling ### JOIN handling
#@hook("join") @hook("join")
def join_chan(cli, rawnick, chan, account=None, realname=None): def join_chan(cli, rawnick, chan, account=None, realname=None):
"""Handle a user joining a channel, which may be the bot itself. """Handle a user joining a channel, which may be the bot itself.
@ -533,19 +502,19 @@ def join_chan(cli, rawnick, chan, account=None, realname=None):
realname = None realname = None
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
ch.state = 2 ch.state = channels._States.Joined
if users.parse_rawnick_as_dict(rawnick)["nick"] == users.Bot.nick: # we may not be fully set up yet if users.parse_rawnick_as_dict(rawnick)["nick"] == users.Bot.nick: # we may not be fully set up yet
ch.mode() ch.mode()
ch.mode(Features["CHANMODES"][0]) ch.mode(Features["CHANMODES"][0])
who(ch) ch.who()
user = users.Bot user = users.Bot
else: else:
try: # FIXME try: # FIXME
user = users._get(rawnick, account=account, realname=realname, raw_nick=True) user = users._get(rawnick, account=account, realname=realname)
except KeyError: except KeyError:
user = users._add(cli, nick=rawnick, account=account, realname=realname, raw_nick=True) user = users._add(cli, nick=rawnick, account=account, realname=realname)
ch.users.add(user) ch.users.add(user)
user.channels[ch] = set() user.channels[ch] = set()
@ -554,7 +523,7 @@ def join_chan(cli, rawnick, chan, account=None, realname=None):
### PART handling ### PART handling
#@hook("part") @hook("part")
def part_chan(cli, rawnick, chan, reason=""): def part_chan(cli, rawnick, chan, reason=""):
"""Handle a user leaving a channel, which may be the bot itself. """Handle a user leaving a channel, which may be the bot itself.
@ -571,7 +540,7 @@ def part_chan(cli, rawnick, chan, reason=""):
""" """
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
user = users._get(rawnick, allow_bot=True, raw_nick=True) # FIXME user = users._get(rawnick, allow_bot=True) # FIXME
if user is users.Bot: # oh snap! we're no longer in the channel! if user is users.Bot: # oh snap! we're no longer in the channel!
ch._clear() ch._clear()
@ -582,7 +551,7 @@ def part_chan(cli, rawnick, chan, reason=""):
### KICK handling ### KICK handling
#@hook("kick") @hook("kick")
def kicked_from_chan(cli, rawnick, chan, target, reason): def kicked_from_chan(cli, rawnick, chan, target, reason):
"""Handle a user being kicked from a channel. """Handle a user being kicked from a channel.
@ -597,13 +566,13 @@ def kicked_from_chan(cli, rawnick, chan, target, reason):
""" """
ch = channels.add(chan, cli) ch = channels.add(chan, cli)
actor = users._get(rawnick, allow_bot=True, raw_nick=True) # FIXME actor = users._get(rawnick, allow_bot=True) # FIXME
user = users._get(target, allow_bot=True) # FIXME user = users._get(target, allow_bot=True) # FIXME
if user is users.Bot: if user is users.Bot:
ch._clear() ch._clear()
else: else:
ch.remove_user(val) ch.remove_user(user)
Event("chan_kick", {}).dispatch(var, ch, actor, user, reason) Event("chan_kick", {}).dispatch(var, ch, actor, user, reason)
@ -618,10 +587,10 @@ def quit(context, message=""):
plog("Tried to QUIT but everything was being torn down.") plog("Tried to QUIT but everything was being torn down.")
return return
with cli: # TODO: Make the client into a context manager with cli:
cli.send("QUIT :{0}".format(message)) cli.send("QUIT :{0}".format(message))
#@hook("quit") @hook("quit")
def on_quit(cli, rawnick, reason): def on_quit(cli, rawnick, reason):
"""Handle a user quitting the IRC server. """Handle a user quitting the IRC server.
@ -639,7 +608,7 @@ def on_quit(cli, rawnick, reason):
""" """
user = users._get(rawnick, allow_bot=True, raw_nick=True) # FIXME user = users._get(rawnick, allow_bot=True) # FIXME
for chan in set(user.channels): for chan in set(user.channels):
if user is users.Bot: if user is users.Bot:
@ -648,3 +617,5 @@ def on_quit(cli, rawnick, reason):
chan.remove_user(user) chan.remove_user(user)
Event("server_quit", {}).dispatch(var, user, reason) Event("server_quit", {}).dispatch(var, user, reason)
# vim: set sw=4 expandtab:

View File

@ -199,6 +199,7 @@ CHANSERV_OP_COMMAND = "OP {channel}"
GUEST_NICK_PATTERN = r"^Guest\d+$|^\d|away.+|.+away" GUEST_NICK_PATTERN = r"^Guest\d+$|^\d|away.+|.+away"
LOG_CHANNEL = "" # Log !fwarns to this channel, if set LOG_CHANNEL = "" # Log !fwarns to this channel, if set
LOG_PREFIX = "" # Message prefix for LOG_CHANNEL
# TODO: move this to a game mode called "fixed" once we implement a way to randomize roles (and have that game mode be called "random") # TODO: move this to a game mode called "fixed" once we implement a way to randomize roles (and have that game mode be called "random")
DEFAULT_ROLE = "villager" DEFAULT_ROLE = "villager"

View File

@ -3,7 +3,7 @@ import re
import botconfig import botconfig
import src.settings as var import src.settings as var
from src import db from src import channels, db
from src.utilities import * from src.utilities import *
from src.decorators import cmd from src.decorators import cmd
from src.events import Event from src.events import Event
@ -46,14 +46,14 @@ def decrement_stasis(nick=None):
db.expire_stasis() db.expire_stasis()
db.init_vars() db.init_vars()
def expire_tempbans(cli): def expire_tempbans():
acclist, hmlist = db.expire_tempbans() acclist, hmlist = db.expire_tempbans()
cmodes = [] cmodes = []
for acc in acclist: for acc in acclist:
cmodes.append(("-b", "{0}{1}".format(var.ACCOUNT_PREFIX, acc))) cmodes.append(("-b", "{0}{1}".format(var.ACCOUNT_PREFIX, acc)))
for hm in hmlist: for hm in hmlist:
cmodes.append(("-b", "*!*@{0}".format(hm))) cmodes.append(("-b", "*!*@{0}".format(hm)))
mass_mode(cli, cmodes, []) channels.Main.mode(*cmodes)
def parse_warning_target(target, lower=False): def parse_warning_target(target, lower=False):
if target[0] == "=": if target[0] == "=":
@ -677,7 +677,7 @@ def fwarn(cli, nick, chan, rest):
reply(cli, nick, chan, messages["fwarn_done"]) reply(cli, nick, chan, messages["fwarn_done"])
if var.LOG_CHANNEL: if var.LOG_CHANNEL:
cli.msg(var.LOG_CHANNEL, messages["fwarn_log_del"].format(warn_id, warning["target"], hm, cli.msg(var.LOG_PREFIX + var.LOG_CHANNEL, messages["fwarn_log_del"].format(warn_id, warning["target"], hm,
warning["reason"], (" | " + warning["notes"]) if warning["notes"] else "")) warning["reason"], (" | " + warning["notes"]) if warning["notes"] else ""))
return return

View File

@ -46,7 +46,7 @@ import botconfig
import src import src
import src.settings as var import src.settings as var
from src.utilities import * from src.utilities import *
from src import db, decorators, events, users, logger, proxy, debuglog, errlog, plog from src import db, decorators, events, channels, users, hooks, logger, proxy, debuglog, errlog, plog
from src.decorators import cmd, hook, handle_error, event_listener, COMMANDS from src.decorators import cmd, hook, handle_error, event_listener, COMMANDS
from src.messages import messages from src.messages import messages
from src.warnings import * from src.warnings import *
@ -90,7 +90,7 @@ var.DISCONNECTED = {} # players who got disconnected
var.RESTARTING = False var.RESTARTING = False
var.OPPED = False # Keeps track of whether the bot is opped #var.OPPED = False # Keeps track of whether the bot is opped
var.BITTEN_ROLES = {} var.BITTEN_ROLES = {}
var.LYCAN_ROLES = {} var.LYCAN_ROLES = {}
@ -124,7 +124,7 @@ if botconfig.DEBUG_MODE and var.DISABLE_DEBUG_MODE_TIME_LORD:
plog("Loading Werewolf IRC bot") plog("Loading Werewolf IRC bot")
def connect_callback(cli): def connect_callback():
db.init_vars() db.init_vars()
SIGUSR1 = getattr(signal, "SIGUSR1", None) SIGUSR1 = getattr(signal, "SIGUSR1", None)
SIGUSR2 = getattr(signal, "SIGUSR2", None) SIGUSR2 = getattr(signal, "SIGUSR2", None)
@ -151,113 +151,58 @@ def connect_callback(cli):
if SIGUSR2: if SIGUSR2:
signal.signal(SIGUSR2, sighandler) signal.signal(SIGUSR2, sighandler)
to_be_devoiced = [] def who_end(event, var, request):
cmodes = [] if request == channels.Main.name:
if "WHOX" not in hooks.Features:
@hook("quietlist", hookid=294)
def on_quietlist(cli, server, botnick, channel, q, quieted, by, something):
if re.search(r"^{0}.+\!\*@\*$".format(var.QUIET_PREFIX), quieted): # only unquiet people quieted by bot
cmodes.append(("-{0}".format(var.QUIET_MODE), quieted))
@hook("banlist", hookid=294)
def on_banlist(cli, server, botnick, channel, ban, by, timestamp):
if re.search(r"^{0}.+\!\*@\*$".format(var.QUIET_PREFIX), ban):
cmodes.append(("-{0}".format(var.QUIET_MODE), ban))
@hook("whoreply", hookid=295)
def on_whoreply(cli, svr, botnick, chan, user, host, server, nick, status, rest):
if not var.DISABLE_ACCOUNTS: if not var.DISABLE_ACCOUNTS:
plog("IRCd does not support accounts, disabling account-related features.") plog("IRCd does not support accounts, disabling account-related features.")
var.DISABLE_ACCOUNTS = True var.DISABLE_ACCOUNTS = True
var.ACCOUNTS_ONLY = False var.ACCOUNTS_ONLY = False
if users.exists(nick, user, host):
return
if nick == botconfig.NICK:
cli.nickname = nick
cli.ident = user
cli.hostmask = host
if "+" in status:
to_be_devoiced.append(nick)
newstat = ""
for stat in status:
if not stat in var.MODES_PREFIXES:
continue
newstat += var.MODES_PREFIXES[stat]
users.add(nick, ident=user,host=host,account="*",inchan=True,modes=set(newstat),moded=set())
@hook("whospcrpl", hookid=295)
def on_whoreply(cli, server, nick, ident, host, _, user, status, acc):
if users.exists(user, ident, host):
return # Don't add someone who is already there
if user == botconfig.NICK:
cli.nickname = user
cli.ident = ident
cli.hostmask = host
if acc == "0":
acc = "*"
if "+" in status:
to_be_devoiced.append(user)
newstat = ""
for stat in status:
if not stat in var.MODES_PREFIXES:
continue
newstat += var.MODES_PREFIXES[stat]
users.add(user, ident=ident,host=host,account=acc,inchan=True,modes=set(newstat),moded=set())
@hook("endofwho", hookid=295)
def afterwho(*args):
# Devoice all on connect # Devoice all on connect
for nick in to_be_devoiced: prefix = "-" + hooks.Features["PREFIX"]["+"]
cmodes.append(("-v", nick)) pending = []
for user in channels.Main.modes.get(prefix, ()):
pending.append((prefix, user.nick))
accumulator.send(pending)
next(accumulator, None)
# Expire tempbans # Expire tempbans
expire_tempbans(cli) expire_tempbans()
# If the bot was restarted in the middle of the join phase, ping players that were joined.
players = db.get_pre_restart_state() players = db.get_pre_restart_state()
if players: if players:
msg = "PING! " + break_long_message(players).replace("\n", "\nPING! ") msg = "PING! " + break_long_message(players).replace("\n", "\nPING! ")
cli.msg(botconfig.CHANNEL, msg) channel.Main.send(msg)
cli.msg(botconfig.CHANNEL, messages["game_restart_cancel"]) channel.Main.send(messages["game_restart_cancel"])
# Unhook the WHO hooks events.remove_listener("who_end", who_end)
hook.unhook(295)
def end_listmode(event, var, chan, mode):
if chan is channels.Main and mode == var.QUIET_MODE:
pending = []
for quiet in chan.modes.get(mode, ()):
if re.search(r"^{0}.+\!\*@\*$".format(var.QUIET_PREFIX), quiet):
pending.append(("-" + mode, quiet))
accumulator.send(pending)
next(accumulator, None)
#bot can be tricked into thinking it's still opped by doing multiple modes at once events.remove_listener("end_listmode", end_listmode)
@hook("mode", hookid=296)
def on_give_me_ops(cli, nick, chan, modeaction, target="", *other):
if chan != botconfig.CHANNEL:
return
if modeaction == "+o" and target == botconfig.NICK:
var.OPPED = True
if users.exists(botconfig.NICK):
users.get(botconfig.NICK).modes.add("o")
if var.PHASE == "none": events.add_listener("who_end", who_end)
@hook("quietlistend", hookid=297) events.add_listener("end_listmode", end_listmode)
def on_quietlist_end(cli, svr, nick, chan, *etc):
if chan == botconfig.CHANNEL:
mass_mode(cli, cmodes, ["-m"])
@hook("endofbanlist", hookid=297)
def on_banlist_end(cli, svr, nick, chan, *etc):
if chan == botconfig.CHANNEL:
mass_mode(cli, cmodes, ["-m"])
cli.mode(botconfig.CHANNEL, var.QUIET_MODE) # unquiet all def accumulate_cmodes(count):
elif modeaction == "-o" and target == botconfig.NICK: modes = []
var.OPPED = False for i in range(count):
if var.CHANSERV_OP_COMMAND: item = yield
cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL)) modes.extend(item)
yield i
channel.Main.mode(*modes)
if var.DISABLE_ACCOUNTS: accumulator = accumulate_cmodes(2)
cli.who(botconfig.CHANNEL) accumulator.send(None)
else:
cli.who(botconfig.CHANNEL, "%uhsnfa")
@hook("mode") # XXX Get rid of this when the user/channel refactor is done @hook("mode") # XXX Get rid of this when the user/channel refactor is done
def check_for_modes(cli, rnick, chan, modeaction, *target): def check_for_modes(cli, rnick, chan, modeaction, *target):
@ -308,6 +253,10 @@ def check_for_modes(cli, rnick, chan, modeaction, *target):
if "!" not in rnick: if "!" not in rnick:
sync_modes(cli) sync_modes(cli)
@cmd("break")
def break_stuff(*s):
1/0
def reset_settings(): def reset_settings():
var.CURRENT_GAMEMODE.teardown() var.CURRENT_GAMEMODE.teardown()
var.CURRENT_GAMEMODE = var.GAME_MODES["default"][0]() var.CURRENT_GAMEMODE = var.GAME_MODES["default"][0]()
@ -400,7 +349,7 @@ def refreshdb(cli, nick, chan, rest):
"""Updates our tracking vars to the current db state.""" """Updates our tracking vars to the current db state."""
db.expire_stasis() db.expire_stasis()
db.init_vars() db.init_vars()
expire_tempbans(cli) expire_tempbans()
reply(cli, nick, chan, "Done.") reply(cli, nick, chan, "Done.")
@cmd("die", "bye", "fdie", "fbye", flag="D", pm=True) @cmd("die", "bye", "fdie", "fbye", flag="D", pm=True)
@ -875,9 +824,9 @@ def join_timer_handler(cli):
return return
@hook("whoreply", hookid=387) @hook("whoreply", hookid=387)
def ping_altpingers_noacc(cli, svr, botnick, chan, ident, host, server, nick, status, rest): def ping_altpingers_noacc(cli, bot_server, bot_nick, chan, ident, host, server, nick, status, hopcount_gecos):
if ("G" in status or is_user_stasised(nick) or not var.PINGING_IFS or if ("G" in status or is_user_stasised(nick) or not var.PINGING_IFS or
nick == botnick or nick in pl): nick == bot_nick or nick in pl):
return return
ident = irc_lower(ident) ident = irc_lower(ident)
@ -888,24 +837,24 @@ def join_timer_handler(cli):
var.PINGED_ALREADY.add(hostmask) var.PINGED_ALREADY.add(hostmask)
@hook("whospcrpl", hookid=387) @hook("whospcrpl", hookid=387)
def ping_altpingers(cli, server, nick, ident, host, _, user, status, acc): def ping_altpingers(cli, bot_server, bot_nick, data, chan, ident, ip_address, host, server, nick, status, hop, idle, account, realname):
if ("G" in status or is_user_stasised(user) or not var.PINGING_IFS or if ("G" in status or is_user_stasised(nick) or not var.PINGING_IFS or
user == botconfig.NICK or user in pl): nick == botconfig.NICK or nick in pl):
return return
# Create list of players to ping # Create list of players to ping
acc = irc_lower(acc) account = irc_lower(account)
ident = irc_lower(ident) ident = irc_lower(ident)
host = host.lower() host = host.lower()
if acc and acc != "*": if account and account != "*":
if acc in chk_acc: if account in chk_acc:
to_ping.append(user) to_ping.append(nick)
var.PINGED_ALREADY_ACCS.add(acc) var.PINGED_ALREADY_ACCS.add(account)
elif not var.ACCOUNTS_ONLY: elif not var.ACCOUNTS_ONLY:
hostmask = ident + "@" + host hostmask = ident + "@" + host
to_ping.append(user) to_ping.append(nick)
var.PINGED_ALREADY.add(hostmask) var.PINGED_ALREADY.add(hostmask)
@hook("endofwho", hookid=387) @hook("endofwho", hookid=387)
@ -926,8 +875,9 @@ def join_timer_handler(cli):
cli.msg(botconfig.CHANNEL, msg) cli.msg(botconfig.CHANNEL, msg)
# FIXME
if not var.DISABLE_ACCOUNTS: if not var.DISABLE_ACCOUNTS:
cli.who(botconfig.CHANNEL, "%uhsnfa") cli.who(botconfig.CHANNEL, "%tcuihsnfdlar,")
else: else:
cli.who(botconfig.CHANNEL) cli.who(botconfig.CHANNEL)
@ -1067,12 +1017,6 @@ def join_player(cli, player, chan, who=None, forced=False, *, sanity=True):
if chan != botconfig.CHANNEL: if chan != botconfig.CHANNEL:
return False return False
if not var.OPPED:
cli.notice(who, messages["bot_not_opped"].format(chan))
if var.CHANSERV_OP_COMMAND:
cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL))
return False
if users.exists(player): if users.exists(player):
ident = irc_lower(users.get(player).ident) ident = irc_lower(users.get(player).ident)
host = users.get(player).host.lower() host = users.get(player).host.lower()
@ -1222,7 +1166,7 @@ def kill_join(cli, chan):
# use this opportunity to expire pending stasis # use this opportunity to expire pending stasis
db.expire_stasis() db.expire_stasis()
db.init_vars() db.init_vars()
expire_tempbans(cli) expire_tempbans()
if var.AFTER_FLASTGAME is not None: if var.AFTER_FLASTGAME is not None:
var.AFTER_FLASTGAME() var.AFTER_FLASTGAME()
var.AFTER_FLASTGAME = None var.AFTER_FLASTGAME = None
@ -1241,11 +1185,6 @@ def fjoin(cli, nick, chan, rest):
return return
noticed = False noticed = False
fake = False fake = False
if not var.OPPED:
cli.notice(nick, messages["bot_not_opped"].format(chan))
if var.CHANSERV_OP_COMMAND:
cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL))
return
if not rest.strip(): if not rest.strip():
evt.data["join_player"](cli, nick, chan, forced=True) evt.data["join_player"](cli, nick, chan, forced=True)
@ -2565,7 +2504,7 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
reset_modes_timers(cli) reset_modes_timers(cli)
reset() reset()
expire_tempbans(cli) expire_tempbans()
# This must be after reset() # This must be after reset()
if var.AFTER_FLASTGAME is not None: if var.AFTER_FLASTGAME is not None:
@ -3283,15 +3222,15 @@ def on_join(cli, raw_nick, chan, acc="*", rname=""):
if nick in var.DCED_PLAYERS.keys(): if nick in var.DCED_PLAYERS.keys():
var.PLAYERS[nick] = var.DCED_PLAYERS.pop(nick) var.PLAYERS[nick] = var.DCED_PLAYERS.pop(nick)
if nick == botconfig.NICK: if nick == botconfig.NICK:
var.OPPED = False #var.OPPED = False
cli.send("NAMES " + chan) cli.send("NAMES " + chan)
if nick == var.CHANSERV and not var.OPPED and var.CHANSERV_OP_COMMAND: #if nick == var.CHANSERV and not var.OPPED and var.CHANSERV_OP_COMMAND:
cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL)) # cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL))
@hook("namreply") #@hook("namreply")
def on_names(cli, _, __, *names): #def on_names(cli, _, __, *names):
if "@" + botconfig.NICK in names: # if "@" + botconfig.NICK in names:
var.OPPED = True # var.OPPED = True
@cmd("goat", playing=True, phases=("day",)) @cmd("goat", playing=True, phases=("day",))
def goat(cli, nick, chan, rest): def goat(cli, nick, chan, rest):
@ -6787,17 +6726,16 @@ def show_admins(cli, nick, chan, rest):
var.ADMIN_PINGING = True var.ADMIN_PINGING = True
@hook("whoreply", hookid=4) def admin_whoreply(event, var, chan, user):
def on_whoreply(cli, server, _, chan, ident, host, ___, user, status, ____): if not var.ADMIN_PINGING or chan is not channels.Main:
if not var.ADMIN_PINGING:
return return
if is_admin(user) and "G" not in status and user != botconfig.NICK: if is_admin(user.nick): # FIXME: Using the old interface for now; user.is_admin() is better
admins.append(user) if user is not users.Bot and not event.params.away:
admins.append(user.nick) # FIXME
@hook("endofwho", hookid=4) def admin_endwho(event, var, target):
def show(*args): if not var.ADMIN_PINGING or target != channels.Main.name:
if not var.ADMIN_PINGING:
return return
admins.sort(key=str.lower) admins.sort(key=str.lower)
@ -6806,10 +6744,15 @@ def show_admins(cli, nick, chan, rest):
reply(cli, nick, chan, msg) reply(cli, nick, chan, msg)
hook.unhook(4)
var.ADMIN_PINGING = False var.ADMIN_PINGING = False
cli.who(botconfig.CHANNEL) events.remove_listener("who_result", admin_whoreply)
events.remove_listener("who_end", admin_endwho)
events.add_listener("who_result", admin_whoreply)
events.add_listener("who_end", admin_endwho)
channels.Main.who()
@cmd("coin", pm=True) @cmd("coin", pm=True)
def coin(cli, nick, chan, rest): def coin(cli, nick, chan, rest):

View File

@ -45,6 +45,14 @@ except ImportError:
"", "- The lykos developers", sep="\n") "", "- The lykos developers", sep="\n")
sys.exit(1) sys.exit(1)
try: # FIXME
botconfig.DEV_PREFIX
except AttributeError:
print("Please set up your config to include a DEV_PREFIX variable",
"If you have a prefix in your DEV_CHANNEL config, move it out into DEV_PREFIX",
sep="\n")
sys.exit(1)
from oyoyo.client import IRCClient from oyoyo.client import IRCClient
import src import src