This commit is contained in:
nyuszika7h 2014-07-19 19:05:49 +02:00
parent 6f16848302
commit 4189d5c874
11 changed files with 1644 additions and 1117 deletions

View File

@ -1,4 +1,5 @@
# The bot commands implemented in here are present no matter which module is loaded
# The bot commands implemented in here are present no matter which module
# is loaded
import botconfig
from tools import decorators
@ -9,17 +10,19 @@ from settings import common as var
from base64 import b64encode
import imp
def on_privmsg(cli, rawnick, chan, msg, notice = False):
def on_privmsg(cli, rawnick, chan, msg, notice=False):
currmod = ld.MODULES[ld.CURRENT_MODULE]
if botconfig.IGNORE_HIDDEN_COMMANDS and (chan.startswith("@#") or chan.startswith("+#")):
if botconfig.IGNORE_HIDDEN_COMMANDS and (
chan.startswith("@#") or chan.startswith("+#")):
return
if (notice and ((chan != botconfig.NICK and not botconfig.ALLOW_NOTICE_COMMANDS) or
(chan == botconfig.NICK and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
if (notice and ((chan != botconfig.NICK and not botconfig.ALLOW_NOTICE_COMMANDS) or (
chan == botconfig.NICK and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
return # not allowed in settings
if chan != botconfig.NICK: #not a PM
if chan != botconfig.NICK: # not a PM
if currmod and "" in currmod.COMMANDS.keys():
for fn in currmod.COMMANDS[""]:
try:
@ -29,13 +32,15 @@ def on_privmsg(cli, rawnick, chan, msg, notice = False):
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
cli.msg(
chan,
"An error has occurred and has been logged.")
# Now that is always called first.
for x in set(list(COMMANDS.keys()) + (list(currmod.COMMANDS.keys()) if currmod else list())):
if x and msg.lower().startswith(botconfig.CMD_CHAR+x):
h = msg[len(x)+1:]
if x and msg.lower().startswith(botconfig.CMD_CHAR + x):
h = msg[len(x) + 1:]
if not h or h[0] == " " or not x:
for fn in COMMANDS.get(x,[])+(currmod.COMMANDS.get(x,[]) if currmod else []):
for fn in COMMANDS.get(x, []) + (currmod.COMMANDS.get(x, []) if currmod else []):
try:
fn(cli, rawnick, chan, h.lstrip())
except Exception as e:
@ -43,18 +48,20 @@ def on_privmsg(cli, rawnick, chan, msg, notice = False):
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
cli.msg(
chan,
"An error has occurred and has been logged.")
else:
for x in set(list(PM_COMMANDS.keys()) + (list(currmod.PM_COMMANDS.keys()) if currmod else list())):
if msg.lower().startswith(botconfig.CMD_CHAR+x):
h = msg[len(x)+1:]
if msg.lower().startswith(botconfig.CMD_CHAR + x):
h = msg[len(x) + 1:]
elif not x or msg.lower().startswith(x):
h = msg[len(x):]
else:
continue
if not h or h[0] == " " or not x:
for fn in PM_COMMANDS.get(x, [])+(currmod.PM_COMMANDS.get(x,[]) if currmod else []):
for fn in PM_COMMANDS.get(x, []) + (currmod.PM_COMMANDS.get(x, []) if currmod else []):
try:
fn(cli, rawnick, h.lstrip())
except Exception as e:
@ -62,16 +69,22 @@ def on_privmsg(cli, rawnick, chan, msg, notice = False):
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
cli.msg(
chan,
"An error has occurred and has been logged.")
def __unhandled__(cli, prefix, cmd, *args):
currmod = ld.MODULES[ld.CURRENT_MODULE]
if cmd in set(list(HOOKS.keys())+(list(currmod.HOOKS.keys()) if currmod else list())):
if cmd in set(
list(HOOKS.keys()) +
(list(currmod.HOOKS.keys()) if currmod else list())):
largs = list(args)
for i,arg in enumerate(largs):
if isinstance(arg, bytes): largs[i] = arg.decode('ascii')
for fn in HOOKS.get(cmd, [])+(currmod.HOOKS.get(cmd, []) if currmod else []):
for i, arg in enumerate(largs):
if isinstance(arg, bytes):
largs[i] = arg.decode('ascii')
for fn in HOOKS.get(cmd, []) + (currmod.HOOKS.get(cmd, []) if currmod else []):
try:
fn(cli, prefix, *largs)
except Exception as e:
@ -79,13 +92,17 @@ def __unhandled__(cli, prefix, cmd, *args):
raise e
else:
logging.error(traceback.format_exc())
cli.msg(botconfig.CHANNEL, "An error has occurred and has been logged.")
cli.msg(
botconfig.CHANNEL,
"An error has occurred and has been logged.")
else:
logging.debug('Unhandled command {0}({1})'.format(cmd, [arg.decode('utf_8')
for arg in args
if isinstance(arg, bytes)]))
logging.debug(
'Unhandled command {0}({1})'.format(
cmd, [
arg.decode('utf_8') for arg in args if isinstance(
arg, bytes)]))
COMMANDS = {}
PM_COMMANDS = {}
HOOKS = {}
@ -94,27 +111,28 @@ cmd = decorators.generate(COMMANDS)
pmcmd = decorators.generate(PM_COMMANDS)
hook = decorators.generate(HOOKS, raw_nick=True, permissions=False)
def connect_callback(cli):
def prepare_stuff(*args):
def prepare_stuff(*args):
cli.join(botconfig.CHANNEL)
cli.msg("ChanServ", "op "+botconfig.CHANNEL)
cli.msg("ChanServ", "op " + botconfig.CHANNEL)
cli.cap("REQ", "extended-join")
cli.cap("REQ", "account-notify")
try:
ld.MODULES[ld.CURRENT_MODULE].connect_callback(cli)
except AttributeError:
pass # no connect_callback for this one
pass # no connect_callback for this one
cli.nick(botconfig.NICK) # very important (for regain/release)
prepare_stuff = hook("endofmotd", hookid=294)(prepare_stuff)
def mustregain(cli, *blah):
cli.ns_regain()
cli.ns_regain()
def mustrelease(cli, *rest):
cli.ns_release()
cli.nick(botconfig.NICK)
@ -122,51 +140,53 @@ def connect_callback(cli):
@hook("unavailresource", hookid=239)
@hook("nicknameinuse", hookid=239)
def must_use_temp_nick(cli, *etc):
cli.nick(botconfig.NICK+"_")
cli.nick(botconfig.NICK + "_")
cli.user(botconfig.NICK, "")
decorators.unhook(HOOKS, 239)
hook("unavailresource")(mustrelease)
hook("nicknameinuse")(mustregain)
if botconfig.SASL_AUTHENTICATION:
@hook("authenticate")
def auth_plus(cli, something, plus):
if plus == "+":
nick_b = bytes(botconfig.USERNAME if botconfig.USERNAME else botconfig.NICK, "utf-8")
nick_b = bytes(
botconfig.USERNAME if botconfig.USERNAME else botconfig.NICK,
"utf-8")
pass_b = bytes(botconfig.PASS, "utf-8")
secrt_msg = b'\0'.join((nick_b, nick_b, pass_b))
cli.send("AUTHENTICATE " + b64encode(secrt_msg).decode("utf-8"))
cli.send(
"AUTHENTICATE " +
b64encode(secrt_msg).decode("utf-8"))
@hook("cap")
def on_cap(cli, svr, mynick, ack, cap):
if ack.upper() == "ACK" and "sasl" in cap:
cli.send("AUTHENTICATE PLAIN")
@hook("903")
def on_successful_auth(cli, blah, blahh, blahhh):
cli.cap("END")
@hook("904")
@hook("905")
@hook("906")
@hook("907")
def on_failure_auth(cli, *etc):
cli.quit()
print("Authentication failed. Did you fill the account name "+
print("Authentication failed. Did you fill the account name " +
"in botconfig.USERNAME if it's different from the bot nick?")
@hook("ping")
def on_ping(cli, prefix, server):
cli.send('PONG', server)
if botconfig.DEBUG_MODE:
@cmd("module", admin_only = True)
@cmd("module", admin_only=True)
def ch_module(cli, nick, chan, rest):
rest = rest.strip()
if rest in ld.MODULES.keys():

File diff suppressed because it is too large Load Diff

View File

@ -22,15 +22,20 @@ import traceback
import sys
import ssl
from oyoyo.parse import parse_raw_irc_command
from oyoyo.parse import parse_raw_irc_command
# Adapted from
# http://code.activestate.com/recipes/511490-implementation-of-the-token-bucket-algorithm/
# Adapted from http://code.activestate.com/recipes/511490-implementation-of-the-token-bucket-algorithm/
class TokenBucket(object):
"""An implementation of the token bucket algorithm.
>>> bucket = TokenBucket(80, 0.5)
>>> bucket.consume(1)
"""
def __init__(self, tokens, fill_rate):
"""tokens is the total tokens in the bucket. fill_rate is the
rate in tokens/second that the bucket will be refilled."""
@ -55,9 +60,8 @@ class TokenBucket(object):
self._tokens = min(self.capacity, self._tokens + delta)
self.timestamp = now
return self._tokens
def add_commands(d):
def dec(cls):
for c in d:
@ -68,36 +72,39 @@ def add_commands(d):
setattr(cls, c, func(c))
return cls
return dec
@add_commands(("join",
"mode",
"nick",
"who",
"cap"))
class IRCClient(object):
""" IRC Client class. This handles one connection to a server.
This can be used either with or without IRCApp ( see connect() docs )
"""
def __init__(self, cmd_handler, **kwargs):
""" the first argument should be an object with attributes/methods named
as the irc commands. You may subclass from one of the classes in
oyoyo.cmdhandler for convenience but it is not required. The
methods should have arguments (prefix, args). prefix is
""" the first argument should be an object with attributes/methods named
as the irc commands. You may subclass from one of the classes in
oyoyo.cmdhandler for convenience but it is not required. The
methods should have arguments (prefix, args). prefix is
normally the sender of the command. args is a list of arguments.
Its recommened you subclass oyoyo.cmdhandler.DefaultCommandHandler,
this class provides defaults for callbacks that are required for
Its recommened you subclass oyoyo.cmdhandler.DefaultCommandHandler,
this class provides defaults for callbacks that are required for
normal IRC operation.
all other arguments should be keyword arguments. The most commonly
used will be nick, host and port. You can also specify an "on connect"
callback. ( check the source for others )
Warning: By default this class will not block on socket operations, this
Warning: By default this class will not block on socket operations, this
means if you use a plain while loop your app will consume 100% cpu.
To enable blocking pass blocking=True.
To enable blocking pass blocking=True.
"""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.nickname = ""
self.hostmask = ""
self.ident = ""
@ -111,12 +118,12 @@ class IRCClient(object):
self.sasl_auth = False
self.use_ssl = False
self.lock = threading.RLock()
self.tokenbucket = TokenBucket(23, 1.73)
self.__dict__.update(kwargs)
self.command_handler = cmd_handler
if self.use_ssl:
self.socket = ssl.wrap_socket(self.socket)
@ -124,8 +131,8 @@ class IRCClient(object):
def send(self, *args, **kwargs):
""" send a message to the connected server. all arguments are joined
with a space for convenience, for example the following are identical
with a space for convenience, for example the following are identical
>>> cli.send("JOIN " + some_room)
>>> cli.send("JOIN", some_room)
@ -134,13 +141,13 @@ class IRCClient(object):
the 'encoding' keyword argument (default 'utf8').
In python 3, all args must be of type str or bytes, *BUT* if they are
str they will be converted to bytes with the encoding specified by the
'encoding' keyword argument (default 'utf8').
'encoding' keyword argument (default 'utf8').
"""
with self.lock:
# Convert all args to bytes if not already
encoding = kwargs.get('encoding') or 'utf_8'
bargs = []
for i,arg in enumerate(args):
for i, arg in enumerate(args):
if isinstance(arg, str):
bargs.append(bytes(arg, encoding))
elif isinstance(arg, bytes):
@ -148,20 +155,25 @@ class IRCClient(object):
elif arg is None:
continue
else:
raise Exception(('Refusing to send arg at index {1} of the args from '+
'provided: {0}').format(repr([(type(arg), arg)
for arg in args]), i))
raise Exception(
('Refusing to send arg at index {1} of the args from ' +
'provided: {0}').format(
repr(
[
(type(arg),
arg) for arg in args]),
i))
msg = bytes(" ", "utf_8").join(bargs)
logging.info('---> send {0}'.format(str(msg)[1:]))
while not self.tokenbucket.consume(1):
time.sleep(0.3)
self.socket.send(msg + bytes("\r\n", "utf_8"))
def connect(self):
""" initiates the connection to the server set in self.host:self.port
and returns a generator object.
""" initiates the connection to the server set in self.host:self.port
and returns a generator object.
>>> cli = IRCClient(my_handler, host="irc.freenode.net", port=6667)
>>> g = cli.connect()
@ -183,32 +195,34 @@ class IRCClient(object):
break
if not self.blocking:
self.socket.setblocking(0)
if not self.sasl_auth:
self.send("PASS {0}:{1}".format(self.authname if self.authname else self.nickname,
self.password if self.password else "NOPASS"))
self.send(
"PASS {0}:{1}".format(
self.authname if self.authname else self.nickname,
self.password if self.password else "NOPASS"))
else:
self.cap("LS")
self.nick(self.nickname)
self.user(self.nickname, self.real_name)
if self.sasl_auth:
self.cap("REQ", "multi-prefix")
self.cap("REQ", "sasl")
if self.connect_cb:
try:
self.connect_cb(self)
except Exception as e:
traceback.print_exc()
raise e
buffer = bytes()
while not self._end:
try:
buffer += self.socket.recv(1024)
except socket.error as e:
except socket.error as e:
if False and not self.blocking and e.errno == 11:
pass
else:
@ -219,16 +233,25 @@ class IRCClient(object):
for el in data:
prefix, command, args = parse_raw_irc_command(el)
try:
enc = "utf8"
fargs = [arg.decode(enc) for arg in args if isinstance(arg,bytes)]
fargs = [
arg.decode(enc) for arg in args if isinstance(
arg,
bytes)]
except UnicodeDecodeError:
enc = "latin1"
fargs = [arg.decode(enc) for arg in args if isinstance(arg,bytes)]
logging.debug("processCommand ({2}){0}({1})".format(command,
fargs, prefix))
fargs = [
arg.decode(enc) for arg in args if isinstance(
arg,
bytes)]
logging.debug(
"processCommand ({2}){0}({1})".format(
command,
fargs,
prefix))
try:
largs = list(args)
if prefix is not None:
@ -236,21 +259,32 @@ class IRCClient(object):
# for i,arg in enumerate(largs):
# if arg is not None: largs[i] = arg.decode(enc)
if command in self.command_handler:
self.command_handler[command](self, prefix,*fargs)
self.command_handler[command](
self,
prefix,
*
fargs)
elif "" in self.command_handler:
self.command_handler[""](self, prefix, command, *fargs)
self.command_handler[""](
self,
prefix,
command,
*
fargs)
except Exception as e:
traceback.print_exc()
raise e # ?
yield True
finally:
if self.socket:
if self.socket:
logging.info('closing socket')
self.socket.close()
yield False
def msg(self, user, msg):
for line in msg.split('\n'):
maxchars = 494 - len(self.nickname+self.ident+self.hostmask+user)
maxchars = 494 - len(
self.nickname + self.ident + self.hostmask + user)
while line:
extra = ""
if len(line) > maxchars:
@ -259,9 +293,11 @@ class IRCClient(object):
self.send("PRIVMSG", user, ":{0}".format(line))
line = extra
privmsg = msg # Same thing
def notice(self, user, msg):
for line in msg.split('\n'):
maxchars = 495 - len(self.nickname+self.ident+self.hostmask+user)
maxchars = 495 - len(
self.nickname + self.ident + self.hostmask + user)
while line:
extra = ""
if len(line) > maxchars:
@ -269,27 +305,35 @@ class IRCClient(object):
line = line[:maxchars]
self.send("NOTICE", user, ":{0}".format(line))
line = extra
def quit(self, msg=""):
self.send("QUIT :{0}".format(msg))
def part(self, chan, msg=""):
self.send("PART {0} :{1}".format(chan, msg))
def kick(self, chan, nick, msg=""):
self.send("KICK", chan, nick, ":"+msg)
self.send("KICK", chan, nick, ":" + msg)
def ns_identify(self, passwd):
self.msg("NickServ", "IDENTIFY {0} {1}".format(self.nickname, passwd))
def ns_ghost(self):
self.msg("NickServ", "GHOST "+self.nickname)
self.msg("NickServ", "GHOST " + self.nickname)
def ns_release(self):
self.msg("NickServ", "RELEASE "+self.nickname)
self.msg("NickServ", "RELEASE " + self.nickname)
def ns_regain(self):
self.msg("NickServ", "REGAIN "+self.nickname)
self.msg("NickServ", "REGAIN " + self.nickname)
def user(self, uname, rname):
self.send("USER", uname, self.host, self.host,
rname or uname)
self.send("USER", uname, self.host, self.host,
rname or uname)
def mainLoop(self):
conn = self.connect()
while True:
if not next(conn):
print("Calling sys.exit()...")
sys.exit()

View File

@ -141,7 +141,7 @@ numeric_events = {
b"423": "noadmininfo",
b"424": "fileerror",
b"431": "nonicknamegiven",
b"432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
b"432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
b"433": "nicknameinuse",
b"436": "nickcollision",
b"437": "unavailresource", # "Nick temporally unavailable"
@ -156,7 +156,7 @@ numeric_events = {
b"462": "alreadyregistered",
b"463": "nopermforhost",
b"464": "passwdmismatch",
b"465": "yourebannedcreep", # I love this one...
b"465": "yourebannedcreep", # I love this one...
b"466": "youwillbebanned",
b"467": "keyset",
b"471": "channelisfull",
@ -208,4 +208,3 @@ protocol_events = [
]
all_events = generated_events + protocol_events + list(numeric_events.values())

View File

@ -18,9 +18,11 @@
import logging
from oyoyo.ircevents import generated_events, protocol_events,\
all_events, numeric_events
all_events, numeric_events
# avoiding regex
def parse_raw_irc_command(element):
"""
This function parses a raw irc command and returns a tuple
@ -56,12 +58,13 @@ def parse_raw_irc_command(element):
except KeyError:
logging.debug('unknown numeric event {0}'.format(command))
command = command.lower()
if isinstance(command, bytes): command = command.decode("utf_8")
if isinstance(command, bytes):
command = command.decode("utf_8")
if args[0].startswith(bytes(':', 'utf_8')):
args = [bytes(" ", "utf_8").join(args)[1:]]
else:
for idx, arg in enumerate(args):
for idx, arg in enumerate(args):
if arg.startswith(bytes(':', 'utf_8')):
args = args[:idx] + [bytes(" ", 'utf_8').join(args[idx:])[1:]]
break
@ -89,4 +92,3 @@ def parse_nick(name):
return (nick, mode, rest, None)
return (nick, mode, user, host)

View File

@ -1,6 +1,6 @@
PING_WAIT = 300 # Seconds
PING_MIN_WAIT = 30
MINIMUM_WAIT = 60
MINIMUM_WAIT = 60
EXTRA_WAIT = 20
MAXIMUM_WAITED = 2 # limit for amount of !wait's
STATS_RATE_LIMIT = 15
@ -22,128 +22,140 @@ MAX_PRIVMSG_TARGETS = 1
LOG_FILENAME = ""
BARE_LOG_FILENAME = ""
# HIT MISS SUICIDE
GUN_CHANCES = ( 5/7 , 1/7 , 1/7 )
DRUNK_GUN_CHANCES = ( 2/7 , 4/7 , 1/7 )
MANSLAUGHTER_CHANCE = 1/5 # ACCIDENTAL HEADSHOT (FATAL)
# HIT MISS SUICIDE
GUN_CHANCES = (5 / 7, 1 / 7, 1 / 7)
DRUNK_GUN_CHANCES = (2 / 7, 4 / 7, 1 / 7)
MANSLAUGHTER_CHANCE = 1 / 5 # ACCIDENTAL HEADSHOT (FATAL)
GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 0
GUARDIAN_ANGEL_DIES_CHANCE = 1/2
DETECTIVE_REVEALED_CHANCE = 2/5
GUARDIAN_ANGEL_DIES_CHANCE = 1 / 2
DETECTIVE_REVEALED_CHANCE = 2 / 5
#################################################################################################################
##########################################################################
# ROLE INDEX: PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER CROW ANGEL DETECTIVE ##
#################################################################################################################
ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ##
6 : ( 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ), ##
8 : ( 1 , 2 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 ), ##
10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 ), ##
11 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 ), ##
15 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ##
22 : ( 1 , 4 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ##
29 : ( 1 , 5 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ##
None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ##
#################################################################################################################
##########################################################################
ROLES_GUIDE = {4: (1, 1, 0, 0, 0, 0, 0, 0, 0, 0),
6: (1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
8: (1, 2, 1, 1, 1, 0, 0, 0, 0, 0),
10: (1, 2, 1, 1, 1, 1, 1, 0, 0, 0),
11: (1, 2, 1, 1, 1, 1, 1, 0, 1, 0),
15: (1, 3, 1, 1, 1, 1, 1, 0, 1, 1),
22: (1, 4, 1, 1, 1, 1, 1, 0, 1, 1),
29: (1, 5, 1, 1, 1, 1, 1, 0, 1, 1),
None: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0)}
##########################################################################
# Notes: ##
#################################################################################################################
##########################################################################
GAME_MODES = {}
AWAY = [] # cloaks of people who are away.
SIMPLE_NOTIFY = [] # cloaks of people who !simple, who want everything /notice'd
# cloaks of people who !simple, who want everything /notice'd
SIMPLE_NOTIFY = []
ROLE_INDICES = {0 : "seer",
1 : "wolf",
2 : "cursed villager",
3 : "village drunk",
4 : "harlot",
5 : "traitor",
6 : "gunner",
7 : "werecrow",
8 : "guardian angel",
9 : "detective"}
INDEX_OF_ROLE = dict((v,k) for k,v in ROLE_INDICES.items())
ROLE_INDICES = {0: "seer",
1: "wolf",
2: "cursed villager",
3: "village drunk",
4: "harlot",
5: "traitor",
6: "gunner",
7: "werecrow",
8: "guardian angel",
9: "detective"}
INDEX_OF_ROLE = dict((v, k) for k, v in ROLE_INDICES.items())
NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.",
"A pool of blood and wolf paw prints are found.",
"Traces of wolf fur are found.")
LYNCH_MESSAGES = ("The villagers, after much debate, finally decide on lynching \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"The mob drags a protesting \u0002{0}\u0002 to the hanging tree. S/He succumbs to the will of the horde, and is hanged. It is discovered (s)he was a \u0002{1}\u0002.",
"Resigned to his/her fate, \u0002{0}\u0002 is led to the gallows. After death, it is discovered (s)he was a \u0002{1}\u0002.")
LYNCH_MESSAGES = (
"The villagers, after much debate, finally decide on lynching \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"The mob drags a protesting \u0002{0}\u0002 to the hanging tree. S/He succumbs to the will of the horde, and is hanged. It is discovered (s)he was a \u0002{1}\u0002.",
"Resigned to his/her fate, \u0002{0}\u0002 is led to the gallows. After death, it is discovered (s)he was a \u0002{1}\u0002.")
import botconfig
RULES = (botconfig.CHANNEL + " channel rules: 1) Be nice to others. 2) Do not share information "+
"after death. 3) No bots allowed. 4) Do not play with clones.\n"+
"5) Do not quit unless you need to leave. 6) No swearing and keep it "+
"family-friendly. 7) Do not paste PM's from the bot during the game. "+
"8) Use common sense. 9) Waiting for timeouts is discouraged.")
RULES = (
botconfig.CHANNEL +
" channel rules: 1) Be nice to others. 2) Do not share information " +
"after death. 3) No bots allowed. 4) Do not play with clones.\n" +
"5) Do not quit unless you need to leave. 6) No swearing and keep it " +
"family-friendly. 7) Do not paste PM's from the bot during the game. " +
"8) Use common sense. 9) Waiting for timeouts is discouraged.")
# Other settings:
START_WITH_DAY = False
WOLF_STEALS_GUN = False # at night, the wolf can steal steal the victim's bullets
# at night, the wolf can steal steal the victim's bullets
WOLF_STEALS_GUN = False
OPT_IN_PING = False # instead of !away/!back, users can opt-in to be pinged
PING_IN = [] # cloaks of users who have opted in for ping
is_role = lambda plyr, rol: rol in ROLES and plyr in ROLES[rol]
def plural(role):
if role == "wolf": return "wolves"
elif role == "person": return "people"
else: return role + "s"
if role == "wolf":
return "wolves"
elif role == "person":
return "people"
else:
return role + "s"
def list_players():
pl = []
for x in ROLES.values():
pl.extend(x)
return pl
def list_players_and_roles():
plr = {}
for x in ROLES.keys():
for p in ROLES[x]:
plr[p] = x
return plr
get_role = lambda plyr: list_players_and_roles()[plyr]
def del_player(pname):
prole = get_role(pname)
ROLES[prole].remove(pname)
class InvalidModeException(Exception): pass
class InvalidModeException(Exception):
pass
def game_mode(name):
def decor(c):
GAME_MODES[name] = c
return c
return decor
CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"],
"wolves" : INDEX_OF_ROLE["wolf"],
"cursed" : INDEX_OF_ROLE["cursed villager"],
"drunks" : INDEX_OF_ROLE["village drunk"],
"harlots" : INDEX_OF_ROLE["harlot"],
"traitors" : INDEX_OF_ROLE["traitor"],
"gunners" : INDEX_OF_ROLE["gunner"],
"werecrows" : INDEX_OF_ROLE["werecrow"],
"angels" : INDEX_OF_ROLE["guardian angel"],
"detectives" : INDEX_OF_ROLE["detective"]}
CHANGEABLE_ROLES = {"seers": INDEX_OF_ROLE["seer"],
"wolves": INDEX_OF_ROLE["wolf"],
"cursed": INDEX_OF_ROLE["cursed villager"],
"drunks": INDEX_OF_ROLE["village drunk"],
"harlots": INDEX_OF_ROLE["harlot"],
"traitors": INDEX_OF_ROLE["traitor"],
"gunners": INDEX_OF_ROLE["gunner"],
"werecrows": INDEX_OF_ROLE["werecrow"],
"angels": INDEX_OF_ROLE["guardian angel"],
"detectives": INDEX_OF_ROLE["detective"]}
# TODO: implement game modes
@game_mode("roles")
class ChangedRolesMode(object):
"""Example: !fgame roles=wolves:1,seers:0,angels:1"""
def __init__(self, arg):
self.ROLES_GUIDE = ROLES_GUIDE.copy()
lx = list(ROLES_GUIDE[None])
@ -161,102 +173,111 @@ class ChangedRolesMode(object):
try:
lx[CHANGEABLE_ROLES[role.lower()]] = num
except KeyError:
raise InvalidModeException(("The role \u0002{0}\u0002 "+
raise InvalidModeException(("The role \u0002{0}\u0002 " +
"is not valid.").format(role))
except ValueError:
raise InvalidModeException("A bad value was used in mode roles.")
raise InvalidModeException(
"A bad value was used in mode roles.")
for k in ROLES_GUIDE.keys():
self.ROLES_GUIDE[k] = tuple(lx)
# Persistence
# Load saved settings
import sqlite3
import os
conn = sqlite3.connect("data.sqlite3", check_same_thread = False)
conn = sqlite3.connect("data.sqlite3", check_same_thread=False)
with conn:
c = conn.cursor()
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)') # whoops, i mean cloak, not nick
c.execute('CREATE TABLE IF NOT EXISTS simple_role_notify (cloak TEXT)') # people who understand each role
# whoops, i mean cloak, not nick
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)')
# people who understand each role
c.execute('CREATE TABLE IF NOT EXISTS simple_role_notify (cloak TEXT)')
c.execute('SELECT * FROM away')
for row in c:
AWAY.append(row[0])
c.execute('SELECT * FROM simple_role_notify')
for row in c:
SIMPLE_NOTIFY.append(row[0])
# populate the roles table
c.execute('DROP TABLE IF EXISTS roles')
c.execute('CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)')
c.execute(
'CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)')
for x in ["villager"]+list(ROLE_INDICES.values()):
for x in ["villager"] + list(ROLE_INDICES.values()):
c.execute("INSERT OR REPLACE INTO roles (role) VALUES (?)", (x,))
c.execute(('CREATE TABLE IF NOT EXISTS rolestats (player TEXT, role TEXT, '+
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+
'UNIQUE(player, role))'))
c.execute(
('CREATE TABLE IF NOT EXISTS rolestats (player TEXT, role TEXT, ' +
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, ' +
'UNIQUE(player, role))'))
if OPT_IN_PING:
c.execute('CREATE TABLE IF NOT EXISTS ping (cloak text)')
c.execute('SELECT * FROM ping')
for row in c:
PING_IN.append(row[0])
def remove_away(clk):
with conn:
c.execute('DELETE from away where nick=?', (clk,))
def add_away(clk):
with conn:
c.execute('INSERT into away VALUES (?)', (clk,))
def remove_simple_rolemsg(clk):
with conn:
c.execute('DELETE from simple_role_notify where cloak=?', (clk,))
def add_simple_rolemsg(clk):
with conn:
c.execute('INSERT into simple_role_notify VALUES (?)', (clk,))
def remove_ping(clk):
with conn:
c.execute('DELETE from ping where cloak=?', (clk,))
def add_ping(clk):
with conn:
c.execute('INSERT into ping VALUES (?)', (clk,))
c.execute('INSERT into ping VALUES (?)', (clk,))
def update_role_stats(acc, role, won, iwon):
with conn:
wins, iwins, totalgames = 0, 0, 0
c.execute(("SELECT teamwins, individualwins, totalgames FROM rolestats "+
"WHERE player=? AND role=?"), (acc, role))
c.execute(
("SELECT teamwins, individualwins, totalgames FROM rolestats " +
"WHERE player=? AND role=?"),
(acc, role))
row = c.fetchone()
if row:
wins, iwins, total = row
else:
wins, iwins, total = 0,0,0
wins, iwins, total = 0, 0, 0
if won:
wins += 1
if iwon:
iwins += 1
total += 1
c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)",
(acc, role, wins, iwins, total))

View File

@ -1,9 +1,9 @@
PING_WAIT = 300 # Seconds
PING_MIN_WAIT = 30 # How long !start has to wait after a !ping
PING_MIN_WAIT = 30 # How long !start has to wait after a !ping
MINIMUM_WAIT = 60
EXTRA_WAIT = 20
EXTRA_WAIT_JOIN = 0 # Add this many seconds to the waiting time for each !join
WAIT_AFTER_JOIN = 10 # Wait at least this many seconds after the last join
EXTRA_WAIT_JOIN = 0 # Add this many seconds to the waiting time for each !join
WAIT_AFTER_JOIN = 10 # Wait at least this many seconds after the last join
MAXIMUM_WAITED = 3 # limit for amount of !wait's
STATS_RATE_LIMIT = 60
VOTES_RATE_LIMIT = 60
@ -21,14 +21,15 @@ DAY_TIME_LIMIT_WARN = 600
DAY_TIME_LIMIT_CHANGE = 120 # seconds after DAY_TIME_LIMIT_WARN has passed
JOIN_TIME_LIMIT = 3600
# May only be set if the above are also set
SHORT_DAY_PLAYERS = 6 # Number of players left to have a short day
SHORT_DAY_PLAYERS = 6 # Number of players left to have a short day
SHORT_DAY_LIMIT_WARN = 400
SHORT_DAY_LIMIT_CHANGE = 120
KILL_IDLE_TIME = 300
WARN_IDLE_TIME = 180
PART_GRACE_TIME = 30
QUIT_GRACE_TIME = 30
# controls how many people it does in one /msg; only works for messages that are the same
# controls how many people it does in one /msg; only works for messages
# that are the same
MAX_PRIVMSG_TARGETS = 4
LEAVE_STASIS_PENALTY = 1
IDLE_STASIS_PENALTY = 1
@ -47,57 +48,59 @@ KILL_BOLD = False
LOG_FILENAME = ""
BARE_LOG_FILENAME = ""
# HIT MISS SUICIDE
GUN_CHANCES = ( 5/7 , 1/7 , 1/7 )
DRUNK_GUN_CHANCES = ( 2/7 , 3/7 , 2/7 )
MANSLAUGHTER_CHANCE = 2/5 # ACCIDENTAL HEADSHOT (FATAL)
# HIT MISS SUICIDE
GUN_CHANCES = (5 / 7, 1 / 7, 1 / 7)
DRUNK_GUN_CHANCES = (2 / 7, 3 / 7, 2 / 7)
MANSLAUGHTER_CHANCE = 2 / 5 # ACCIDENTAL HEADSHOT (FATAL)
GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 1/4
GUARDIAN_ANGEL_DIES_CHANCE = 1/2
DETECTIVE_REVEALED_CHANCE = 2/5
GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 1 / 4
GUARDIAN_ANGEL_DIES_CHANCE = 1 / 2
DETECTIVE_REVEALED_CHANCE = 2 / 5
#################################################################################################################
##########################################################################
# ROLE INDEX: PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER CROW ANGEL DETECTIVE ##
#################################################################################################################
ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ##
6 : ( 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ##
8 : ( 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 ), ##
10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 ), ##
12 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 ), ##
15 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 ), ##
17 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ##
18 : ( 1 , 3 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ##
20 : ( 1 , 4 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ##
None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ##
#################################################################################################################
##########################################################################
ROLES_GUIDE = {4: (1, 1, 0, 0, 0, 0, 0, 0, 0, 0),
6: (1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
8: (1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
10: (1, 2, 1, 1, 1, 1, 1, 0, 0, 0),
12: (1, 2, 1, 1, 1, 1, 1, 1, 0, 1),
15: (1, 3, 1, 1, 1, 1, 1, 1, 0, 1),
17: (1, 3, 1, 1, 1, 1, 1, 1, 1, 1),
18: (1, 3, 2, 1, 1, 1, 1, 1, 1, 1),
20: (1, 4, 2, 1, 1, 1, 1, 1, 1, 1),
None: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0)}
##########################################################################
# Notes: ##
#################################################################################################################
##########################################################################
GAME_MODES = {}
AWAY = ['services.', 'services.int'] # cloaks of people who are away.
SIMPLE_NOTIFY = [] # cloaks of people who !simple, who want everything /notice'd
# cloaks of people who !simple, who want everything /notice'd
SIMPLE_NOTIFY = []
ROLE_INDICES = {0 : "seer",
1 : "wolf",
2 : "cursed villager",
3 : "village drunk",
4 : "harlot",
5 : "traitor",
6 : "gunner",
7 : "werecrow",
8 : "guardian angel",
9 : "detective"}
ROLE_INDICES = {0: "seer",
1: "wolf",
2: "cursed villager",
3: "village drunk",
4: "harlot",
5: "traitor",
6: "gunner",
7: "werecrow",
8: "guardian angel",
9: "detective"}
INDEX_OF_ROLE = dict((v,k) for k,v in ROLE_INDICES.items())
INDEX_OF_ROLE = dict((v, k) for k, v in ROLE_INDICES.items())
NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.",
"A pool of blood and wolf paw prints are found.",
"Traces of wolf fur are found.")
LYNCH_MESSAGES = ("The villagers, after much debate, finally decide on lynching \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged. The villagers have killed a \u0002{1}\u0002.",
"Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows. Once the twitching stops, it is discovered that the village lynched a \u0002{1}\u0002.",
"Before the rope is pulled, \u0002{0}\u0002, the \u0002{1}\u0002, throws a grenade at the mob. The grenade explodes early.")
LYNCH_MESSAGES = (
"The villagers, after much debate, finally decide on lynching \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.",
"Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged. The villagers have killed a \u0002{1}\u0002.",
"Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows. Once the twitching stops, it is discovered that the village lynched a \u0002{1}\u0002.",
"Before the rope is pulled, \u0002{0}\u0002, the \u0002{1}\u0002, throws a grenade at the mob. The grenade explodes early.")
import botconfig
@ -105,17 +108,23 @@ RULES = (botconfig.CHANNEL + " channel rules: http://wolf.xnrand.com/rules")
# Other settings:
START_WITH_DAY = False
WOLF_STEALS_GUN = True # at night, the wolf can steal steal the victim's bullets
# at night, the wolf can steal steal the victim's bullets
WOLF_STEALS_GUN = True
OPT_IN_PING = False # instead of !away/!back, users can opt-in to be pinged
PING_IN = [] # cloaks of users who have opted in for ping
is_role = lambda plyr, rol: rol in ROLES and plyr in ROLES[rol]
def plural(role):
if role == "wolf": return "wolves"
elif role == "person": return "people"
else: return role + "s"
if role == "wolf":
return "wolves"
elif role == "person":
return "people"
else:
return role + "s"
def list_players():
pl = []
@ -123,6 +132,7 @@ def list_players():
pl.extend(x)
return pl
def list_players_and_roles():
plr = {}
for x in ROLES.keys():
@ -132,19 +142,23 @@ def list_players_and_roles():
get_role = lambda plyr: list_players_and_roles()[plyr]
def get_reveal_role(nick):
if HIDDEN_TRAITOR and get_role(nick) == "traitor":
return "villager"
else:
return get_role(nick)
def del_player(pname):
prole = get_role(pname)
ROLES[prole].remove(pname)
class InvalidModeException(Exception):
pass
class InvalidModeException(Exception): pass
def game_mode(name):
def decor(c):
GAME_MODES[name] = c
@ -152,23 +166,22 @@ def game_mode(name):
return decor
CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"],
"wolves" : INDEX_OF_ROLE["wolf"],
"cursed" : INDEX_OF_ROLE["cursed villager"],
"drunks" : INDEX_OF_ROLE["village drunk"],
"harlots" : INDEX_OF_ROLE["harlot"],
"traitors" : INDEX_OF_ROLE["traitor"],
"gunners" : INDEX_OF_ROLE["gunner"],
"werecrows" : INDEX_OF_ROLE["werecrow"],
"angels" : INDEX_OF_ROLE["guardian angel"],
"detectives" : INDEX_OF_ROLE["detective"]}
CHANGEABLE_ROLES = {"seers": INDEX_OF_ROLE["seer"],
"wolves": INDEX_OF_ROLE["wolf"],
"cursed": INDEX_OF_ROLE["cursed villager"],
"drunks": INDEX_OF_ROLE["village drunk"],
"harlots": INDEX_OF_ROLE["harlot"],
"traitors": INDEX_OF_ROLE["traitor"],
"gunners": INDEX_OF_ROLE["gunner"],
"werecrows": INDEX_OF_ROLE["werecrow"],
"angels": INDEX_OF_ROLE["guardian angel"],
"detectives": INDEX_OF_ROLE["detective"]}
# TODO: implement game modes
@game_mode("roles")
class ChangedRolesMode(object):
"""Example: !fgame roles=wolves:1,seers:0,angels:1"""
def __init__(self, arg):
@ -187,10 +200,11 @@ class ChangedRolesMode(object):
try:
lx[CHANGEABLE_ROLES[role.lower()]] = num
except KeyError:
raise InvalidModeException(("The role \u0002{0}\u0002 "+
raise InvalidModeException(("The role \u0002{0}\u0002 " +
"is not valid.").format(role))
except ValueError:
raise InvalidModeException("A bad value was used in mode roles.")
raise InvalidModeException(
"A bad value was used in mode roles.")
for k in ROLES_GUIDE.keys():
self.ROLES_GUIDE[k] = tuple(lx)
@ -201,13 +215,15 @@ class ChangedRolesMode(object):
# Load saved settings
import sqlite3
conn = sqlite3.connect("data.sqlite3", check_same_thread = False)
conn = sqlite3.connect("data.sqlite3", check_same_thread=False)
with conn:
c = conn.cursor()
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)') # whoops, i mean cloak, not nick
# whoops, i mean cloak, not nick
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)')
c.execute('CREATE TABLE IF NOT EXISTS simple_role_notify (cloak TEXT)') # people who understand each role
# people who understand each role
c.execute('CREATE TABLE IF NOT EXISTS simple_role_notify (cloak TEXT)')
c.execute('SELECT * FROM away')
for row in c:
@ -219,21 +235,21 @@ with conn:
# populate the roles table
c.execute('DROP TABLE IF EXISTS roles')
c.execute('CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)')
c.execute(
'CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)')
for x in ["villager"]+list(ROLE_INDICES.values()):
for x in ["villager"] + list(ROLE_INDICES.values()):
c.execute("INSERT OR REPLACE INTO roles (role) VALUES (?)", (x,))
c.execute(
('CREATE TABLE IF NOT EXISTS rolestats (player TEXT, role TEXT, ' +
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, ' +
'UNIQUE(player, role))'))
c.execute(('CREATE TABLE IF NOT EXISTS rolestats (player TEXT, role TEXT, '+
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+
'UNIQUE(player, role))'))
c.execute(
('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' +
'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))'))
c.execute(('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' +
'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))'))
if OPT_IN_PING:
c.execute('CREATE TABLE IF NOT EXISTS ping (cloak text)')
@ -246,22 +262,27 @@ def remove_away(clk):
with conn:
c.execute('DELETE from away where nick=?', (clk,))
def add_away(clk):
with conn:
c.execute('INSERT into away VALUES (?)', (clk,))
def remove_simple_rolemsg(clk):
with conn:
c.execute('DELETE from simple_role_notify where cloak=?', (clk,))
def add_simple_rolemsg(clk):
with conn:
c.execute('INSERT into simple_role_notify VALUES (?)', (clk,))
def remove_ping(clk):
with conn:
c.execute('DELETE from ping where cloak=?', (clk,))
def add_ping(clk):
with conn:
c.execute('INSERT into ping VALUES (?)', (clk,))
@ -271,8 +292,10 @@ def update_role_stats(acc, role, won, iwon):
with conn:
wins, iwins, total = 0, 0, 0
c.execute(("SELECT teamwins, individualwins, totalgames FROM rolestats "+
"WHERE player=? AND role=?"), (acc, role))
c.execute(
("SELECT teamwins, individualwins, totalgames FROM rolestats " +
"WHERE player=? AND role=?"),
(acc, role))
row = c.fetchone()
if row:
wins, iwins, total = row
@ -286,75 +309,106 @@ def update_role_stats(acc, role, won, iwon):
c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)",
(acc, role, wins, iwins, total))
def update_game_stats(size, winner):
with conn:
vwins, wwins, total = 0, 0, 0
c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+
"WHERE size=?", (size,))
c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats " +
"WHERE size=?", (size,))
row = c.fetchone()
if row:
vwins, wwins, total = row
if winner == "wolves":
wwins += 1
elif winner == "villagers":
vwins += 1
total += 1
c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)",
(size, vwins, wwins, total))
(size, vwins, wwins, total))
def get_player_stats(acc, role):
if role.lower() not in ["villager"] + [v.lower() for k, v in ROLE_INDICES.items()]:
if role.lower() not in ["villager"] + [v.lower()
for k, v in ROLE_INDICES.items()]:
return "No such role: {0}".format(role)
with conn:
c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,))
c.execute(
"SELECT player FROM rolestats WHERE player=? COLLATE NOCASE",
(acc,))
player = c.fetchone()
if player:
for row in c.execute("SELECT * FROM rolestats WHERE player=? COLLATE NOCASE AND role=? COLLATE NOCASE", (acc, role)):
msg = "\u0002{0}\u0002 as \u0002{1}\u0002 | Team wins: {2} (%d%%), Individual wins: {3} (%d%%), Total games: {4}".format(*row)
return msg % (round(row[2]/row[4] * 100), round(row[3]/row[4] * 100))
msg = "\u0002{0}\u0002 as \u0002{1}\u0002 | Team wins: {2} (%d%%), Individual wins: {3} (%d%%), Total games: {4}".format(
*
row)
return msg % (
round(row[2] / row[4] * 100),
round(row[3] / row[4] * 100))
else:
return "No stats for {0} as {1}.".format(player[0], role)
return "{0} has not played any games.".format(acc)
def get_player_totals(acc):
role_totals = []
with conn:
c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,))
c.execute(
"SELECT player FROM rolestats WHERE player=? COLLATE NOCASE",
(acc,))
player = c.fetchone()
if player:
for role in ["villager"] + [v for k, v in ROLE_INDICES.items()]:
c.execute("SELECT totalgames FROM rolestats WHERE player=? COLLATE NOCASE AND role=? COLLATE NOCASE", (acc, role))
c.execute(
"SELECT totalgames FROM rolestats WHERE player=? COLLATE NOCASE AND role=? COLLATE NOCASE",
(acc, role))
row = c.fetchone()
if row:
role_totals.append("\u0002{0}\u0002: {1}".format(role, *row))
c.execute("SELECT SUM(totalgames) from rolestats WHERE player=? COLLATE NOCASE AND role!='cursed villager' AND role!='gunner'", (acc,))
role_totals.append(
"\u0002{0}\u0002: {1}".format(
role,
*
row))
c.execute(
"SELECT SUM(totalgames) from rolestats WHERE player=? COLLATE NOCASE AND role!='cursed villager' AND role!='gunner'",
(acc,))
row = c.fetchone()
return "\u0002{0}\u0002's totals | \u0002{1}\u0002 games | {2}".format(player[0], row[0], ", ".join(role_totals))
return "\u0002{0}\u0002's totals | \u0002{1}\u0002 games | {2}".format(
player[0],
row[0],
", ".join(role_totals))
else:
return "\u0002{0}\u0002 has not played any games.".format(acc)
def get_game_stats(size):
with conn:
for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)):
msg = "\u0002{0}\u0002 player games | Village wins: {1} (%d%%), Wolf wins: {2} (%d%%), Total games: {3}".format(*row)
return msg % (round(row[1]/row[3] * 100), round(row[2]/row[3] * 100))
msg = "\u0002{0}\u0002 player games | Village wins: {1} (%d%%), Wolf wins: {2} (%d%%), Total games: {3}".format(
*
row)
return msg % (
round(row[1] / row[3] * 100),
round(row[2] / row[3] * 100))
else:
return "No stats for \u0002{0}\u0002 player games.".format(size)
def get_game_totals():
size_totals = []
total = 0
with conn:
for size in range(MIN_PLAYERS, MAX_PLAYERS + 1):
c.execute("SELECT size, totalgames FROM gamestats WHERE size=?", (size,))
c.execute(
"SELECT size, totalgames FROM gamestats WHERE size=?",
(size,))
row = c.fetchone()
if row:
size_totals.append("\u0002{0}p\u0002: {1}".format(*row))
total += row[1]
if len(size_totals) == 0:
return "No games have been played."
else:

