Jimmy Cao
14 years ago
7 changed files with 3735 additions and 3732 deletions
@ -1,94 +1,94 @@ |
|||||||
# Copyright (c) 2011, Jimmy Cao |
# Copyright (c) 2011, Jimmy Cao |
||||||
# All rights reserved. |
# 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 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. |
# 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 |
from oyoyo.parse import parse_nick |
||||||
import fnmatch |
import fnmatch |
||||||
import botconfig |
import botconfig |
||||||
|
|
||||||
def generate(fdict, permissions=True, **kwargs): |
def generate(fdict, permissions=True, **kwargs): |
||||||
"""Generates a decorator generator. Always use this""" |
"""Generates a decorator generator. Always use this""" |
||||||
def cmd(*s, raw_nick=False, admin_only=False, owner_only=False, id=-1): |
def cmd(*s, raw_nick=False, admin_only=False, owner_only=False, id=-1): |
||||||
def dec(f): |
def dec(f): |
||||||
def innerf(*args): |
def innerf(*args): |
||||||
largs = list(args) |
largs = list(args) |
||||||
if len(largs) > 1 and largs[1]: |
if len(largs) > 1 and largs[1]: |
||||||
nick, _, _, cloak = parse_nick(largs[1]) |
nick, _, _, cloak = parse_nick(largs[1]) |
||||||
|
|
||||||
if cloak is None: |
if cloak is None: |
||||||
cloak = "" |
cloak = "" |
||||||
else: |
else: |
||||||
nick = "" |
nick = "" |
||||||
cloak = "" |
cloak = "" |
||||||
|
|
||||||
if not raw_nick and len(largs) > 1 and largs[1]: |
if not raw_nick and len(largs) > 1 and largs[1]: |
||||||
largs[1] = nick |
largs[1] = nick |
||||||
#if largs[1].startswith("#"): |
#if largs[1].startswith("#"): |
||||||
if not permissions or "" in s: |
if not permissions or "" in s: |
||||||
return f(*largs) |
return f(*largs) |
||||||
if cloak: |
if cloak: |
||||||
for pattern in botconfig.DENY.keys(): |
for pattern in botconfig.DENY.keys(): |
||||||
if fnmatch.fnmatch(cloak.lower(), pattern.lower()): |
if fnmatch.fnmatch(cloak.lower(), pattern.lower()): |
||||||
for cmdname in s: |
for cmdname in s: |
||||||
if cmdname in botconfig.DENY[pattern]: |
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 |
return |
||||||
for pattern in botconfig.ALLOW.keys(): |
for pattern in botconfig.ALLOW.keys(): |
||||||
if fnmatch.fnmatch(cloak.lower(), pattern.lower()): |
if fnmatch.fnmatch(cloak.lower(), pattern.lower()): |
||||||
for cmdname in s: |
for cmdname in s: |
||||||
if cmdname in botconfig.ALLOW[pattern]: |
if cmdname in botconfig.ALLOW[pattern]: |
||||||
return f(*largs) # no questions |
return f(*largs) # no questions |
||||||
if owner_only: |
if owner_only: |
||||||
if cloak and [ptn for ptn in botconfig.OWNERS |
if cloak and [ptn for ptn in botconfig.OWNERS |
||||||
if fnmatch.fnmatch(cloak.lower(), ptn.lower())]: |
if fnmatch.fnmatch(cloak.lower(), ptn.lower())]: |
||||||
return f(*largs) |
return f(*largs) |
||||||
elif cloak: |
elif cloak: |
||||||
largs[0].notice(nick, "You are not the owner.") |
largs[0].notice(nick, "You are not the owner.") |
||||||
return |
return |
||||||
if admin_only: |
if admin_only: |
||||||
if cloak and [ptn for ptn in botconfig.ADMINS+botconfig.OWNERS |
if cloak and [ptn for ptn in botconfig.ADMINS+botconfig.OWNERS |
||||||
if fnmatch.fnmatch(cloak.lower(), ptn.lower())]: |
if fnmatch.fnmatch(cloak.lower(), ptn.lower())]: |
||||||
return f(*largs) |
return f(*largs) |
||||||
elif cloak: |
elif cloak: |
||||||
largs[0].notice(nick, "You are not an admin.") |
largs[0].notice(nick, "You are not an admin.") |
||||||
return |
return |
||||||
return f(*largs) |
return f(*largs) |
||||||
alias = False |
alias = False |
||||||
innerf.aliases = [] |
innerf.aliases = [] |
||||||
for x in s: |
for x in s: |
||||||
if x not in fdict.keys(): |
if x not in fdict.keys(): |
||||||
fdict[x] = [] |
fdict[x] = [] |
||||||
else: |
else: |
||||||
for fn in fdict[x]: |
for fn in fdict[x]: |
||||||
if (fn.owner_only != owner_only or |
if (fn.owner_only != owner_only or |
||||||
fn.admin_only != admin_only): |
fn.admin_only != admin_only): |
||||||
raise Exception("Command: "+x+" has non-matching protection levels!") |
raise Exception("Command: "+x+" has non-matching protection levels!") |
||||||
fdict[x].append(innerf) |
fdict[x].append(innerf) |
||||||
if alias: |
if alias: |
||||||
innerf.aliases.append(x) |
innerf.aliases.append(x) |
||||||
alias = True |
alias = True |
||||||
innerf.owner_only = owner_only |
innerf.owner_only = owner_only |
||||||
innerf.raw_nick = raw_nick |
innerf.raw_nick = raw_nick |
||||||
innerf.admin_only = admin_only |
innerf.admin_only = admin_only |
||||||
innerf.id = id |
innerf.id = id |
||||||
innerf.__doc__ = f.__doc__ |
innerf.__doc__ = f.__doc__ |
||||||
return innerf |
return innerf |
||||||
|
|
||||||
return dec |
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, id): |
def unhook(hdict, id): |
||||||
for cmd in list(hdict.keys()): |
for cmd in list(hdict.keys()): |
||||||
for x in hdict[cmd]: |
for x in hdict[cmd]: |
||||||
if x.id == id: |
if x.id == id: |
||||||
hdict[cmd].remove(x) |
hdict[cmd].remove(x) |
||||||
if not hdict[cmd]: |
if not hdict[cmd]: |
||||||
del hdict[cmd] |
del hdict[cmd] |
||||||
|
@ -1,275 +1,275 @@ |
|||||||
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao |
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao |
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
# of this software and associated documentation files (the "Software"), to deal |
# of this software and associated documentation files (the "Software"), to deal |
||||||
# in the Software without restriction, including without limitation the rights |
# in the Software without restriction, including without limitation the rights |
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
# copies of the Software, and to permit persons to whom the Software is |
# copies of the Software, and to permit persons to whom the Software is |
||||||
# furnished to do so, subject to the following conditions: |
# furnished to do so, subject to the following conditions: |
||||||
# The above copyright notice and this permission notice shall be included in |
# The above copyright notice and this permission notice shall be included in |
||||||
# all copies or substantial portions of the Software. |
# all copies or substantial portions of the Software. |
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
# 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 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
# THE SOFTWARE. |
# THE SOFTWARE. |
||||||
import logging |
import logging |
||||||
import socket |
import socket |
||||||
import time |
import time |
||||||
import threading |
import threading |
||||||
import traceback |
import traceback |
||||||
import sys |
import sys |
||||||
|
|
||||||
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): |
class TokenBucket(object): |
||||||
"""An implementation of the token bucket algorithm. |
"""An implementation of the token bucket algorithm. |
||||||
|
|
||||||
>>> bucket = TokenBucket(80, 0.5) |
>>> bucket = TokenBucket(80, 0.5) |
||||||
>>> bucket.consume(1) |
>>> bucket.consume(1) |
||||||
""" |
""" |
||||||
def __init__(self, tokens, fill_rate): |
def __init__(self, tokens, fill_rate): |
||||||
"""tokens is the total tokens in the bucket. fill_rate is the |
"""tokens is the total tokens in the bucket. fill_rate is the |
||||||
rate in tokens/second that the bucket will be refilled.""" |
rate in tokens/second that the bucket will be refilled.""" |
||||||
self.capacity = float(tokens) |
self.capacity = float(tokens) |
||||||
self._tokens = float(tokens) |
self._tokens = float(tokens) |
||||||
self.fill_rate = float(fill_rate) |
self.fill_rate = float(fill_rate) |
||||||
self.timestamp = time.time() |
self.timestamp = time.time() |
||||||
|
|
||||||
def consume(self, tokens): |
def consume(self, tokens): |
||||||
"""Consume tokens from the bucket. Returns True if there were |
"""Consume tokens from the bucket. Returns True if there were |
||||||
sufficient tokens otherwise False.""" |
sufficient tokens otherwise False.""" |
||||||
if tokens <= self.tokens: |
if tokens <= self.tokens: |
||||||
self._tokens -= tokens |
self._tokens -= tokens |
||||||
return True |
return True |
||||||
return False |
return False |
||||||
|
|
||||||
@property |
@property |
||||||
def tokens(self): |
def tokens(self): |
||||||
now = time.time() |
now = time.time() |
||||||
if self._tokens < self.capacity: |
if self._tokens < self.capacity: |
||||||
delta = self.fill_rate * (now - self.timestamp) |
delta = self.fill_rate * (now - self.timestamp) |
||||||
self._tokens = min(self.capacity, self._tokens + delta) |
self._tokens = min(self.capacity, self._tokens + delta) |
||||||
self.timestamp = now |
self.timestamp = now |
||||||
return self._tokens |
return self._tokens |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_commands(d): |
def add_commands(d): |
||||||
def dec(cls): |
def dec(cls): |
||||||
for c in d: |
for c in d: |
||||||
def func(x): |
def func(x): |
||||||
def gen(self, *a): |
def gen(self, *a): |
||||||
self.send(x.upper(), *a) |
self.send(x.upper(), *a) |
||||||
return gen |
return gen |
||||||
setattr(cls, c, func(c)) |
setattr(cls, c, func(c)) |
||||||
return cls |
return cls |
||||||
return dec |
return dec |
||||||
@add_commands(("join", |
@add_commands(("join", |
||||||
"mode", |
"mode", |
||||||
"nick", |
"nick", |
||||||
"who")) |
"who")) |
||||||
class IRCClient(object): |
class IRCClient(object): |
||||||
""" IRC Client class. This handles one connection to a server. |
""" IRC Client class. This handles one connection to a server. |
||||||
This can be used either with or without IRCApp ( see connect() docs ) |
This can be used either with or without IRCApp ( see connect() docs ) |
||||||
""" |
""" |
||||||
|
|
||||||
def __init__(self, cmd_handler, **kwargs): |
def __init__(self, cmd_handler, **kwargs): |
||||||
""" the first argument should be an object with attributes/methods named |
""" the first argument should be an object with attributes/methods named |
||||||
as the irc commands. You may subclass from one of the classes in |
as the irc commands. You may subclass from one of the classes in |
||||||
oyoyo.cmdhandler for convenience but it is not required. The |
oyoyo.cmdhandler for convenience but it is not required. The |
||||||
methods should have arguments (prefix, args). prefix is |
methods should have arguments (prefix, args). prefix is |
||||||
normally the sender of the command. args is a list of arguments. |
normally the sender of the command. args is a list of arguments. |
||||||
Its recommened you subclass oyoyo.cmdhandler.DefaultCommandHandler, |
Its recommened you subclass oyoyo.cmdhandler.DefaultCommandHandler, |
||||||
this class provides defaults for callbacks that are required for |
this class provides defaults for callbacks that are required for |
||||||
normal IRC operation. |
normal IRC operation. |
||||||
|
|
||||||
all other arguments should be keyword arguments. The most commonly |
all other arguments should be keyword arguments. The most commonly |
||||||
used will be nick, host and port. You can also specify an "on connect" |
used will be nick, host and port. You can also specify an "on connect" |
||||||
callback. ( check the source for others ) |
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. |
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.nickname = "" |
||||||
self.hostmask = "" |
self.hostmask = "" |
||||||
self.ident = "" |
self.ident = "" |
||||||
self.real_name = "" |
self.real_name = "" |
||||||
self.host = None |
self.host = None |
||||||
self.port = None |
self.port = None |
||||||
self.password = "" |
self.password = "" |
||||||
self.authname = "" |
self.authname = "" |
||||||
self.connect_cb = None |
self.connect_cb = None |
||||||
self.blocking = True |
self.blocking = True |
||||||
self.lock = threading.RLock() |
self.lock = threading.RLock() |
||||||
|
|
||||||
self.tokenbucket = TokenBucket(28, 1.73) |
self.tokenbucket = TokenBucket(28, 1.73) |
||||||
|
|
||||||
self.__dict__.update(kwargs) |
self.__dict__.update(kwargs) |
||||||
self.command_handler = cmd_handler |
self.command_handler = cmd_handler |
||||||
|
|
||||||
self._end = 0 |
self._end = 0 |
||||||
|
|
||||||
def send(self, *args, **kwargs): |
def send(self, *args, **kwargs): |
||||||
""" send a message to the connected server. all arguments are joined |
""" 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) |
||||||
>>> 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 |
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 |
unicode they will be converted to str with the encoding specified by |
||||||
the 'encoding' keyword argument (default 'utf8'). |
the 'encoding' keyword argument (default 'utf8'). |
||||||
In python 3, all args must be of type str or bytes, *BUT* if they are |
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 |
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: |
with self.lock: |
||||||
# Convert all args to bytes if not already |
# Convert all args to bytes if not already |
||||||
encoding = kwargs.get('encoding') or 'utf_8' |
encoding = kwargs.get('encoding') or 'utf_8' |
||||||
bargs = [] |
bargs = [] |
||||||
for i,arg in enumerate(args): |
for i,arg in enumerate(args): |
||||||
if isinstance(arg, str): |
if isinstance(arg, str): |
||||||
bargs.append(bytes(arg, encoding)) |
bargs.append(bytes(arg, encoding)) |
||||||
elif isinstance(arg, bytes): |
elif isinstance(arg, bytes): |
||||||
bargs.append(arg) |
bargs.append(arg) |
||||||
elif arg is None: |
elif arg is None: |
||||||
continue |
continue |
||||||
else: |
else: |
||||||
raise Exception(('Refusing to send arg at index {1} of the args from '+ |
raise Exception(('Refusing to send arg at index {1} of the args from '+ |
||||||
'provided: {0}').format(repr([(type(arg), arg) |
'provided: {0}').format(repr([(type(arg), arg) |
||||||
for arg in args]), i)) |
for arg in args]), i)) |
||||||
|
|
||||||
msg = bytes(" ", "utf_8").join(bargs) |
msg = bytes(" ", "utf_8").join(bargs) |
||||||
logging.info('---> send "{0}"'.format(msg)) |
logging.info('---> send "{0}"'.format(msg)) |
||||||
|
|
||||||
while not self.tokenbucket.consume(1): |
while not self.tokenbucket.consume(1): |
||||||
time.sleep(0.3) |
time.sleep(0.3) |
||||||
self.socket.send(msg + bytes("\r\n", "utf_8")) |
self.socket.send(msg + bytes("\r\n", "utf_8")) |
||||||
|
|
||||||
def connect(self): |
def connect(self): |
||||||
""" initiates the connection to the server set in self.host:self.port |
""" initiates the connection to the server set in self.host:self.port |
||||||
and returns a generator object. |
and returns a generator object. |
||||||
|
|
||||||
>>> cli = IRCClient(my_handler, host="irc.freenode.net", port=6667) |
>>> cli = IRCClient(my_handler, host="irc.freenode.net", port=6667) |
||||||
>>> g = cli.connect() |
>>> g = cli.connect() |
||||||
>>> while 1: |
>>> while 1: |
||||||
... next(g) |
... next(g) |
||||||
|
|
||||||
""" |
""" |
||||||
try: |
try: |
||||||
logging.info('connecting to {0}:{1}'.format(self.host, self.port)) |
logging.info('connecting to {0}:{1}'.format(self.host, self.port)) |
||||||
retries = 0 |
retries = 0 |
||||||
while True: |
while True: |
||||||
try: |
try: |
||||||
self.socket.connect(("{0}".format(self.host), self.port)) |
self.socket.connect(("{0}".format(self.host), self.port)) |
||||||
break |
break |
||||||
except socket.error as e: |
except socket.error as e: |
||||||
retries += 1 |
retries += 1 |
||||||
logging.warning('Error: {0}'.format(e)) |
logging.warning('Error: {0}'.format(e)) |
||||||
if retries > 3: |
if retries > 3: |
||||||
break |
break |
||||||
if not self.blocking: |
if not self.blocking: |
||||||
self.socket.setblocking(0) |
self.socket.setblocking(0) |
||||||
|
|
||||||
self.send("PASS {0}:{1}".format(self.authname if self.authname else self.nickname, |
self.send("PASS {0}:{1}".format(self.authname if self.authname else self.nickname, |
||||||
self.password if self.password else "NOPASS")) |
self.password if self.password else "NOPASS")) |
||||||
|
|
||||||
self.nick(self.nickname) |
self.nick(self.nickname) |
||||||
self.user(self.nickname, self.real_name) |
self.user(self.nickname, self.real_name) |
||||||
|
|
||||||
if self.connect_cb: |
if self.connect_cb: |
||||||
self.connect_cb(self) |
self.connect_cb(self) |
||||||
|
|
||||||
buffer = bytes() |
buffer = bytes() |
||||||
while not self._end: |
while not self._end: |
||||||
try: |
try: |
||||||
buffer += self.socket.recv(1024) |
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: |
if False and not self.blocking and e.errno == 11: |
||||||
pass |
pass |
||||||
else: |
else: |
||||||
raise e |
raise e |
||||||
else: |
else: |
||||||
data = buffer.split(bytes("\n", "utf_8")) |
data = buffer.split(bytes("\n", "utf_8")) |
||||||
buffer = data.pop() |
buffer = data.pop() |
||||||
|
|
||||||
for el in data: |
for el in data: |
||||||
prefix, command, args = parse_raw_irc_command(el) |
prefix, command, args = parse_raw_irc_command(el) |
||||||
|
|
||||||
try: |
try: |
||||||
enc = "utf8" |
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: |
except UnicodeDecodeError: |
||||||
enc = "latin1" |
enc = "latin1" |
||||||
fargs = [arg.decode(enc) for arg in args if isinstance(arg,bytes)] |
fargs = [arg.decode(enc) for arg in args if isinstance(arg,bytes)] |
||||||
|
|
||||||
logging.debug("processCommand ({2}){0}({1})".format(command, |
logging.debug("processCommand ({2}){0}({1})".format(command, |
||||||
fargs, prefix)) |
fargs, prefix)) |
||||||
try: |
try: |
||||||
largs = list(args) |
largs = list(args) |
||||||
if prefix is not None: |
if prefix is not None: |
||||||
prefix = prefix.decode(enc) |
prefix = prefix.decode(enc) |
||||||
# for i,arg in enumerate(largs): |
# for i,arg in enumerate(largs): |
||||||
# if arg is not None: largs[i] = arg.decode(enc) |
# if arg is not None: largs[i] = arg.decode(enc) |
||||||
if command in self.command_handler: |
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: |
elif "" in self.command_handler: |
||||||
self.command_handler[""](self, prefix, command, *fargs) |
self.command_handler[""](self, prefix, command, *fargs) |
||||||
except Exception as e: |
except Exception as e: |
||||||
traceback.print_exc() |
traceback.print_exc() |
||||||
raise e # ? |
raise e # ? |
||||||
yield True |
yield True |
||||||
finally: |
finally: |
||||||
if self.socket: |
if self.socket: |
||||||
logging.info('closing socket') |
logging.info('closing socket') |
||||||
self.socket.close() |
self.socket.close() |
||||||
yield False |
yield False |
||||||
def msg(self, user, msg): |
def msg(self, user, msg): |
||||||
for line in msg.split('\n'): |
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: |
while line: |
||||||
extra = "" |
extra = "" |
||||||
if len(line) > maxchars: |
if len(line) > maxchars: |
||||||
extra = line[maxchars:] |
extra = line[maxchars:] |
||||||
line = line[:maxchars] |
line = line[:maxchars] |
||||||
self.send("PRIVMSG", user, ":{0}".format(line)) |
self.send("PRIVMSG", user, ":{0}".format(line)) |
||||||
line = extra |
line = extra |
||||||
privmsg = msg # Same thing |
privmsg = msg # Same thing |
||||||
def notice(self, user, msg): |
def notice(self, user, msg): |
||||||
for line in msg.split('\n'): |
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: |
while line: |
||||||
extra = "" |
extra = "" |
||||||
if len(line) > maxchars: |
if len(line) > maxchars: |
||||||
extra = line[maxchars:] |
extra = line[maxchars:] |
||||||
line = line[:maxchars] |
line = line[:maxchars] |
||||||
self.send("NOTICE", user, ":{0}".format(line)) |
self.send("NOTICE", user, ":{0}".format(line)) |
||||||
line = extra |
line = extra |
||||||
def quit(self, msg=""): |
def quit(self, msg=""): |
||||||
self.send("QUIT :{0}".format(msg)) |
self.send("QUIT :{0}".format(msg)) |
||||||
def part(self, chan, msg=""): |
def part(self, chan, msg=""): |
||||||
self.send("PART {0} :{1}".format(chan, msg)) |
self.send("PART {0} :{1}".format(chan, msg)) |
||||||
def kick(self, chan, nick, msg=""): |
def kick(self, chan, nick, msg=""): |
||||||
self.send("KICK", chan, nick, ":"+msg) |
self.send("KICK", chan, nick, ":"+msg) |
||||||
def ns_identify(self, passwd): |
def ns_identify(self, passwd): |
||||||
self.msg("NickServ", "IDENTIFY {0} {1}".format(self.nickname, passwd)) |
self.msg("NickServ", "IDENTIFY {0} {1}".format(self.nickname, passwd)) |
||||||
def ns_ghost(self): |
def ns_ghost(self): |
||||||
self.msg("NickServ", "GHOST "+self.nickname) |
self.msg("NickServ", "GHOST "+self.nickname) |
||||||
def ns_release(self): |
def ns_release(self): |
||||||
self.msg("NickServ", "RELEASE "+self.nickname) |
self.msg("NickServ", "RELEASE "+self.nickname) |
||||||
def user(self, uname, rname): |
def user(self, uname, rname): |
||||||
self.send("USER", uname, self.host, self.host, |
self.send("USER", uname, self.host, self.host, |
||||||
rname or uname) |
rname or uname) |
||||||
def mainLoop(self): |
def mainLoop(self): |
||||||
conn = self.connect() |
conn = self.connect() |
||||||
while True: |
while True: |
||||||
if not next(conn): |
if not next(conn): |
||||||
print("Calling sys.exit()...") |
print("Calling sys.exit()...") |
||||||
sys.exit() |
sys.exit() |
||||||
|
|
||||||
|
@ -1,208 +1,208 @@ |
|||||||
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao |
# Copyright (c) 2011 Duncan Fordyce, Jimmy Cao |
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
# of this software and associated documentation files (the "Software"), to deal |
# of this software and associated documentation files (the "Software"), to deal |
||||||
# in the Software without restriction, including without limitation the rights |
# in the Software without restriction, including without limitation the rights |
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
# copies of the Software, and to permit persons to whom the Software is |
# copies of the Software, and to permit persons to whom the Software is |
||||||
# furnished to do so, subject to the following conditions: |
# furnished to do so, subject to the following conditions: |
||||||
# The above copyright notice and this permission notice shall be included in |
# The above copyright notice and this permission notice shall be included in |
||||||
# all copies or substantial portions of the Software. |
# all copies or substantial portions of the Software. |
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
# 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 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
# THE SOFTWARE. |
# THE SOFTWARE. |
||||||
|
|
||||||
numeric_events = { |
numeric_events = { |
||||||
b"001": "welcome", |
b"001": "welcome", |
||||||
b"002": "yourhost", |
b"002": "yourhost", |
||||||
b"003": "created", |
b"003": "created", |
||||||
b"004": "myinfo", |
b"004": "myinfo", |
||||||
b"005": "featurelist", # XXX |
b"005": "featurelist", # XXX |
||||||
b"200": "tracelink", |
b"200": "tracelink", |
||||||
b"201": "traceconnecting", |
b"201": "traceconnecting", |
||||||
b"202": "tracehandshake", |
b"202": "tracehandshake", |
||||||
b"203": "traceunknown", |
b"203": "traceunknown", |
||||||
b"204": "traceoperator", |
b"204": "traceoperator", |
||||||
b"205": "traceuser", |
b"205": "traceuser", |
||||||
b"206": "traceserver", |
b"206": "traceserver", |
||||||
b"207": "traceservice", |
b"207": "traceservice", |
||||||
b"208": "tracenewtype", |
b"208": "tracenewtype", |
||||||
b"209": "traceclass", |
b"209": "traceclass", |
||||||
b"210": "tracereconnect", |
b"210": "tracereconnect", |
||||||
b"211": "statslinkinfo", |
b"211": "statslinkinfo", |
||||||
b"212": "statscommands", |
b"212": "statscommands", |
||||||
b"213": "statscline", |
b"213": "statscline", |
||||||
b"214": "statsnline", |
b"214": "statsnline", |
||||||
b"215": "statsiline", |
b"215": "statsiline", |
||||||
b"216": "statskline", |
b"216": "statskline", |
||||||
b"217": "statsqline", |
b"217": "statsqline", |
||||||
b"218": "statsyline", |
b"218": "statsyline", |
||||||
b"219": "endofstats", |
b"219": "endofstats", |
||||||
b"221": "umodeis", |
b"221": "umodeis", |
||||||
b"231": "serviceinfo", |
b"231": "serviceinfo", |
||||||
b"232": "endofservices", |
b"232": "endofservices", |
||||||
b"233": "service", |
b"233": "service", |
||||||
b"234": "servlist", |
b"234": "servlist", |
||||||
b"235": "servlistend", |
b"235": "servlistend", |
||||||
b"241": "statslline", |
b"241": "statslline", |
||||||
b"242": "statsuptime", |
b"242": "statsuptime", |
||||||
b"243": "statsoline", |
b"243": "statsoline", |
||||||
b"244": "statshline", |
b"244": "statshline", |
||||||
b"250": "luserconns", |
b"250": "luserconns", |
||||||
b"251": "luserclient", |
b"251": "luserclient", |
||||||
b"252": "luserop", |
b"252": "luserop", |
||||||
b"253": "luserunknown", |
b"253": "luserunknown", |
||||||
b"254": "luserchannels", |
b"254": "luserchannels", |
||||||
b"255": "luserme", |
b"255": "luserme", |
||||||
b"256": "adminme", |
b"256": "adminme", |
||||||
b"257": "adminloc1", |
b"257": "adminloc1", |
||||||
b"258": "adminloc2", |
b"258": "adminloc2", |
||||||
b"259": "adminemail", |
b"259": "adminemail", |
||||||
b"261": "tracelog", |
b"261": "tracelog", |
||||||
b"262": "endoftrace", |
b"262": "endoftrace", |
||||||
b"263": "tryagain", |
b"263": "tryagain", |
||||||
b"265": "n_local", |
b"265": "n_local", |
||||||
b"266": "n_global", |
b"266": "n_global", |
||||||
b"300": "none", |
b"300": "none", |
||||||
b"301": "away", |
b"301": "away", |
||||||
b"302": "userhost", |
b"302": "userhost", |
||||||
b"303": "ison", |
b"303": "ison", |
||||||
b"305": "unaway", |
b"305": "unaway", |
||||||
b"306": "nowaway", |
b"306": "nowaway", |
||||||
b"311": "whoisuser", |
b"311": "whoisuser", |
||||||
b"312": "whoisserver", |
b"312": "whoisserver", |
||||||
b"313": "whoisoperator", |
b"313": "whoisoperator", |
||||||
b"314": "whowasuser", |
b"314": "whowasuser", |
||||||
b"315": "endofwho", |
b"315": "endofwho", |
||||||
b"316": "whoischanop", |
b"316": "whoischanop", |
||||||
b"317": "whoisidle", |
b"317": "whoisidle", |
||||||
b"318": "endofwhois", |
b"318": "endofwhois", |
||||||
b"319": "whoischannels", |
b"319": "whoischannels", |
||||||
b"321": "liststart", |
b"321": "liststart", |
||||||
b"322": "list", |
b"322": "list", |
||||||
b"323": "listend", |
b"323": "listend", |
||||||
b"324": "channelmodeis", |
b"324": "channelmodeis", |
||||||
b"329": "channelcreate", |
b"329": "channelcreate", |
||||||
b"331": "notopic", |
b"331": "notopic", |
||||||
b"332": "currenttopic", |
b"332": "currenttopic", |
||||||
b"333": "topicinfo", |
b"333": "topicinfo", |
||||||
b"341": "inviting", |
b"341": "inviting", |
||||||
b"342": "summoning", |
b"342": "summoning", |
||||||
b"346": "invitelist", |
b"346": "invitelist", |
||||||
b"347": "endofinvitelist", |
b"347": "endofinvitelist", |
||||||
b"348": "exceptlist", |
b"348": "exceptlist", |
||||||
b"349": "endofexceptlist", |
b"349": "endofexceptlist", |
||||||
b"351": "version", |
b"351": "version", |
||||||
b"352": "whoreply", |
b"352": "whoreply", |
||||||
b"353": "namreply", |
b"353": "namreply", |
||||||
b"361": "killdone", |
b"361": "killdone", |
||||||
b"362": "closing", |
b"362": "closing", |
||||||
b"363": "closeend", |
b"363": "closeend", |
||||||
b"364": "links", |
b"364": "links", |
||||||
b"365": "endoflinks", |
b"365": "endoflinks", |
||||||
b"366": "endofnames", |
b"366": "endofnames", |
||||||
b"367": "banlist", |
b"367": "banlist", |
||||||
b"368": "endofbanlist", |
b"368": "endofbanlist", |
||||||
b"369": "endofwhowas", |
b"369": "endofwhowas", |
||||||
b"371": "info", |
b"371": "info", |
||||||
b"372": "motd", |
b"372": "motd", |
||||||
b"373": "infostart", |
b"373": "infostart", |
||||||
b"374": "endofinfo", |
b"374": "endofinfo", |
||||||
b"375": "motdstart", |
b"375": "motdstart", |
||||||
b"376": "endofmotd", |
b"376": "endofmotd", |
||||||
b"377": "motd2", # 1997-10-16 -- tkil |
b"377": "motd2", # 1997-10-16 -- tkil |
||||||
b"381": "youreoper", |
b"381": "youreoper", |
||||||
b"382": "rehashing", |
b"382": "rehashing", |
||||||
b"384": "myportis", |
b"384": "myportis", |
||||||
b"391": "time", |
b"391": "time", |
||||||
b"392": "usersstart", |
b"392": "usersstart", |
||||||
b"393": "users", |
b"393": "users", |
||||||
b"394": "endofusers", |
b"394": "endofusers", |
||||||
b"395": "nousers", |
b"395": "nousers", |
||||||
b"396": "event_hosthidden", |
b"396": "event_hosthidden", |
||||||
b"401": "nosuchnick", |
b"401": "nosuchnick", |
||||||
b"402": "nosuchserver", |
b"402": "nosuchserver", |
||||||
b"403": "nosuchchannel", |
b"403": "nosuchchannel", |
||||||
b"404": "cannotsendtochan", |
b"404": "cannotsendtochan", |
||||||
b"405": "toomanychannels", |
b"405": "toomanychannels", |
||||||
b"406": "wasnosuchnick", |
b"406": "wasnosuchnick", |
||||||
b"407": "toomanytargets", |
b"407": "toomanytargets", |
||||||
b"409": "noorigin", |
b"409": "noorigin", |
||||||
b"411": "norecipient", |
b"411": "norecipient", |
||||||
b"412": "notexttosend", |
b"412": "notexttosend", |
||||||
b"413": "notoplevel", |
b"413": "notoplevel", |
||||||
b"414": "wildtoplevel", |
b"414": "wildtoplevel", |
||||||
b"421": "unknowncommand", |
b"421": "unknowncommand", |
||||||
b"422": "nomotd", |
b"422": "nomotd", |
||||||
b"423": "noadmininfo", |
b"423": "noadmininfo", |
||||||
b"424": "fileerror", |
b"424": "fileerror", |
||||||
b"431": "nonicknamegiven", |
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"433": "nicknameinuse", |
||||||
b"436": "nickcollision", |
b"436": "nickcollision", |
||||||
b"437": "unavailresource", # "Nick temporally unavailable" |
b"437": "unavailresource", # "Nick temporally unavailable" |
||||||
b"441": "usernotinchannel", |
b"441": "usernotinchannel", |
||||||
b"442": "notonchannel", |
b"442": "notonchannel", |
||||||
b"443": "useronchannel", |
b"443": "useronchannel", |
||||||
b"444": "nologin", |
b"444": "nologin", |
||||||
b"445": "summondisabled", |
b"445": "summondisabled", |
||||||
b"446": "usersdisabled", |
b"446": "usersdisabled", |
||||||
b"451": "notregistered", |
b"451": "notregistered", |
||||||
b"461": "needmoreparams", |
b"461": "needmoreparams", |
||||||
b"462": "alreadyregistered", |
b"462": "alreadyregistered", |
||||||
b"463": "nopermforhost", |
b"463": "nopermforhost", |
||||||
b"464": "passwdmismatch", |
b"464": "passwdmismatch", |
||||||
b"465": "yourebannedcreep", # I love this one... |
b"465": "yourebannedcreep", # I love this one... |
||||||
b"466": "youwillbebanned", |
b"466": "youwillbebanned", |
||||||
b"467": "keyset", |
b"467": "keyset", |
||||||
b"471": "channelisfull", |
b"471": "channelisfull", |
||||||
b"472": "unknownmode", |
b"472": "unknownmode", |
||||||
b"473": "inviteonlychan", |
b"473": "inviteonlychan", |
||||||
b"474": "bannedfromchan", |
b"474": "bannedfromchan", |
||||||
b"475": "badchannelkey", |
b"475": "badchannelkey", |
||||||
b"476": "badchanmask", |
b"476": "badchanmask", |
||||||
b"477": "nochanmodes", # "Channel doesn't support modes" |
b"477": "nochanmodes", # "Channel doesn't support modes" |
||||||
b"478": "banlistfull", |
b"478": "banlistfull", |
||||||
b"481": "noprivileges", |
b"481": "noprivileges", |
||||||
b"482": "chanoprivsneeded", |
b"482": "chanoprivsneeded", |
||||||
b"483": "cantkillserver", |
b"483": "cantkillserver", |
||||||
b"484": "restricted", # Connection is restricted |
b"484": "restricted", # Connection is restricted |
||||||
b"485": "uniqopprivsneeded", |
b"485": "uniqopprivsneeded", |
||||||
b"491": "nooperhost", |
b"491": "nooperhost", |
||||||
b"492": "noservicehost", |
b"492": "noservicehost", |
||||||
b"501": "umodeunknownflag", |
b"501": "umodeunknownflag", |
||||||
b"502": "usersdontmatch", |
b"502": "usersdontmatch", |
||||||
} |
} |
||||||
|
|
||||||
generated_events = [ |
generated_events = [ |
||||||
# Generated events |
# Generated events |
||||||
"dcc_connect", |
"dcc_connect", |
||||||
"dcc_disconnect", |
"dcc_disconnect", |
||||||
"dccmsg", |
"dccmsg", |
||||||
"disconnect", |
"disconnect", |
||||||
"ctcp", |
"ctcp", |
||||||
"ctcpreply", |
"ctcpreply", |
||||||
] |
] |
||||||
|
|
||||||
protocol_events = [ |
protocol_events = [ |
||||||
# IRC protocol events |
# IRC protocol events |
||||||
"error", |
"error", |
||||||
"join", |
"join", |
||||||
"kick", |
"kick", |
||||||
"mode", |
"mode", |
||||||
"part", |
"part", |
||||||
"ping", |
"ping", |
||||||
"privmsg", |
"privmsg", |
||||||
"privnotice", |
"privnotice", |
||||||
"pubmsg", |
"pubmsg", |
||||||
"pubnotice", |
"pubnotice", |
||||||
"quit", |
"quit", |
||||||
"invite", |
"invite", |
||||||
"pong", |
"pong", |
||||||
] |
] |
||||||
|
|
||||||
all_events = generated_events + protocol_events + list(numeric_events.values()) |
all_events = generated_events + protocol_events + list(numeric_events.values()) |
||||||
|
|
||||||
|
@ -1,269 +1,272 @@ |
|||||||
PING_WAIT = 300 # Seconds |
PING_WAIT = 300 # Seconds |
||||||
MINIMUM_WAIT = 60 |
MINIMUM_WAIT = 60 |
||||||
EXTRA_WAIT = 20 |
EXTRA_WAIT = 20 |
||||||
MAXIMUM_WAITED = 2 # limit for amount of !wait's |
MAXIMUM_WAITED = 2 # limit for amount of !wait's |
||||||
STATS_RATE_LIMIT = 15 |
STATS_RATE_LIMIT = 15 |
||||||
VOTES_RATE_LIMIT = 15 |
VOTES_RATE_LIMIT = 15 |
||||||
ADMINS_RATE_LIMIT = 300 |
ADMINS_RATE_LIMIT = 300 |
||||||
SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given |
SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given |
||||||
MAX_PLAYERS = 30 |
MAX_PLAYERS = 30 |
||||||
DRUNK_SHOTS_MULTIPLIER = 3 |
DRUNK_SHOTS_MULTIPLIER = 3 |
||||||
NIGHT_TIME_LIMIT = 120 |
NIGHT_TIME_LIMIT = 120 |
||||||
DAY_TIME_LIMIT_WARN = 780 |
DAY_TIME_LIMIT_WARN = 780 |
||||||
DAY_TIME_LIMIT_CHANGE = 120 # After DAY_TIME_LIMIT_WARN has passed |
DAY_TIME_LIMIT_CHANGE = 120 # After DAY_TIME_LIMIT_WARN has passed |
||||||
START_WITH_DAY = False |
START_WITH_DAY = False |
||||||
KILL_IDLE_TIME = 300 |
KILL_IDLE_TIME = 300 |
||||||
WARN_IDLE_TIME = 180 |
WARN_IDLE_TIME = 180 |
||||||
PART_GRACE_TIME = 7 |
PART_GRACE_TIME = 7 |
||||||
QUIT_GRACE_TIME = 30 |
QUIT_GRACE_TIME = 30 |
||||||
|
|
||||||
LOG_FILENAME = "" |
LOG_FILENAME = "" |
||||||
BARE_LOG_FILENAME = "barelog.txt" |
BARE_LOG_FILENAME = "barelog.txt" |
||||||
|
|
||||||
# HIT MISS SUICIDE |
# HIT MISS SUICIDE |
||||||
GUN_CHANCES = ( 5/7 , 1/7 , 1/7 ) |
GUN_CHANCES = ( 5/7 , 1/7 , 1/7 ) |
||||||
DRUNK_GUN_CHANCES = ( 2/7 , 4/7 , 1/7 ) |
DRUNK_GUN_CHANCES = ( 2/7 , 4/7 , 1/7 ) |
||||||
MANSLAUGHTER_CHANCE = 1/5 # ACCIDENTAL HEADSHOT (FATAL) |
MANSLAUGHTER_CHANCE = 1/5 # ACCIDENTAL HEADSHOT (FATAL) |
||||||
|
|
||||||
GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 0 |
GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 0 |
||||||
GUARDIAN_ANGEL_DIES_CHANCE = 1/2 |
GUARDIAN_ANGEL_DIES_CHANCE = 1/2 |
||||||
DETECTIVE_REVEALED_CHANCE = 2/5 |
DETECTIVE_REVEALED_CHANCE = 2/5 |
||||||
|
|
||||||
################################################################################################################# |
################################################################################################################# |
||||||
# ROLE INDEX: PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER CROW ANGEL DETECTIVE ## |
# 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 ), ## |
ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ## |
||||||
6 : ( 1 , 1 , 1 , 1 , 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 ), ## |
8 : ( 1 , 2 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 ), ## |
||||||
10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 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 ), ## |
11 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 ), ## |
||||||
15 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ## |
15 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ## |
||||||
22 : ( 1 , 4 , 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 ), ## |
29 : ( 1 , 5 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ## |
||||||
None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ## |
None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ## |
||||||
################################################################################################################# |
################################################################################################################# |
||||||
# Notes: ## |
# Notes: ## |
||||||
################################################################################################################# |
################################################################################################################# |
||||||
|
|
||||||
GAME_MODES = {} |
GAME_MODES = {} |
||||||
AWAY = [] # cloaks of people who are away. |
AWAY = [] # cloaks of people who are away. |
||||||
|
|
||||||
ROLE_INDICES = {0 : "seer", |
ROLE_INDICES = {0 : "seer", |
||||||
1 : "wolf", |
1 : "wolf", |
||||||
2 : "cursed villager", |
2 : "cursed villager", |
||||||
3 : "village drunk", |
3 : "village drunk", |
||||||
4 : "harlot", |
4 : "harlot", |
||||||
5 : "traitor", |
5 : "traitor", |
||||||
6 : "gunner", |
6 : "gunner", |
||||||
7 : "werecrow", |
7 : "werecrow", |
||||||
8 : "guardian angel", |
8 : "guardian angel", |
||||||
9 : "detective"} |
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.", |
NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.", |
||||||
"A pool of blood and wolf paw prints are found.", |
"A pool of blood and wolf paw prints are found.", |
||||||
"Traces of wolf fur 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.", |
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.", |
"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.", |
"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.") |
"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 "+ |
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"+ |
"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 "+ |
"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. "+ |
"family-friendly. 7) Do not paste PM's from the bot during the game. "+ |
||||||
"8) Use common sense. 9) Waiting for timeouts is discouraged.") |
"8) Use common sense. 9) Waiting for timeouts is discouraged.") |
||||||
|
|
||||||
is_role = lambda plyr, rol: rol in ROLES and plyr in ROLES[rol] |
is_role = lambda plyr, rol: rol in ROLES and plyr in ROLES[rol] |
||||||
|
|
||||||
def plural(role): |
def plural(role): |
||||||
if role == "wolf": return "wolves" |
if role == "wolf": return "wolves" |
||||||
elif role == "person": return "people" |
elif role == "person": return "people" |
||||||
else: return role + "s" |
else: return role + "s" |
||||||
|
|
||||||
def list_players(): |
def list_players(): |
||||||
pl = [] |
pl = [] |
||||||
for x in ROLES.values(): |
for x in ROLES.values(): |
||||||
pl.extend(x) |
pl.extend(x) |
||||||
return pl |
return pl |
||||||
|
|
||||||
def list_players_and_roles(): |
def list_players_and_roles(): |
||||||
plr = {} |
plr = {} |
||||||
for x in ROLES.keys(): |
for x in ROLES.keys(): |
||||||
for p in ROLES[x]: |
for p in ROLES[x]: |
||||||
plr[p] = x |
plr[p] = x |
||||||
return plr |
return plr |
||||||
|
|
||||||
get_role = lambda plyr: list_players_and_roles()[plyr] |
get_role = lambda plyr: list_players_and_roles()[plyr] |
||||||
|
|
||||||
|
|
||||||
def del_player(pname): |
def del_player(pname): |
||||||
prole = get_role(pname) |
prole = get_role(pname) |
||||||
ROLES[prole].remove(pname) |
ROLES[prole].remove(pname) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidModeException(Exception): pass |
class InvalidModeException(Exception): pass |
||||||
def game_mode(name): |
def game_mode(name): |
||||||
def decor(c): |
def decor(c): |
||||||
GAME_MODES[name] = c |
GAME_MODES[name] = c |
||||||
return c |
return c |
||||||
return decor |
return decor |
||||||
|
|
||||||
|
|
||||||
CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"], |
CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"], |
||||||
"wolves" : INDEX_OF_ROLE["wolf"], |
"wolves" : INDEX_OF_ROLE["wolf"], |
||||||
"cursed villager" : INDEX_OF_ROLE["cursed villager"], |
"cursed villager" : INDEX_OF_ROLE["cursed villager"], |
||||||
"drunks" : INDEX_OF_ROLE["village drunk"], |
"drunks" : INDEX_OF_ROLE["village drunk"], |
||||||
"harlots" : INDEX_OF_ROLE["harlot"], |
"harlots" : INDEX_OF_ROLE["harlot"], |
||||||
"traitors" : INDEX_OF_ROLE["traitor"], |
"traitors" : INDEX_OF_ROLE["traitor"], |
||||||
"gunners" : INDEX_OF_ROLE["gunner"], |
"gunners" : INDEX_OF_ROLE["gunner"], |
||||||
"werecrows" : INDEX_OF_ROLE["werecrow"], |
"werecrows" : INDEX_OF_ROLE["werecrow"], |
||||||
"angels" : INDEX_OF_ROLE["guardian angel"], |
"angels" : INDEX_OF_ROLE["guardian angel"], |
||||||
"detectives" : INDEX_OF_ROLE["detective"]} |
"detectives" : INDEX_OF_ROLE["detective"]} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@game_mode("normal") |
@game_mode("normal") |
||||||
class Normal(object): |
class Normal(object): |
||||||
pass |
pass |
||||||
|
|
||||||
|
|
||||||
# Example !game roles=wolves:1,seers:0 |
# Example !game roles=wolves:1,seers:0 |
||||||
# TODO: implement game modes |
# TODO: implement game modes |
||||||
@game_mode("roles") |
@game_mode("roles") |
||||||
class ChangedRolesMode(object): |
class ChangedRolesMode(object): |
||||||
def __init__(self, arg): |
def __init__(self, arg): |
||||||
self.ROLES_GUIDE = ROLES_GUIDE.copy() |
self.ROLES_GUIDE = ROLES_GUIDE.copy() |
||||||
lx = list(ROLES_GUIDE[None]) |
lx = list(ROLES_GUIDE[None]) |
||||||
pairs = arg.split(",") |
pairs = arg.split(",") |
||||||
pl = list_players() |
pl = list_players() |
||||||
if not pairs: |
if not pairs: |
||||||
raise InvalidModeException("Invalid syntax for mode roles.") |
raise InvalidModeException("Invalid syntax for mode roles.") |
||||||
for pair in pairs: |
for pair in pairs: |
||||||
change = pair.split(":") |
change = pair.split(":") |
||||||
if len(change) != 2: |
if len(change) != 2: |
||||||
raise InvalidModeException("Invalid syntax for mode roles.") |
raise InvalidModeException("Invalid syntax for mode roles.") |
||||||
role, num = change |
role, num = change |
||||||
try: |
try: |
||||||
num = int(num) |
num = int(num) |
||||||
try: |
try: |
||||||
lx[CHANGEABLE_ROLES[role.lower()]] = num |
lx[CHANGEABLE_ROLES[role.lower()]] = num |
||||||
except KeyError: |
except KeyError: |
||||||
raise InvalidModeException(("The role \u0002{0}\u0002 "+ |
raise InvalidModeException(("The role \u0002{0}\u0002 "+ |
||||||
"is not valid.").format(role)) |
"is not valid.").format(role)) |
||||||
except ValueError: |
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(): |
for k in ROLES_GUIDE.keys(): |
||||||
self.ROLES_GUIDE[k] = tuple(lx) |
self.ROLES_GUIDE[k] = tuple(lx) |
||||||
|
|
||||||
|
|
||||||
# Persistence |
# Persistence |
||||||
|
|
||||||
|
|
||||||
# Load saved settings |
# Load saved settings |
||||||
import sqlite3 |
import sqlite3 |
||||||
import os |
import os |
||||||
|
|
||||||
conn = sqlite3.connect("data.sqlite3", check_same_thread = False) |
conn = sqlite3.connect("data.sqlite3", check_same_thread = False) |
||||||
|
|
||||||
with conn: |
with conn: |
||||||
c = conn.cursor() |
c = conn.cursor() |
||||||
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)') |
c.execute('CREATE TABLE IF NOT EXISTS away (nick TEXT)') |
||||||
|
|
||||||
c.execute('SELECT * FROM away') |
c.execute('SELECT * FROM away') |
||||||
|
|
||||||
for row in c: |
for row in c: |
||||||
AWAY.append(row[0]) |
AWAY.append(row[0]) |
||||||
|
|
||||||
# populate the roles table |
# populate the roles table |
||||||
c.execute('DROP TABLE IF EXISTS roles') |
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("INSERT OR REPLACE INTO roles (role) VALUES (?)", (x,)) |
||||||
|
|
||||||
|
|
||||||
c.execute(('CREATE TABLE IF NOT EXISTS rolestats (playerid INTEGER, roleid INTEGER, '+ |
c.execute(('CREATE TABLE IF NOT EXISTS rolestats (playerid INTEGER, roleid INTEGER, '+ |
||||||
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+ |
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+ |
||||||
'UNIQUE(playerid, roleid))')) |
'UNIQUE(playerid, roleid))')) |
||||||
|
|
||||||
|
|
||||||
# create the players table |
# create the players table |
||||||
c.execute("CREATE TABLE IF NOT EXISTS players (id INTEGER PRIMARY KEY AUTOINCREMENT, nick TEXT, cloak TEXT, "+ |
c.execute("CREATE TABLE IF NOT EXISTS players (id INTEGER PRIMARY KEY AUTOINCREMENT, nick TEXT, cloak TEXT, "+ |
||||||
"UNIQUE(nick, cloak))") |
"UNIQUE(nick, cloak))") |
||||||
|
|
||||||
|
|
||||||
# create nick change table |
# create nick change table |
||||||
c.execute("CREATE TABLE IF NOT EXISTS nick_changes (old INTEGER, new INTEGER)") |
c.execute("CREATE TABLE IF NOT EXISTS nick_changes (old INTEGER, new INTEGER)") |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def remove_away(clk): |
def remove_away(clk): |
||||||
with conn: |
with conn: |
||||||
c.execute('DELETE from away where nick=?', (clk,)) |
c.execute('DELETE from away where nick=?', (clk,)) |
||||||
|
|
||||||
def add_away(clk): |
def add_away(clk): |
||||||
with conn: |
with conn: |
||||||
c.execute('INSERT into away VALUES (?)', (clk,)) |
c.execute('INSERT into away VALUES (?)', (clk,)) |
||||||
|
|
||||||
|
|
||||||
def add_player_record(nick, cloak): |
def add_player_record(nick, cloak): |
||||||
with conn: |
with conn: |
||||||
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (nick, cloak)) |
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (nick, cloak)) |
||||||
|
|
||||||
|
|
||||||
def record_nick_change(from_nick, to_nick, cloak): |
def record_nick_change(from_nick, to_nick, cloak): |
||||||
with conn: |
with conn: |
||||||
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (from_nick, cloak)) |
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (from_nick, cloak)) |
||||||
row = c.fetchone() |
row = c.fetchone() |
||||||
if not row: |
if not row: |
||||||
return # No records for this player |
return # No records for this player |
||||||
old_plid = row[0] |
old_plid = row[0] |
||||||
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (to_nick, cloak)) |
c.execute('INSERT OR IGNORE INTO players (nick, cloak) VALUES (?,?)', (to_nick, cloak)) |
||||||
|
|
||||||
# create a new entry in the players table for this nick |
# create a new entry in the players table for this nick |
||||||
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (to_nick, cloak)) |
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (to_nick, cloak)) |
||||||
new_plid = c.fetchone()[0] |
new_plid = c.fetchone()[0] |
||||||
|
|
||||||
c.execute('SELECT * FROM nick_changes WHERE old=? AND new=?', (new_plid, old_plid)) |
c.execute('SELECT * FROM nick_changes WHERE old=? AND new=?', (new_plid, old_plid)) |
||||||
|
|
||||||
if not c.fetchone(): # not recorded yet |
if not c.fetchone(): # not recorded yet |
||||||
c.execute('INSERT OR IGNORE INTO nick_changes (old, new) VALUES (?, ?)', (old_plid, new_plid)) |
c.execute('INSERT OR IGNORE INTO nick_changes (old, new) VALUES (?, ?)', (old_plid, new_plid)) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_role_stats(nick, clk, role, won, iwon): |
def update_role_stats(nick, clk, role, won, iwon): |
||||||
|
|
||||||
with conn: |
with conn: |
||||||
wins, iwins, totalgames = 0, 0, 0 |
wins, iwins, totalgames = 0, 0, 0 |
||||||
|
|
||||||
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk)) |
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk)) |
||||||
row = c.fetchone() |
row = c.fetchone() |
||||||
if row: |
if row: |
||||||
plid = row[0] |
plid = row[0] |
||||||
else: |
else: |
||||||
c.execute('INSERT INTO players (nick, cloak) VALUES (?,?)', (nick, clk)) |
c.execute('INSERT INTO players (nick, cloak) VALUES (?,?)', (nick, clk)) |
||||||
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk)) |
c.execute('SELECT id FROM players WHERE nick=? AND cloak=?', (nick, clk)) |
||||||
plid = c.fetchone()[0] |
plid = c.fetchone()[0] |
||||||
|
|
||||||
c.execute('SELECT id FROM roles WHERE role=?', (role,)) |
c.execute('SELECT id FROM roles WHERE role=?', (role,)) |
||||||
rid = c.fetchone()[0] |
rid = c.fetchone()[0] |
||||||
|
|
||||||
c.execute(("SELECT teamwins, individualwins, totalgames FROM rolestats "+ |
c.execute(("SELECT teamwins, individualwins, totalgames FROM rolestats "+ |
||||||
"WHERE playerid=? AND roleid=?"), (plid, rid)) |
"WHERE playerid=? AND roleid=?"), (plid, rid)) |
||||||
row = c.fetchone() |
row = c.fetchone() |
||||||
if row: |
if row: |
||||||
wins, iwins, total = row |
wins, iwins, total = row |
||||||
else: |
else: |
||||||
wins, iwins, total = 0,0,0 |
wins, iwins, total = 0,0,0 |
||||||
|
|
||||||
if won: |
if won: |
||||||
wins += 1 |
wins += 1 |
||||||
if iwon: |
if iwon: |
||||||
iwins += 1 |
iwins += 1 |
||||||
total += 1 |
total += 1 |
||||||
|
|
||||||
c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)", |
c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)", |
||||||
(plid, rid, wins, iwins, total)) |
(plid, rid, wins, iwins, total)) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,123 +1,123 @@ |
|||||||
#!/usr/bin/env python3.2 |
#!/usr/bin/env python3.2 |
||||||
|
|
||||||
# Copyright (c) 2011 Jimmy Cao |
# Copyright (c) 2011 Jimmy Cao |
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
# of this software and associated documentation files (the "Software"), to deal |
# of this software and associated documentation files (the "Software"), to deal |
||||||
# in the Software without restriction, including without limitation the rights |
# in the Software without restriction, including without limitation the rights |
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
# copies of the Software, and to permit persons to whom the Software is |
# copies of the Software, and to permit persons to whom the Software is |
||||||
# furnished to do so, subject to the following conditions: |
# furnished to do so, subject to the following conditions: |
||||||
# The above copyright notice and this permission notice shall be included in |
# The above copyright notice and this permission notice shall be included in |
||||||
# all copies or substantial portions of the Software. |
# all copies or substantial portions of the Software. |
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
# 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 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
# THE SOFTWARE. |
# THE SOFTWARE. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from oyoyo.client import IRCClient |
from oyoyo.client import IRCClient |
||||||
from oyoyo.parse import parse_nick |
from oyoyo.parse import parse_nick |
||||||
import logging |
import logging |
||||||
import botconfig |
import botconfig |
||||||
import wolfgame |
import wolfgame |
||||||
import time |
import time |
||||||
import traceback |
import traceback |
||||||
|
|
||||||
class UTCFormatter(logging.Formatter): |
class UTCFormatter(logging.Formatter): |
||||||
converter = time.gmtime |
converter = time.gmtime |
||||||
|
|
||||||
def on_privmsg(cli, rawnick, chan, msg): |
def on_privmsg(cli, rawnick, chan, msg): |
||||||
if chan != botconfig.NICK: #not a PM |
if chan != botconfig.NICK: #not a PM |
||||||
if "" in wolfgame.COMMANDS.keys(): |
if "" in wolfgame.COMMANDS.keys(): |
||||||
for fn in wolfgame.COMMANDS[""]: |
for fn in wolfgame.COMMANDS[""]: |
||||||
try: |
try: |
||||||
fn(cli, rawnick, chan, msg) |
fn(cli, rawnick, chan, msg) |
||||||
except Exception as e: |
except Exception as e: |
||||||
if botconfig.DEBUG_MODE: |
if botconfig.DEBUG_MODE: |
||||||
raise e |
raise e |
||||||
else: |
else: |
||||||
logging.error(traceback.format_exc()) |
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. |
# Now that is always called first. |
||||||
for x in wolfgame.COMMANDS.keys(): |
for x in wolfgame.COMMANDS.keys(): |
||||||
if x and msg.lower().startswith(botconfig.CMD_CHAR+x): |
if x and msg.lower().startswith(botconfig.CMD_CHAR+x): |
||||||
h = msg[len(x)+1:] |
h = msg[len(x)+1:] |
||||||
if not h or h[0] == " " or not x: |
if not h or h[0] == " " or not x: |
||||||
for fn in wolfgame.COMMANDS[x]: |
for fn in wolfgame.COMMANDS[x]: |
||||||
try: |
try: |
||||||
fn(cli, rawnick, chan, h.lstrip()) |
fn(cli, rawnick, chan, h.lstrip()) |
||||||
except Exception as e: |
except Exception as e: |
||||||
if botconfig.DEBUG_MODE: |
if botconfig.DEBUG_MODE: |
||||||
raise e |
raise e |
||||||
else: |
else: |
||||||
logging.error(traceback.format_exc()) |
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: |
else: |
||||||
for x in wolfgame.PM_COMMANDS.keys(): |
for x in wolfgame.PM_COMMANDS.keys(): |
||||||
if msg.lower().startswith(botconfig.CMD_CHAR+x): |
if msg.lower().startswith(botconfig.CMD_CHAR+x): |
||||||
h = msg[len(x)+1:] |
h = msg[len(x)+1:] |
||||||
elif not x or msg.lower().startswith(x): |
elif not x or msg.lower().startswith(x): |
||||||
h = msg[len(x):] |
h = msg[len(x):] |
||||||
else: |
else: |
||||||
continue |
continue |
||||||
if not h or h[0] == " " or not x: |
if not h or h[0] == " " or not x: |
||||||
for fn in wolfgame.PM_COMMANDS[x]: |
for fn in wolfgame.PM_COMMANDS[x]: |
||||||
try: |
try: |
||||||
fn(cli, rawnick, h.lstrip()) |
fn(cli, rawnick, h.lstrip()) |
||||||
except Exception as e: |
except Exception as e: |
||||||
if botconfig.DEBUG_MODE: |
if botconfig.DEBUG_MODE: |
||||||
raise e |
raise e |
||||||
else: |
else: |
||||||
logging.error(traceback.format_exc()) |
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): |
def __unhandled__(cli, prefix, cmd, *args): |
||||||
if cmd in wolfgame.HOOKS.keys(): |
if cmd in wolfgame.HOOKS.keys(): |
||||||
largs = list(args) |
largs = list(args) |
||||||
for i,arg in enumerate(largs): |
for i,arg in enumerate(largs): |
||||||
if isinstance(arg, bytes): largs[i] = arg.decode('ascii') |
if isinstance(arg, bytes): largs[i] = arg.decode('ascii') |
||||||
for fn in wolfgame.HOOKS[cmd]: |
for fn in wolfgame.HOOKS[cmd]: |
||||||
try: |
try: |
||||||
fn(cli, prefix, *largs) |
fn(cli, prefix, *largs) |
||||||
except Exception as e: |
except Exception as e: |
||||||
if botconfig.DEBUG_MODE: |
if botconfig.DEBUG_MODE: |
||||||
raise e |
raise e |
||||||
else: |
else: |
||||||
logging.error(traceback.format_exc()) |
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: |
else: |
||||||
logging.debug('Unhandled command {0}({1})'.format(cmd, [arg.decode('utf_8') |
logging.debug('Unhandled command {0}({1})'.format(cmd, [arg.decode('utf_8') |
||||||
for arg in args |
for arg in args |
||||||
if isinstance(arg, bytes)])) |
if isinstance(arg, bytes)])) |
||||||
|
|
||||||
def main(): |
def main(): |
||||||
if not botconfig.DEBUG_MODE: |
if not botconfig.DEBUG_MODE: |
||||||
logging.basicConfig(filename='errors.log', filemode='a', level=logging.WARNING) |
logging.basicConfig(filename='errors.log', filemode='a', level=logging.WARNING) |
||||||
formatter = UTCFormatter('[%(asctime)s] %(message)s', '%d/%b/%Y %H:%M:%S') |
formatter = UTCFormatter('[%(asctime)s] %(message)s', '%d/%b/%Y %H:%M:%S') |
||||||
for handler in logging.getLogger().handlers: |
for handler in logging.getLogger().handlers: |
||||||
handler.setFormatter(formatter) |
handler.setFormatter(formatter) |
||||||
else: |
else: |
||||||
logging.basicConfig(level=logging.DEBUG) |
logging.basicConfig(level=logging.DEBUG) |
||||||
|
|
||||||
cli = IRCClient( |
cli = IRCClient( |
||||||
{"privmsg":on_privmsg, |
{"privmsg":on_privmsg, |
||||||
"":__unhandled__}, |
"":__unhandled__}, |
||||||
host=botconfig.HOST, |
host=botconfig.HOST, |
||||||
port=botconfig.PORT, |
port=botconfig.PORT, |
||||||
authname=botconfig.USERNAME, |
authname=botconfig.USERNAME, |
||||||
password=botconfig.PASS, |
password=botconfig.PASS, |
||||||
nickname=botconfig.NICK, |
nickname=botconfig.NICK, |
||||||
connect_cb=wolfgame.connect_callback |
connect_cb=wolfgame.connect_callback |
||||||
) |
) |
||||||
cli.mainLoop() |
cli.mainLoop() |
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": |
if __name__ == "__main__": |
||||||
try: |
try: |
||||||
main() |
main() |
||||||
except Exception: |
except Exception: |
||||||
logging.error(traceback.format_exc()) |
logging.error(traceback.format_exc()) |
||||||
|
@ -1,38 +1,38 @@ |
|||||||
import botconfig |
import botconfig |
||||||
from datetime import datetime |
from datetime import datetime |
||||||
|
|
||||||
class WolfgameLogger(object): |
class WolfgameLogger(object): |
||||||
|
|
||||||
def __init__(self, outfile, boutfile): |
def __init__(self, outfile, boutfile): |
||||||
self.outfile = outfile |
self.outfile = outfile |
||||||
self.boutfile = boutfile |
self.boutfile = boutfile |
||||||
|
|
||||||
self.logged = "" |
self.logged = "" |
||||||
self.barelogged = "" |
self.barelogged = "" |
||||||
|
|
||||||
def log(self, message): |
def log(self, message): |
||||||
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + message + "\n" |
self.logged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + message + "\n" |
||||||
|
|
||||||
def logBare(self, *args): |
def logBare(self, *args): |
||||||
self.barelogged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + " ".join(args) + "\n" |
self.barelogged += datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S ") + " ".join(args) + "\n" |
||||||
|
|
||||||
def logChannelMessage(self, who, message): |
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): |
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): |
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): |
def saveToFile(self): |
||||||
if self.outfile: |
if self.outfile: |
||||||
with open(self.outfile, "a") as lf: |
with open(self.outfile, "a") as lf: |
||||||
lf.write(self.logged) |
lf.write(self.logged) |
||||||
|
|
||||||
if self.boutfile: |
if self.boutfile: |
||||||
with open(self.boutfile, "a") as bl: |
with open(self.boutfile, "a") as bl: |
||||||
bl.write(self.barelogged) |
bl.write(self.barelogged) |
||||||
|
|
||||||
self.logged = "" |
self.logged = "" |
||||||
self.barelogged = "" |
self.barelogged = "" |
Loading…
Reference in new issue