Browse Source

change line endings to unix style

master
Jimmy Cao 13 years ago
parent
commit
3f36fb3fb5
  1. 188
      decorators.py
  2. 550
      oyoyo/client.py
  3. 416
      oyoyo/ircevents.py
  4. 541
      var.py
  5. 246
      wolfbot.py
  6. 5452
      wolfgame.py
  7. 74
      wolfgamelogger.py

188
decorators.py

@ -1,94 +1,94 @@ @@ -1,94 +1,94 @@
# 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:
# 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.
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, id=-1):
def dec(f):
def innerf(*args):
largs = list(args)
if len(largs) > 1 and largs[1]:
nick, _, _, cloak = parse_nick(largs[1])
if cloak is None:
cloak = ""
else:
nick = ""
cloak = ""
if not raw_nick and len(largs) > 1 and largs[1]:
largs[1] = nick
#if largs[1].startswith("#"):
if not permissions or "" in s:
return f(*largs)
if cloak:
for pattern in botconfig.DENY.keys():
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.")
return
for pattern in botconfig.ALLOW.keys():
if fnmatch.fnmatch(cloak.lower(), pattern.lower()):
for cmdname in s:
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())]:
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())]:
return f(*largs)
elif cloak:
largs[0].notice(nick, "You are not an admin.")
return
return f(*largs)
alias = False
innerf.aliases = []
for x in s:
if x not in fdict.keys():
fdict[x] = []
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!")
fdict[x].append(innerf)
if alias:
innerf.aliases.append(x)
alias = True
innerf.owner_only = owner_only
innerf.raw_nick = raw_nick
innerf.admin_only = admin_only
innerf.id = id
innerf.__doc__ = f.__doc__
return innerf
return dec
return lambda *args, **kwarargs: cmd(*args, **kwarargs) if kwarargs else cmd(*args, **kwargs)
def unhook(hdict, id):
for cmd in list(hdict.keys()):
for x in hdict[cmd]:
if x.id == id:
hdict[cmd].remove(x)
if not hdict[cmd]:
del hdict[cmd]
# 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:
# 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.
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, id=-1):
def dec(f):
def innerf(*args):
largs = list(args)
if len(largs) > 1 and largs[1]:
nick, _, _, cloak = parse_nick(largs[1])
if cloak is None:
cloak = ""
else:
nick = ""
cloak = ""
if not raw_nick and len(largs) > 1 and largs[1]:
largs[1] = nick
#if largs[1].startswith("#"):
if not permissions or "" in s:
return f(*largs)
if cloak:
for pattern in botconfig.DENY.keys():
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.")
return
for pattern in botconfig.ALLOW.keys():
if fnmatch.fnmatch(cloak.lower(), pattern.lower()):
for cmdname in s:
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())]:
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())]:
return f(*largs)
elif cloak:
largs[0].notice(nick, "You are not an admin.")
return
return f(*largs)
alias = False
innerf.aliases = []
for x in s:
if x not in fdict.keys():
fdict[x] = []
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!")
fdict[x].append(innerf)
if alias:
innerf.aliases.append(x)
alias = True
innerf.owner_only = owner_only
innerf.raw_nick = raw_nick
innerf.admin_only = admin_only
innerf.id = id
innerf.__doc__ = f.__doc__
return innerf
return dec
return lambda *args, **kwarargs: cmd(*args, **kwarargs) if kwarargs else cmd(*args, **kwargs)
def unhook(hdict, id):
for cmd in list(hdict.keys()):
for x in hdict[cmd]:
if x.id == id:
hdict[cmd].remove(x)
if not hdict[cmd]:
del hdict[cmd]

550
oyoyo/client.py

