jcao219
14 years ago
commit
34502dc153
10 changed files with 1070 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||||||
|
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. |
@ -0,0 +1,6 @@ |
|||||||
|
PASS = "" |
||||||
|
CHANNEL = "#example" |
||||||
|
HOST = "irc.freenode.net" |
||||||
|
PORT = 6667 |
||||||
|
NICK = "" |
||||||
|
ADMINS = ("") |
@ -0,0 +1,22 @@ |
|||||||
|
# Copyright (c) 2008 Duncan Fordyce |
||||||
|
# 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. |
||||||
|
|
||||||
|
"""A small, simple irc lib for python suitable for bots, clients and anything else. |
||||||
|
|
||||||
|
For more information and documentation about this package: |
||||||
|
http://code.google.com/p/oyoyo/ |
||||||
|
""" |
@ -0,0 +1,271 @@ |
|||||||
|
# Copyright (c) 2008 Duncan Fordyce |
||||||
|
# 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 sys |
||||||
|
import re |
||||||
|
import string |
||||||
|
import time |
||||||
|
import threading |
||||||
|
import os |
||||||
|
import traceback |
||||||
|
|
||||||
|
from oyoyo.parse import * |
||||||
|
from oyoyo import helpers |
||||||
|
from oyoyo.cmdhandler import CommandError |
||||||
|
import collections |
||||||
|
|
||||||
|
# Python < 3 compatibility |
||||||
|
if sys.version_info < (3,): |
||||||
|
class bytes(object): |
||||||
|
def __new__(self, b='', encoding='utf8'): |
||||||
|
return str(b) |
||||||
|
|
||||||
|
|
||||||
|
class IRCClientError(Exception): |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
class IRCClient: |
||||||
|
""" 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. |
||||||
|
|
||||||
|
>>> class My_Handler(DefaultCommandHandler): |
||||||
|
... def privmsg(self, prefix, command, args): |
||||||
|
... print "%s said %s" % (prefix, args[1]) |
||||||
|
... |
||||||
|
>>> def connect_callback(c): |
||||||
|
... helpers.join(c, '#myroom') |
||||||
|
... |
||||||
|
>>> cli = IRCClient(My_Handler, |
||||||
|
... host="irc.freenode.net", |
||||||
|
... port=6667, |
||||||
|
... nick="myname", |
||||||
|
... connect_cb=connect_callback) |
||||||
|
... |
||||||
|
>>> cli_con = cli.connect() |
||||||
|
>>> while 1: |
||||||
|
... cli_con.next() |
||||||
|
... |
||||||
|
""" |
||||||
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
||||||
|
self.nick = None |
||||||
|
self.real_name = None |
||||||
|
self.host = None |
||||||
|
self.port = None |
||||||
|
self.connect_cb = None |
||||||
|
self.blocking = True |
||||||
|
|
||||||
|
self.__dict__.update(kwargs) |
||||||
|
self.command_handler = cmd_handler(self) |
||||||
|
|
||||||
|
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 %s" % 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'). |
||||||
|
""" |
||||||
|
# Convert all args to bytes if not already |
||||||
|
encoding = kwargs.get('encoding') or 'utf_8' |
||||||
|
bargs = [] |
||||||
|
for arg in args: |
||||||
|
if isinstance(arg, str): |
||||||
|
bargs.append(bytes(arg, encoding)) |
||||||
|
elif isinstance(arg, bytes): |
||||||
|
bargs.append(arg) |
||||||
|
elif type(arg).__name__ == 'unicode': |
||||||
|
bargs.append(arg.encode(encoding)) |
||||||
|
else: |
||||||
|
raise IRCClientError('Refusing to send one of the args from provided: %s' |
||||||
|
% repr([(type(arg), arg) for arg in args])) |
||||||
|
|
||||||
|
msg = bytes(" ", "ascii").join(bargs) |
||||||
|
logging.info('---> send "%s"' % msg) |
||||||
|
|
||||||
|
self.socket.send(msg + bytes("\r\n", "ascii")) |
||||||
|
|
||||||
|
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: |
||||||
|
... g.next() |
||||||
|
|
||||||
|
""" |
||||||
|
try: |
||||||
|
logging.info('connecting to %s:%s' % (self.host, self.port)) |
||||||
|
self.socket.connect(("%s" % self.host, self.port)) |
||||||
|
if not self.blocking: |
||||||
|
self.socket.setblocking(0) |
||||||
|
|
||||||
|
helpers.nick(self, self.nick) |
||||||
|
helpers.user(self, self.nick, 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: |
||||||
|
try: # a little dance of compatibility to get the errno |
||||||
|
errno = e.errno |
||||||
|
except AttributeError: |
||||||
|
errno = e[0] |
||||||
|
if not self.blocking and errno == 11: |
||||||
|
pass |
||||||
|
else: |
||||||
|
raise e |
||||||
|
else: |
||||||
|
data = buffer.split(bytes("\n", "ascii")) |
||||||
|
buffer = data.pop() |
||||||
|
|
||||||
|
for el in data: |
||||||
|
prefix, command, args = parse_raw_irc_command(el) |
||||||
|
|
||||||
|
try: |
||||||
|
self.command_handler.run(command, prefix, *args) |
||||||
|
except CommandError: |
||||||
|
# error will of already been logged by the handler |
||||||
|
pass |
||||||
|
|
||||||
|
yield True |
||||||
|
finally: |
||||||
|
if self.socket: |
||||||
|
logging.info('closing socket') |
||||||
|
self.socket.close() |
||||||
|
|
||||||
|
|
||||||
|
class IRCApp: |
||||||
|
""" This class manages several IRCClient instances without the use of threads. |
||||||
|
(Non-threaded) Timer functionality is also included. |
||||||
|
""" |
||||||
|
|
||||||
|
class _ClientDesc: |
||||||
|
def __init__(self, **kwargs): |
||||||
|
self.con = None |
||||||
|
self.autoreconnect = False |
||||||
|
self.__dict__.update(kwargs) |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self._clients = {} |
||||||
|
self._timers = [] |
||||||
|
self.running = False |
||||||
|
self.sleep_time = 0.5 |
||||||
|
|
||||||
|
def addClient(self, client, autoreconnect=False): |
||||||
|
""" add a client object to the application. setting autoreconnect |
||||||
|
to true will mean the application will attempt to reconnect the client |
||||||
|
after every disconnect. you can also set autoreconnect to a number |
||||||
|
to specify how many reconnects should happen. |
||||||
|
|
||||||
|
warning: if you add a client that has blocking set to true, |
||||||
|
timers will no longer function properly """ |
||||||
|
logging.info('added client %s (ar=%s)' % (client, autoreconnect)) |
||||||
|
self._clients[client] = self._ClientDesc(autoreconnect=autoreconnect) |
||||||
|
|
||||||
|
def addTimer(self, seconds, cb): |
||||||
|
""" add a timed callback. accuracy is not specified, you can only |
||||||
|
garuntee the callback will be called after seconds has passed. |
||||||
|
( the only advantage to these timers is they dont use threads ) |
||||||
|
""" |
||||||
|
assert isinstance(cb, collections.Callable) |
||||||
|
logging.info('added timer to call %s in %ss' % (cb, seconds)) |
||||||
|
self._timers.append((time.time() + seconds, cb)) |
||||||
|
|
||||||
|
def run(self): |
||||||
|
""" run the application. this will block until stop() is called """ |
||||||
|
# TODO: convert this to use generators too? |
||||||
|
self.running = True |
||||||
|
while self.running: |
||||||
|
found_one_alive = False |
||||||
|
|
||||||
|
for client, clientdesc in self._clients.items(): |
||||||
|
if clientdesc.con is None: |
||||||
|
clientdesc.con = client.connect() |
||||||
|
|
||||||
|
try: |
||||||
|
next(clientdesc.con) |
||||||
|
except Exception as e: |
||||||
|
logging.error('client error %s' % e) |
||||||
|
logging.error(traceback.format_exc()) |
||||||
|
if clientdesc.autoreconnect: |
||||||
|
clientdesc.con = None |
||||||
|
if isinstance(clientdesc.autoreconnect, (int, float)): |
||||||
|
clientdesc.autoreconnect -= 1 |
||||||
|
found_one_alive = True |
||||||
|
else: |
||||||
|
clientdesc.con = False |
||||||
|
else: |
||||||
|
found_one_alive = True |
||||||
|
|
||||||
|
if not found_one_alive: |
||||||
|
logging.info('nothing left alive... quiting') |
||||||
|
self.stop() |
||||||
|
|
||||||
|
now = time.time() |
||||||
|
timers = self._timers[:] |
||||||
|
self._timers = [] |
||||||
|
for target_time, cb in timers: |
||||||
|
if now > target_time: |
||||||
|
logging.info('calling timer cb %s' % cb) |
||||||
|
cb() |
||||||
|
else: |
||||||
|
self._timers.append((target_time, cb)) |
||||||
|
|
||||||
|
time.sleep(self.sleep_time) |
||||||
|
|
||||||
|
def stop(self): |
||||||
|
""" stop the application """ |
||||||
|
self.running = False |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,223 @@ |
|||||||
|
# Copyright (c) 2008 Duncan Fordyce |
||||||
|
# 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 inspect |
||||||
|
import logging |
||||||
|
import sys |
||||||
|
import traceback |
||||||
|
|
||||||
|
from oyoyo import helpers |
||||||
|
from oyoyo.parse import parse_nick |
||||||
|
|
||||||
|
# Python < 3 compatibility |
||||||
|
if sys.version_info < (3,): |
||||||
|
class bytes(object): |
||||||
|
def __new__(self, b='', encoding='utf8'): |
||||||
|
return str(b) |
||||||
|
|
||||||
|
|
||||||
|
def protected(func): |
||||||
|
""" decorator to protect functions from being called """ |
||||||
|
func.protected = True |
||||||
|
return func |
||||||
|
|
||||||
|
|
||||||
|
class CommandError(Exception): |
||||||
|
def __init__(self, cmd): |
||||||
|
self.cmd = cmd |
||||||
|
|
||||||
|
class NoSuchCommandError(CommandError): |
||||||
|
def __str__(self): |
||||||
|
return 'No such command "%s"' % ".".join(self.cmd) |
||||||
|
|
||||||
|
class ProtectedCommandError(CommandError): |
||||||
|
def __str__(self): |
||||||
|
return 'Command "%s" is protected' % ".".join(self.cmd) |
||||||
|
|
||||||
|
|
||||||
|
class CommandHandler(object): |
||||||
|
""" The most basic CommandHandler """ |
||||||
|
|
||||||
|
def __init__(self, client): |
||||||
|
self.client = client |
||||||
|
|
||||||
|
@protected |
||||||
|
def get(self, in_command_parts): |
||||||
|
""" finds a command |
||||||
|
commands may be dotted. each command part is checked that it does |
||||||
|
not start with and underscore and does not have an attribute |
||||||
|
"protected". if either of these is true, ProtectedCommandError |
||||||
|
is raised. |
||||||
|
its possible to pass both "command.sub.func" and |
||||||
|
["command", "sub", "func"]. |
||||||
|
""" |
||||||
|
|
||||||
|
if isinstance(in_command_parts, bytes): |
||||||
|
in_command_parts = in_command_parts.split(b'.') |
||||||
|
else: |
||||||
|
in_command_parts = in_command_parts.split('.') |
||||||
|
|
||||||
|
command_parts = [] |
||||||
|
for cmdpart in in_command_parts: |
||||||
|
if isinstance(cmdpart, bytes): |
||||||
|
cmdpart = cmdpart.decode('ascii') |
||||||
|
command_parts.append(cmdpart) |
||||||
|
|
||||||
|
p = self |
||||||
|
while command_parts: |
||||||
|
cmd = command_parts.pop(0) |
||||||
|
if cmd.startswith('_'): |
||||||
|
raise ProtectedCommandError(in_command_parts) |
||||||
|
|
||||||
|
try: |
||||||
|
f = getattr(p, cmd) |
||||||
|
except AttributeError: |
||||||
|
raise NoSuchCommandError(in_command_parts) |
||||||
|
|
||||||
|
if hasattr(f, 'protected'): |
||||||
|
raise ProtectedCommandError(in_command_parts) |
||||||
|
|
||||||
|
if isinstance(f, CommandHandler) and command_parts: |
||||||
|
return f.get(command_parts) |
||||||
|
p = f |
||||||
|
|
||||||
|
return f |
||||||
|
|
||||||
|
@protected |
||||||
|
def run(self, command, *args): |
||||||
|
""" finds and runs a command """ |
||||||
|
logging.debug("processCommand %s(%s)" % (command, args)) |
||||||
|
|
||||||
|
try: |
||||||
|
f = self.get(command) |
||||||
|
except NoSuchCommandError: |
||||||
|
self.__unhandled__(command, *args) |
||||||
|
return |
||||||
|
|
||||||
|
logging.debug('f %s' % f) |
||||||
|
|
||||||
|
try: |
||||||
|
largs = list(args) |
||||||
|
for i,arg in enumerate(largs): |
||||||
|
if arg: largs[i] = arg.decode('ascii') |
||||||
|
f(*largs) |
||||||
|
except Exception as e: |
||||||
|
logging.error('command raised %s' % e) |
||||||
|
logging.error(traceback.format_exc()) |
||||||
|
raise CommandError(command) |
||||||
|
|
||||||
|
@protected |
||||||
|
def __unhandled__(self, cmd, *args): |
||||||
|
"""The default handler for commands. Override this method to |
||||||
|
apply custom behavior (example, printing) unhandled commands. |
||||||
|
""" |
||||||
|
logging.debug('unhandled command %s(%s)' % (cmd, args)) |
||||||
|
|
||||||
|
|
||||||
|
class DefaultCommandHandler(CommandHandler): |
||||||
|
""" CommandHandler that provides methods for the normal operation of IRC. |
||||||
|
If you want your bot to properly respond to pings, etc, you should subclass this. |
||||||
|
""" |
||||||
|
|
||||||
|
def ping(self, prefix, server): |
||||||
|
self.client.send('PONG', server) |
||||||
|
|
||||||
|
|
||||||
|
class DefaultBotCommandHandler(CommandHandler): |
||||||
|
""" default command handler for bots. methods/attributes are made |
||||||
|
available as commands """ |
||||||
|
|
||||||
|
@protected |
||||||
|
def getVisibleCommands(self, obj=None): |
||||||
|
test = (lambda x: isinstance(x, CommandHandler) or \ |
||||||
|
inspect.ismethod(x) or inspect.isfunction(x)) |
||||||
|
members = inspect.getmembers(obj or self, test) |
||||||
|
return [m for m, _ in members |
||||||
|
if (not m.startswith('_') and |
||||||
|
not hasattr(getattr(obj, m), 'protected'))] |
||||||
|
|
||||||
|
def help(self, sender, dest, arg=None): |
||||||
|
"""list all available commands or get help on a specific command""" |
||||||
|
logging.info('help sender=%s dest=%s arg=%s' % (sender, dest, arg)) |
||||||
|
if not arg: |
||||||
|
commands = self.getVisibleCommands() |
||||||
|
commands.sort() |
||||||
|
helpers.msg(self.client, dest, |
||||||
|
"available commands: %s" % " ".join(commands)) |
||||||
|
else: |
||||||
|
try: |
||||||
|
f = self.get(arg) |
||||||
|
except CommandError as e: |
||||||
|
helpers.msg(self.client, dest, str(e)) |
||||||
|
return |
||||||
|
|
||||||
|
doc = f.__doc__.strip() if f.__doc__ else "No help available" |
||||||
|
|
||||||
|
if not inspect.ismethod(f): |
||||||
|
subcommands = self.getVisibleCommands(f) |
||||||
|
if subcommands: |
||||||
|
doc += " [sub commands: %s]" % " ".join(subcommands) |
||||||
|
|
||||||
|
helpers.msg(self.client, dest, "%s: %s" % (arg, doc)) |
||||||
|
|
||||||
|
|
||||||
|
class BotCommandHandler(DefaultCommandHandler): |
||||||
|
""" complete command handler for bots """ |
||||||
|
|
||||||
|
def __init__(self, client, command_handler): |
||||||
|
DefaultCommandHandler.__init__(self, client) |
||||||
|
self.command_handler = command_handler |
||||||
|
|
||||||
|
def privmsg(self, prefix, dest, msg): |
||||||
|
self.tryBotCommand(prefix, dest, msg) |
||||||
|
|
||||||
|
@protected |
||||||
|
def tryBotCommand(self, prefix, dest, msg): |
||||||
|
""" tests a command to see if its a command for the bot, returns True |
||||||
|
and calls self.processBotCommand(cmd, sender) if its is. |
||||||
|
""" |
||||||
|
|
||||||
|
logging.debug("tryBotCommand('%s' '%s' '%s')" % (prefix, dest, msg)) |
||||||
|
|
||||||
|
if dest == self.client.nick: |
||||||
|
dest = parse_nick(prefix)[0] |
||||||
|
elif msg.startswith(self.client.nick): |
||||||
|
msg = msg[len(self.client.nick)+1:] |
||||||
|
else: |
||||||
|
return False |
||||||
|
|
||||||
|
msg = msg.strip() |
||||||
|
|
||||||
|
parts = msg.split(' ', 1) |
||||||
|
command = parts[0] |
||||||
|
arg = parts[1:] |
||||||
|
|
||||||
|
try: |
||||||
|
self.command_handler.run(command, prefix, dest, *arg) |
||||||
|
except CommandError as e: |
||||||
|
helpers.msg(self.client, dest, str(e)) |
||||||
|
return True |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,45 @@ |
|||||||
|
#!/usr/bin/python |
||||||
|
"""Example bot for oyoyo that responds to !say""" |
||||||
|
|
||||||
|
import logging |
||||||
|
import re |
||||||
|
|
||||||
|
from oyoyo.client import IRCClient |
||||||
|
from oyoyo.cmdhandler import DefaultCommandHandler |
||||||
|
from oyoyo import helpers |
||||||
|
|
||||||
|
|
||||||
|
HOST = 'irc.freenode.net' |
||||||
|
PORT = 6667 |
||||||
|
NICK = 'oyoyo-example' |
||||||
|
CHANNEL = '#oyoyo-test' |
||||||
|
|
||||||
|
|
||||||
|
class MyHandler(DefaultCommandHandler): |
||||||
|
def privmsg(self, nick, chan, msg): |
||||||
|
msg = msg.decode() |
||||||
|
match = re.match('\!say (.*)', msg) |
||||||
|
if match: |
||||||
|
to_say = match.group(1).strip() |
||||||
|
print(('Saying, "%s"' % to_say)) |
||||||
|
helpers.msg(self.client, chan, to_say) |
||||||
|
|
||||||
|
|
||||||
|
def connect_cb(cli): |
||||||
|
helpers.join(cli, CHANNEL) |
||||||
|
|
||||||
|
|
||||||
|
def main(): |
||||||
|
logging.basicConfig(level=logging.DEBUG) |
||||||
|
|
||||||
|
cli = IRCClient(MyHandler, host=HOST, port=PORT, nick=NICK, |
||||||
|
connect_cb=connect_cb) |
||||||
|
conn = cli.connect() |
||||||
|
|
||||||
|
while True: |
||||||
|
next(conn) ## python 2 |
||||||
|
# next(conn) ## python 3 |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
main() |
@ -0,0 +1,90 @@ |
|||||||
|
# Copyright (c) 2008 Duncan Fordyce |
||||||
|
# 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. |
||||||
|
|
||||||
|
""" contains helper functions for common irc commands """ |
||||||
|
|
||||||
|
import random |
||||||
|
|
||||||
|
def msg(cli, user, msg): |
||||||
|
for line in msg.split('\n'): |
||||||
|
cli.send("PRIVMSG", user, ":%s" % line) |
||||||
|
|
||||||
|
def msgrandom(cli, choices, dest, user=None): |
||||||
|
o = "%s: " % user if user else "" |
||||||
|
o += random.choice(choices) |
||||||
|
msg(cli, dest, o) |
||||||
|
|
||||||
|
def _makeMsgRandomFunc(choices): |
||||||
|
def func(cli, dest, user=None): |
||||||
|
msgrandom(cli, choices, dest, user) |
||||||
|
return func |
||||||
|
|
||||||
|
msgYes = _makeMsgRandomFunc(['yes', 'alright', 'ok']) |
||||||
|
msgOK = _makeMsgRandomFunc(['ok', 'done']) |
||||||
|
msgNo = _makeMsgRandomFunc(['no', 'no-way']) |
||||||
|
|
||||||
|
|
||||||
|
def ns(cli, *args): |
||||||
|
msg(cli, "NickServ", " ".join(args)) |
||||||
|
|
||||||
|
def cs(cli, *args): |
||||||
|
msg(cli, "ChanServ", " ".join(args)) |
||||||
|
|
||||||
|
def identify(cli, passwd, authuser="NickServ"): |
||||||
|
msg(cli, authuser, "IDENTIFY %s" % passwd) |
||||||
|
|
||||||
|
def quit(cli, msg='gone'): |
||||||
|
cli.send("QUIT :%s" % msg) |
||||||
|
cli._end = 1 |
||||||
|
|
||||||
|
def mode(cli, chan, mod): |
||||||
|
cli.send("MODE", chan, mod) |
||||||
|
|
||||||
|
def user(cli, username, realname=None): |
||||||
|
cli.send("USER", username, cli.host, cli.host, |
||||||
|
realname or username) |
||||||
|
|
||||||
|
_simple = ( |
||||||
|
'join', |
||||||
|
'part', |
||||||
|
'nick', |
||||||
|
'notice', |
||||||
|
) |
||||||
|
def _addsimple(): |
||||||
|
import sys |
||||||
|
def simplecmd(cmd_name): |
||||||
|
def f(cli, *args): |
||||||
|
cli.send(cmd_name, *args) |
||||||
|
return f |
||||||
|
m = sys.modules[__name__] |
||||||
|
for t in _simple: |
||||||
|
setattr(m, t, simplecmd(t.upper())) |
||||||
|
_addsimple() |
||||||
|
|
||||||
|
def _addNumerics(): |
||||||
|
import sys |
||||||
|
from oyoyo import ircevents |
||||||
|
def numericcmd(cmd_num, cmd_name): |
||||||
|
def f(cli, *args): |
||||||
|
cli.send(cmd_num, *args) |
||||||
|
return f |
||||||
|
m = sys.modules[__name__] |
||||||
|
for num, name in ircevents.numeric_events.items(): |
||||||
|
setattr(m, name, numericcmd(num, name)) |
||||||
|
|
||||||
|
_addNumerics() |
||||||
|
|
@ -0,0 +1,209 @@ |
|||||||
|
# Copyright (c) 2008 Duncan Fordyce |
||||||
|
# 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. |
||||||
|
|
||||||
|
# taken from python irclib.. who took it from... |
||||||
|
# Numeric table mostly stolen from the Perl IRC module (Net::IRC). |
||||||
|
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"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()) |
||||||
|
|
@ -0,0 +1,97 @@ |
|||||||
|
# Copyright (c) 2008 Duncan Fordyce |
||||||
|
# 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 sys |
||||||
|
|
||||||
|
from oyoyo.ircevents import * |
||||||
|
|
||||||
|
# Python < 3 compatibility |
||||||
|
if sys.version_info < (3,): |
||||||
|
class bytes(object): |
||||||
|
def __new__(self, b='', encoding='utf8'): |
||||||
|
return str(b) |
||||||
|
|
||||||
|
|
||||||
|
def parse_raw_irc_command(element): |
||||||
|
""" |
||||||
|
This function parses a raw irc command and returns a tuple |
||||||
|
of (prefix, command, args). |
||||||
|
The following is a psuedo BNF of the input text: |
||||||
|
|
||||||
|
<message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf> |
||||||
|
<prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ] |
||||||
|
<command> ::= <letter> { <letter> } | <number> <number> <number> |
||||||
|
<SPACE> ::= ' ' { ' ' } |
||||||
|
<params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ] |
||||||
|
|
||||||
|
<middle> ::= <Any *non-empty* sequence of octets not including SPACE |
||||||
|
or NUL or CR or LF, the first of which may not be ':'> |
||||||
|
<trailing> ::= <Any, possibly *empty*, sequence of octets not including |
||||||
|
NUL or CR or LF> |
||||||
|
|
||||||
|
<crlf> ::= CR LF |
||||||
|
""" |
||||||
|
parts = element.strip().split(bytes(" ", "ascii")) |
||||||
|
if parts[0].startswith(bytes(':', 'ascii')): |
||||||
|
prefix = parts[0][1:] |
||||||
|
command = parts[1] |
||||||
|
args = parts[2:] |
||||||
|
else: |
||||||
|
prefix = None |
||||||
|
command = parts[0] |
||||||
|
args = parts[1:] |
||||||
|
|
||||||
|
if command.isdigit(): |
||||||
|
try: |
||||||
|
command = numeric_events[command] |
||||||
|
except KeyError: |
||||||
|
logging.warn('unknown numeric event %s' % command) |
||||||
|
command = command.lower() |
||||||
|
|
||||||
|
if args[0].startswith(bytes(':', 'ascii')): |
||||||
|
args = [bytes(" ", "ascii").join(args)[1:]] |
||||||
|
else: |
||||||
|
for idx, arg in enumerate(args): |
||||||
|
if arg.startswith(bytes(':', 'ascii')): |
||||||
|
args = args[:idx] + [bytes(" ", 'ascii').join(args[idx:])[1:]] |
||||||
|
break |
||||||
|
|
||||||
|
return (prefix, command, args) |
||||||
|
|
||||||
|
|
||||||
|
def parse_nick(name): |
||||||
|
""" parse a nickname and return a tuple of (nick, mode, user, host) |
||||||
|
|
||||||
|
<nick> [ '!' [<mode> = ] <user> ] [ '@' <host> ] |
||||||
|
""" |
||||||
|
|
||||||
|
try: |
||||||
|
nick, rest = name.split('!') |
||||||
|
except ValueError: |
||||||
|
return (name, None, None, None) |
||||||
|
try: |
||||||
|
mode, rest = rest.split('=') |
||||||
|
except ValueError: |
||||||
|
mode, rest = None, rest |
||||||
|
try: |
||||||
|
user, host = rest.split('@') |
||||||
|
except ValueError: |
||||||
|
return (nick, mode, rest, None) |
||||||
|
|
||||||
|
return (nick, mode, user, host) |
||||||
|
|
@ -0,0 +1,91 @@ |
|||||||
|
from oyoyo.client import IRCClient |
||||||
|
from oyoyo.cmdhandler import DefaultCommandHandler |
||||||
|
from oyoyo import helpers |
||||||
|
from oyoyo.parse import parse_nick |
||||||
|
import logging |
||||||
|
import botconfig |
||||||
|
|
||||||
|
def connect_callback(cli): |
||||||
|
helpers.identify(cli, botconfig.PASS) |
||||||
|
helpers.join(cli, botconfig.CHANNEL) |
||||||
|
helpers.msg(cli, "ChanServ", "op "+botconfig.CHANNEL) |
||||||
|
helpers.msg(cli, botconfig.CHANNEL, "\u0002Wolfbot2 is here.\u0002") |
||||||
|
|
||||||
|
G_PM_COMMANDS = [] |
||||||
|
G_COMMANDS = [] |
||||||
|
COMMANDS = {} |
||||||
|
PM_COMMANDS = {} |
||||||
|
|
||||||
|
HOOKS = {} |
||||||
|
|
||||||
|
def cmd(s, pmOnly = False): |
||||||
|
def dec(f): |
||||||
|
if s is None and pmOnly: |
||||||
|
G_PM_COMMANDS.append(f) |
||||||
|
elif s is None and not pmOnly: |
||||||
|
G_COMMANDS.append(f) |
||||||
|
elif pmOnly: |
||||||
|
if s in PM_COMMANDS: |
||||||
|
PM_COMMANDS[s].append(f) |
||||||
|
else: PM_COMMANDS[s] = [f] |
||||||
|
else: |
||||||
|
if s in COMMANDS: |
||||||
|
COMMANDS[s].append(f) |
||||||
|
else: COMMANDS[s] = [f] |
||||||
|
return f |
||||||
|
return dec |
||||||
|
|
||||||
|
def hook(s): |
||||||
|
def dec(f): |
||||||
|
HOOKS[s] = f |
||||||
|
return f |
||||||
|
return dec |
||||||
|
|
||||||
|
class WolfBotHandler(DefaultCommandHandler): |
||||||
|
def __init__(self, client): |
||||||
|
super().__init__(client) |
||||||
|
|
||||||
|
def privmsg(self, rawnick, chan, msg): |
||||||
|
print("{0} in {1} said: {2}".format(rawnick, chan, msg)) |
||||||
|
|
||||||
|
if chan != botconfig.NICK: #not a PM |
||||||
|
for x in COMMANDS: |
||||||
|
if msg.startswith(x): |
||||||
|
msg = msg.replace(x, "", 1) |
||||||
|
for f in COMMANDS[x]: |
||||||
|
f(self.client, rawnick, chan, msg.lstrip()) |
||||||
|
else: |
||||||
|
for x in PM_COMMANDS: |
||||||
|
if msg.startswith(x): |
||||||
|
msg = msg.replace(x, "", 1) |
||||||
|
for f in PM_COMMANDS[x]: |
||||||
|
f(self.client, rawnick, msg.lstrip()) |
||||||
|
|
||||||
|
def nick(self, fro, to): |
||||||
|
print(fro, to) |
||||||
|
|
||||||
|
def main(): |
||||||
|
cli = IRCClient(WolfBotHandler, host="irc.freenode.net", port=6667, nick="wolfbot2-alpha", |
||||||
|
connect_cb=connect_callback) |
||||||
|
|
||||||
|
conn = cli.connect() |
||||||
|
while True: |
||||||
|
next(conn) |
||||||
|
|
||||||
|
#Game Logic Begins: |
||||||
|
|
||||||
|
@cmd("!say", True) |
||||||
|
def join(cli, rawnick, rest): |
||||||
|
helpers.msg(cli, botconfig.CHANNEL, "{0} says: {1}".format(parse_nick(rawnick)[0], rest)) |
||||||
|
|
||||||
|
@cmd("!bye", True) |
||||||
|
@cmd("!bye", False) |
||||||
|
def forced_exit(cli, rawnick, *rest): |
||||||
|
if parse_nick(rawnick)[0] in botconfig.ADMINS: |
||||||
|
helpers.quit(cli, "Forced quit from admin") |
||||||
|
raise SystemExit |
||||||
|
|
||||||
|
#Game Logic Ends |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
main() |
Loading…
Reference in new issue