View File

@ -1,17 +1,30 @@
# Copyright (c) 2011, Jimmy Cao
# All rights reserved.
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from oyoyo.parse import parse_nick
import fnmatch
import botconfig
def generate(fdict, permissions=True, **kwargs):
"""Generates a decorator generator. Always use this"""
def cmd(*s, raw_nick=False, admin_only=False, owner_only=False, hookid=-1):
@ -26,10 +39,10 @@ def generate(fdict, permissions=True, **kwargs):
else:
nick = ""
cloak = ""
if not raw_nick and len(largs) > 1 and largs[1]:
largs[1] = nick
#if largs[1].startswith("#"):
# if largs[1].startswith("#"):
if not permissions or "" in s:
return f(*largs)
if cloak:
@ -37,7 +50,9 @@ def generate(fdict, permissions=True, **kwargs):
if fnmatch.fnmatch(cloak.lower(), pattern.lower()):
for cmdname in s:
if cmdname in botconfig.DENY[pattern]:
largs[0].notice(nick, "You do not have permission to use that command.")
largs[0].notice(
nick,
"You do not have permission to use that command.")
return
for pattern in botconfig.ALLOW.keys():
if fnmatch.fnmatch(cloak.lower(), pattern.lower()):
@ -45,15 +60,20 @@ def generate(fdict, permissions=True, **kwargs):
if cmdname in botconfig.ALLOW[pattern]:
return f(*largs) # no questions
if owner_only:
if cloak and [ptn for ptn in botconfig.OWNERS
if fnmatch.fnmatch(cloak.lower(), ptn.lower())]:
if cloak and [
ptn for ptn in botconfig.OWNERS if fnmatch.fnmatch(
cloak.lower(),
ptn.lower())]:
return f(*largs)
elif cloak:
largs[0].notice(nick, "You are not the owner.")
return
if admin_only:
if cloak and [ptn for ptn in botconfig.ADMINS+botconfig.OWNERS
if fnmatch.fnmatch(cloak.lower(), ptn.lower())]:
if cloak and[ptn
for ptn in botconfig.ADMINS + botconfig.OWNERS
if fnmatch.fnmatch(
cloak.lower(),
ptn.lower())]:
return f(*largs)
elif cloak:
largs[0].notice(nick, "You are not an admin.")
@ -67,8 +87,11 @@ def generate(fdict, permissions=True, **kwargs):
else:
for fn in fdict[x]:
if (fn.owner_only != owner_only or
fn.admin_only != admin_only):
raise Exception("Command: "+x+" has non-matching protection levels!")
fn.admin_only != admin_only):
raise Exception(
"Command: " +
x +
" has non-matching protection levels!")
fdict[x].append(innerf)
if alias:
innerf.aliases.append(x)
@ -79,12 +102,13 @@ def generate(fdict, permissions=True, **kwargs):
innerf.hookid = hookid
innerf.__doc__ = f.__doc__
return innerf
return dec
return lambda *args, **kwarargs: cmd(*args, **kwarargs) if kwarargs else cmd(*args, **kwargs)
return lambda *args, **kwarargs: cmd(*args, **
kwarargs) if kwarargs else cmd(*args, **kwargs)
def unhook(hdict, hookid):
for cmd in list(hdict.keys()):
for x in hdict[cmd]:

