first commit
BIN
Pesterchum.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
22
oyoyo/__init__.py
Normal file
|
@ -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/
|
||||||
|
"""
|
BIN
oyoyo/__init__.pyc
Normal file
268
oyoyo/client.py
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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 = False
|
||||||
|
|
||||||
|
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 'utf8'
|
||||||
|
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, 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 loggingged 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 callable(cb)
|
||||||
|
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.iteritems():
|
||||||
|
if clientdesc.con is None:
|
||||||
|
clientdesc.con = client.connect()
|
||||||
|
|
||||||
|
try:
|
||||||
|
clientdesc.con.next()
|
||||||
|
except Exception, 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
oyoyo/client.pyc
Normal file
212
oyoyo/cmdhandler.py
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
# 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, (str, bytes)):
|
||||||
|
in_command_parts = in_command_parts.split(bytes('.', 'ascii'))
|
||||||
|
command_parts = in_command_parts[:]
|
||||||
|
|
||||||
|
p = self
|
||||||
|
while command_parts:
|
||||||
|
cmd = command_parts.pop(0).decode('ascii')
|
||||||
|
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:
|
||||||
|
f(*args)
|
||||||
|
except Exception, 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, 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, e:
|
||||||
|
helpers.msg(self.client, dest, str(e))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
oyoyo/cmdhandler.pyc
Normal file
45
oyoyo/examplebot.py
Normal file
|
@ -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:
|
||||||
|
conn.next() ## python 2
|
||||||
|
# next(conn) ## python 3
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
87
oyoyo/helpers.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# 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 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.iteritems():
|
||||||
|
setattr(m, name, numericcmd(num, name))
|
||||||
|
|
||||||
|
_addNumerics()
|
||||||
|
|
BIN
oyoyo/helpers.pyc
Normal file
209
oyoyo/ircevents.py
Normal file
|
@ -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 = {
|
||||||
|
"001": "welcome",
|
||||||
|
"002": "yourhost",
|
||||||
|
"003": "created",
|
||||||
|
"004": "myinfo",
|
||||||
|
"005": "featurelist", # XXX
|
||||||
|
"200": "tracelink",
|
||||||
|
"201": "traceconnecting",
|
||||||
|
"202": "tracehandshake",
|
||||||
|
"203": "traceunknown",
|
||||||
|
"204": "traceoperator",
|
||||||
|
"205": "traceuser",
|
||||||
|
"206": "traceserver",
|
||||||
|
"207": "traceservice",
|
||||||
|
"208": "tracenewtype",
|
||||||
|
"209": "traceclass",
|
||||||
|
"210": "tracereconnect",
|
||||||
|
"211": "statslinkinfo",
|
||||||
|
"212": "statscommands",
|
||||||
|
"213": "statscline",
|
||||||
|
"214": "statsnline",
|
||||||
|
"215": "statsiline",
|
||||||
|
"216": "statskline",
|
||||||
|
"217": "statsqline",
|
||||||
|
"218": "statsyline",
|
||||||
|
"219": "endofstats",
|
||||||
|
"221": "umodeis",
|
||||||
|
"231": "serviceinfo",
|
||||||
|
"232": "endofservices",
|
||||||
|
"233": "service",
|
||||||
|
"234": "servlist",
|
||||||
|
"235": "servlistend",
|
||||||
|
"241": "statslline",
|
||||||
|
"242": "statsuptime",
|
||||||
|
"243": "statsoline",
|
||||||
|
"244": "statshline",
|
||||||
|
"250": "luserconns",
|
||||||
|
"251": "luserclient",
|
||||||
|
"252": "luserop",
|
||||||
|
"253": "luserunknown",
|
||||||
|
"254": "luserchannels",
|
||||||
|
"255": "luserme",
|
||||||
|
"256": "adminme",
|
||||||
|
"257": "adminloc1",
|
||||||
|
"258": "adminloc2",
|
||||||
|
"259": "adminemail",
|
||||||
|
"261": "tracelog",
|
||||||
|
"262": "endoftrace",
|
||||||
|
"263": "tryagain",
|
||||||
|
"265": "n_local",
|
||||||
|
"266": "n_global",
|
||||||
|
"300": "none",
|
||||||
|
"301": "away",
|
||||||
|
"302": "userhost",
|
||||||
|
"303": "ison",
|
||||||
|
"305": "unaway",
|
||||||
|
"306": "nowaway",
|
||||||
|
"311": "whoisuser",
|
||||||
|
"312": "whoisserver",
|
||||||
|
"313": "whoisoperator",
|
||||||
|
"314": "whowasuser",
|
||||||
|
"315": "endofwho",
|
||||||
|
"316": "whoischanop",
|
||||||
|
"317": "whoisidle",
|
||||||
|
"318": "endofwhois",
|
||||||
|
"319": "whoischannels",
|
||||||
|
"321": "liststart",
|
||||||
|
"322": "list",
|
||||||
|
"323": "listend",
|
||||||
|
"324": "channelmodeis",
|
||||||
|
"329": "channelcreate",
|
||||||
|
"331": "notopic",
|
||||||
|
"332": "currenttopic",
|
||||||
|
"333": "topicinfo",
|
||||||
|
"341": "inviting",
|
||||||
|
"342": "summoning",
|
||||||
|
"346": "invitelist",
|
||||||
|
"347": "endofinvitelist",
|
||||||
|
"348": "exceptlist",
|
||||||
|
"349": "endofexceptlist",
|
||||||
|
"351": "version",
|
||||||
|
"352": "whoreply",
|
||||||
|
"353": "namreply",
|
||||||
|
"361": "killdone",
|
||||||
|
"362": "closing",
|
||||||
|
"363": "closeend",
|
||||||
|
"364": "links",
|
||||||
|
"365": "endoflinks",
|
||||||
|
"366": "endofnames",
|
||||||
|
"367": "banlist",
|
||||||
|
"368": "endofbanlist",
|
||||||
|
"369": "endofwhowas",
|
||||||
|
"371": "info",
|
||||||
|
"372": "motd",
|
||||||
|
"373": "infostart",
|
||||||
|
"374": "endofinfo",
|
||||||
|
"375": "motdstart",
|
||||||
|
"376": "endofmotd",
|
||||||
|
"377": "motd2", # 1997-10-16 -- tkil
|
||||||
|
"381": "youreoper",
|
||||||
|
"382": "rehashing",
|
||||||
|
"384": "myportis",
|
||||||
|
"391": "time",
|
||||||
|
"392": "usersstart",
|
||||||
|
"393": "users",
|
||||||
|
"394": "endofusers",
|
||||||
|
"395": "nousers",
|
||||||
|
"401": "nosuchnick",
|
||||||
|
"402": "nosuchserver",
|
||||||
|
"403": "nosuchchannel",
|
||||||
|
"404": "cannotsendtochan",
|
||||||
|
"405": "toomanychannels",
|
||||||
|
"406": "wasnosuchnick",
|
||||||
|
"407": "toomanytargets",
|
||||||
|
"409": "noorigin",
|
||||||
|
"411": "norecipient",
|
||||||
|
"412": "notexttosend",
|
||||||
|
"413": "notoplevel",
|
||||||
|
"414": "wildtoplevel",
|
||||||
|
"421": "unknowncommand",
|
||||||
|
"422": "nomotd",
|
||||||
|
"423": "noadmininfo",
|
||||||
|
"424": "fileerror",
|
||||||
|
"431": "nonicknamegiven",
|
||||||
|
"432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
|
||||||
|
"433": "nicknameinuse",
|
||||||
|
"436": "nickcollision",
|
||||||
|
"437": "unavailresource", # "Nick temporally unavailable"
|
||||||
|
"441": "usernotinchannel",
|
||||||
|
"442": "notonchannel",
|
||||||
|
"443": "useronchannel",
|
||||||
|
"444": "nologin",
|
||||||
|
"445": "summondisabled",
|
||||||
|
"446": "usersdisabled",
|
||||||
|
"451": "notregistered",
|
||||||
|
"461": "needmoreparams",
|
||||||
|
"462": "alreadyregistered",
|
||||||
|
"463": "nopermforhost",
|
||||||
|
"464": "passwdmismatch",
|
||||||
|
"465": "yourebannedcreep", # I love this one...
|
||||||
|
"466": "youwillbebanned",
|
||||||
|
"467": "keyset",
|
||||||
|
"471": "channelisfull",
|
||||||
|
"472": "unknownmode",
|
||||||
|
"473": "inviteonlychan",
|
||||||
|
"474": "bannedfromchan",
|
||||||
|
"475": "badchannelkey",
|
||||||
|
"476": "badchanmask",
|
||||||
|
"477": "nochanmodes", # "Channel doesn't support modes"
|
||||||
|
"478": "banlistfull",
|
||||||
|
"481": "noprivileges",
|
||||||
|
"482": "chanoprivsneeded",
|
||||||
|
"483": "cantkillserver",
|
||||||
|
"484": "restricted", # Connection is restricted
|
||||||
|
"485": "uniqopprivsneeded",
|
||||||
|
"491": "nooperhost",
|
||||||
|
"492": "noservicehost",
|
||||||
|
"501": "umodeunknownflag",
|
||||||
|
"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 + numeric_events.values()
|
||||||
|
|
BIN
oyoyo/ircevents.pyc
Normal file
97
oyoyo/parse.py
Normal file
|
@ -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 (name, mode, rest, None)
|
||||||
|
|
||||||
|
return (name, mode, user, host)
|
||||||
|
|
BIN
oyoyo/parse.pyc
Normal file
6
pesterchum.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"chums": ["gamblingGenocider",
|
||||||
|
"grimAuxiliatrix",
|
||||||
|
"gardenGnostic"
|
||||||
|
]
|
||||||
|
}
|
117
pesterchum.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# pesterchum
|
||||||
|
from oyoyo.client import IRCClient
|
||||||
|
from oyoyo.cmdhandler import DefaultCommandHandler
|
||||||
|
from oyoyo import helpers
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from PyQt4 import QtGui, QtCore
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
class PesterHandler(DefaultCommandHandler):
|
||||||
|
def privmsg(self, nick, chan, msg):
|
||||||
|
# display msg, do other stuff
|
||||||
|
print "%s: %s" % (nick, msg)
|
||||||
|
if chan == "#pesterchum":
|
||||||
|
# follow instructions
|
||||||
|
self.window.newMessage()
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# private message
|
||||||
|
pass
|
||||||
|
def welcome(self, server, nick, msg):
|
||||||
|
helpers.join(self.client, "#pesterchum")
|
||||||
|
|
||||||
|
class userConfig(object):
|
||||||
|
def __init__(self):
|
||||||
|
fp = open("pesterchum.js")
|
||||||
|
self.config = json.load(fp)
|
||||||
|
fp.close()
|
||||||
|
def chums(self):
|
||||||
|
return self.config['chums']
|
||||||
|
|
||||||
|
class exitButton(QtGui.QPushButton):
|
||||||
|
def __init__(self, icon, parent=None):
|
||||||
|
QtGui.QPushButton.__init__(self, icon, "", parent)
|
||||||
|
self.setFlat(True)
|
||||||
|
def clicked(self):
|
||||||
|
print "X"
|
||||||
|
|
||||||
|
class chumArea(QtGui.QListWidget):
|
||||||
|
def __init__(self, chums, parent=None):
|
||||||
|
QtGui.QListWidget.__init__(self, parent)
|
||||||
|
self.setGeometry(75, 100, 350, 500)
|
||||||
|
self.setStyleSheet("""
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
font: bold;
|
||||||
|
font-family: "Courier New";
|
||||||
|
""")
|
||||||
|
self.chums = chums
|
||||||
|
for c in self.chums:
|
||||||
|
chumLabel = QtGui.QListWidgetItem(c)
|
||||||
|
# chumLabel.setFont(QtGui.QFont("Courier New", pointSize=12,
|
||||||
|
# weight=75))
|
||||||
|
self.addItem(chumLabel)
|
||||||
|
|
||||||
|
|
||||||
|
class PesterWindow(QtGui.QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QtGui.QWidget.__init__(self, parent,
|
||||||
|
flags=QtCore.Qt.CustomizeWindowHint)
|
||||||
|
self.config = userConfig()
|
||||||
|
self.setGeometry(100,100, 500, 700)
|
||||||
|
self.setWindowIcon(QtGui.QIcon('themes/pesterchum/trayicon.gif'))
|
||||||
|
self.setStyleSheet("""
|
||||||
|
background-color: #fdb302;
|
||||||
|
""")
|
||||||
|
self.closeButton = exitButton(QtGui.QIcon('themes/pesterchum/x.gif'), self)
|
||||||
|
s = self.size() - self.closeButton.icon().availableSizes()[0]
|
||||||
|
self.closeButton.move(s.width(), 0)
|
||||||
|
self.connect(self.closeButton, QtCore.SIGNAL('clicked()'),
|
||||||
|
self, QtCore.SLOT('close()'))
|
||||||
|
self.chumList = chumArea(self.config.chums(), self)
|
||||||
|
|
||||||
|
self.cli = IRCClient(PesterHandler, host="irc.tymoon.eu", port=6667, nick="superGhost")
|
||||||
|
self.cli.command_handler.window = self
|
||||||
|
|
||||||
|
self.conn = self.cli.connect()
|
||||||
|
self.irctimer = QtCore.QTimer(self)
|
||||||
|
self.connect(self.irctimer, QtCore.SIGNAL('timeout()'),
|
||||||
|
self, QtCore.SLOT('updateIRC()'))
|
||||||
|
self.irctimer.start()
|
||||||
|
self.moving = None
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
if self.moving:
|
||||||
|
move = event.globalPos() - self.moving
|
||||||
|
self.move(self.pos() + move)
|
||||||
|
self.moving = event.globalPos()
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
if event.button() == 1:
|
||||||
|
self.moving = event.globalPos()
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if event.button() == 1:
|
||||||
|
self.moving = None
|
||||||
|
@QtCore.pyqtSlot()
|
||||||
|
def updateIRC(self):
|
||||||
|
self.conn.next()
|
||||||
|
def newMessage(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
widget = PesterWindow()
|
||||||
|
widget.show()
|
||||||
|
trayicon = QtGui.QSystemTrayIcon(QtGui.QIcon("themes/pesterchum/trayicon.gif"), app)
|
||||||
|
traymenu = QtGui.QMenu()
|
||||||
|
traymenu.addAction("Hi!! HI!!")
|
||||||
|
trayicon.setContextMenu(traymenu)
|
||||||
|
trayicon.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
BIN
themes/pesterchum/Thumbs.db
Normal file
BIN
themes/pesterchum/abouticon.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
themes/pesterchum/alarm.wav
Normal file
BIN
themes/pesterchum/chummy.gif
Normal file
After Width: | Height: | Size: 106 B |
BIN
themes/pesterchum/detestful.gif
Normal file
After Width: | Height: | Size: 164 B |
BIN
themes/pesterchum/devious.gif
Normal file
After Width: | Height: | Size: 127 B |
BIN
themes/pesterchum/discontent.gif
Normal file
After Width: | Height: | Size: 127 B |
BIN
themes/pesterchum/distraught.gif
Normal file
After Width: | Height: | Size: 93 B |
BIN
themes/pesterchum/estatic.gif
Normal file
After Width: | Height: | Size: 137 B |
BIN
themes/pesterchum/h.gif
Normal file
After Width: | Height: | Size: 101 B |
BIN
themes/pesterchum/m.gif
Normal file
After Width: | Height: | Size: 79 B |
BIN
themes/pesterchum/offline.gif
Normal file
After Width: | Height: | Size: 71 B |
BIN
themes/pesterchum/pcbg.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
themes/pesterchum/pleasant.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
themes/pesterchum/rancorous.gif
Normal file
After Width: | Height: | Size: 138 B |
BIN
themes/pesterchum/relaxed.gif
Normal file
After Width: | Height: | Size: 125 B |
BIN
themes/pesterchum/sleek.gif
Normal file
After Width: | Height: | Size: 134 B |
BIN
themes/pesterchum/smooth.gif
Normal file
After Width: | Height: | Size: 86 B |
162
themes/pesterchum/style.pcs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
#PESTERCHUM STYLE
|
||||||
|
|
||||||
|
// The title will appear at the top of the window.
|
||||||
|
// Instead of a space in the name or title, use an '_'. It will come out as a space.
|
||||||
|
// The name and author will only be in the 'about' section.
|
||||||
|
// The name you will enter in the 'options' is the name of the folder this is in.
|
||||||
|
// The alarm only plays when a message is recieved.
|
||||||
|
// The mode can be set to 'regular' or 'trollian'.
|
||||||
|
|
||||||
|
name Pesterchum
|
||||||
|
title Pesterchum
|
||||||
|
author Grimlive95
|
||||||
|
alarm alarm.wav
|
||||||
|
mode regular
|
||||||
|
|
||||||
|
// Colors are in the format of 'red/green/blue/alpha', alpha being transparency.
|
||||||
|
// 255 is solid and 000 is invisible.
|
||||||
|
|
||||||
|
// MAIN WINDOW
|
||||||
|
|
||||||
|
// If you have a background image, set 'c 3' alpa to '000'.
|
||||||
|
// If your background image has a header on it, do the same with the title text color.
|
||||||
|
|
||||||
|
c 1 000 000 000 000 // title text
|
||||||
|
c 2 000 000 000 255 // chumhandle/ mood label text
|
||||||
|
c 3 000 000 000 255 // outside menu text
|
||||||
|
c 4 000 000 000 255 // inside menu text
|
||||||
|
c 5 255 181 000 000 // main background
|
||||||
|
c 6 255 181 000 255 // menu background
|
||||||
|
|
||||||
|
// BUTTONS
|
||||||
|
|
||||||
|
// Main buttons are the moods that aren't 'rancorous', the 'pester' and 'add chum' buttons.
|
||||||
|
// They are also the buttons on the Options, Quirks Manager, and Trollslum.
|
||||||
|
// Block buttons are the 'rancorous' and 'block' buttons.
|
||||||
|
|
||||||
|
c 7 196 135 000 255 // main button borders
|
||||||
|
c 8 196 135 000 255 // block button borders
|
||||||
|
c 9 196 135 000 255 // offline button border
|
||||||
|
|
||||||
|
c 10 255 255 000 255 // main buttons
|
||||||
|
c 11 240 000 000 255 // block buttons
|
||||||
|
c 12 000 000 000 255 // offline button
|
||||||
|
|
||||||
|
c 13 000 000 000 255 // main button text
|
||||||
|
c 14 000 000 000 255 // block button text
|
||||||
|
c 15 255 255 255 255 // offline button text
|
||||||
|
|
||||||
|
// CHUMROLL & CHUMHANDLE
|
||||||
|
|
||||||
|
c 16 255 255 000 255 // chumroll border
|
||||||
|
c 17 000 000 000 255 // chumroll background
|
||||||
|
c 18 150 150 150 255 // chumroll highlight
|
||||||
|
|
||||||
|
c 19 100 100 100 255 // chumroll usertext
|
||||||
|
c 20 255 255 255 255 // chumroll highlighted usertext
|
||||||
|
|
||||||
|
c 21 255 255 000 255 // my chumhandle border
|
||||||
|
c 22 000 000 000 255 // my chumhandle background
|
||||||
|
c 23 255 255 255 255 // my chumhandle usertext
|
||||||
|
|
||||||
|
// PESTER WINDOW
|
||||||
|
|
||||||
|
c 24 255 181 000 255 // pester window background
|
||||||
|
c 25 255 255 255 255 // pesterlog background
|
||||||
|
c 26 255 255 255 255 // text field background
|
||||||
|
c 27 000 000 000 255 // text field text color
|
||||||
|
|
||||||
|
c 28 196 135 000 255 // pesterwindow topbar
|
||||||
|
c 29 255 255 255 255 // pesterwindow top bar text
|
||||||
|
|
||||||
|
c 30 196 135 000 255 // pesterlog border
|
||||||
|
c 31 196 135 000 255 // text field border
|
||||||
|
|
||||||
|
// PESTER WINDOW BUTTONS
|
||||||
|
|
||||||
|
c 32 196 135 000 255 // main button borders
|
||||||
|
c 33 196 135 000 255 // block button border
|
||||||
|
|
||||||
|
c 34 255 255 000 255 // main buttons
|
||||||
|
c 35 255 000 000 255 // block button
|
||||||
|
|
||||||
|
c 36 000 000 000 255 // pesterwindow button text
|
||||||
|
c 37 000 000 000 255 // pesterwindow block button text
|
||||||
|
|
||||||
|
// OPTIONS WINDOW
|
||||||
|
|
||||||
|
c 38 255 181 000 255 // background
|
||||||
|
c 39 000 000 000 255 // text color
|
||||||
|
c 40 196 135 000 255 // button borders
|
||||||
|
c 41 255 255 000 255 // buttons
|
||||||
|
c 42 000 000 000 255 // button text
|
||||||
|
|
||||||
|
// QUIRKS MANAGER WINDOW
|
||||||
|
|
||||||
|
c 43 255 181 000 255 // background
|
||||||
|
c 44 000 000 000 255 // text color
|
||||||
|
c 45 196 135 000 255 // button borders
|
||||||
|
c 46 255 255 000 255 // buttons
|
||||||
|
c 47 000 000 000 255 // button text
|
||||||
|
|
||||||
|
// TROLLSLUM WINDOW
|
||||||
|
|
||||||
|
c 48 255 181 000 255 // background
|
||||||
|
c 49 000 000 000 255 // text color
|
||||||
|
c 50 196 135 000 255 // button borders
|
||||||
|
c 51 255 255 000 255 // buttons
|
||||||
|
c 52 000 000 000 255 // button text
|
||||||
|
|
||||||
|
// FONTS (In the format: Font_name MODE size)
|
||||||
|
|
||||||
|
// menu is for the top menu.
|
||||||
|
// tagandmood is for the CHUMHANDLE and MOOD labels.
|
||||||
|
// message is for the text field in the pester window.
|
||||||
|
// topbar is for the ::nameName:: text on the top of the pester window.
|
||||||
|
// tray is for your tray popups.
|
||||||
|
// debug is for the debug console.
|
||||||
|
|
||||||
|
f title Courier BOLD 14
|
||||||
|
f menu Courier BOLD 12
|
||||||
|
f buttons Courier BOLD 14
|
||||||
|
|
||||||
|
f tagandmood Courier BOLD 14
|
||||||
|
f chumroll Courier BOLD 14
|
||||||
|
f chumhandle Courier BOLD 14
|
||||||
|
|
||||||
|
f message Courier BOLD 13
|
||||||
|
f topbar Courier BOLD 13
|
||||||
|
|
||||||
|
f tray Courier BOLD 12
|
||||||
|
|
||||||
|
f debug Courier BOLD 13
|
||||||
|
|
||||||
|
// PESTERCHUM MOOD ICONS
|
||||||
|
|
||||||
|
i ichummy chummy.gif
|
||||||
|
i ipleasant pleasant.gif
|
||||||
|
i idistraught distraught.gif
|
||||||
|
i iunruly unruly.gif
|
||||||
|
i ismooth smooth.gif
|
||||||
|
i irancorous rancorous.gif
|
||||||
|
i ioffline offline.gif
|
||||||
|
|
||||||
|
// TROLLIAN MOOD ICONS
|
||||||
|
|
||||||
|
i iestatic estatic.gif
|
||||||
|
i irelaxed relaxed.gif
|
||||||
|
i idiscontent discontent.gif
|
||||||
|
i idevious devious.gif
|
||||||
|
i isleek sleek.gif
|
||||||
|
i idetestful detestful.gif
|
||||||
|
|
||||||
|
// ETC.
|
||||||
|
|
||||||
|
// iicon only appears in the about section.
|
||||||
|
// ticon will appear in both your tray and your pesterwindow.
|
||||||
|
|
||||||
|
i ticon trayicon2.png
|
||||||
|
i iicon abouticon.png
|
||||||
|
i iclose x.gif
|
||||||
|
i ihide m.gif
|
||||||
|
i ibg pcbg.png
|
BIN
themes/pesterchum/trayicon.gif
Normal file
After Width: | Height: | Size: 116 B |
BIN
themes/pesterchum/trayicon2.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
themes/pesterchum/unruly.gif
Normal file
After Width: | Height: | Size: 99 B |
BIN
themes/pesterchum/x.gif
Normal file
After Width: | Height: | Size: 105 B |