Make discrimination based on case ilegel

All hostmask and account comparisons are now case-insensitive (nicks
still aren't, related to #217 -- changing nick sensitivity would break
everything in numerous places).

Also, refactor some things into other files where it makes sense to do
so, because putting unrelated things into the same commit is fun.
This commit is contained in:
skizzerz 2016-07-27 17:03:34 -05:00
parent 1e21445e43
commit 1b7b2f6799
11 changed files with 691 additions and 664 deletions

View File

@ -4,34 +4,10 @@ import time
import botconfig
import src.settings as var
from src import logger
from src.logger import stream, stream_handler, debuglog, errlog, plog
from src import db
# Segue to logger, since src.gamemodes requires it
# TODO: throw this into a logger.py perhaps so we aren't breaking up imports with non-import stuff
def logger(file, write=True, display=True):
if file is not None:
open(file, "a").close() # create the file if it doesn't exist
def log(*output, write=write, display=display):
output = " ".join([str(x) for x in output]).replace("\u0002", "").replace("\\x02", "") # remove bold
if botconfig.DEBUG_MODE:
write = True
if botconfig.DEBUG_MODE or botconfig.VERBOSE_MODE:
display = True
timestamp = get_timestamp()
if display:
print(timestamp + output, file=utf8stdout)
if write and file is not None:
with open(file, "a", errors="replace") as f:
f.seek(0, 2)
f.write(timestamp + output + "\n")
return log
stream_handler = logger(None)
debuglog = logger("debug.log", write=False, display=False)
errlog = logger("errors.log")
plog = logger(None) # use this instead of print so that logs have timestamps
# Import the user-defined game modes
# These are not required, so failing to import it doesn't matter
# The file then imports our game modes
@ -84,33 +60,4 @@ if args.normal: normal = True
botconfig.DEBUG_MODE = debug_mode if not normal else False
botconfig.VERBOSE_MODE = verbose if not normal else False
# Logger
# replace characters that can't be encoded with '?'
# since windows likes to use weird encodings by default
utf8stdout = open(1, 'w', errors="replace", closefd=False) # stdout
def get_timestamp(use_utc=None, ts_format=None):
"""Return a timestamp with timezone + offset from UTC."""
if use_utc is None:
use_utc = botconfig.USE_UTC
if ts_format is None:
ts_format = botconfig.TIMESTAMP_FORMAT
if use_utc:
tmf = datetime.datetime.utcnow().strftime(ts_format)
tz = "UTC"
offset = "+0000"
else:
tmf = time.strftime(ts_format)
tz = time.tzname[0]
offset = "+"
if datetime.datetime.utcnow().hour > datetime.datetime.now().hour:
offset = "-"
offset += str(time.timezone // 36).zfill(4)
return tmf.format(tzname=tz, tzoffset=offset).strip().upper() + " "
def stream(output, level="normal"):
if botconfig.VERBOSE_MODE or botconfig.DEBUG_MODE:
stream_handler(output)
elif level == "warning":
stream_handler(output)
# vim: set sw=4 expandtab:

View File

@ -1,5 +1,3 @@
import botconfig
import src.settings as var
import sqlite3
import os
import json
@ -9,6 +7,10 @@ import time
from collections import defaultdict
import threading
import botconfig
import src.settings as var
from src.utilities import irc_lower, break_long_message, role_order, singular
# increment this whenever making a schema change so that the schema upgrade functions run on start
# they do not run by default for performance reasons
SCHEMA_VERSION = 2
@ -57,6 +59,7 @@ def init_vars():
for acc, host, notice, simple, dc, pi, stasis, stasisexp, flags in c:
if acc is not None:
acc = irc_lower(acc)
if simple == 1:
var.SIMPLE_NOTIFY_ACCS.add(acc)
if notice == 1:
@ -71,6 +74,12 @@ def init_vars():
if flags:
var.FLAGS_ACCS[acc] = flags
elif host is not None:
# nick!ident lowercased per irc conventions, host uses normal casing
try:
hl, hr = host.split("@", 1)
host = irc_lower(hl) + "@" + hr.lower()
except ValueError:
host = host.lower()
if simple == 1:
var.SIMPLE_NOTIFY.add(host)
if notice == 1:
@ -105,8 +114,10 @@ def init_vars():
)""")
for acc, host, command in c:
if acc is not None:
acc = irc_lower(acc)
var.DENY_ACCS[acc].add(command)
if host is not None:
host = irc_lower(host)
var.DENY[host].add(command)
def decrement_stasis(acc=None, hostmask=None):
@ -324,13 +335,13 @@ def get_player_totals(acc, hostmask):
totals = []
for row in c:
tmp[row[0]] = row[1]
order = var.role_order()
order = role_order()
name = _get_display_name(peid)
#ordered role stats
totals = ["\u0002{0}\u0002: {1}".format(r, tmp[r]) for r in order if r in tmp]
#lover or any other special stats
totals += ["\u0002{0}\u0002: {1}".format(r, t) for r, t in tmp.items() if r not in order]
return "\u0002{0}\u0002's totals | \u0002{1}\u0002 games | {2}".format(name, total_games, var.break_long_message(totals, ", "))
return "\u0002{0}\u0002's totals | \u0002{1}\u0002 games | {2}".format(name, total_games, break_long_message(totals, ", "))
def get_game_stats(mode, size):
conn = _conn()
@ -356,7 +367,7 @@ def get_game_stats(mode, size):
msg = "\u0002{0}\u0002 player games | {1}"
bits = []
for row in c:
bits.append("%s wins: %d (%d%%)" % (var.singular(row[0]), row[1], round(row[1]/total_games * 100)))
bits.append("%s wins: %d (%d%%)" % (singular(row[0]), row[1], round(row[1]/total_games * 100)))
bits.append("total games: {0}".format(total_games))
return msg.format(size, ", ".join(bits))
@ -872,6 +883,5 @@ elif ver < SCHEMA_VERSION:
_upgrade(ver)
del need_install, conn, c, ver
init_vars()
# vim: set expandtab:sw=4:ts=4:

View File

@ -10,11 +10,10 @@ from oyoyo.parse import parse_nick
import botconfig
import src.settings as var
from src.utilities import *
from src import logger, db
from src import logger, errlog
from src.messages import messages
adminlog = logger("audit.log")
errlog = logger("errors.log")
adminlog = logger.logger("audit.log")
COMMANDS = defaultdict(list)
HOOKS = defaultdict(list)
@ -135,9 +134,12 @@ class cmd:
return
if nick in var.USERS and var.USERS[nick]["account"] != "*":
acc = var.USERS[nick]["account"]
acc = irc_lower(var.USERS[nick]["account"])
else:
acc = None
nick = irc_lower(nick)
ident = irc_lower(ident)
host = host.lower()
hostmask = nick + "!" + ident + "@" + host
if "" in self.cmds:
@ -146,7 +148,7 @@ class cmd:
if self.phases and var.PHASE not in self.phases:
return
if self.playing and (nick not in var.list_players() or nick in var.DISCONNECTED):
if self.playing and (nick not in list_players() or nick in var.DISCONNECTED):
if chan == nick:
pm(cli, nick, messages["player_not_playing"])
else:
@ -176,9 +178,9 @@ class cmd:
forced_owner_only = True
break
is_owner = var.is_owner(nick, ident, host)
owner = is_owner(nick, ident, host)
if self.owner_only or forced_owner_only:
if is_owner:
if owner:
adminlog(chan, rawnick, self.name, rest)
return self.func(*largs)
@ -189,8 +191,8 @@ class cmd:
return
flags = var.FLAGS[hostmask] + var.FLAGS_ACCS[acc]
is_full_admin = var.is_admin(nick, ident, host)
if self.flag and (is_full_admin or is_owner):
admin = is_admin(nick, ident, host)
if self.flag and (admin or owner):
adminlog(chan, rawnick, self.name, rest)
return self.func(*largs)

View File

@ -22,7 +22,7 @@ reset_roles = lambda i: OrderedDict([(role, (0,) * len(i)) for role in var.ROLE_
def get_lovers():
lovers = []
pl = var.list_players()
pl = list_players()
for lover in var.LOVERS:
done = None
for i, lset in enumerate(lovers):
@ -65,12 +65,12 @@ class GameMode:
pair, *pairs = pairs[0].split(",", 1)
change = pair.lower().replace(":", " ").strip().rsplit(None, 1)
if len(change) != 2:
raise var.InvalidModeException(messages["invalid_mode_args"].format(arg))
raise InvalidModeException(messages["invalid_mode_args"].format(arg))
key, val = change
if key in ("role reveal", "reveal roles"):
if val not in ("on", "off", "team"):
raise var.InvalidModeException(messages["invalid_reveal"].format(val))
raise InvalidModeException(messages["invalid_reveal"].format(val))
self.ROLE_REVEAL = val
if val == "off" and not hasattr(self, "STATS_TYPE"):
self.STATS_TYPE = "disabled"
@ -78,11 +78,11 @@ class GameMode:
self.STATS_TYPE = "team"
elif key in ("stats type", "stats"):
if val not in ("default", "accurate", "team", "disabled"):
raise var.InvalidModeException(messages["invalid_stats"].format(val))
raise InvalidModeException(messages["invalid_stats"].format(val))
self.STATS_TYPE = val
elif key == "abstain":
if val not in ("enabled", "restricted", "disabled"):
raise var.InvalidModeException(messages["invalid_abstain"].format(val))
raise InvalidModeException(messages["invalid_abstain"].format(val))
if val == "enabled":
self.ABSTAIN_ENABLED = True
self.LIMIT_ABSTAIN = False
@ -133,11 +133,11 @@ class ChangedRolesMode(GameMode):
pair, *pairs = pairs[0].split(",", 1)
change = pair.replace(":", " ").strip().rsplit(None, 1)
if len(change) != 2:
raise var.InvalidModeException(messages["invalid_mode_roles"].format(arg))
raise InvalidModeException(messages["invalid_mode_roles"].format(arg))
role, num = change
try:
if role.lower() in var.DISABLED_ROLES:
raise var.InvalidModeException(messages["role_disabled"].format(role))
raise InvalidModeException(messages["role_disabled"].format(role))
elif role.lower() in self.ROLE_GUIDE:
self.ROLE_GUIDE[role.lower()] = tuple([int(num)] * len(var.ROLE_INDEX))
elif role.lower() == "default" and num.lower() in self.ROLE_GUIDE:
@ -146,9 +146,9 @@ class ChangedRolesMode(GameMode):
# handled in parent constructor
pass
else:
raise var.InvalidModeException(messages["specific_invalid_role"].format(role))
raise InvalidModeException(messages["specific_invalid_role"].format(role))
except ValueError:
raise var.InvalidModeException(messages["bad_role_value"])
raise InvalidModeException(messages["bad_role_value"])
@game_mode("default", minp = 4, maxp = 24, likelihood = 20)
class DefaultMode(GameMode):
@ -198,7 +198,7 @@ class VillagergameMode(GameMode):
def transition_day(self, evt, cli, var):
# 30% chance we kill a safe, otherwise kill at random
# when killing safes, go after seer, then harlot, then shaman
pl = var.list_players()
pl = list_players()
tgt = None
seer = None
hlt = None
@ -321,8 +321,8 @@ class EvilVillageMode(GameMode):
events.remove_listener("chk_win", self.chk_win)
def chk_win(self, evt, var, lpl, lwolves, lrealwolves):
lsafes = len(var.list_players(["oracle", "seer", "guardian angel", "shaman", "hunter", "villager"]))
lcultists = len(var.list_players(["cultist"]))
lsafes = len(list_players(["oracle", "seer", "guardian angel", "shaman", "hunter", "villager"]))
lcultists = len(list_players(["cultist"]))
evt.stop_processing = True
if lrealwolves == 0 and lsafes == 0:
@ -728,7 +728,7 @@ class GuardianMode(GameMode):
events.remove_listener("chk_win", self.chk_win)
def chk_win(self, evt, var, lpl, lwolves, lrealwolves):
lguardians = len(var.list_players(["guardian angel", "bodyguard"]))
lguardians = len(list_players(["guardian angel", "bodyguard"]))
if lpl < 1:
# handled by default win cond checking
@ -869,7 +869,7 @@ class SleepyMode(GameMode):
self.do_nightmare = decorators.handle_error(self.do_nightmare)
self.having_nightmare = True
with var.WARNING_LOCK:
t = threading.Timer(60, self.do_nightmare, (cli, var, random.choice(var.list_players()), var.NIGHT_COUNT))
t = threading.Timer(60, self.do_nightmare, (cli, var, random.choice(list_players()), var.NIGHT_COUNT))
t.daemon = True
t.start()
else:
@ -882,7 +882,7 @@ class SleepyMode(GameMode):
def do_nightmare(self, cli, var, target, night):
if var.PHASE != "night" or var.NIGHT_COUNT != night:
return
if target not in var.list_players():
if target not in list_players():
return
self.having_nightmare = target
pm(cli, self.having_nightmare, messages["sleepy_nightmare_begin"])
@ -1082,7 +1082,7 @@ class SleepyMode(GameMode):
def nightmare_kill(self, evt, cli, var):
# if True, it means night ended before 1 minute
if self.having_nightmare is not None and self.having_nightmare is not True and self.having_nightmare in var.list_players():
if self.having_nightmare is not None and self.having_nightmare is not True and self.having_nightmare in list_players():
var.DYING.add(self.having_nightmare)
pm(cli, self.having_nightmare, messages["sleepy_nightmare_death"])
@ -1158,10 +1158,10 @@ class MaelstromMode(GameMode):
def _on_join(self, cli, var, nick, chan):
role = random.choice(self.roles)
lpl = len(var.list_players()) + 1
lwolves = len(var.list_players(var.WOLFCHAT_ROLES))
lpl = len(list_players()) + 1
lwolves = len(list_players(var.WOLFCHAT_ROLES))
lcubs = len(var.ROLES["wolf cub"])
lrealwolves = len(var.list_players(var.WOLF_ROLES)) - lcubs
lrealwolves = len(list_players(var.WOLF_ROLES)) - lcubs
lmonsters = len(var.ROLES["monster"])
ldemoniacs = len(var.ROLES["demoniac"])
ltraitors = len(var.ROLES["traitor"])
@ -1196,7 +1196,7 @@ class MaelstromMode(GameMode):
var.PLAYERS[nick] = var.USERS[nick]
if role == "doctor":
lpl = len(var.list_players())
lpl = len(list_players())
var.DOCTORS[nick] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * lpl)
# let them know their role
# FIXME: this is fugly
@ -1206,12 +1206,12 @@ class MaelstromMode(GameMode):
if role in var.WOLFCHAT_ROLES:
relay_wolfchat_command(cli, nick, messages["wolfchat_new_member"].format(nick, role), var.WOLFCHAT_ROLES, is_wolf_command=True, is_kill_command=True)
# TODO: make this part of !myrole instead, no reason we can't give out wofllist in that
wolves = var.list_players(var.WOLFCHAT_ROLES)
pl = var.list_players()
wolves = list_players(var.WOLFCHAT_ROLES)
pl = list_players()
random.shuffle(pl)
pl.remove(nick)
for i, player in enumerate(pl):
prole = var.get_role(player)
prole = get_role(player)
if prole in var.WOLFCHAT_ROLES:
cursed = ""
if player in var.ROLES["cursed villager"]:
@ -1229,7 +1229,7 @@ class MaelstromMode(GameMode):
# don't do this n1
if var.FIRST_NIGHT:
return
villagers = var.list_players()
villagers = list_players()
lpl = len(villagers)
addroles = self._role_attribution(cli, var, villagers, False)

View File

@ -9,10 +9,8 @@ from oyoyo.parse import parse_nick
import botconfig
import src.settings as var
from src import decorators, logger, wolfgame
log = logger("errors.log")
alog = logger(None)
from src import decorators, wolfgame, errlog as log, stream_handler as alog
from src.utilities import irc_equals
hook = decorators.hook
@ -38,11 +36,11 @@ def on_privmsg(cli, rawnick, chan, msg, notice = False):
log("Server did not send a case mapping; falling back to rfc1459.")
var.CASEMAPPING = "rfc1459"
if (notice and ((not var.irc_equals(chan, botconfig.NICK) and not botconfig.ALLOW_NOTICE_COMMANDS) or
(var.irc_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
if var.irc_equals(chan, botconfig.NICK):
if irc_equals(chan, botconfig.NICK):
chan = parse_nick(rawnick)[0]
for fn in decorators.COMMANDS[""]:

60
src/logger.py Normal file
View File

@ -0,0 +1,60 @@
import datetime
import time
import botconfig
def logger(file, write=True, display=True):
if file is not None:
open(file, "a").close() # create the file if it doesn't exist
def log(*output, write=write, display=display):
output = " ".join([str(x) for x in output]).replace("\u0002", "").replace("\\x02", "") # remove bold
if botconfig.DEBUG_MODE:
write = True
if botconfig.DEBUG_MODE or botconfig.VERBOSE_MODE:
display = True
timestamp = get_timestamp()
if display:
print(timestamp + output, file=utf8stdout)
if write and file is not None:
with open(file, "a", errors="replace") as f:
f.seek(0, 2)
f.write(timestamp + output + "\n")
return log
stream_handler = logger(None)
debuglog = logger("debug.log", write=False, display=False)
errlog = logger("errors.log")
plog = stream_handler # use this instead of print so that logs have timestamps
# replace characters that can't be encoded with '?'
# since windows likes to use weird encodings by default
utf8stdout = open(1, 'w', errors="replace", closefd=False) # stdout
def get_timestamp(use_utc=None, ts_format=None):
"""Return a timestamp with timezone + offset from UTC."""
if use_utc is None:
use_utc = botconfig.USE_UTC
if ts_format is None:
ts_format = botconfig.TIMESTAMP_FORMAT
if use_utc:
tmf = datetime.datetime.utcnow().strftime(ts_format)
tz = "UTC"
offset = "+0000"
else:
tmf = time.strftime(ts_format)
tz = time.tzname[0]
offset = "+"
if datetime.datetime.utcnow().hour > datetime.datetime.now().hour:
offset = "-"
offset += str(time.timezone // 36).zfill(4)
return tmf.format(tzname=tz, tzoffset=offset).strip().upper() + " "
def stream(output, level="normal"):
if botconfig.VERBOSE_MODE or botconfig.DEBUG_MODE:
plog(output)
elif level == "warning":
plog(output)
# vim: set sw=4 expandtab:

View File

@ -1,7 +1,5 @@
import inspect
from src.decorators import handle_error
""" This module introduces two decorators - @proxy.stub and @proxy.impl
@proxy.stub is used to decorate a stub method that should be filled in
@ -61,6 +59,9 @@ def impl(f):
_sigmatch(f)
# Always wrap proxy implementations in an error handler
# proxy needs to be a top level (no dependencies) module, so can't import this
# up top or else we get loops
from src.decorators import handle_error
IMPLS[f.__name__] = handle_error(f)
# allows this method to be called directly in our module rather
# than forcing use of the stub's module