View File

@ -10,15 +10,15 @@ for modfile in os.listdir("modules"):
continue
if not modfile.endswith(".py"):
continue # not a module
if not os.path.isfile("modules/"+modfile):
if not os.path.isfile("modules/" + modfile):
continue # not a file
modfile = modfile[:-3]
print("Loading module "+modfile)
MODULES[modfile] = getattr(__import__("modules."+modfile), modfile)
print("Loading module " + modfile)
MODULES[modfile] = getattr(__import__("modules." + modfile), modfile)
if botconfig.DEFAULT_MODULE in MODULES.keys():
CURRENT_MODULE = botconfig.DEFAULT_MODULE.lower()
else:

View File

@ -1,38 +1,42 @@
import botconfig
from datetime import datetime
class WolfgameLogger(object):
def __init__(self, outfile, boutfile):
self.outfile = outfile
self.boutfile = boutfile
self.logged = ""
self.barelogged = ""
def log(self, message):
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + message + "\n"
def logBare(self, *args):
self.barelogged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + " ".join(args) + "\n"
def logChannelMessage(self, who, message):
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + "<{0}> {1}\n".format(who, message)
self.logged += datetime.utcnow().strftime(
"%Y-%m-%d %H:%M:%S ") + "<{0}> {1}\n".format(who, message)
def logCommand(self, who, cmd, rest):
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + "<{0}> {1}{2} {3}".format(who, botconfig.CMD_CHAR, cmd, rest) + "\n"
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + \
"<{0}> {1}{2} {3}".format(who, botconfig.CMD_CHAR, cmd, rest) + "\n"
def logMessage(self, message):
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + "<{0}> ".format(botconfig.NICK)+message+"\n"
self.logged += datetime.utcnow().strftime(
"%Y-%m-%d %H:%M:%S ") + "<{0}> ".format(botconfig.NICK) + message + "\n"
def saveToFile(self):
if self.outfile:
with open(self.outfile, "a") as lf:
lf.write(self.logged)
if self.boutfile:
with open(self.boutfile, "a") as bl:
bl.write(self.barelogged)
self.logged = ""
self.barelogged = ""

View File

@ -49,17 +49,17 @@ def main():
handler.setFormatter(formatter)
cli = IRCClient(
{"privmsg": modules.common.on_privmsg,
"notice": lambda a, b, c, d: modules.common.on_privmsg(a, b, c, d, True),
"": modules.common.__unhandled__},
host=botconfig.HOST,
port=botconfig.PORT,
authname=botconfig.USERNAME,
password=botconfig.PASS,
nickname=botconfig.NICK,
sasl_auth=botconfig.SASL_AUTHENTICATION,
use_ssl=botconfig.USE_SSL,
connect_cb=modules.common.connect_callback
{"privmsg": modules.common.on_privmsg,
"notice": lambda a, b, c, d: modules.common.on_privmsg(a, b, c, d, True),
"": modules.common.__unhandled__},
host=botconfig.HOST,
port=botconfig.PORT,
authname=botconfig.USERNAME,
password=botconfig.PASS,
nickname=botconfig.NICK,
sasl_auth=botconfig.SASL_AUTHENTICATION,
use_ssl=botconfig.USE_SSL,
connect_cb=modules.common.connect_callback
)
cli.mainLoop()