jcao219
14 years ago
commit
34502dc153
10 changed files with 1070 additions and 0 deletions
@ -0,0 +1,16 @@
@@ -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 @@
@@ -0,0 +1,6 @@
|
||||
PASS = "" |
||||
CHANNEL = "#example" |
||||
HOST = "irc.freenode.net" |
||||
PORT = 6667 |
||||
NICK = "" |
||||
ADMINS = ("") |
@ -0,0 +1,22 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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