View File

@ -320,211 +320,4 @@ GRAVEYARD_LOCK = threading.RLock()
WARNING_LOCK = threading.RLock()
WAIT_TB_LOCK = threading.RLock()
#TODO: move all of these to util.py or other files, as they are certainly NOT settings!
is_role = lambda plyr, rol: rol in ROLES and plyr in ROLES[rol]
def match_hostmask(hostmask, nick, ident, host):
# support n!u@h, u@h, or just h by itself
matches = re.match('(?:(?:(.*?)!)?(.*?)@)?(.*)', hostmask.lower())
if ((not matches.group(1) or fnmatch.fnmatch(nick.lower(), matches.group(1))) and
(not matches.group(2) or fnmatch.fnmatch(ident.lower(), matches.group(2))) and
fnmatch.fnmatch(host.lower(), matches.group(3))):
return True
return False
def is_owner(nick, ident=None, host=None, acc=None):
hosts = set(botconfig.OWNERS)
accounts = set(botconfig.OWNERS_ACCOUNTS)
if nick in USERS:
if not ident:
ident = USERS[nick]["ident"]
if not host:
host = USERS[nick]["host"]
if not acc:
acc = USERS[nick]["account"]
if not DISABLE_ACCOUNTS and acc and acc != "*":
for pattern in accounts:
if fnmatch.fnmatch(acc.lower(), pattern.lower()):
return True
if host:
for hostmask in hosts:
if match_hostmask(hostmask, nick, ident, host):
return True
return False
def is_admin(nick, ident=None, host=None, acc=None):
if nick in USERS:
if not ident:
ident = USERS[nick]["ident"]
if not host:
host = USERS[nick]["host"]
if not acc:
acc = USERS[nick]["account"]
hostmask = nick + "!" + ident + "@" + host
flags = FLAGS[hostmask] + FLAGS_ACCS[acc]
if not "F" in flags:
try:
hosts = set(botconfig.ADMINS)
accounts = set(botconfig.ADMINS_ACCOUNTS)
if not DISABLE_ACCOUNTS and acc and acc != "*":
for pattern in accounts:
if fnmatch.fnmatch(acc.lower(), pattern.lower()):
return True
if host:
for hostmask in hosts:
if match_hostmask(hostmask, nick, ident, host):
return True
except AttributeError:
pass
return is_owner(nick, ident, host, acc)
return True
def irc_lower(nick):
mapping = {
"[": "{",
"]": "}",
"\\": "|",
"^": "~",
}
if CASEMAPPING == "strict-rfc1459":
mapping.pop("^")
elif CASEMAPPING == "ascii":
mapping = {}
return nick.lower().translate(str.maketrans(mapping))
def irc_equals(nick1, nick2):
return irc_lower(nick1) == irc_lower(nick2)
def plural(role, count=2):
if count == 1:
return role
bits = role.split()
if bits[-1][-2:] == "'s":
bits[-1] = plural(bits[-1][:-2], count)
bits[-1] += "'" if bits[-1][-1] == "s" else "'s"
else:
bits[-1] = {"person": "people",
"wolf": "wolves",
"has": "have",
"succubus": "succubi",
"child": "children"}.get(bits[-1], bits[-1] + "s")
return " ".join(bits)
def singular(plural):
# converse of plural above (kinda)
# this is used to map plural team names back to singular,
# so we don't need to worry about stuff like possessives
# Note that this is currently only ever called on team names,
# and will require adjustment if one wishes to use it on roles.
conv = {"wolves": "wolf",
"succubi": "succubus"}
if plural in conv:
return conv[plural]
# otherwise we just added an s on the end
return plural[:-1]
def list_players(roles = None):
if roles is None:
roles = ROLES.keys()
pl = set()
for x in roles:
if x in TEMPLATE_RESTRICTIONS.keys():
continue
for p in ROLES.get(x, ()):
pl.add(p)
return [p for p in ALL_PLAYERS if p in pl]
def list_players_and_roles():
plr = {}
for x in ROLES.keys():
if x in TEMPLATE_RESTRICTIONS.keys():
continue # only get actual roles
for p in ROLES[x]:
plr[p] = x
return plr
def get_role(p):
for role, pl in ROLES.items():
if role in TEMPLATE_RESTRICTIONS.keys():
continue # only get actual roles
if p in pl:
return role
def get_reveal_role(nick):
if HIDDEN_TRAITOR and get_role(nick) == "traitor":
role = DEFAULT_ROLE
elif HIDDEN_AMNESIAC and nick in ORIGINAL_ROLES["amnesiac"]:
role = "amnesiac"
elif HIDDEN_CLONE and nick in ORIGINAL_ROLES["clone"]:
role = "clone"
elif nick in WILD_CHILDREN:
role = "wild child"
else:
role = get_role(nick)
if ROLE_REVEAL != "team":
return role
if role in WOLFTEAM_ROLES:
return "wolf"
elif role in TRUE_NEUTRAL_ROLES:
return "neutral player"
else:
return "villager"
def del_player(pname):
prole = get_role(pname)
ROLES[prole].remove(pname)
tpls = get_templates(pname)
for t in tpls:
ROLES[t].remove(pname)
if pname in BITTEN_ROLES:
del BITTEN_ROLES[pname]
if pname in CHARMED:
CHARMED.remove(pname)
def get_templates(nick):
tpl = []
for x in TEMPLATE_RESTRICTIONS.keys():
try:
if nick in ROLES[x]:
tpl.append(x)
except KeyError:
pass
return tpl
role_order = lambda: ROLE_GUIDE
def break_long_message(phrases, joinstr = " "):
message = []
count = 0
for phrase in phrases:
# IRC max is 512, but freenode splits around 380ish, make 300 to have plenty of wiggle room
if count + len(joinstr) + len(phrase) > 300:
message.append("\n" + phrase)
count = len(phrase)
else:
if not message:
count = len(phrase)
else:
count += len(joinstr) + len(phrase)
message.append(phrase)
return joinstr.join(message)
class InvalidModeException(Exception): pass
# vim: set sw=4 expandtab:

