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 |