@ -1,275 +1,275 @@ @@ -1,275 +1,275 @@
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import logging
import socket
import time
import threading
import traceback
import sys
from oyoyo.parse import parse_raw_irc_command
# 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."""
self.capacity = float(tokens)
self._tokens = float(tokens)
self.fill_rate = float(fill_rate)
self.timestamp = time.time()
def consume(self, tokens):
"""Consume tokens from the bucket. Returns True if there were
sufficient tokens otherwise False."""
if tokens <= self.tokens:
self._tokens -= tokens
return True
return False
@property
def tokens(self):
now = time.time()
if self._tokens < self.capacity:
delta = self.fill_rate * (now - self.timestamp)
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:
def func(x):
def gen(self, *a):
self.send(x.upper(), *a)
return gen
setattr(cls, c, func(c))
return cls
return dec
@add_commands(("join",
"mode",
"nick",
"who"))
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
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
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
means if you use a plain while loop your app will consume 100% cpu.
To enable blocking pass blocking=True.
"""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.nickname = ""
self.hostmask = ""
self.ident = ""
self.real_name = ""
self.host = None
self.port = None
self.password = ""
self.authname = ""
self.connect_cb = None
self.blocking = True
self.lock = threading.RLock()
self.tokenbucket = TokenBucket(28, 1.73)
self.__dict__.update(kwargs)
self.command_handler = cmd_handler
self._end = 0
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
>>> cli.send("JOIN " + some_room)
>>> cli.send("JOIN", some_room)
In python 2, all args must be of type str or unicode, *BUT* if they are
unicode they will be converted to str with the encoding specified by
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').
"""
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):
if isinstance(arg, str):
bargs.append(bytes(arg, encoding))
elif isinstance(arg, bytes):
bargs.append(arg)
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))
msg = bytes(" ", "utf_8").join(bargs)
logging.info('---> send "{0}"'.format(msg))
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.
>>> cli = IRCClient(my_handler, host="irc.freenode.net", port=6667)
>>> g = cli.connect()
>>> while 1:
... next(g)
"""
try:
logging.info('connecting to {0}:{1}'.format(self.host, self.port))
retries = 0
while True:
try:
self.socket.connect(("{0}".format(self.host), self.port))
break
except socket.error as e:
retries += 1
logging.warning('Error: {0}'.format(e))
if retries > 3:
break
if not self.blocking:
self.socket.setblocking(0)
self.send("PASS {0}:{1}".format(self.authname if self.authname else self.nickname,
self.password if self.password else "NOPASS"))
self.nick(self.nickname)
self.user(self.nickname, self.real_name)
if self.connect_cb:
self.connect_cb(self)
buffer = bytes()
while not self._end:
try:
buffer += self.socket.recv(1024)
except socket.error as e:
if False and not self.blocking and e.errno == 11:
pass
else:
raise e
else:
data = buffer.split(bytes("\n", "utf_8"))
buffer = data.pop()
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)]
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))
try:
largs = list(args)
if prefix is not None:
prefix = prefix.decode(enc)
# 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)
elif "" in self.command_handler:
self.command_handler[""](self, prefix, command, *fargs)
except Exception as e:
traceback.print_exc()
raise e # ?
yield True
finally:
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)
while line:
extra = ""
if len(line) > maxchars:
extra = line[maxchars:]
line = line[:maxchars]
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)
while line:
extra = ""
if len(line) > maxchars:
extra = line[maxchars:]
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)
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)
def ns_release(self):
self.msg("NickServ", "RELEASE "+self.nickname)
def user(self, uname, rname):
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()
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import logging
import socket
import time
import threading
import traceback
import sys
from oyoyo.parse import parse_raw_irc_command
# 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."""
self.capacity = float(tokens)
self._tokens = float(tokens)
self.fill_rate = float(fill_rate)
self.timestamp = time.time()
def consume(self, tokens):
"""Consume tokens from the bucket. Returns True if there were
sufficient tokens otherwise False."""
if tokens <= self.tokens:
self._tokens -= tokens
return True
return False
@property
def tokens(self):
now = time.time()
if self._tokens < self.capacity:
delta = self.fill_rate * (now - self.timestamp)
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:
def func(x):
def gen(self, *a):
self.send(x.upper(), *a)
return gen
setattr(cls, c, func(c))
return cls
return dec
@add_commands(("join",
"mode",
"nick",
"who"))
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
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
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
means if you use a plain while loop your app will consume 100% cpu.
To enable blocking pass blocking=True.
"""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.nickname = ""
self.hostmask = ""
self.ident = ""
self.real_name = ""
self.host = None
self.port = None
self.password = ""
self.authname = ""
self.connect_cb = None
self.blocking = True
self.lock = threading.RLock()
self.tokenbucket = TokenBucket(28, 1.73)
self.__dict__.update(kwargs)
self.command_handler = cmd_handler
self._end = 0
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
>>> cli.send("JOIN " + some_room)
>>> cli.send("JOIN", some_room)
In python 2, all args must be of type str or unicode, *BUT* if they are
unicode they will be converted to str with the encoding specified by
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').
"""
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):
if isinstance(arg, str):
bargs.append(bytes(arg, encoding))
elif isinstance(arg, bytes):
bargs.append(arg)
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))
msg = bytes(" ", "utf_8").join(bargs)
logging.info('---> send "{0}"'.format(msg))
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.
>>> cli = IRCClient(my_handler, host="irc.freenode.net", port=6667)
>>> g = cli.connect()
>>> while 1:
... next(g)
"""
try:
logging.info('connecting to {0}:{1}'.format(self.host, self.port))
retries = 0
while True:
try:
self.socket.connect(("{0}".format(self.host), self.port))
break
except socket.error as e:
retries += 1
logging.warning('Error: {0}'.format(e))
if retries > 3:
break
if not self.blocking:
self.socket.setblocking(0)
self.send("PASS {0}:{1}".format(self.authname if self.authname else self.nickname,
self.password if self.password else "NOPASS"))
self.nick(self.nickname)
self.user(self.nickname, self.real_name)
if self.connect_cb:
self.connect_cb(self)
buffer = bytes()
while not self._end:
try:
buffer += self.socket.recv(1024)
except socket.error as e:
if False and not self.blocking and e.errno == 11:
pass
else:
raise e
else:
data = buffer.split(bytes("\n", "utf_8"))
buffer = data.pop()
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)]
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))
try:
largs = list(args)
if prefix is not None:
prefix = prefix.decode(enc)
# 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)
elif "" in self.command_handler:
self.command_handler[""](self, prefix, command, *fargs)
except Exception as e:
traceback.print_exc()
raise e # ?
yield True
finally:
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)
while line:
extra = ""
if len(line) > maxchars:
extra = line[maxchars:]
line = line[:maxchars]
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)
while line:
extra = ""
if len(line) > maxchars:
extra = line[maxchars:]
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)
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)
def ns_release(self):
self.msg("NickServ", "RELEASE "+self.nickname)
def user(self, uname, rname):
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()

416
oyoyo/ircevents.py

@ -1,208 +1,208 @@ @@ -1,208 +1,208 @@
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
numeric_events = {
b"001": "welcome",
b"002": "yourhost",
b"003": "created",
b"004": "myinfo",
b"005": "featurelist", # XXX
b"200": "tracelink",
b"201": "traceconnecting",
b"202": "tracehandshake",
b"203": "traceunknown",
b"204": "traceoperator",
b"205": "traceuser",
b"206": "traceserver",
b"207": "traceservice",
b"208": "tracenewtype",
b"209": "traceclass",
b"210": "tracereconnect",
b"211": "statslinkinfo",
b"212": "statscommands",
b"213": "statscline",
b"214": "statsnline",
b"215": "statsiline",
b"216": "statskline",
b"217": "statsqline",
b"218": "statsyline",
b"219": "endofstats",
b"221": "umodeis",
b"231": "serviceinfo",
b"232": "endofservices",
b"233": "service",
b"234": "servlist",
b"235": "servlistend",
b"241": "statslline",
b"242": "statsuptime",
b"243": "statsoline",
b"244": "statshline",
b"250": "luserconns",
b"251": "luserclient",
b"252": "luserop",
b"253": "luserunknown",
b"254": "luserchannels",
b"255": "luserme",
b"256": "adminme",
b"257": "adminloc1",
b"258": "adminloc2",
b"259": "adminemail",
b"261": "tracelog",
b"262": "endoftrace",
b"263": "tryagain",
b"265": "n_local",
b"266": "n_global",
b"300": "none",
b"301": "away",
b"302": "userhost",
b"303": "ison",
b"305": "unaway",
b"306": "nowaway",
b"311": "whoisuser",
b"312": "whoisserver",
b"313": "whoisoperator",
b"314": "whowasuser",
b"315": "endofwho",
b"316": "whoischanop",
b"317": "whoisidle",
b"318": "endofwhois",
b"319": "whoischannels",
b"321": "liststart",
b"322": "list",
b"323": "listend",
b"324": "channelmodeis",
b"329": "channelcreate",
b"331": "notopic",
b"332": "currenttopic",
b"333": "topicinfo",
b"341": "inviting",
b"342": "summoning",
b"346": "invitelist",
b"347": "endofinvitelist",
b"348": "exceptlist",
b"349": "endofexceptlist",
b"351": "version",
b"352": "whoreply",
b"353": "namreply",
b"361": "killdone",
b"362": "closing",
b"363": "closeend",
b"364": "links",
b"365": "endoflinks",
b"366": "endofnames",
b"367": "banlist",
b"368": "endofbanlist",
b"369": "endofwhowas",
b"371": "info",
b"372": "motd",
b"373": "infostart",
b"374": "endofinfo",
b"375": "motdstart",
b"376": "endofmotd",
b"377": "motd2", # 1997-10-16 -- tkil
b"381": "youreoper",
b"382": "rehashing",
b"384": "myportis",
b"391": "time",
b"392": "usersstart",
b"393": "users",
b"394": "endofusers",
b"395": "nousers",
b"396": "event_hosthidden",
b"401": "nosuchnick",
b"402": "nosuchserver",
b"403": "nosuchchannel",
b"404": "cannotsendtochan",
b"405": "toomanychannels",
b"406": "wasnosuchnick",
b"407": "toomanytargets",
b"409": "noorigin",
b"411": "norecipient",
b"412": "notexttosend",
b"413": "notoplevel",
b"414": "wildtoplevel",
b"421": "unknowncommand",
b"422": "nomotd",
b"423": "noadmininfo",
b"424": "fileerror",
b"431": "nonicknamegiven",
b"432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
b"433": "nicknameinuse",
b"436": "nickcollision",
b"437": "unavailresource", # "Nick temporally unavailable"
b"441": "usernotinchannel",
b"442": "notonchannel",
b"443": "useronchannel",
b"444": "nologin",
b"445": "summondisabled",
b"446": "usersdisabled",
b"451": "notregistered",
b"461": "needmoreparams",
b"462": "alreadyregistered",
b"463": "nopermforhost",
b"464": "passwdmismatch",
b"465": "yourebannedcreep", # I love this one...
b"466": "youwillbebanned",
b"467": "keyset",
b"471": "channelisfull",
b"472": "unknownmode",
b"473": "inviteonlychan",
b"474": "bannedfromchan",
b"475": "badchannelkey",
b"476": "badchanmask",
b"477": "nochanmodes", # "Channel doesn't support modes"
b"478": "banlistfull",
b"481": "noprivileges",
b"482": "chanoprivsneeded",
b"483": "cantkillserver",
b"484": "restricted", # Connection is restricted
b"485": "uniqopprivsneeded",
b"491": "nooperhost",
b"492": "noservicehost",
b"501": "umodeunknownflag",
b"502": "usersdontmatch",
}
generated_events = [
# Generated events
"dcc_connect",
"dcc_disconnect",
"dccmsg",
"disconnect",
"ctcp",
"ctcpreply",
]
protocol_events = [
# IRC protocol events
"error",
"join",
"kick",
"mode",
"part",
"ping",
"privmsg",
"privnotice",
"pubmsg",
"pubnotice",
"quit",
"invite",
"pong",
]
all_events = generated_events + protocol_events + list(numeric_events.values())
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
numeric_events = {
b"001": "welcome",
b"002": "yourhost",
b"003": "created",
b"004": "myinfo",
b"005": "featurelist", # XXX
b"200": "tracelink",
b"201": "traceconnecting",
b"202": "tracehandshake",
b"203": "traceunknown",
b"204": "traceoperator",
b"205": "traceuser",
b"206": "traceserver",
b"207": "traceservice",
b"208": "tracenewtype",
b"209": "traceclass",
b"210": "tracereconnect",
b"211": "statslinkinfo",
b"212": "statscommands",
b"213": "statscline",
b"214": "statsnline",
b"215": "statsiline",
b"216": "statskline",
b"217": "statsqline",
b"218": "statsyline",
b"219": "endofstats",
b"221": "umodeis",
b"231": "serviceinfo",
b"232": "endofservices",
b"233": "service",
b"234": "servlist",
b"235": "servlistend",
b"241": "statslline",
b"242": "statsuptime",
b"243": "statsoline",
b"244": "statshline",
b"250": "luserconns",
b"251": "luserclient",
b"252": "luserop",
b"253": "luserunknown",
b"254": "luserchannels",
b"255": "luserme",
b"256": "adminme",
b"257": "adminloc1",
b"258": "adminloc2",
b"259": "adminemail",
b"261": "tracelog",
b"262": "endoftrace",
b"263": "tryagain",
b"265": "n_local",
b"266": "n_global",
b"300": "none",
b"301": "away",
b"302": "userhost",
b"303": "ison",
b"305": "unaway",
b"306": "nowaway",
b"311": "whoisuser",
b"312": "whoisserver",
b"313": "whoisoperator",
b"314": "whowasuser",
b"315": "endofwho",
b"316": "whoischanop",
b"317": "whoisidle",
b"318": "endofwhois",
b"319": "whoischannels",
b"321": "liststart",
b"322": "list",
b"323": "listend",
b"324": "channelmodeis",
b"329": "channelcreate",
b"331": "notopic",
b"332": "currenttopic",
b"333": "topicinfo",
b"341": "inviting",
b"342": "summoning",
b"346": "invitelist",
b"347": "endofinvitelist",
b"348": "exceptlist",
b"349": "endofexceptlist",
b"351": "version",
b"352": "whoreply",
b"353": "namreply",
b"361": "killdone",
b"362": "closing",
b"363": "closeend",
b"364": "links",
b"365": "endoflinks",
b"366": "endofnames",
b"367": "banlist",
b"368": "endofbanlist",
b"369": "endofwhowas",
b"371": "info",
b"372": "motd",
b"373": "infostart",
b"374": "endofinfo",
b"375": "motdstart",
b"376": "endofmotd",
b"377": "motd2", # 1997-10-16 -- tkil
b"381": "youreoper",
b"382": "rehashing",
b"384": "myportis",
b"391": "time",
b"392": "usersstart",
b"393": "users",
b"394": "endofusers",
b"395": "nousers",
b"396": "event_hosthidden",
b"401": "nosuchnick",
b"402": "nosuchserver",
b"403": "nosuchchannel",
b"404": "cannotsendtochan",
b"405": "toomanychannels",
b"406": "wasnosuchnick",
b"407": "toomanytargets",
b"409": "noorigin",
b"411": "norecipient",
b"412": "notexttosend",
b"413": "notoplevel",
b"414": "wildtoplevel",
b"421": "unknowncommand",
b"422": "nomotd",
b"423": "noadmininfo",
b"424": "fileerror",
b"431": "nonicknamegiven",
b"432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
b"433": "nicknameinuse",
b"436": "nickcollision",
b"437": "unavailresource", # "Nick temporally unavailable"
b"441": "usernotinchannel",
b"442": "notonchannel",
b"443": "useronchannel",
b"444": "nologin",
b"445": "summondisabled",
b"446": "usersdisabled",
b"451": "notregistered",
b"461": "needmoreparams",
b"462": "alreadyregistered",
b"463": "nopermforhost",
b"464": "passwdmismatch",
b"465": "yourebannedcreep", # I love this one...
b"466": "youwillbebanned",
b"467": "keyset",
b"471": "channelisfull",
b"472": "unknownmode",
b"473": "inviteonlychan",
b"474": "bannedfromchan",
b"475": "badchannelkey",
b"476": "badchanmask",
b"477": "nochanmodes", # "Channel doesn't support modes"
b"478": "banlistfull",
b"481": "noprivileges",
b"482": "chanoprivsneeded",
b"483": "cantkillserver",
b"484": "restricted", # Connection is restricted
b"485": "uniqopprivsneeded",
b"491": "nooperhost",
b"492": "noservicehost",
b"501": "umodeunknownflag",
b"502": "usersdontmatch",
}
generated_events = [
# Generated events
"dcc_connect",
"dcc_disconnect",
"dccmsg",
"disconnect",
"ctcp",
"ctcpreply",
]
protocol_events = [
# IRC protocol events
"error",
"join",
"kick",
"mode",
"part",
"ping",
"privmsg",
"privnotice",
"pubmsg",
"pubnotice",
"quit",
"invite",
"pong",
]
all_events = generated_events + protocol_events + list(numeric_events.values())

541
var.py

@ -1,269 +1,272 @@ @@ -1,269 +1,272 @@
PING_WAIT = 300 # Seconds
MINIMUM_WAIT = 60
EXTRA_WAIT = 20
MAXIMUM_WAITED = 2 # limit for amount of !wait's
STATS_RATE_LIMIT = 15
VOTES_RATE_LIMIT = 15
ADMINS_RATE_LIMIT = 300
SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given
MAX_PLAYERS = 30
DRUNK_SHOTS_MULTIPLIER = 3
NIGHT_TIME_LIMIT = 120
DAY_TIME_LIMIT_WARN = 780
DAY_TIME_LIMIT_CHANGE = 120 # After DAY_TIME_LIMIT_WARN has passed
START_WITH_DAY = False
KILL_IDLE_TIME = 300
WARN_IDLE_TIME = 180
PART_GRACE_TIME = 7
QUIT_GRACE_TIME = 30
LOG_FILENAME = ""
BARE_LOG_FILENAME = "barelog.txt"
# 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
#################################################################################################################
# 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 )} ##
#################################################################################################################
# Notes: ##
#################################################################################################################
GAME_MODES = {}
AWAY = [] # cloaks of people who are away.
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.")
RULES = ("#wolfgame 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.")
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"
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
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 villager" : 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"]}
@game_mode("normal")
class Normal(object):
pass
# Example !game roles=wolves:1,seers:0
# TODO: implement game modes
@game_mode("roles")
class ChangedRolesMode(object):
def __init__(self, arg):
self.ROLES_GUIDE = ROLES_GUIDE.copy()
lx = list(ROLES_GUIDE[None])
pairs = arg.split(",")
pl = list_players()
if not pairs:
raise InvalidModeException("Invalid syntax for mode roles.")
for pair in pairs:
change = pair.split(":")
if len(change) != 2:
raise InvalidModeException("Invalid syntax for mode roles.")
role, num = change
try:
num = int(num)
try:
lx[CHANGEABLE_ROLES[role.lower()]] = num
except KeyError:
raise InvalidModeException(("The role \u0002{0}\u0002 "+
"is not valid.").format(role))
except ValueError:
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)
with conn:
c = conn.cursor()
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)')
c.execute('SELECT * FROM away')
for row in c:
AWAY.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)')
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 (playerid INTEGER, roleid INTEGER, '+
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+
'UNIQUE(playerid, roleid))'))
# create the players table
c.execute("CREATE TABLE IF NOT EXISTS players (id INTEGER PRIMARY KEY AUTOINCREMENT, nick TEXT, cloak TEXT, "+
"UNIQUE(nick, cloak))")
# create nick change table
c.execute("CREATE TABLE IF NOT EXISTS nick_changes (old INTEGER, new INTEGER)")
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 add_player_record(nick, cloak):
with conn:
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (nick, cloak))
def record_nick_change(from_nick, to_nick, cloak):
with conn:
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (from_nick, cloak))
row = c.fetchone()
if not row:
return # No records for this player
old_plid = row[0]
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (to_nick, cloak))
# create a new entry in the players table for this nick
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (to_nick, cloak))
new_plid = c.fetchone()[0]
c.execute('SELECT * FROM nick_changes WHERE old=? AND new=?', (new_plid, old_plid))
if not c.fetchone(): # not recorded yet
c.execute('INSERT OR IGNORE INTO nick_changes (old, new) VALUES (?, ?)', (old_plid, new_plid))
def update_role_stats(nick, clk, role, won, iwon):
with conn:
wins, iwins, totalgames = 0, 0, 0
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk))
row = c.fetchone()
if row:
plid = row[0]
else:
c.execute('INSERT INTO players (nick, cloak) VALUES (?,?)', (nick, clk))
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk))
plid = c.fetchone()[0]
c.execute('SELECT id FROM roles WHERE role=?', (role,))
rid = c.fetchone()[0]
c.execute(("SELECT teamwins, individualwins, totalgames FROM rolestats "+
"WHERE playerid=? AND roleid=?"), (plid, rid))
row = c.fetchone()
if row:
wins, iwins, total = row
else:
wins, iwins, total = 0,0,0
if won:
wins += 1
if iwon:
iwins += 1
total += 1
c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)",
(plid, rid, wins, iwins, total))
PING_WAIT = 300 # Seconds
MINIMUM_WAIT = 60
EXTRA_WAIT = 20
MAXIMUM_WAITED = 2 # limit for amount of !wait's
STATS_RATE_LIMIT = 15
VOTES_RATE_LIMIT = 15
ADMINS_RATE_LIMIT = 300
SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given
MAX_PLAYERS = 30
DRUNK_SHOTS_MULTIPLIER = 3
NIGHT_TIME_LIMIT = 120
DAY_TIME_LIMIT_WARN = 780
DAY_TIME_LIMIT_CHANGE = 120 # After DAY_TIME_LIMIT_WARN has passed
START_WITH_DAY = False
KILL_IDLE_TIME = 300
WARN_IDLE_TIME = 180
PART_GRACE_TIME = 7
QUIT_GRACE_TIME = 30
LOG_FILENAME = ""
BARE_LOG_FILENAME = "barelog.txt"
# 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
#################################################################################################################
# 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 )} ##
#################################################################################################################
# Notes: ##
#################################################################################################################
GAME_MODES = {}
AWAY = [] # cloaks of people who are away.
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.")
RULES = ("#wolfgame 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.")
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"
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
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 villager" : 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"]}
@game_mode("normal")
class Normal(object):
pass
# Example !game roles=wolves:1,seers:0
# TODO: implement game modes
@game_mode("roles")
class ChangedRolesMode(object):
def __init__(self, arg):
self.ROLES_GUIDE = ROLES_GUIDE.copy()
lx = list(ROLES_GUIDE[None])
pairs = arg.split(",")
pl = list_players()
if not pairs:
raise InvalidModeException("Invalid syntax for mode roles.")
for pair in pairs:
change = pair.split(":")
if len(change) != 2:
raise InvalidModeException("Invalid syntax for mode roles.")
role, num = change
try:
num = int(num)
try:
lx[CHANGEABLE_ROLES[role.lower()]] = num
except KeyError:
raise InvalidModeException(("The role \u0002{0}\u0002 "+
"is not valid.").format(role))
except ValueError:
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)
with conn:
c = conn.cursor()
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)')
c.execute('SELECT * FROM away')
for row in c:
AWAY.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)')
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 (playerid INTEGER, roleid INTEGER, '+
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+
'UNIQUE(playerid, roleid))'))
# create the players table
c.execute("CREATE TABLE IF NOT EXISTS players (id INTEGER PRIMARY KEY AUTOINCREMENT, nick TEXT, cloak TEXT, "+
"UNIQUE(nick, cloak))")
# create nick change table
c.execute("CREATE TABLE IF NOT EXISTS nick_changes (old INTEGER, new INTEGER)")
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 add_player_record(nick, cloak):
with conn:
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (nick, cloak))
def record_nick_change(from_nick, to_nick, cloak):
with conn:
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (from_nick, cloak))
row = c.fetchone()
if not row:
return # No records for this player
old_plid = row[0]
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (to_nick, cloak))
# create a new entry in the players table for this nick
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (to_nick, cloak))
new_plid = c.fetchone()[0]
c.execute('SELECT * FROM nick_changes WHERE old=? AND new=?', (new_plid, old_plid))
if not c.fetchone(): # not recorded yet
c.execute('INSERT OR IGNORE INTO nick_changes (old, new) VALUES (?, ?)', (old_plid, new_plid))
def update_role_stats(nick, clk, role, won, iwon):
with conn:
wins, iwins, totalgames = 0, 0, 0
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk))
row = c.fetchone()
if row:
plid = row[0]
else:
c.execute('INSERT INTO players (nick, cloak) VALUES (?,?)', (nick, clk))
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk))
plid = c.fetchone()[0]
c.execute('SELECT id FROM roles WHERE role=?', (role,))
rid = c.fetchone()[0]
c.execute(("SELECT teamwins, individualwins, totalgames FROM rolestats "+
"WHERE playerid=? AND roleid=?"), (plid, rid))
row = c.fetchone()
if row:
wins, iwins, total = row
else:
wins, iwins, total = 0,0,0
if won:
wins += 1
if iwon:
iwins += 1
total += 1
c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)",
(plid, rid, wins, iwins, total))

246
wolfbot.py

@ -1,123 +1,123 @@ @@ -1,123 +1,123 @@
#!/usr/bin/env python3.2
# Copyright (c) 2011 Jimmy Cao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from oyoyo.client import IRCClient
from oyoyo.parse import parse_nick
import logging
import botconfig
import wolfgame
import time
import traceback
class UTCFormatter(logging.Formatter):
converter = time.gmtime
def on_privmsg(cli, rawnick, chan, msg):
if chan != botconfig.NICK: #not a PM
if "" in wolfgame.COMMANDS.keys():
for fn in wolfgame.COMMANDS[""]:
try:
fn(cli, rawnick, chan, msg)
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
# Now that is always called first.
for x in wolfgame.COMMANDS.keys():
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 wolfgame.COMMANDS[x]:
try:
fn(cli, rawnick, chan, h.lstrip())
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
else:
for x in wolfgame.PM_COMMANDS.keys():
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 wolfgame.PM_COMMANDS[x]:
try:
fn(cli, rawnick, h.lstrip())
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
def __unhandled__(cli, prefix, cmd, *args):
if cmd in wolfgame.HOOKS.keys():
largs = list(args)
for i,arg in enumerate(largs):
if isinstance(arg, bytes): largs[i] = arg.decode('ascii')
for fn in wolfgame.HOOKS[cmd]:
try:
fn(cli, prefix, *largs)
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
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)]))
def main():
if not botconfig.DEBUG_MODE:
logging.basicConfig(filename='errors.log', filemode='a', level=logging.WARNING)
formatter = UTCFormatter('[%(asctime)s] %(message)s', '%d/%b/%Y %H:%M:%S')
for handler in logging.getLogger().handlers:
handler.setFormatter(formatter)
else:
logging.basicConfig(level=logging.DEBUG)
cli = IRCClient(
{"privmsg":on_privmsg,
"":__unhandled__},
host=botconfig.HOST,
port=botconfig.PORT,
authname=botconfig.USERNAME,
password=botconfig.PASS,
nickname=botconfig.NICK,
connect_cb=wolfgame.connect_callback
)
cli.mainLoop()
if __name__ == "__main__":
try:
main()
except Exception:
logging.error(traceback.format_exc())
#!/usr/bin/env python3.2
# Copyright (c) 2011 Jimmy Cao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from oyoyo.client import IRCClient
from oyoyo.parse import parse_nick
import logging
import botconfig
import wolfgame
import time
import traceback
class UTCFormatter(logging.Formatter):
converter = time.gmtime
def on_privmsg(cli, rawnick, chan, msg):
if chan != botconfig.NICK: #not a PM
if "" in wolfgame.COMMANDS.keys():
for fn in wolfgame.COMMANDS[""]:
try:
fn(cli, rawnick, chan, msg)
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
# Now that is always called first.
for x in wolfgame.COMMANDS.keys():
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 wolfgame.COMMANDS[x]:
try:
fn(cli, rawnick, chan, h.lstrip())
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
else:
for x in wolfgame.PM_COMMANDS.keys():
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 wolfgame.PM_COMMANDS[x]:
try:
fn(cli, rawnick, h.lstrip())
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
cli.msg(chan, "An error has occurred and has been logged.")
def __unhandled__(cli, prefix, cmd, *args):
if cmd in wolfgame.HOOKS.keys():
largs = list(args)
for i,arg in enumerate(largs):
if isinstance(arg, bytes): largs[i] = arg.decode('ascii')
for fn in wolfgame.HOOKS[cmd]:
try:
fn(cli, prefix, *largs)
except Exception as e:
if botconfig.DEBUG_MODE:
raise e
else:
logging.error(traceback.format_exc())
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)]))
def main():
if not botconfig.DEBUG_MODE:
logging.basicConfig(filename='errors.log', filemode='a', level=logging.WARNING)
formatter = UTCFormatter('[%(asctime)s] %(message)s', '%d/%b/%Y %H:%M:%S')
for handler in logging.getLogger().handlers:
handler.setFormatter(formatter)
else:
logging.basicConfig(level=logging.DEBUG)
cli = IRCClient(
{"privmsg":on_privmsg,
"":__unhandled__},
host=botconfig.HOST,
port=botconfig.PORT,
authname=botconfig.USERNAME,
password=botconfig.PASS,
nickname=botconfig.NICK,
connect_cb=wolfgame.connect_callback
)
cli.mainLoop()
if __name__ == "__main__":
try:
main()
except Exception:
logging.error(traceback.format_exc())

5452
wolfgame.py

File diff suppressed because it is too large Load Diff

74
wolfgamelogger.py

@ -1,38 +1,38 @@ @@ -1,38 +1,38 @@
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)
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"
def logMessage(self, message):
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 = ""
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)
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"
def logMessage(self, message):
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 = ""
Loading…
Cancel
Save