View File

@ -1,10 +1,11 @@
import re
import fnmatch
import botconfig
import src.settings as var
from src import proxy, debuglog
# message either privmsg or notice, depending on user settings
# used in decorators (imported by proxy), so needs to go here
def pm(cli, target, message):
if is_fake_nick(target) and botconfig.DEBUG_MODE:
debuglog("Would message fake nick {0}: {1!r}".format(target, message))
@ -16,10 +17,6 @@ def pm(cli, target, message):
cli.msg(target, message)
from src import proxy, debuglog
# Some miscellaneous helper functions
is_fake_nick = re.compile(r"^[0-9]+$").search
def mass_mode(cli, md_param, md_plain):
@ -86,7 +83,7 @@ def mass_privmsg(cli, targets, msg, notice=False, privmsg=False):
def reply(cli, nick, chan, msg, private=False, prefix_nick=False):
if chan == nick:
pm(cli, nick, msg)
elif private or (nick not in var.list_players() and var.PHASE in var.GAME_PHASES and chan == botconfig.CHANNEL):
elif private or (nick not in list_players() and var.PHASE in var.GAME_PHASES and chan == botconfig.CHANNEL):
cli.notice(nick, msg)
else:
if prefix_nick:
@ -96,9 +93,9 @@ def reply(cli, nick, chan, msg, private=False, prefix_nick=False):
def is_user_simple(nick):
if nick in var.USERS:
ident = var.USERS[nick]["ident"]
host = var.USERS[nick]["host"]
acc = var.USERS[nick]["account"]
ident = irc_lower(var.USERS[nick]["ident"])
host = var.USERS[nick]["host"].lower()
acc = irc_lower(var.USERS[nick]["account"])
else:
return False
if acc and acc != "*" and not var.DISABLE_ACCOUNTS:
@ -107,25 +104,25 @@ def is_user_simple(nick):
return False
elif not var.ACCOUNTS_ONLY:
for hostmask in var.SIMPLE_NOTIFY:
if var.match_hostmask(hostmask, nick, ident, host):
if match_hostmask(hostmask, nick, ident, host):
return True
return False
def is_user_notice(nick):
if nick in var.USERS and var.USERS[nick]["account"] and var.USERS[nick]["account"] != "*" and not var.DISABLE_ACCOUNTS:
if var.USERS[nick]["account"] in var.PREFER_NOTICE_ACCS:
if irc_lower(var.USERS[nick]["account"]) in var.PREFER_NOTICE_ACCS:
return True
if nick in var.USERS and not var.ACCOUNTS_ONLY:
ident = var.USERS[nick]["ident"]
host = var.USERS[nick]["host"]
ident = irc_lower(var.USERS[nick]["ident"])
host = var.USERS[nick]["host"].lower()
for hostmask in var.PREFER_NOTICE:
if var.match_hostmask(hostmask, nick, ident, host):
if match_hostmask(hostmask, nick, ident, host):
return True
return False
def in_wolflist(nick, who):
myrole = var.get_role(nick)
role = var.get_role(who)
myrole = get_role(nick)
role = get_role(who)
wolves = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
@ -157,7 +154,7 @@ def relay_wolfchat_command(cli, nick, message, roles, is_wolf_command=False, is_
else:
wcroles = var.WOLF_ROLES | {"traitor"}
wcwolves = var.list_players(wcroles)
wcwolves = list_players(wcroles)
wcwolves.remove(nick)
mass_privmsg(cli, wcwolves, message)
mass_privmsg(cli, var.SPECTATING_WOLFCHAT, "[wolfchat] " + message)
@ -174,4 +171,201 @@ def chk_decision(cli, force=""):
def chk_win(cli, end_game=True, winner=None):
pass
def irc_lower(nick):
if nick is None:
return None
mapping = {
"[": "{",
"]": "}",
"\\": "|",
"^": "~",
}
if var.CASEMAPPING == "strict-rfc1459":
mapping.pop("^")
elif var.CASEMAPPING == "ascii":
mapping = {}
return nick.lower().translate(str.maketrans(mapping))
def irc_equals(nick1, nick2):
return irc_lower(nick1) == irc_lower(nick2)
is_role = lambda plyr, rol: rol in var.ROLES and plyr in var.ROLES[rol]
def match_hostmask(hostmask, nick, ident, host):
# support n!u@h, u@h, or just h by itself
matches = re.match('(?:(?:(.*?)!)?(.*?)@)?(.*)', hostmask)
if ((not matches.group(1) or fnmatch.fnmatch(irc_lower(nick), irc_lower(matches.group(1)))) and
(not matches.group(2) or fnmatch.fnmatch(irc_lower(ident), irc_lower(matches.group(2)))) and
fnmatch.fnmatch(host.lower(), matches.group(3).lower())):
return True
return False
def is_owner(nick, ident=None, host=None, acc=None):
hosts = set(botconfig.OWNERS)
accounts = set(botconfig.OWNERS_ACCOUNTS)
if nick in var.USERS:
if not ident:
ident = var.USERS[nick]["ident"]
if not host:
host = var.USERS[nick]["host"]
if not acc:
acc = var.USERS[nick]["account"]
if not var.DISABLE_ACCOUNTS and acc and acc != "*":
for pattern in accounts:
if fnmatch.fnmatch(irc_lower(acc), irc_lower(pattern)):
return True
if host:
for hostmask in hosts:
if match_hostmask(hostmask, nick, ident, host):
return True
return False
def is_admin(nick, ident=None, host=None, acc=None):
if nick in var.USERS:
if not ident:
ident = var.USERS[nick]["ident"]
if not host:
host = var.USERS[nick]["host"]
if not acc:
acc = var.USERS[nick]["account"]
acc = irc_lower(acc)
hostmask = irc_lower(nick) + "!" + irc_lower(ident) + "@" + host.lower()
flags = var.FLAGS[hostmask] + var.FLAGS_ACCS[acc]
if not "F" in flags:
try:
hosts = set(botconfig.ADMINS)
accounts = set(botconfig.ADMINS_ACCOUNTS)
if not var.DISABLE_ACCOUNTS and acc and acc != "*":
for pattern in accounts:
if fnmatch.fnmatch(irc_lower(acc), irc_lower(pattern)):
return True
if host:
for hostmask in hosts:
if match_hostmask(hostmask, nick, ident, host):
return True
except AttributeError:
pass
return is_owner(nick, ident, host, acc)
return True
def plural(role, count=2):
if count == 1:
return role
bits = role.split()
if bits[-1][-2:] == "'s":
bits[-1] = plural(bits[-1][:-2], count)
bits[-1] += "'" if bits[-1][-1] == "s" else "'s"
else:
bits[-1] = {"person": "people",
"wolf": "wolves",
"has": "have",
"succubus": "succubi",
"child": "children"}.get(bits[-1], bits[-1] + "s")
return " ".join(bits)
def singular(plural):
# converse of plural above (kinda)
# this is used to map plural team names back to singular,
# so we don't need to worry about stuff like possessives
# Note that this is currently only ever called on team names,
# and will require adjustment if one wishes to use it on roles.
conv = {"wolves": "wolf",
"succubi": "succubus"}
if plural in conv:
return conv[plural]
# otherwise we just added an s on the end
return plural[:-1]
def list_players(roles = None):
if roles is None:
roles = var.ROLES.keys()
pl = set()
for x in roles:
if x in var.TEMPLATE_RESTRICTIONS.keys():
continue
for p in var.ROLES.get(x, ()):
pl.add(p)
return [p for p in var.ALL_PLAYERS if p in pl]
def list_players_and_roles():
plr = {}
for x in var.ROLES.keys():
if x in var.TEMPLATE_RESTRICTIONS.keys():
continue # only get actual roles
for p in var.ROLES[x]:
plr[p] = x
return plr
def get_role(p):
for role, pl in var.ROLES.items():
if role in var.TEMPLATE_RESTRICTIONS.keys():
continue # only get actual roles
if p in pl:
return role
def get_reveal_role(nick):
if var.HIDDEN_TRAITOR and get_role(nick) == "traitor":
role = var.DEFAULT_ROLE
elif var.HIDDEN_AMNESIAC and nick in var.ORIGINAL_ROLES["amnesiac"]:
role = "amnesiac"
elif var.HIDDEN_CLONE and nick in var.ORIGINAL_ROLES["clone"]:
role = "clone"
elif nick in var.WILD_CHILDREN:
role = "wild child"
else:
role = get_role(nick)
if var.ROLE_REVEAL != "team":
return role
if role in var.WOLFTEAM_ROLES:
return "wolf"
elif role in var.TRUE_NEUTRAL_ROLES:
return "neutral player"
else:
return "villager"
def get_templates(nick):
tpl = []
for x in var.TEMPLATE_RESTRICTIONS.keys():
try:
if nick in var.ROLES[x]:
tpl.append(x)
except KeyError:
pass
return tpl
role_order = lambda: var.ROLE_GUIDE
def break_long_message(phrases, joinstr = " "):
message = []
count = 0
for phrase in phrases:
# IRC max is 512, but freenode splits around 380ish, make 300 to have plenty of wiggle room
if count + len(joinstr) + len(phrase) > 300:
message.append("\n" + phrase)
count = len(phrase)
else:
if not message:
count = len(phrase)
else:
count += len(joinstr) + len(phrase)
message.append(phrase)
return joinstr.join(message)
class InvalidModeException(Exception): pass
# vim: set sw=4 expandtab:

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,7 @@ import src
from src import handler
def main():
src.logger(None)("Connecting to {0}:{1}{2}".format(botconfig.HOST, "+" if botconfig.USE_SSL else "", botconfig.PORT))
src.plog("Connecting to {0}:{1}{2}".format(botconfig.HOST, "+" if botconfig.USE_SSL else "", botconfig.PORT))
cli = IRCClient(
{"privmsg": handler.on_privmsg,
"notice": lambda a, b, c, d: handler.on_privmsg(a, b, c, d, True),
@ -77,4 +77,4 @@ if __name__ == "__main__":
try:
main()
except Exception:
src.logger("errors.log")(traceback.format_exc())
src.errlog(traceback.format_exc())