1
0
mirror of http://aero2k.de/t/repos/urlbot-native.git synced 2017-09-06 15:25:38 +02:00

refactored pluginsystem

This commit is contained in:
braph
2016-04-05 14:18:22 +02:00
committed by Thorsten S
parent d59645bacf
commit 5c1aa9b516
18 changed files with 1710 additions and 1712 deletions

142
common.py
View File

@@ -3,104 +3,13 @@
import html.parser import html.parser
import logging import logging
import re import re
import time
import requests import requests
from collections import namedtuple
from urllib.error import URLError from urllib.error import URLError
import threading
RATE_NO_LIMIT = 0x00
RATE_GLOBAL = 0x01
RATE_NO_SILENCE = 0x02
RATE_INTERACTIVE = 0x04
RATE_CHAT = 0x08
RATE_URL = 0x10
RATE_EVENT = 0x20
RATE_FUN = 0x40
BUFSIZ = 8192 BUFSIZ = 8192
EVENTLOOP_DELAY = 0.100 # seconds
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:31.0) ' \ USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:31.0) ' \
'Gecko/20100101 Firefox/31.0 Iceweasel/31.0' 'Gecko/20100101 Firefox/31.0 Iceweasel/31.0'
Bucket = namedtuple("BucketConfig", ["history", "period", "max_hist_len"])
buckets = {
# everything else
RATE_GLOBAL: Bucket(history=[], period=60, max_hist_len=10),
# bot writes with no visible stimuli
RATE_NO_SILENCE: Bucket(history=[], period=10, max_hist_len=5),
# interactive stuff like ping
RATE_INTERACTIVE: Bucket(history=[], period=30, max_hist_len=5),
# chitty-chat, master volume control
RATE_CHAT: Bucket(history=[], period=10, max_hist_len=5),
# reacting on URLs
RATE_URL: Bucket(history=[], period=10, max_hist_len=5),
# triggering events
RATE_EVENT: Bucket(history=[], period=60, max_hist_len=10),
# bot blames people, produces cake and entertains
RATE_FUN: Bucket(history=[], period=180, max_hist_len=5),
}
rate_limit_classes = buckets.keys()
plugin_lock = threading.Lock()
def rate_limit(rate_class=RATE_GLOBAL):
"""
Remember N timestamps,
if N[0] newer than now()-T then do not output, do not append.
else pop(0); append()
:param rate_class: the type of message to verify
:return: False if blocked, True if allowed
"""
if rate_class not in rate_limit_classes:
return all(rate_limit(c) for c in rate_limit_classes if c & rate_class)
now = time.time()
bucket = buckets[rate_class]
logging.getLogger(__name__).debug(
"[ratelimit][bucket=%x][time=%s]%s",
rate_class, now, bucket.history
)
if len(bucket.history) >= bucket.max_hist_len and bucket.history[0] > (now - bucket.period):
# print("blocked")
return False
else:
if bucket.history and len(bucket.history) > bucket.max_hist_len:
bucket.history.pop(0)
bucket.history.append(now)
return True
def rate_limited(max_per_second):
"""
very simple flow control context manager
:param max_per_second: how many events per second may be executed - more are delayed
:return:
"""
min_interval = 1.0 / float(max_per_second)
def decorate(func):
lasttimecalled = [0.0]
def ratelimitedfunction(*args, **kargs):
elapsed = time.clock() - lasttimecalled[0]
lefttowait = min_interval - elapsed
if lefttowait > 0:
time.sleep(lefttowait)
ret = func(*args, **kargs)
lasttimecalled[0] = time.clock()
return ret
return ratelimitedfunction
return decorate
def get_version_git(): def get_version_git():
import subprocess import subprocess
@@ -185,46 +94,6 @@ def giphy(subject, api_key):
return giphy_url return giphy_url
def config_locked(f):
"""A decorator that makes access to the config thread-safe"""
def decorate(*args, **kwargs):
plugin_lock.acquire()
try:
return f(*args, **kwargs)
except:
raise
finally:
plugin_lock.release()
return decorate
def pluginfunction(name, desc, plugin_type, ratelimit_class=RATE_GLOBAL, enabled=True):
"""A decorator to make a plugin out of a function
:param enabled:
:param ratelimit_class:
:param plugin_type:
:param desc:
:param name:
"""
if plugin_type not in ptypes:
raise TypeError('Illegal plugin_type: %s' % plugin_type)
def decorate(f):
f.is_plugin = True
f.is_enabled = enabled
f.plugin_name = name
f.plugin_desc = desc
f.plugin_type = plugin_type
f.ratelimit_class = ratelimit_class
return f
return decorate
def get_nick_from_object(message_obj): def get_nick_from_object(message_obj):
""" """
not quite correct yet, also the private property access isn't nice. not quite correct yet, also the private property access isn't nice.
@@ -233,7 +102,10 @@ def get_nick_from_object(message_obj):
return nick return nick
ptypes_PARSE = 'parser' def else_command(args):
ptypes_COMMAND = 'command' log = logging.getLogger(__name__)
ptypes_MUC_ONLINE = 'muc_online' log.info('sent short info')
ptypes = [ptypes_PARSE, ptypes_COMMAND, ptypes_MUC_ONLINE] return {
'msg': args['reply_user'] + ''': I'm a bot (highlight me with 'info' for more information).'''
}

View File

@@ -14,6 +14,7 @@ import logging
import os import os
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
import threading
from fasteners import interprocess_locked from fasteners import interprocess_locked
from configobj import ConfigObj from configobj import ConfigObj
@@ -33,6 +34,8 @@ runtime_config_store = ConfigObj(
encoding='utf-8' encoding='utf-8'
) )
config_lock = threading.Lock()
result = __config_store.validate(Validator()) result = __config_store.validate(Validator())
# copy is essential to store values with a default.. see configobj.py:2053 # copy is essential to store values with a default.. see configobj.py:2053
assert runtime_config_store.validate(Validator(), copy=True) assert runtime_config_store.validate(Validator(), copy=True)
@@ -84,6 +87,23 @@ def runtimeconf_persist():
runtime_config_store.write() runtime_config_store.write()
def config_locked(f):
"""A decorator that makes access to the config thread-safe"""
def decorate(*args, **kwargs):
config_lock.acquire()
try:
return f(*args, **kwargs)
except:
raise
finally:
config_lock.release()
return decorate
def runtimeconf_deepget(key, default=None): def runtimeconf_deepget(key, default=None):
""" """
access a nested key with get("plugins.moin.enabled") access a nested key with get("plugins.moin.enabled")

42
events.py Normal file
View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import time
import sched
import threading
EVENTLOOP_DELAY = 0.100 # seconds
event_list = sched.scheduler(time.time, time.sleep)
def register_active_event(t, callback, args, action_runner, plugin, msg_obj):
"""
Execute a callback at a given time and react on the output
:param t: when to execute the job
:param callback: the function to execute
:param args: parameters for said function
:param action_runner: bots action dict parser
:param plugin: pass-through object for action parser
:param msg_obj: pass-through object for action parser
:return:
"""
def func(func_args):
action = callback(*func_args)
if action:
action_runner(action=action, plugin=plugin, msg_obj=msg_obj)
event_list.enterabs(t, 0, func, args)
def register_event(t, callback, args):
event_list.enterabs(t, 0, callback, args)
class EventLoop(threading.Thread):
#def __init__(self):
# threading.Thread.__init__(self)
def run(self):
while 1:
event_list.run(False)
time.sleep(EVENTLOOP_DELAY)
event_loop = EventLoop()

View File

@@ -3,8 +3,9 @@
import logging import logging
import time import time
import sys import sys
from common import VERSION, EVENTLOOP_DELAY
import config import config
import events
from common import VERSION
from sleekxmpp import ClientXMPP from sleekxmpp import ClientXMPP
@@ -101,13 +102,6 @@ def start(botclass, active=False):
rooms=config.conf_get('rooms'), rooms=config.conf_get('rooms'),
nick=config.conf_get('bot_nickname') nick=config.conf_get('bot_nickname')
) )
import plugins
if active:
plugins.register_all()
# if plugins.plugin_enabled_get(plugins.command_dsa_watcher):
# first result is lost.
# plugins.command_dsa_watcher(['dsa-watcher', 'crawl'])
bot.connect() bot.connect()
bot.register_plugin('xep_0045') bot.register_plugin('xep_0045')
@@ -115,13 +109,10 @@ def start(botclass, active=False):
config.runtimeconf_set('start_time', -time.time()) config.runtimeconf_set('start_time', -time.time())
while 1: if active:
try: import plugins
plugins.joblist.run(False)
time.sleep(EVENTLOOP_DELAY) events.event_loop.start()
except KeyboardInterrupt:
print('')
exit(130)
if '__main__' == __name__: if '__main__' == __name__:

65
plugin_system.py Normal file
View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
from enum import Enum
import config
from rate_limit import RATE_GLOBAL
class ptypes(Enum):
PARSE = 1
COMMAND = 2
MUC_ONLINE = 3
plugin_storage = { ptype: [] for ptype in ptypes }
def pluginfunction(name, desc, plugin_type, ratelimit_class=RATE_GLOBAL, enabled=True):
"""A decorator to make a plugin out of a function
:param enabled:
:param ratelimit_class:
:param plugin_type:
:param desc:
:param name:
"""
def decorate(f):
f.is_plugin = True
f.is_enabled = enabled
f.plugin_name = name
f.plugin_desc = desc
f.plugin_type = plugin_type
f.ratelimit_class = ratelimit_class
plugin_storage[plugin_type].append(f)
return f
return decorate
#def plugin_alias(name):
# """A decorator to add an alias to a plugin function"""
#
# def decorate(f):
# plugin_storage[f.plugin_type].append(f)
# return f
#
# return decorate
def plugin_enabled_get(urlbot_plugin):
plugin_section = config.runtimeconf_deepget('plugins.{}'.format(urlbot_plugin.plugin_name))
if plugin_section and "enabled" in plugin_section:
return plugin_section.as_bool("enabled")
else:
return urlbot_plugin.is_enabled
@config.config_locked
def plugin_enabled_set(plugin, enabled):
if plugin.plugin_name not in config.runtime_config_store['plugins']:
config.runtime_config_store['plugins'][plugin.plugin_name] = {}
config.runtime_config_store['plugins'][plugin.plugin_name]['enabled'] = enabled
config.runtimeconf_persist()

View File

@@ -1,230 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging
import time
import traceback
import types
import sched
import config from os.path import dirname, basename, isfile
from common import RATE_NO_LIMIT, pluginfunction, config_locked, ptypes_PARSE, ptypes_COMMAND, ptypes_MUC_ONLINE, ptypes from glob import glob
from plugins import commands, parsers, muc_online
joblist = sched.scheduler(time.time, time.sleep) __all__ = [ ]
plugins = {p: [] for p in ptypes}
log = logging.getLogger(__name__)
for f in glob(dirname(__file__) + "/*.py"):
if not basename(f).startswith('_') and isfile(f):
__all__.append(basename(f)[:-3])
def plugin_enabled_get(urlbot_plugin): from . import *
plugin_section = config.runtimeconf_deepget('plugins.{}'.format(urlbot_plugin.plugin_name))
if plugin_section and "enabled" in plugin_section:
return plugin_section.as_bool("enabled")
else:
return urlbot_plugin.is_enabled
@config_locked
def plugin_enabled_set(plugin, enabled):
if plugin.plugin_name not in config.runtime_config_store['plugins']:
config.runtime_config_store['plugins'][plugin.plugin_name] = {}
config.runtime_config_store['plugins'][plugin.plugin_name]['enabled'] = enabled
config.runtimeconf_persist()
def register_active_event(t, callback, args, action_runner, plugin, msg_obj):
"""
Execute a callback at a given time and react on the output
:param t: when to execute the job
:param callback: the function to execute
:param args: parameters for said function
:param action_runner: bots action dict parser
:param plugin: pass-through object for action parser
:param msg_obj: pass-through object for action parser
:return:
"""
def func(func_args):
action = callback(*func_args)
if action:
action_runner(action=action, plugin=plugin, msg_obj=msg_obj)
joblist.enterabs(t, 0, func, args)
def register_event(t, callback, args):
joblist.enterabs(t, 0, callback, args)
def else_command(args):
log.info('sent short info')
return {
'msg': args['reply_user'] + ''': I'm a bot (highlight me with 'info' for more information).'''
}
def register(func_type):
"""
Register plugins.
:param func_type: plugin functions with this type (ptypes) will be loaded
"""
if func_type == ptypes_COMMAND:
local_commands = [command_plugin_activation, command_list, command_help, reset_jobs]
plugin_funcs = list(commands.__dict__.values()) + local_commands
elif func_type == ptypes_PARSE:
plugin_funcs = parsers.__dict__.values()
elif func_type == ptypes_MUC_ONLINE:
plugin_funcs = muc_online.__dict__.values()
else:
raise RuntimeError("invalid func type: {}".format(func_type))
functions = [
f for f in plugin_funcs if
isinstance(f, types.FunctionType) and
all([
f.__dict__.get('is_plugin', False),
getattr(f, 'plugin_type', None) == func_type
])
]
log.info('auto-reg %s: %s' % (func_type, ', '.join(
f.plugin_name for f in functions
)))
for f in functions:
register_plugin(f, func_type)
def register_plugin(function, func_type):
try:
plugins[func_type].append(function)
except Exception as e:
log.warn('registering %s failed: %s, %s' % (function, e, traceback.format_exc()))
def register_all():
register(ptypes_PARSE)
register(ptypes_COMMAND)
register(ptypes_MUC_ONLINE)
@pluginfunction('help', 'print help for a command or all known commands', ptypes_COMMAND)
def command_help(argv, **args):
what = argv[0] if argv else None
logger = logging.getLogger(__name__)
if not what:
logger.info('empty help request, sent all commands')
commands = args['cmd_list']
commands.sort()
parsers = args['parser_list']
parsers.sort()
return {
'msg': [
'%s: known commands: %s' % (
args['reply_user'], ', '.join(commands)
),
'known parsers: %s' % ', '.join(parsers)
]
}
for p in plugins[ptypes_COMMAND] + plugins[ptypes_PARSE]:
if what == p.plugin_name:
logger.info('sent help for %s' % what)
return {
'msg': args['reply_user'] + ': help for %s %s %s: %s' % (
'enabled' if plugin_enabled_get(p) else 'disabled',
'parser' if p.plugin_type == ptypes_PARSE else 'command',
what, p.plugin_desc
)
}
logger.info('no help found for %s' % what)
return {
'msg': args['reply_user'] + ': no such command: %s' % what
}
@pluginfunction('plugin', "'disable' or 'enable' plugins", ptypes_COMMAND)
def command_plugin_activation(argv, **args):
if not argv:
return
command = argv[0]
plugin = argv[1] if len(argv) > 1 else None
if command not in ('enable', 'disable'):
return
log.info('plugin activation plugin called')
if not plugin:
return {
'msg': args['reply_user'] + ': no plugin given'
}
elif command_plugin_activation.plugin_name == plugin:
return {
'msg': args['reply_user'] + ': not allowed'
}
for p in plugins[ptypes_COMMAND] + plugins[ptypes_PARSE]:
if p.plugin_name == plugin:
plugin_enabled_set(p, 'enable' == command)
return {
'msg': args['reply_user'] + ': %sd %s' % (
command, plugin
)
}
return {
'msg': args['reply_user'] + ': unknown plugin %s' % plugin
}
@pluginfunction('list', 'list plugin and parser status', ptypes_COMMAND)
def command_list(argv, **args):
log.info('list plugin called')
if 'enabled' in argv and 'disabled' in argv:
return {
'msg': args['reply_user'] + ": both 'enabled' and 'disabled' makes no sense"
}
# if not given, assume both
if 'command' not in argv and 'parser' not in argv:
argv.append('command')
argv.append('parser')
out_command = []
out_parser = []
if 'command' in argv:
out_command = plugins[ptypes_COMMAND]
if 'parser' in argv:
out_parser = plugins[ptypes_PARSE]
if 'enabled' in argv:
out_command = [p for p in out_command if plugin_enabled_get(p)]
out_parser = [p for p in out_parser if plugin_enabled_get(p)]
if 'disabled' in argv:
out_command = [p for p in out_command if not plugin_enabled_get(p)]
out_parser = [p for p in out_parser if not plugin_enabled_get(p)]
msg = [args['reply_user'] + ': list of plugins:']
if out_command:
msg.append('commands: %s' % ', '.join([p.plugin_name for p in out_command]))
if out_parser:
msg.append('parsers: %s' % ', '.join([p.plugin_name for p in out_parser]))
return {'msg': msg}
@pluginfunction('reset-jobs', "reset joblist", ptypes_COMMAND, ratelimit_class=RATE_NO_LIMIT)
def reset_jobs(argv, **args):
if args['reply_user'] != config.conf_get('bot_owner'):
return
else:
for event in joblist.queue:
joblist.cancel(event)
return {'msg': 'done.'}

47
plugins/cake.py Normal file
View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
import random
from plugin_system import pluginfunction, ptypes
from rate_limit import RATE_FUN, RATE_GLOBAL
@pluginfunction('cake', 'displays a cake ASCII art', ptypes.COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_cake(argv, **args):
if {'please', 'bitte'}.intersection(set(argv)):
return {
'msg': 'cake for {}: {}'.format(args['reply_user'], giphy('cake', 'dc6zaTOxFJmzC'))
}
return {
'msg': args['reply_user'] + ': %s' % random.choice(cakes)
}
@pluginfunction('keks', 'keks!', ptypes.COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_cookie(argv, **args):
if {'please', 'bitte'}.intersection(set(argv)):
return {
'msg': 'keks für {}: {}'.format(args['reply_user'], giphy('cookie', 'dc6zaTOxFJmzC'))
}
return {
'msg': args['reply_user'] + ': %s' % random.choice(cakes)
}
cakes = [
"No cake for you!",
("The Enrichment Center is required to remind you "
"that you will be baked, and then there will be cake."),
"The cake is a lie!",
("This is your fault. I'm going to kill you. "
"And all the cake is gone. You don't even care, do you?"),
"Quit now and cake will be served immediately.",
("Enrichment Center regulations require both hands to be "
"empty before any cake..."),
("Uh oh. Somebody cut the cake. I told them to wait for "
"you, but they did it anyway. There is still some left, "
"though, if you hurry back."),
"I'm going to kill you, and all the cake is gone.",
"Who's gonna make the cake when I'm gone? You?"
]

View File

@@ -1,8 +1,10 @@
import json # -*- coding: utf-8 -*-
import logging import logging
log = logging.getLogger(__name__)
import events
import json
import random import random
import re
import shlex
import time import time
import traceback import traceback
import unicodedata import unicodedata
@@ -11,20 +13,131 @@ import requests
from lxml import etree from lxml import etree
import config import config
from common import ( from common import VERSION, giphy, get_nick_from_object
VERSION, RATE_FUN, RATE_GLOBAL, RATE_INTERACTIVE, RATE_NO_LIMIT, from rate_limit import RATE_FUN, RATE_GLOBAL, RATE_INTERACTIVE, RATE_NO_SILENCE, RATE_NO_LIMIT
giphy, pluginfunction, config_locked, from plugin_system import pluginfunction, ptypes, plugin_storage
ptypes_COMMAND,
RATE_NO_SILENCE, @pluginfunction('help', 'print help for a command or all known commands', ptypes.COMMAND)
get_nick_from_object def command_help(argv, **args):
what = argv[0] if argv else None
if not what:
log.info('empty help request, sent all commands')
commands = args['cmd_list']
commands.sort()
parsers = args['parser_list']
parsers.sort()
return {
'msg': [
'%s: known commands: %s' % (
args['reply_user'], ', '.join(commands)
),
'known parsers: %s' % ', '.join(parsers)
]
}
for p in plugin_storage[ptypes.COMMAND] + plugin_storage[ptypes.PARSE]:
if what == p.plugin_name:
log.info('sent help for %s' % what)
return {
'msg': args['reply_user'] + ': help for %s %s %s: %s' % (
'enabled' if plugin_enabled_get(p) else 'disabled',
'parser' if p.plugin_type == ptypes.PARSE else 'command',
what, p.plugin_desc
) )
from plugins import quiz }
from string_constants import cakes, excuses, moin_strings_hi, moin_strings_bye, languages log.info('no help found for %s' % what)
return {
log = logging.getLogger(__name__) 'msg': args['reply_user'] + ': no such command: %s' % what
}
@pluginfunction('version', 'prints version', ptypes_COMMAND) @pluginfunction('plugin', "'disable' or 'enable' plugins", ptypes.COMMAND)
def command_plugin_activation(argv, **args):
if not argv:
return
command = argv[0]
plugin = argv[1] if len(argv) > 1 else None
if command not in ('enable', 'disable'):
return
log.info('plugin activation plugin called')
if not plugin:
return {
'msg': args['reply_user'] + ': no plugin given'
}
elif command_plugin_activation.plugin_name == plugin:
return {
'msg': args['reply_user'] + ': not allowed'
}
for p in plugin_storage[ptypes.COMMAND] + plugin_storage[ptypes.PARSE]:
if p.plugin_name == plugin:
plugin_enabled_set(p, 'enable' == command)
return {
'msg': args['reply_user'] + ': %sd %s' % (
command, plugin
)
}
return {
'msg': args['reply_user'] + ': unknown plugin %s' % plugin
}
@pluginfunction('list', 'list plugin and parser status', ptypes.COMMAND)
def command_list(argv, **args):
log.info('list plugin called')
if 'enabled' in argv and 'disabled' in argv:
return {
'msg': args['reply_user'] + ": both 'enabled' and 'disabled' makes no sense"
}
# if not given, assume both
if 'command' not in argv and 'parser' not in argv:
argv.append('command')
argv.append('parser')
out_command = []
out_parser = []
if 'command' in argv:
out_command = plugin_storage[ptypes.COMMAND]
if 'parser' in argv:
out_parser = plugin_storage[ptypes.PARSE]
if 'enabled' in argv:
out_command = [p for p in out_command if plugin_enabled_get(p)]
out_parser = [p for p in out_parser if plugin_enabled_get(p)]
if 'disabled' in argv:
out_command = [p for p in out_command if not plugin_enabled_get(p)]
out_parser = [p for p in out_parser if not plugin_enabled_get(p)]
msg = [args['reply_user'] + ': list of plugins:']
if out_command:
msg.append('commands: %s' % ', '.join([p.plugin_name for p in out_command]))
if out_parser:
msg.append('parsers: %s' % ', '.join([p.plugin_name for p in out_parser]))
return {'msg': msg}
@pluginfunction('reset-jobs', "reset joblist", ptypes.COMMAND, ratelimit_class=RATE_NO_LIMIT)
def reset_jobs(argv, **args):
if args['reply_user'] != config.conf_get('bot_owner'):
return
else:
for event in events.event_list.queue:
events.event_list.cancel(event)
return {'msg': 'done.'}
@pluginfunction('version', 'prints version', ptypes.COMMAND)
def command_version(argv, **args): def command_version(argv, **args):
log.info('sent version string') log.info('sent version string')
return { return {
@@ -32,7 +145,7 @@ def command_version(argv, **args):
} }
@pluginfunction('uptime', 'prints uptime', ptypes_COMMAND) @pluginfunction('uptime', 'prints uptime', ptypes.COMMAND)
def command_uptime(argv, **args): def command_uptime(argv, **args):
u = int(config.runtimeconf_get('start_time') + time.time()) u = int(config.runtimeconf_get('start_time') + time.time())
plural_uptime = 's' plural_uptime = 's'
@@ -50,7 +163,7 @@ def command_uptime(argv, **args):
} }
@pluginfunction('info', 'prints info message', ptypes_COMMAND) @pluginfunction('info', 'prints info message', ptypes.COMMAND)
def command_info(argv, **args): def command_info(argv, **args):
log.info('sent long info') log.info('sent long info')
return { return {
@@ -63,7 +176,7 @@ def command_info(argv, **args):
} }
@pluginfunction('ping', 'sends pong', ptypes_COMMAND, ratelimit_class=RATE_INTERACTIVE) @pluginfunction('ping', 'sends pong', ptypes.COMMAND, ratelimit_class=RATE_INTERACTIVE)
def command_ping(argv, **args): def command_ping(argv, **args):
rnd = random.randint(0, 3) # 1:4 rnd = random.randint(0, 3) # 1:4
if 0 == rnd: if 0 == rnd:
@@ -81,7 +194,7 @@ def command_ping(argv, **args):
} }
@pluginfunction('klammer', 'prints an anoying paper clip aka. Karl Klammer', ptypes_COMMAND, @pluginfunction('klammer', 'prints an anoying paper clip aka. Karl Klammer', ptypes.COMMAND,
ratelimit_class=RATE_FUN | RATE_GLOBAL) ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_klammer(argv, **args): def command_klammer(argv, **args):
log.info('sent karl klammer') log.info('sent karl klammer')
@@ -98,25 +211,14 @@ def command_klammer(argv, **args):
} }
@pluginfunction('excuse', 'prints BOFH style excuses', ptypes_COMMAND) @pluginfunction('terminate', 'hidden prototype', ptypes.COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_excuse(argv, **args):
log.info('BOFH plugin called')
excuse = random.sample(excuses, 1)[0]
return {
'msg': args['reply_user'] + ': ' + excuse
}
@pluginfunction('terminate', 'hidden prototype', ptypes_COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_terminate(argv, **args): def command_terminate(argv, **args):
return { return {
'msg': 'insufficient power supply, please connect fission module' 'msg': 'insufficient power supply, please connect fission module'
} }
@pluginfunction('source', 'prints git URL', ptypes_COMMAND) @pluginfunction('source', 'prints git URL', ptypes.COMMAND)
def command_source(argv, **_): def command_source(argv, **_):
log.info('sent source URL') log.info('sent source URL')
return { return {
@@ -124,7 +226,7 @@ def command_source(argv, **_):
} }
@pluginfunction('unikot', 'prints an unicode string', ptypes_COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL) @pluginfunction('unikot', 'prints an unicode string', ptypes.COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_unicode(argv, **args): def command_unicode(argv, **args):
log.info('sent some unicode') log.info('sent some unicode')
return { return {
@@ -137,7 +239,7 @@ def command_unicode(argv, **args):
} }
@pluginfunction('dice', 'rolls a dice, optional N times', ptypes_COMMAND, ratelimit_class=RATE_INTERACTIVE) @pluginfunction('dice', 'rolls a dice, optional N times', ptypes.COMMAND, ratelimit_class=RATE_INTERACTIVE)
def command_dice(argv, **args): def command_dice(argv, **args):
try: try:
count = 1 if not argv else int(argv[0]) count = 1 if not argv else int(argv[0])
@@ -176,7 +278,7 @@ def command_dice(argv, **args):
} }
@pluginfunction('choose', 'chooses randomly between arguments', ptypes_COMMAND, ratelimit_class=RATE_INTERACTIVE) @pluginfunction('choose', 'chooses randomly between arguments', ptypes.COMMAND, ratelimit_class=RATE_INTERACTIVE)
def command_choose(argv, **args): def command_choose(argv, **args):
alternatives = argv alternatives = argv
if len(alternatives) < 2: if len(alternatives) < 2:
@@ -192,7 +294,7 @@ def command_choose(argv, **args):
@pluginfunction('teatimer', 'sets a tea timer to $1 or currently %d seconds' % config.conf_get('tea_steep_time'), @pluginfunction('teatimer', 'sets a tea timer to $1 or currently %d seconds' % config.conf_get('tea_steep_time'),
ptypes_COMMAND) ptypes.COMMAND)
def command_teatimer(argv, **args): def command_teatimer(argv, **args):
steep = config.conf_get('tea_steep_time') steep = config.conf_get('tea_steep_time')
@@ -226,7 +328,7 @@ def command_teatimer(argv, **args):
} }
@pluginfunction('unicode-lookup', 'search unicode characters', ptypes_COMMAND, @pluginfunction('unicode-lookup', 'search unicode characters', ptypes.COMMAND,
ratelimit_class=RATE_INTERACTIVE) ratelimit_class=RATE_INTERACTIVE)
def command_unicode_lookup(argv, **args): def command_unicode_lookup(argv, **args):
if not argv: if not argv:
@@ -263,7 +365,7 @@ def command_unicode_lookup(argv, **args):
} }
@pluginfunction('decode', 'prints the long description of an unicode character', ptypes_COMMAND, @pluginfunction('decode', 'prints the long description of an unicode character', ptypes.COMMAND,
ratelimit_class=RATE_INTERACTIVE) ratelimit_class=RATE_INTERACTIVE)
def command_decode(argv, **args): def command_decode(argv, **args):
if not argv: if not argv:
@@ -303,7 +405,7 @@ def command_decode(argv, **args):
} }
@pluginfunction('show-blacklist', 'show the current URL blacklist, optionally filtered', ptypes_COMMAND) @pluginfunction('show-blacklist', 'show the current URL blacklist, optionally filtered', ptypes.COMMAND)
def command_show_blacklist(argv, **args): def command_show_blacklist(argv, **args):
log.info('sent URL blacklist') log.info('sent URL blacklist')
if argv: if argv:
@@ -339,8 +441,8 @@ def usersetting_get(argv, args):
} }
@pluginfunction('set', 'modify a user setting', ptypes_COMMAND, ratelimit_class=RATE_NO_LIMIT) @pluginfunction('set', 'modify a user setting', ptypes.COMMAND, ratelimit_class=RATE_NO_LIMIT)
@config_locked @config.config_locked
def command_usersetting(argv, **args): def command_usersetting(argv, **args):
settings = ['spoiler'] settings = ['spoiler']
arg_user = args['reply_user'] arg_user = args['reply_user']
@@ -372,36 +474,12 @@ def command_usersetting(argv, **args):
return usersetting_get(argv, args) return usersetting_get(argv, args)
@pluginfunction('cake', 'displays a cake ASCII art', ptypes_COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL) @pluginfunction('wp-en', 'crawl the english Wikipedia', ptypes.COMMAND)
def command_cake(argv, **args):
if {'please', 'bitte'}.intersection(set(argv)):
return {
'msg': 'cake for {}: {}'.format(args['reply_user'], giphy('cake', 'dc6zaTOxFJmzC'))
}
return {
'msg': args['reply_user'] + ': %s' % (random.sample(cakes, 1)[0])
}
@pluginfunction('keks', 'keks!', ptypes_COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def command_cookie(argv, **args):
if {'please', 'bitte'}.intersection(set(argv)):
return {
'msg': 'keks für {}: {}'.format(args['reply_user'], giphy('cookie', 'dc6zaTOxFJmzC'))
}
return {
'msg': args['reply_user'] + ': %s' % (random.sample(cakes, 1)[0])
}
@pluginfunction('wp-en', 'crawl the english Wikipedia', ptypes_COMMAND)
def command_wp_en(argv, **args): def command_wp_en(argv, **args):
return command_wp(argv, lang='en', **args) return command_wp(argv, lang='en', **args)
@pluginfunction('wp', 'crawl the german Wikipedia', ptypes_COMMAND) @pluginfunction('wp', 'crawl the german Wikipedia', ptypes.COMMAND)
def command_wp(argv, lang='de', **args): def command_wp(argv, lang='de', **args):
query = ' '.join(argv) query = ' '.join(argv)
@@ -453,80 +531,9 @@ def command_wp(argv, lang='de', **args):
} }
@pluginfunction('show-moinlist', 'show the current moin reply list, optionally filtered', ptypes_COMMAND)
def command_show_moinlist(argv, **args):
log.info('sent moin reply list')
user = None if not argv else argv[0]
return {
'msg':
'%s: moin reply list%s: %s' % (
args['reply_user'],
'' if not user else ' (limited to %s)' % user,
', '.join([
b for b in moin_strings_hi + moin_strings_bye
if not user or user.lower() in b.lower()
])
)
}
@pluginfunction(
'record', 'record a message for a now offline user (usage: record {user} {some message};'
' {some message} == "previous" to use the last channel message)', ptypes_COMMAND)
@config_locked
def command_record(argv, **args):
if len(argv) < 2:
return {
'msg': '%s: usage: record {user} {some message}' % args['reply_user']
}
target_user = argv[0].lower().strip(':')
message = '{} ({}): '.format(args['reply_user'], time.strftime('%Y-%m-%d %H:%M'))
if argv[1] == "previous":
prev_message_obj = args['stack'][-1]
message += '[{}]: '.format(get_nick_from_object(prev_message_obj))
message += prev_message_obj['body']
else:
message += ' '.join(argv[1:])
if target_user not in config.runtime_config_store['user_records']:
config.runtime_config_store['user_records'][target_user] = []
config.runtime_config_store['user_records'][target_user].append(message)
config.runtimeconf_persist()
return {
'msg': '%s: message saved for %s' % (args['reply_user'], target_user)
}
@pluginfunction('show-records', 'show current offline records', ptypes_COMMAND)
def command_show_recordlist(argv, **args):
log.info('sent offline records list')
user = None if not argv else argv[0]
return {
'msg':
'%s: offline records%s: %s' % (
args['reply_user'],
'' if not user else ' (limited to %s)' % user,
', '.join(
[
'%s (%d)' % (key, len(val)) for key, val in config.runtime_config_store['user_records'].items()
if not user or user.lower() in key.lower()
]
)
)
}
@pluginfunction( @pluginfunction(
'dsa-watcher', 'dsa-watcher',
'automatically crawls for newly published Debian Security Announces', ptypes_COMMAND, 'automatically crawls for newly published Debian Security Announces', ptypes.COMMAND,
ratelimit_class=RATE_NO_SILENCE, enabled=True) ratelimit_class=RATE_NO_SILENCE, enabled=True)
def command_dsa_watcher(argv=None, **_): def command_dsa_watcher(argv=None, **_):
""" """
@@ -589,14 +596,14 @@ def command_dsa_watcher(argv=None, **_):
} }
@pluginfunction("provoke-bots", "search for other bots", ptypes_COMMAND) @pluginfunction("provoke-bots", "search for other bots", ptypes.COMMAND)
def provoke_bots(argv, **args): def provoke_bots(argv, **args):
return { return {
'msg': 'Searching for other less intelligent lifeforms... skynet? You here?' 'msg': 'Searching for other less intelligent lifeforms... skynet? You here?'
} }
@pluginfunction("remove-from-botlist", "remove a user from the botlist", ptypes_COMMAND) @pluginfunction("remove-from-botlist", "remove a user from the botlist", ptypes.COMMAND)
def remove_from_botlist(argv, **args): def remove_from_botlist(argv, **args):
if not argv: if not argv:
return {'msg': "wrong number of arguments!"} return {'msg': "wrong number of arguments!"}
@@ -613,7 +620,7 @@ def remove_from_botlist(argv, **args):
return False return False
@pluginfunction("add-to-botlist", "add a user to the botlist", ptypes_COMMAND) @pluginfunction("add-to-botlist", "add a user to the botlist", ptypes.COMMAND)
def add_to_botlist(argv, **args): def add_to_botlist(argv, **args):
if not argv: if not argv:
return {'msg': "wrong number of arguments!"} return {'msg': "wrong number of arguments!"}
@@ -630,7 +637,7 @@ def add_to_botlist(argv, **args):
return {'msg': '%s is already in the botlist.' % suspect} return {'msg': '%s is already in the botlist.' % suspect}
@pluginfunction("set-status", "set bot status", ptypes_COMMAND) @pluginfunction("set-status", "set bot status", ptypes.COMMAND)
def set_status(argv, **args): def set_status(argv, **args):
if not argv: if not argv:
return return
@@ -654,7 +661,7 @@ def set_status(argv, **args):
} }
@pluginfunction('save-config', "save config", ptypes_COMMAND, ratelimit_class=RATE_NO_LIMIT) @pluginfunction('save-config', "save config", ptypes.COMMAND, ratelimit_class=RATE_NO_LIMIT)
def save_config(argv, **args): def save_config(argv, **args):
if args['reply_user'] != config.conf_get('bot_owner'): if args['reply_user'] != config.conf_get('bot_owner'):
return return
@@ -663,7 +670,7 @@ def save_config(argv, **args):
return {'msg': 'done.'} return {'msg': 'done.'}
@pluginfunction('flausch', "make people flauschig", ptypes_COMMAND, ratelimit_class=RATE_FUN) @pluginfunction('flausch', "make people flauschig", ptypes.COMMAND, ratelimit_class=RATE_FUN)
def flausch(argv, **args): def flausch(argv, **args):
if not argv: if not argv:
return return
@@ -672,7 +679,7 @@ def flausch(argv, **args):
} }
@pluginfunction('slap', "slap people", ptypes_COMMAND, ratelimit_class=RATE_FUN) @pluginfunction('slap', "slap people", ptypes.COMMAND, ratelimit_class=RATE_FUN)
def slap(argv, **args): def slap(argv, **args):
if not argv: if not argv:
return return
@@ -681,7 +688,7 @@ def slap(argv, **args):
} }
@pluginfunction('show-runtimeconfig', "show the current runtimeconfig", ptypes_COMMAND, ratelimit_class=RATE_NO_LIMIT) @pluginfunction('show-runtimeconfig', "show the current runtimeconfig", ptypes.COMMAND, ratelimit_class=RATE_NO_LIMIT)
def show_runtimeconfig(argv, **args): def show_runtimeconfig(argv, **args):
if args['reply_user'] != config.conf_get('bot_owner'): if args['reply_user'] != config.conf_get('bot_owner'):
return return
@@ -690,7 +697,7 @@ def show_runtimeconfig(argv, **args):
return {'priv_msg': msg} return {'priv_msg': msg}
@pluginfunction('reload-runtimeconfig', "reload the runtimeconfig", ptypes_COMMAND, ratelimit_class=RATE_NO_LIMIT) @pluginfunction('reload-runtimeconfig', "reload the runtimeconfig", ptypes.COMMAND, ratelimit_class=RATE_NO_LIMIT)
def reload_runtimeconfig(argv, **args): def reload_runtimeconfig(argv, **args):
if args['reply_user'] != config.conf_get('bot_owner'): if args['reply_user'] != config.conf_get('bot_owner'):
return return
@@ -699,7 +706,7 @@ def reload_runtimeconfig(argv, **args):
return {'msg': 'done'} return {'msg': 'done'}
@pluginfunction('snitch', "tell on a spammy user", ptypes_COMMAND) @pluginfunction('snitch', "tell on a spammy user", ptypes.COMMAND)
def ignore_user(argv, **args): def ignore_user(argv, **args):
if not argv: if not argv:
return {'msg': 'syntax: "{}: snitch username"'.format(config.conf_get("bot_nickname"))} return {'msg': 'syntax: "{}: snitch username"'.format(config.conf_get("bot_nickname"))}
@@ -728,7 +735,7 @@ def ignore_user(argv, **args):
} }
@pluginfunction('search', 'search the web (using duckduckgo)', ptypes_COMMAND) @pluginfunction('search', 'search the web (using duckduckgo)', ptypes.COMMAND)
def search_the_web(argv, **args): def search_the_web(argv, **args):
url = 'http://api.duckduckgo.com/' url = 'http://api.duckduckgo.com/'
params = dict( params = dict(
@@ -760,78 +767,19 @@ def search_the_web(argv, **args):
return {'msg': 'Sorry, no results.'} return {'msg': 'Sorry, no results.'}
@pluginfunction('raise', 'only for debugging', ptypes_COMMAND) @pluginfunction('raise', 'only for debugging', ptypes.COMMAND)
def raise_an_error(argv, **args): def raise_an_error(argv, **args):
if args['reply_user'] == config.conf_get("bot_owner"): if args['reply_user'] == config.conf_get("bot_owner"):
raise RuntimeError("Exception for debugging") raise RuntimeError("Exception for debugging")
@pluginfunction('repeat', 'repeat the last message', ptypes_COMMAND) @pluginfunction('repeat', 'repeat the last message', ptypes.COMMAND)
def repeat_message(argv, **args): def repeat_message(argv, **args):
return { return {
'msg': args['stack'][-1]['body'] 'msg': args['stack'][-1]['body']
} }
@pluginfunction('isdown', 'check if a website is reachable', ptypes.COMMAND)
@pluginfunction('translate', 'translate text fragments, use "translate show" to get a list of languages'
'or "translate that" to get the last message translated (to german)', ptypes_COMMAND)
def translate(argv, **args):
available_languages = [code[0] for code in languages]
if argv and argv[0] == 'show':
return {
'priv_msg': 'All language codes: {}'.format(', '.join(available_languages))
}
elif argv and argv[0] == 'that':
api_key = config.conf_get('detectlanguage_api_key')
if not api_key:
return
message_stack = args['stack']
last_message = message_stack[-1]['body']
data = {
'q': last_message,
'key': api_key
}
result = requests.post('http://ws.detectlanguage.com/0.2/detect', data=data).json()
educated_guess = result['data']['detections'][0]
if not educated_guess['isReliable']:
return {'msg': 'not sure about the language.'}
else:
return translate(['{}|de'.format(educated_guess['language'])] + shlex.split(last_message))
pattern = '^(?P<from_lang>[a-z-]{2})(-(?P<from_ct>[a-z-]{2}))?\|(?P<to_lang>[a-z-]{2})(-(?P<to_ct>[a-z-]{2}))?$'
pair = re.match(pattern, argv[0])
if len(argv) < 2 or not pair:
return {
'msg': 'Usage: translate en|de my favorite bot'
}
else:
pair = pair.groupdict()
from_lang = pair.get('from_lang')
to_lang = pair.get('to_lang')
# TODO: check country code as well
if not all([lang in available_languages for lang in [from_lang, to_lang]]):
return {
'msg': '{}: not a valid language code. Please use ISO 639-1 or RFC3066 codes. '
'Use "translate show" to get a full list of all known language '
'codes (not necessarily supported) as privmsg.'.format(args['reply_user'])
}
words = ' '.join(argv[1:])
url = 'http://api.mymemory.translated.net/get'
params = {
'q': words,
'langpair': argv[0],
'de': config.conf_get('bot_owner_email')
}
response = requests.get(url, params=params).json()
return {
'msg': 'translation: {}'.format(response['responseData']['translatedText'])
}
@pluginfunction('isdown', 'check if a website is reachable', ptypes_COMMAND)
def isdown(argv, **args): def isdown(argv, **args):
if not argv: if not argv:
return return
@@ -847,7 +795,7 @@ def isdown(argv, **args):
return {'msg': '{}: {} does not exist, you\'re trying to fool me?'.format(args['reply_user'], url)} return {'msg': '{}: {} does not exist, you\'re trying to fool me?'.format(args['reply_user'], url)}
@pluginfunction('poll', 'create a poll', ptypes_COMMAND) @pluginfunction('poll', 'create a poll', ptypes.COMMAND)
def poll(argv, **args): def poll(argv, **args):
with config.plugin_config('poll') as pollcfg: with config.plugin_config('poll') as pollcfg:
current_poll = pollcfg.get('subject') current_poll = pollcfg.get('subject')
@@ -899,45 +847,7 @@ def poll(argv, **args):
return {'msg': 'created the poll.'} return {'msg': 'created the poll.'}
@pluginfunction('vote', 'alias for poll', ptypes_COMMAND) @pluginfunction('vote', 'alias for poll', ptypes.COMMAND)
def vote(argv, **args): def vote(argv, **args):
return poll(argv, **args) return poll(argv, **args)
@pluginfunction('quiz', 'play quiz', ptypes_COMMAND)
def quiz_control(argv, **args):
usage = """quiz mode usage: "quiz start [secs interval:default 30]", "quiz stop", "quiz rules;
Not yet implemented: "quiz answer", "quiz skip".
If the quiz mode is active, all messages are parsed and compared against the answer.
"""
if not argv:
return {'msg': usage}
rules = """
The answers will be matched by characters/words. Answers will be
granted points according to the match percentage with a minimum
percentage depending on word count. After a configurable timeout per
quiz game, a single winner will be declared, if any. The timeout can
be cancelled with "quiz answer" or "quiz skip", which results in
a lost round.
"""
with config.plugin_config('quiz') as quizcfg:
if quizcfg is None:
quizcfg = dict()
if argv[0] == 'start':
quizcfg['stop_bit'] = False
interval = int(argv[1]) if len(argv) > 1 else 30
quizcfg['interval'] = interval
return quiz.start_random_question()
elif argv[0] == 'stop':
return quiz.end(quizcfg)
elif argv[0] == 'answer':
return quiz.answer(quizcfg)
elif argv[0] == 'skip':
return quiz.skip(quizcfg)
elif argv[0] == 'rules':
return {
'msg': rules
}

View File

@@ -1,47 +1,15 @@
# -*- coding: utf-8 -*-
import logging import logging
log = logging.getLogger(__name__)
import time import time
import random import random
import config import config
from common import ( from plugin_system import pluginfunction, ptypes
pluginfunction, config_locked,
ptypes_MUC_ONLINE
)
log = logging.getLogger(__name__) @pluginfunction('comment_joins', 'comments frequent joins', ptypes.MUC_ONLINE)
@config.config_locked
@pluginfunction('send_record', 'delivers previously saved message to user', ptypes_MUC_ONLINE)
@config_locked
def send_record(**args):
arg_user = args['reply_user']
arg_user_key = arg_user.lower()
user_records = config.runtimeconf_get('user_records')
if arg_user_key in user_records:
records = user_records[arg_user_key]
if not records:
return None
response = {
'msg': '%s, there %s %d message%s for you:\n%s' % (
arg_user,
'is' if len(records) == 1 else 'are',
len(records),
'' if len(records) == 1 else 's',
'\n'.join(records)
)
}
user_records.pop(arg_user_key)
config.runtimeconf_persist()
return response
@pluginfunction('comment_joins', 'comments frequent joins', ptypes_MUC_ONLINE)
@config_locked
def comment_joins(**args): def comment_joins(**args):
# max elapsed time between the latest and the N latest join # max elapsed time between the latest and the N latest join
timespan = 120 timespan = 120

486
plugins/excuse.py Normal file
View File

@@ -0,0 +1,486 @@
# -*- coding: utf-8 -*-
import logging
log = logging.getLogger(__name__)
import random
from plugin_system import pluginfunction, ptypes
@pluginfunction('excuse', 'prints BOFH style excuses', ptypes.COMMAND)
def command_excuse(argv, **args):
log.info('BOFH plugin called')
return {
'msg': args['reply_user'] + ': ' + random.choice(excuses)
}
# retrieved from http://pages.cs.wisc.edu/~ballard/bofh/excuses
excuses = '''
clock speed
solar flares
electromagnetic radiation from satellite debris
static from nylon underwear
static from plastic slide rules
global warming
poor power conditioning
static buildup
doppler effect
hardware stress fractures
magnetic interference from money/credit cards
dry joints on cable plug
we're waiting for [the phone company] to fix that line
sounds like a Windows problem, try calling Microsoft support
temporary routing anomaly
somebody was calculating pi on the server
fat electrons in the lines
excess surge protection
floating point processor overflow
divide-by-zero error
POSIX compliance problem
monitor resolution too high
improperly oriented keyboard
network packets travelling uphill (use a carrier pigeon)
Decreasing electron flux
first Saturday after first full moon in Winter
radiosity depletion
CPU radiator broken
It works the way the Wang did, what's the problem
positron router malfunction
cellular telephone interference
techtonic stress
piezo-electric interference
(l)user error
working as designed
dynamic software linking table corrupted
heavy gravity fluctuation, move computer to floor rapidly
secretary plugged hairdryer into UPS
terrorist activities
not enough memory, go get system upgrade
interrupt configuration error
spaghetti cable cause packet failure
boss forgot system password
bank holiday - system operating credits not recharged
virus attack, luser responsible
waste water tank overflowed onto computer
Complete Transient Lockout
bad ether in the cables
Bogon emissions
Change in Earth's rotational speed
Cosmic ray particles crashed through the hard disk platter
Smell from unhygienic janitorial staff wrecked the tape heads
Little hamster in running wheel had coronary; waiting for replacement to be Fedexed from Wyoming
Evil dogs hypnotised the night shift
Plumber mistook routing panel for decorative wall fixture
Electricians made popcorn in the power supply
Groundskeepers stole the root password
high pressure system failure
failed trials, system needs redesigned
system has been recalled
not approved by the FCC
need to wrap system in aluminum foil to fix problem
not properly grounded, please bury computer
CPU needs recalibration
system needs to be rebooted
bit bucket overflow
descramble code needed from software company
only available on a need to know basis
knot in cables caused data stream to become twisted and kinked
nesting roaches shorted out the ether cable
The file system is full of it
Satan did it
Daemons did it
You're out of memory
There isn't any problem
Unoptimized hard drive
Typo in the code
Yes, yes, its called a design limitation
Look, buddy: Windows 3.1 IS A General Protection Fault.
That's a great computer you have there; have you considered how it would work as a BSD machine?
Please excuse me, I have to circuit an AC line through my head to get this database working.
Yeah, yo mama dresses you funny and you need a mouse to delete files.
Support staff hung over, send aspirin and come back LATER.
Someone is standing on the ethernet cable, causing a kink in the cable
Windows 95 undocumented "feature"
Runt packets
Password is too complex to decrypt
Boss' kid fucked up the machine
Electromagnetic energy loss
Budget cuts
Mouse chewed through power cable
Stale file handle (next time use Tupperware(tm)!)
Feature not yet implemented
Internet outage
Pentium FDIV bug
Vendor no longer supports the product
Small animal kamikaze attack on power supplies
The vendor put the bug there.
SIMM crosstalk.
IRQ dropout
Collapsed Backbone
Power company testing new voltage spike (creation) equipment
operators on strike due to broken coffee machine
backup tape overwritten with copy of system manager's favourite CD
UPS interrupted the server's power
The electrician didn't know what the yellow cable was so he yanked the ethernet out.
The keyboard isn't plugged in
The air conditioning water supply pipe ruptured over the machine room
The electricity substation in the car park blew up.
The rolling stones concert down the road caused a brown out
The salesman drove over the CPU board.
The monitor is plugged into the serial port
Root nameservers are out of sync
electro-magnetic pulses from French above ground nuke testing.
your keyboard's space bar is generating spurious keycodes.
the real ttys became pseudo ttys and vice-versa.
the printer thinks its a router.
the router thinks its a printer.
evil hackers from Serbia.
we just switched to FDDI.
halon system went off and killed the operators.
because Bill Gates is a Jehovah's witness and so nothing can work on St. Swithin's day.
user to computer ratio too high.
user to computer ration too low.
we just switched to Sprint.
it has Intel Inside
Sticky bits on disk.
Power Company having EMP problems with their reactor
The ring needs another token
new management
telnet: Unable to connect to remote host: Connection refused
SCSI Chain overterminated
It's not plugged in.
because of network lag due to too many people playing deathmatch
You put the disk in upside down.
Daemons loose in system.
User was distributing pornography on server; system seized by FBI.
BNC (brain not connected)
UBNC (user brain not connected)
LBNC (luser brain not connected)
disks spinning backwards - toggle the hemisphere jumper.
new guy cross-connected phone lines with ac power bus.
had to use hammer to free stuck disk drive heads.
Too few computrons available.
Flat tire on station wagon with tapes. ("Never underestimate the bandwidth of a station wagon full of tapes hurling down the highway" Andrew S. Tannenbaum)
Communications satellite used by the military for star wars.
Party-bug in the Aloha protocol.
Insert coin for new game
Dew on the telephone lines.
Arcserve crashed the server again.
Some one needed the powerstrip, so they pulled the switch plug.
My pony-tail hit the on/off switch on the power strip.
Big to little endian conversion error
You can tune a file system, but you can't tune a fish (from most tunefs man pages)
Dumb terminal
Zombie processes haunting the computer
Incorrect time synchronization
Defunct processes
Stubborn processes
non-redundant fan failure
monitor VLF leakage
bugs in the RAID
no "any" key on keyboard
root rot
Backbone Scoliosis
/pub/lunch
excessive collisions & not enough packet ambulances
le0: no carrier: transceiver cable problem?
broadcast packets on wrong frequency
popper unable to process jumbo kernel
NOTICE: alloc: /dev/null: filesystem full
pseudo-user on a pseudo-terminal
Recursive traversal of loopback mount points
Backbone adjustment
OS swapped to disk
vapors from evaporating sticky-note adhesives
sticktion
short leg on process table
multicasts on broken packets
ether leak
Atilla the Hub
endothermal recalibration
filesystem not big enough for Jumbo Kernel Patch
loop found in loop in redundant loopback
system consumed all the paper for paging
permission denied
Reformatting Page. Wait...
..disk or the processor is on fire.
SCSI's too wide.
Proprietary Information.
Just type 'mv * /dev/null'.
runaway cat on system.
Did you pay the new Support Fee?
We only support a 1200 bps connection.
We only support a 28000 bps connection.
Me no internet, only janitor, me just wax floors.
I'm sorry a pentium won't do, you need an SGI to connect with us.
Post-it Note Sludge leaked into the monitor.
the curls in your keyboard cord are losing electricity.
The monitor needs another box of pixels.
RPC_PMAP_FAILURE
kernel panic: write-only-memory (/dev/wom0) capacity exceeded.
Write-only-memory subsystem too slow for this machine. Contact your local dealer.
Just pick up the phone and give modem connect sounds. "Well you said we should get more lines so we don't have voice lines."
Quantum dynamics are affecting the transistors
Police are examining all internet packets in the search for a narco-net-trafficker
We are currently trying a new concept of using a live mouse. Unfortunately, one has yet to survive being hooked up to the computer.....please bear with us.
Your mail is being routed through Germany ... and they're censoring us.
Only people with names beginning with 'A' are getting mail this week (a la Microsoft)
We didn't pay the Internet bill and it's been cut off.
Lightning strikes.
Of course it doesn't work. We've performed a software upgrade.
Change your language to Finnish.
Fluorescent lights are generating negative ions. If turning them off doesn't work, take them out and put tin foil on the ends.
High nuclear activity in your area.
What office are you in? Oh, that one. Did you know that your building was built over the universities first nuclear research site? And wow, aren't you the lucky one, your office is right over where the core is buried!
The MGs ran out of gas.
The UPS doesn't have a battery backup.
Recursivity. Call back if it happens again.
Someone thought The Big Red Button was a light switch.
The mainframe needs to rest. It's getting old, you know.
I'm not sure. Try calling the Internet's head office -- it's in the book.
The lines are all busy (busied out, that is -- why let them in to begin with?).
Jan 9 16:41:27 huber su: 'su root' succeeded for .... on /dev/pts/1
It's those computer people in X {city of world}. They keep stuffing things up.
A star wars satellite accidently blew up the WAN.
Fatal error right in front of screen
That function is not currently supported, but Bill Gates assures us it will be featured in the next upgrade.
wrong polarity of neutron flow
Lusers learning curve appears to be fractal
We had to turn off that service to comply with the CDA Bill.
Ionization from the air-conditioning
TCP/IP UDP alarm threshold is set too low.
Someone is broadcasting pygmy packets and the router doesn't know how to deal with them.
The new frame relay network hasn't bedded down the software loop transmitter yet.
Fanout dropping voltage too much, try cutting some of those little traces
Plate voltage too low on demodulator tube
You did wha... oh _dear_....
CPU needs bearings repacked
Too many little pins on CPU confusing it, bend back and forth until 10-20% are neatly removed. Do _not_ leave metal bits visible!
_Rosin_ core solder? But...
Software uses US measurements, but the OS is in metric...
The computer fleetly, mouse and all.
Your cat tried to eat the mouse.
The Borg tried to assimilate your system. Resistance is futile.
It must have been the lightning storm we had (yesterday) (last week) (last month)
Due to Federal Budget problems we have been forced to cut back on the number of users able to access the system at one time. (namely none allowed....)
Too much radiation coming from the soil.
Unfortunately we have run out of bits/bytes/whatever. Don't worry, the next supply will be coming next week.
Program load too heavy for processor to lift.
Processes running slowly due to weak power supply
Our ISP is having {switching,routing,SMDS,frame relay} problems
We've run out of licenses
Interference from lunar radiation
Standing room only on the bus.
You need to install an RTFM interface.
That would be because the software doesn't work.
That's easy to fix, but I can't be bothered.
Someone's tie is caught in the printer, and if anything else gets printed, he'll be in it too.
We're upgrading /dev/null
The Usenet news is out of date
Our POP server was kidnapped by a weasel.
It's stuck in the Web.
Your modem doesn't speak English.
The mouse escaped.
All of the packets are empty.
The UPS is on strike.
Neutrino overload on the nameserver
Melting hard drives
Someone has messed up the kernel pointers
The kernel license has expired
Netscape has crashed
The cord jumped over and hit the power switch.
It was OK before you touched it.
Bit rot
U.S. Postal Service
Your Flux Capacitor has gone bad.
The Dilithium Crystals need to be rotated.
The static electricity routing is acting up...
Traceroute says that there is a routing problem in the backbone. It's not our problem.
The co-locator cannot verify the frame-relay gateway to the ISDN server.
High altitude condensation from U.S.A.F prototype aircraft has contaminated the primary subnet mask. Turn off your computer for 9 days to avoid damaging it.
Lawn mower blade in your fan need sharpening
Electrons on a bender
Telecommunications is upgrading.
Telecommunications is downgrading.
Telecommunications is downshifting.
Hard drive sleeping. Let it wake up on it's own...
Interference between the keyboard and the chair.
The CPU has shifted, and become decentralized.
Due to the CDA, we no longer have a root account.
We ran out of dial tone and we're and waiting for the phone company to deliver another bottle.
You must've hit the wrong any key.
PCMCIA slave driver
The Token fell out of the ring. Call us when you find it.
The hardware bus needs a new token.
Too many interrupts
Not enough interrupts
The data on your hard drive is out of balance.
Digital Manipulator exceeding velocity parameters
appears to be a Slow/Narrow SCSI-0 Interface problem
microelectronic Riemannian curved-space fault in write-only file system
fractal radiation jamming the backbone
routing problems on the neural net
IRQ-problems with the Un-Interruptible-Power-Supply
CPU-angle has to be adjusted because of vibrations coming from the nearby road
emissions from GSM-phones
CD-ROM server needs recalibration
firewall needs cooling
asynchronous inode failure
transient bus protocol violation
incompatible bit-registration operators
your process is not ISO 9000 compliant
You need to upgrade your VESA local bus to a MasterCard local bus.
The recent proliferation of Nuclear Testing
Elves on strike. (Why do they call EMAG Elf Magic)
Internet exceeded Luser level, please wait until a luser logs off before attempting to log back on.
Your EMAIL is now being delivered by the USPS.
Your computer hasn't been returning all the bits it gets from the Internet.
You've been infected by the Telescoping Hubble virus.
Scheduled global CPU outage
Your Pentium has a heating problem - try cooling it with ice cold water.(Do not turn off your computer, you do not want to cool down the Pentium Chip while he isn't working, do you?)
Your processor has processed too many instructions. Turn it off immediately, do not type any commands!!
Your packets were eaten by the terminator
Your processor does not develop enough heat.
We need a licensed electrician to replace the light bulbs in the computer room.
The POP server is out of Coke
Fiber optics caused gas main leak
Server depressed, needs Prozac
quantum decoherence
those damn raccoons!
suboptimal routing experience
A plumber is needed, the network drain is clogged
50% of the manual is in .pdf readme files
the AA battery in the wallclock sends magnetic interference
the xy axis in the trackball is coordinated with the summer solstice
the butane lighter causes the pincushioning
old inkjet cartridges emanate barium-based fumes
manager in the cable duct
We'll fix that in the next (upgrade, update, patch release, service pack).
HTTPD Error 666 : BOFH was here
HTTPD Error 4004 : very old Intel cpu - insufficient processing power
The ATM board has run out of 10 pound notes. We are having a whip round to refill it, care to contribute ?
Network failure - call NBC
Having to manually track the satellite.
Your/our computer(s) had suffered a memory leak, and we are waiting for them to be topped up.
The rubber band broke
We're on Token Ring, and it looks like the token got loose.
Stray Alpha Particles from memory packaging caused Hard Memory Error on Server.
paradigm shift...without a clutch
PEBKAC (Problem Exists Between Keyboard And Chair)
The cables are not the same length.
Second-system effect.
Chewing gum on /dev/sd3c
Boredom in the Kernel.
the daemons! the daemons! the terrible daemons!
I'd love to help you -- it's just that the Boss won't let me near the computer.
struck by the Good Times virus
YOU HAVE AN I/O ERROR -> Incompetent Operator error
Your parity check is overdrawn and you're out of cache.
Communist revolutionaries taking over the server room and demanding all the computers in the building or they shoot the sysadmin. Poor misguided fools.
Plasma conduit breach
Out of cards on drive D:
Sand fleas eating the Internet cables
parallel processors running perpendicular today
ATM cell has no roaming feature turned on, notebooks can't connect
Webmasters kidnapped by evil cult.
Failure to adjust for daylight savings time.
Virus transmitted from computer to sysadmins.
Virus due to computers having unsafe sex.
Incorrectly configured static routes on the corerouters.
Forced to support NT servers; sysadmins quit.
Suspicious pointer corrupted virtual machine
It's the InterNIC's fault.
Root name servers corrupted.
Budget cuts forced us to sell all the power cords for the servers.
Someone hooked the twisted pair wires into the answering machine.
Operators killed by year 2000 bug bite.
We've picked COBOL as the language of choice.
Operators killed when huge stack of backup tapes fell over.
Robotic tape changer mistook operator's tie for a backup tape.
Someone was smoking in the computer room and set off the halon systems.
Your processor has taken a ride to Heaven's Gate on the UFO behind Hale-Bopp's comet.
it's an ID-10-T error
Dyslexics retyping hosts file on servers
The Internet is being scanned for viruses.
Your computer's union contract is set to expire at midnight.
Bad user karma.
/dev/clue was linked to /dev/null
Increased sunspot activity.
We already sent around a notice about that.
It's union rules. There's nothing we can do about it. Sorry.
Interference from the Van Allen Belt.
Jupiter is aligned with Mars.
Redundant ACLs.
Mail server hit by UniSpammer.
T-1's congested due to porn traffic to the news server.
Data for intranet got routed through the extranet and landed on the internet.
We are a 100% Microsoft Shop.
We are Microsoft. What you are experiencing is not a problem; it is an undocumented feature.
Sales staff sold a product we don't offer.
Secretary sent chain letter to all 5000 employees.
Sysadmin didn't hear pager go off due to loud music from bar-room speakers.
Sysadmin accidentally destroyed pager with a large hammer.
Sysadmins unavailable because they are in a meeting talking about why they are unavailable so much.
Bad cafeteria food landed all the sysadmins in the hospital.
Route flapping at the NAP.
Computers under water due to SYN flooding.
The vulcan-death-grip ping has been applied.
Electrical conduits in machine room are melting.
Traffic jam on the Information Superhighway.
Radial Telemetry Infiltration
Cow-tippers tipped a cow onto the server.
tachyon emissions overloading the system
Maintenance window broken
We're out of slots on the server
Computer room being moved. Our systems are down for the weekend.
Sysadmins busy fighting SPAM.
Repeated reboots of the system failed to solve problem
Feature was not beta tested
Domain controller not responding
Someone else stole your IP address, call the Internet detectives!
It's not RFC-822 compliant.
operation failed because: there is no message for this error (#1014)
stop bit received
internet is needed to catch the etherbunny
network down, IP packets delivered via UPS
Firmware update in the coffee machine
Temporal anomaly
Mouse has out-of-cheese-error
Borg implants are failing
Borg nanites have infested the server
error: one bad user found in front of screen
Please state the nature of the technical emergency
Internet shut down due to maintenance
Daemon escaped from pentagram
crop circles in the corn shell
sticky bit has come loose
Hot Java has gone cold
Cache miss - please take better aim next time
Hash table has woodworm
Trojan horse ran out of hay
Zombie processes detected, machine is haunted.
overflow error in /dev/null
Browser's cookie is corrupted -- someone's been nibbling on it.
Mailer-daemon is busy burning your message in hell.
According to Microsoft, it's by design
vi needs to be upgraded to vii
greenpeace free'd the mallocs
Terrorists crashed an airplane into the server room, have to remove /bin/laden. (rm -rf /bin/laden)
astropneumatic oscillations in the water-cooling
Somebody ran the operating system through a spelling checker.
Rhythmic variations in the voltage reaching the power supply.
Keyboard Actuator Failure. Order and Replace.
Packet held up at customs.
Propagation delay.
High line impedance.
Someone set us up the bomb.
Power surges on the Underground.
Don't worry; it's been deprecated. The new one is worse.
Excess condensation in cloud network
It is a layer 8 problem
The math co-processor had an overflow error that leaked out and shorted the RAM
Leap second overloaded RHEL6 servers
DNS server drank too much and had a hiccup
'''.split('\n')[1:-1]

76
plugins/moin.py Normal file
View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
import logging
log = logging.getLogger(__name__)
from plugin_system import pluginfunction, ptypes
moin_strings_hi = [
'Hi',
'Guten Morgen', 'Morgen',
'Moin',
'Tag', 'Tach',
'NAbend', 'Abend',
'Hallo', 'Hello'
]
moin_strings_bye = [
'Nacht', 'gN8', 'N8',
'bye',
]
@pluginfunction('moin', 'parse hi/bye', ptypes.PARSE, enabled=False)
def parse_moin(**args):
for direction in [moin_strings_hi, moin_strings_bye]:
for d in direction:
words = re.split(r'\W+', args['data'])
# assumption: longer sentences are not greetings
if 3 < len(args['data'].split()):
continue
for w in words:
if d.lower() == w.lower():
if args['reply_user'] in config.conf_get('moin-disabled-user'):
log.info('moin blacklist match')
return
if args['reply_user'] in config.conf_get('moin-modified-user'):
log.info('being "quiet" for %s' % w)
return {
'msg': '/me %s' % random.choice([
"doesn't say anything at all",
'whistles uninterested',
'just ignores this incident'
])
}
log.info('sent %s reply for %s' % (
'hi' if direction is moin_strings_hi else 'bye', w
))
return {
'msg': '''%s, %s''' % (
random.choice(direction),
args['reply_user']
)
}
@pluginfunction('show-moinlist', 'show the current moin reply list, optionally filtered', ptypes.COMMAND)
def command_show_moinlist(argv, **args):
log.info('sent moin reply list')
user = None if not argv else argv[0]
return {
'msg':
'%s: moin reply list%s: %s' % (
args['reply_user'],
'' if not user else ' (limited to %s)' % user,
', '.join([
b for b in moin_strings_hi + moin_strings_bye
if not user or user.lower() in b.lower()
])
)
}

View File

@@ -1,18 +1,18 @@
# -*- coding: utf-8 -*-
import logging import logging
log = logging.getLogger(__name__)
import random import random
import re import re
import time import time
import config import config
from common import RATE_NO_SILENCE, RATE_GLOBAL, extract_title, RATE_FUN, RATE_URL, pluginfunction, ptypes_PARSE from common import extract_title
from rate_limit import RATE_NO_SILENCE, RATE_GLOBAL, RATE_FUN, RATE_URL
from config import runtimeconf_get from config import runtimeconf_get
from plugins import ptypes_PARSE, quiz from plugin_system import pluginfunction, ptypes
from string_constants import moin_strings_hi, moin_strings_bye
log = logging.getLogger(__name__) @pluginfunction('mental_ill', 'parse mental illness', ptypes.PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
@pluginfunction('mental_ill', 'parse mental illness', ptypes_PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
def parse_mental_ill(**args): def parse_mental_ill(**args):
min_ill = 3 min_ill = 3
c = 0 c = 0
@@ -38,7 +38,7 @@ def parse_mental_ill(**args):
} }
@pluginfunction('debbug', 'parse Debian bug numbers', ptypes_PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL) @pluginfunction('debbug', 'parse Debian bug numbers', ptypes.PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
def parse_debbug(**args): def parse_debbug(**args):
bugs = re.findall(r'#(\d{4,})', args['data']) bugs = re.findall(r'#(\d{4,})', args['data'])
if not bugs: if not bugs:
@@ -63,7 +63,7 @@ def parse_debbug(**args):
} }
@pluginfunction('cve', 'parse a CVE handle', ptypes_PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL) @pluginfunction('cve', 'parse a CVE handle', ptypes.PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
def parse_cve(**args): def parse_cve(**args):
cves = re.findall(r'(CVE-\d\d\d\d-\d+)', args['data'].upper()) cves = re.findall(r'(CVE-\d\d\d\d-\d+)', args['data'].upper())
if not cves: if not cves:
@@ -75,7 +75,7 @@ def parse_cve(**args):
} }
@pluginfunction('dsa', 'parse a DSA handle', ptypes_PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL) @pluginfunction('dsa', 'parse a DSA handle', ptypes.PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
def parse_dsa(**args): def parse_dsa(**args):
dsas = re.findall(r'(DSA-\d\d\d\d-\d+)', args['data'].upper()) dsas = re.findall(r'(DSA-\d\d\d\d-\d+)', args['data'].upper())
if not dsas: if not dsas:
@@ -87,7 +87,7 @@ def parse_dsa(**args):
} }
@pluginfunction('skynet', 'parse skynet', ptypes_PARSE, ratelimit_class=RATE_FUN | RATE_GLOBAL) @pluginfunction('skynet', 'parse skynet', ptypes.PARSE, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def parse_skynet(**args): def parse_skynet(**args):
if 'skynet' in args['data'].lower(): if 'skynet' in args['data'].lower():
return { return {
@@ -95,44 +95,7 @@ def parse_skynet(**args):
} }
@pluginfunction('moin', 'parse hi/bye', ptypes_PARSE, enabled=False) @pluginfunction('latex', r'reacts on \LaTeX', ptypes.PARSE, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def parse_moin(**args):
for direction in [moin_strings_hi, moin_strings_bye]:
for d in direction:
words = re.split(r'\W+', args['data'])
# assumption: longer sentences are not greetings
if 3 < len(args['data'].split()):
continue
for w in words:
if d.lower() == w.lower():
if args['reply_user'] in config.conf_get('moin-disabled-user'):
log.info('moin blacklist match')
return
if args['reply_user'] in config.conf_get('moin-modified-user'):
log.info('being "quiet" for %s' % w)
return {
'msg': '/me %s' % random.choice([
"doesn't say anything at all",
'whistles uninterested',
'just ignores this incident'
])
}
log.info('sent %s reply for %s' % (
'hi' if direction is moin_strings_hi else 'bye', w
))
return {
'msg': '''%s, %s''' % (
random.choice(direction),
args['reply_user']
)
}
@pluginfunction('latex', r'reacts on \LaTeX', ptypes_PARSE, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def parse_latex(**args): def parse_latex(**args):
if r'\LaTeX' in args['data']: if r'\LaTeX' in args['data']:
return { return {
@@ -140,7 +103,7 @@ def parse_latex(**args):
} }
@pluginfunction('me-action', 'reacts to /me.*%{bot_nickname}', ptypes_PARSE, ratelimit_class=RATE_FUN | RATE_GLOBAL) @pluginfunction('me-action', 'reacts to /me.*%{bot_nickname}', ptypes.PARSE, ratelimit_class=RATE_FUN | RATE_GLOBAL)
def parse_slash_me(**args): def parse_slash_me(**args):
if args['data'].lower().startswith('/me') and (config.conf_get('bot_nickname') in args['data'].lower()): if args['data'].lower().startswith('/me') and (config.conf_get('bot_nickname') in args['data'].lower()):
log.info('sent /me reply') log.info('sent /me reply')
@@ -158,7 +121,7 @@ def parse_slash_me(**args):
} }
@pluginfunction("recognize_bots", "got ya", ptypes_PARSE) @pluginfunction("recognize_bots", "got ya", ptypes.PARSE)
def recognize_bots(**args): def recognize_bots(**args):
unique_standard_phrases = ( unique_standard_phrases = (
'independent bot and have nothing to do with other artificial intelligence systems', 'independent bot and have nothing to do with other artificial intelligence systems',
@@ -184,7 +147,7 @@ def recognize_bots(**args):
return _add_to_list(args['reply_user'], 'Hey there, buddy!') return _add_to_list(args['reply_user'], 'Hey there, buddy!')
@pluginfunction('resolve-url-title', 'extract titles from urls', ptypes_PARSE, ratelimit_class=RATE_URL) @pluginfunction('resolve-url-title', 'extract titles from urls', ptypes.PARSE, ratelimit_class=RATE_URL)
def resolve_url_title(**args): def resolve_url_title(**args):
user = args['reply_user'] user = args['reply_user']
user_pref_nospoiler = runtimeconf_get('user_pref', {}).get(user, {}).get('spoiler', False) user_pref_nospoiler = runtimeconf_get('user_pref', {}).get(user, {}).get('spoiler', False)
@@ -220,12 +183,3 @@ def resolve_url_title(**args):
'msg': out 'msg': out
} }
@pluginfunction('quizparser', 'react on chat during quiz games', ptypes_PARSE)
def quizparser(**args):
with config.plugin_config('quiz') as quizcfg:
current_quiz_question = quiz.get_current_question(quizcfg)
if current_quiz_question is None:
return
else:
return quiz.rate(quizcfg, args['data'], args['reply_user'])

View File

@@ -6,7 +6,56 @@ from functools import lru_cache
import re import re
import time import time
from config import plugin_config import config
from plugin_system import pluginfunction, ptypes
@pluginfunction('quiz', 'play quiz', ptypes.COMMAND)
def quiz_control(argv, **args):
usage = """quiz mode usage: "quiz start [secs interval:default 30]", "quiz stop", "quiz rules;
Not yet implemented: "quiz answer", "quiz skip".
If the quiz mode is active, all messages are parsed and compared against the answer.
"""
if not argv:
return {'msg': usage}
rules = """
The answers will be matched by characters/words. Answers will be
granted points according to the match percentage with a minimum
percentage depending on word count. After a configurable timeout per
quiz game, a single winner will be declared, if any. The timeout can
be cancelled with "quiz answer" or "quiz skip", which results in
a lost round.
"""
with config.plugin_config('quiz') as quizcfg:
if quizcfg is None:
quizcfg = dict()
if argv[0] == 'start':
quizcfg['stop_bit'] = False
interval = int(argv[1]) if len(argv) > 1 else 30
quizcfg['interval'] = interval
return start_random_question()
elif argv[0] == 'stop':
return end(quizcfg)
elif argv[0] == 'answer':
return answer(quizcfg)
elif argv[0] == 'skip':
return skip(quizcfg)
elif argv[0] == 'rules':
return {
'msg': rules
}
@pluginfunction('quizparser', 'react on chat during quiz games', ptypes.PARSE)
def quizparser(**args):
with config.plugin_config('quiz') as quizcfg:
current_quiz_question = get_current_question(quizcfg)
if current_quiz_question is None:
return
else:
return rate(quizcfg, args['data'], args['reply_user'])
@lru_cache(10) @lru_cache(10)
@@ -22,7 +71,7 @@ def get_questions(directory=None):
def get_random_question(): def get_random_question():
with plugin_config('quiz') as quizcfg: with config.plugin_config('quiz') as quizcfg:
questions = get_questions() questions = get_questions()
# select a random question # select a random question
used_ids = quizcfg.get('used_ids', []) used_ids = quizcfg.get('used_ids', [])
@@ -43,7 +92,7 @@ def get_current_question(quizcfg):
def end_question(): def end_question():
with plugin_config('quiz') as quizcfg: with config.plugin_config('quiz') as quizcfg:
lines = ['Question time over!'] lines = ['Question time over!']
score = float(quizcfg.get('current_max_score', 0)) score = float(quizcfg.get('current_max_score', 0))
@@ -129,7 +178,7 @@ def rate(quizcfg, response, user):
def start_random_question(): def start_random_question():
with plugin_config('quiz') as quizcfg: with config.plugin_config('quiz') as quizcfg:
if quizcfg.get("locked", False): if quizcfg.get("locked", False):
return {'msg': 'already running!'} return {'msg': 'already running!'}

88
plugins/record.py Normal file
View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
import logging
log = logging.getLogger(__name__)
import time
import config
from plugin_system import pluginfunction, ptypes
@pluginfunction('send_record', 'delivers previously saved message to user', ptypes.MUC_ONLINE)
@config.config_locked
def send_record(**args):
arg_user = args['reply_user']
arg_user_key = arg_user.lower()
user_records = config.runtimeconf_get('user_records')
if arg_user_key in user_records:
records = user_records[arg_user_key]
if not records:
return None
response = {
'msg': '%s, there %s %d message%s for you:\n%s' % (
arg_user,
'is' if len(records) == 1 else 'are',
len(records),
'' if len(records) == 1 else 's',
'\n'.join(records)
)
}
user_records.pop(arg_user_key)
config.runtimeconf_persist()
return response
@pluginfunction(
'record', 'record a message for a now offline user (usage: record {user} {some message};'
' {some message} == "previous" to use the last channel message)', ptypes.COMMAND)
@config.config_locked
def command_record(argv, **args):
if len(argv) < 2:
return {
'msg': '%s: usage: record {user} {some message}' % args['reply_user']
}
target_user = argv[0].lower().strip(':')
message = '{} ({}): '.format(args['reply_user'], time.strftime('%Y-%m-%d %H:%M'))
if argv[1] == "previous":
prev_message_obj = args['stack'][-1]
message += '[{}]: '.format(get_nick_from_object(prev_message_obj))
message += prev_message_obj['body']
else:
message += ' '.join(argv[1:])
if target_user not in config.runtime_config_store['user_records']:
config.runtime_config_store['user_records'][target_user] = []
config.runtime_config_store['user_records'][target_user].append(message)
config.runtimeconf_persist()
return {
'msg': '%s: message saved for %s' % (args['reply_user'], target_user)
}
@pluginfunction('show-records', 'show current offline records', ptypes.COMMAND)
def command_show_recordlist(argv, **args):
log.info('sent offline records list')
user = None if not argv else argv[0]
return {
'msg':
'%s: offline records%s: %s' % (
args['reply_user'],
'' if not user else ' (limited to %s)' % user,
', '.join(
[
'%s (%d)' % (key, len(val)) for key, val in config.runtime_config_store['user_records'].items()
if not user or user.lower() in key.lower()
]
)
)
}

527
plugins/translate.py Normal file
View File

@@ -0,0 +1,527 @@
# -*- coding: utf-8 -*-
import re
import shlex
import json
import requests
import config
from plugin_system import pluginfunction, ptypes
@pluginfunction('translate', 'translate text fragments, use "translate show" to get a list of languages'
'or "translate that" to get the last message translated (to german)', ptypes.COMMAND)
def translate(argv, **args):
available_languages = [code[0] for code in languages]
if argv and argv[0] == 'show':
return {
'priv_msg': 'All language codes: {}'.format(', '.join(available_languages))
}
elif argv and argv[0] == 'that':
api_key = config.conf_get('detectlanguage_api_key')
if not api_key:
return
message_stack = args['stack']
last_message = message_stack[-1]['body']
data = {
'q': last_message,
'key': api_key
}
result = requests.post('http://ws.detectlanguage.com/0.2/detect', data=data).json()
educated_guess = result['data']['detections'][0]
if not educated_guess['isReliable']:
return {'msg': 'not sure about the language.'}
else:
return translate(['{}|de'.format(educated_guess['language'])] + shlex.split(last_message))
pattern = '^(?P<from_lang>[a-z-]{2})(-(?P<from_ct>[a-z-]{2}))?\|(?P<to_lang>[a-z-]{2})(-(?P<to_ct>[a-z-]{2}))?$'
pair = re.match(pattern, argv[0])
if len(argv) < 2 or not pair:
return {
'msg': 'Usage: translate en|de my favorite bot'
}
else:
pair = pair.groupdict()
from_lang = pair.get('from_lang')
to_lang = pair.get('to_lang')
# TODO: check country code as well
if not all([lang in available_languages for lang in [from_lang, to_lang]]):
return {
'msg': '{}: not a valid language code. Please use ISO 639-1 or RFC3066 codes. '
'Use "translate show" to get a full list of all known language '
'codes (not necessarily supported) as privmsg.'.format(args['reply_user'])
}
words = ' '.join(argv[1:])
url = 'http://api.mymemory.translated.net/get'
params = {
'q': words,
'langpair': argv[0],
'de': config.conf_get('bot_owner_email')
}
response = requests.get(url, params=params).json()
return {
'msg': 'translation: {}'.format(response['responseData']['translatedText'])
}
languages = [
('aa', 'Afar'),
('ab', 'Abkhazian'),
('af', 'Afrikaans'),
('ak', 'Akan'),
('sq', 'Albanian'),
('am', 'Amharic'),
('ar', 'Arabic'),
('an', 'Aragonese'),
('hy', 'Armenian'),
('as', 'Assamese'),
('av', 'Avaric'),
('ae', 'Avestan'),
('ay', 'Aymara'),
('az', 'Azerbaijani'),
('ba', 'Bashkir'),
('bm', 'Bambara'),
('eu', 'Basque'),
('be', 'Belarusian'),
('bn', 'Bengali'),
('bh', 'Bihari languages'),
('bi', 'Bislama'),
('bo', 'Tibetan'),
('bs', 'Bosnian'),
('br', 'Breton'),
('bg', 'Bulgarian'),
('my', 'Burmese'),
('ca', 'Catalan; Valencian'),
('cs', 'Czech'),
('ch', 'Chamorro'),
('ce', 'Chechen'),
('zh', 'Chinese'),
('cu', 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic'),
('cv', 'Chuvash'),
('kw', 'Cornish'),
('co', 'Corsican'),
('cr', 'Cree'),
('cy', 'Welsh'),
('cs', 'Czech'),
('da', 'Danish'),
('de', 'German'),
('dv', 'Divehi; Dhivehi; Maldivian'),
('nl', 'Dutch; Flemish'),
('dz', 'Dzongkha'),
('el', 'Greek, Modern (1453-)'),
('en', 'English'),
('eo', 'Esperanto'),
('et', 'Estonian'),
('eu', 'Basque'),
('ee', 'Ewe'),
('fo', 'Faroese'),
('fa', 'Persian'),
('fj', 'Fijian'),
('fi', 'Finnish'),
('fr', 'French'),
('fr', 'French'),
('fy', 'Western Frisian'),
('ff', 'Fulah'),
('Ga', 'Georgian'),
('de', 'German'),
('gd', 'Gaelic; Scottish Gaelic'),
('ga', 'Irish'),
('gl', 'Galician'),
('gv', 'Manx'),
('el', 'Greek, Modern (1453-)'),
('gn', 'Guarani'),
('gu', 'Gujarati'),
('ht', 'Haitian; Haitian Creole'),
('ha', 'Hausa'),
('he', 'Hebrew'),
('hz', 'Herero'),
('hi', 'Hindi'),
('ho', 'Hiri Motu'),
('hr', 'Croatian'),
('hu', 'Hungarian'),
('hy', 'Armenian'),
('ig', 'Igbo'),
('is', 'Icelandic'),
('io', 'Ido'),
('ii', 'Sichuan Yi; Nuosu'),
('iu', 'Inuktitut'),
('ie', 'Interlingue; Occidental'),
('ia', 'Interlingua (International Auxiliary Language Association)'),
('id', 'Indonesian'),
('ik', 'Inupiaq'),
('is', 'Icelandic'),
('it', 'Italian'),
('jv', 'Javanese'),
('ja', 'Japanese'),
('kl', 'Kalaallisut; Greenlandic'),
('kn', 'Kannada'),
('ks', 'Kashmiri'),
('ka', 'Georgian'),
('kr', 'Kanuri'),
('kk', 'Kazakh'),
('km', 'Central Khmer'),
('ki', 'Kikuyu; Gikuyu'),
('rw', 'Kinyarwanda'),
('ky', 'Kirghiz; Kyrgyz'),
('kv', 'Komi'),
('kg', 'Kongo'),
('ko', 'Korean'),
('kj', 'Kuanyama; Kwanyama'),
('ku', 'Kurdish'),
('lo', 'Lao'),
('la', 'Latin'),
('lv', 'Latvian'),
('li', 'Limburgan; Limburger; Limburgish'),
('ln', 'Lingala'),
('lt', 'Lithuanian'),
('lb', 'Luxembourgish; Letzeburgesch'),
('lu', 'Luba-Katanga'),
('lg', 'Ganda'),
('mk', 'Macedonian'),
('mh', 'Marshallese'),
('ml', 'Malayalam'),
('mi', 'Maori'),
('mr', 'Marathi'),
('ms', 'Malay'),
('Mi', 'Micmac'),
('mk', 'Macedonian'),
('mg', 'Malagasy'),
('mt', 'Maltese'),
('mn', 'Mongolian'),
('mi', 'Maori'),
('ms', 'Malay'),
('my', 'Burmese'),
('na', 'Nauru'),
('nv', 'Navajo; Navaho'),
('nr', 'Ndebele, South; South Ndebele'),
('nd', 'Ndebele, North; North Ndebele'),
('ng', 'Ndonga'),
('ne', 'Nepali'),
('nl', 'Dutch; Flemish'),
('nn', 'Norwegian Nynorsk; Nynorsk, Norwegian'),
('nb', 'Bokmål, Norwegian; Norwegian Bokmål'),
('no', 'Norwegian'),
('oc', 'Occitan (post 1500)'),
('oj', 'Ojibwa'),
('or', 'Oriya'),
('om', 'Oromo'),
('os', 'Ossetian; Ossetic'),
('pa', 'Panjabi; Punjabi'),
('fa', 'Persian'),
('pi', 'Pali'),
('pl', 'Polish'),
('pt', 'Portuguese'),
('ps', 'Pushto; Pashto'),
('qu', 'Quechua'),
('rm', 'Romansh'),
('ro', 'Romanian; Moldavian; Moldovan'),
('ro', 'Romanian; Moldavian; Moldovan'),
('rn', 'Rundi'),
('ru', 'Russian'),
('sg', 'Sango'),
('sa', 'Sanskrit'),
('si', 'Sinhala; Sinhalese'),
('sk', 'Slovak'),
('sk', 'Slovak'),
('sl', 'Slovenian'),
('se', 'Northern Sami'),
('sm', 'Samoan'),
('sn', 'Shona'),
('sd', 'Sindhi'),
('so', 'Somali'),
('st', 'Sotho, Southern'),
('es', 'Spanish; Castilian'),
('sq', 'Albanian'),
('sc', 'Sardinian'),
('sr', 'Serbian'),
('ss', 'Swati'),
('su', 'Sundanese'),
('sw', 'Swahili'),
('sv', 'Swedish'),
('ty', 'Tahitian'),
('ta', 'Tamil'),
('tt', 'Tatar'),
('te', 'Telugu'),
('tg', 'Tajik'),
('tl', 'Tagalog'),
('th', 'Thai'),
('bo', 'Tibetan'),
('ti', 'Tigrinya'),
('to', 'Tonga (Tonga Islands)'),
('tn', 'Tswana'),
('ts', 'Tsonga'),
('tk', 'Turkmen'),
('tr', 'Turkish'),
('tw', 'Twi'),
('ug', 'Uighur; Uyghur'),
('uk', 'Ukrainian'),
('ur', 'Urdu'),
('uz', 'Uzbek'),
('ve', 'Venda'),
('vi', 'Vietnamese'),
('vo', 'Volapük'),
('cy', 'Welsh'),
('wa', 'Walloon'),
('wo', 'Wolof'),
('xh', 'Xhosa'),
('yi', 'Yiddish'),
('yo', 'Yoruba'),
('za', 'Zhuang; Chuang'),
('zh', 'Chinese'),
('zu', 'Zulu')
]
countries = [
('AF', u'Afghanistan'),
('AX', u'\xc5land Islands'),
('AL', u'Albania'),
('DZ', u'Algeria'),
('AS', u'American Samoa'),
('AD', u'Andorra'),
('AO', u'Angola'),
('AI', u'Anguilla'),
('AQ', u'Antarctica'),
('AG', u'Antigua and Barbuda'),
('AR', u'Argentina'),
('AM', u'Armenia'),
('AW', u'Aruba'),
('AU', u'Australia'),
('AT', u'Austria'),
('AZ', u'Azerbaijan'),
('BS', u'Bahamas'),
('BH', u'Bahrain'),
('BD', u'Bangladesh'),
('BB', u'Barbados'),
('BY', u'Belarus'),
('BE', u'Belgium'),
('BZ', u'Belize'),
('BJ', u'Benin'),
('BM', u'Bermuda'),
('BT', u'Bhutan'),
('BO', u'Bolivia, Plurinational State of'),
('BQ', u'Bonaire, Sint Eustatius and Saba'),
('BA', u'Bosnia and Herzegovina'),
('BW', u'Botswana'),
('BV', u'Bouvet Island'),
('BR', u'Brazil'),
('IO', u'British Indian Ocean Territory'),
('BN', u'Brunei Darussalam'),
('BG', u'Bulgaria'),
('BF', u'Burkina Faso'),
('BI', u'Burundi'),
('KH', u'Cambodia'),
('CM', u'Cameroon'),
('CA', u'Canada'),
('CV', u'Cape Verde'),
('KY', u'Cayman Islands'),
('CF', u'Central African Republic'),
('TD', u'Chad'),
('CL', u'Chile'),
('CN', u'China'),
('CX', u'Christmas Island'),
('CC', u'Cocos (Keeling Islands)'),
('CO', u'Colombia'),
('KM', u'Comoros'),
('CG', u'Congo'),
('CD', u'Congo, The Democratic Republic of the'),
('CK', u'Cook Islands'),
('CR', u'Costa Rica'),
('CI', u"C\xf4te D'ivoire"),
('HR', u'Croatia'),
('CU', u'Cuba'),
('CW', u'Cura\xe7ao'),
('CY', u'Cyprus'),
('CZ', u'Czech Republic'),
('DK', u'Denmark'),
('DJ', u'Djibouti'),
('DM', u'Dominica'),
('DO', u'Dominican Republic'),
('EC', u'Ecuador'),
('EG', u'Egypt'),
('SV', u'El Salvador'),
('GQ', u'Equatorial Guinea'),
('ER', u'Eritrea'),
('EE', u'Estonia'),
('ET', u'Ethiopia'),
('FK', u'Falkland Islands (Malvinas)'),
('FO', u'Faroe Islands'),
('FJ', u'Fiji'),
('FI', u'Finland'),
('FR', u'France'),
('GF', u'French Guiana'),
('PF', u'French Polynesia'),
('TF', u'French Southern Territories'),
('GA', u'Gabon'),
('GM', u'Gambia'),
('GE', u'Georgia'),
('DE', u'Germany'),
('GH', u'Ghana'),
('GI', u'Gibraltar'),
('GR', u'Greece'),
('GL', u'Greenland'),
('GD', u'Grenada'),
('GP', u'Guadeloupe'),
('GU', u'Guam'),
('GT', u'Guatemala'),
('GG', u'Guernsey'),
('GN', u'Guinea'),
('GW', u'Guinea-bissau'),
('GY', u'Guyana'),
('HT', u'Haiti'),
('HM', u'Heard Island and McDonald Islands'),
('VA', u'Holy See (Vatican City State)'),
('HN', u'Honduras'),
('HK', u'Hong Kong'),
('HU', u'Hungary'),
('IS', u'Iceland'),
('IN', u'India'),
('ID', u'Indonesia'),
('IR', u'Iran, Islamic Republic of'),
('IQ', u'Iraq'),
('IE', u'Ireland'),
('IM', u'Isle of Man'),
('IL', u'Israel'),
('IT', u'Italy'),
('JM', u'Jamaica'),
('JP', u'Japan'),
('JE', u'Jersey'),
('JO', u'Jordan'),
('KZ', u'Kazakhstan'),
('KE', u'Kenya'),
('KI', u'Kiribati'),
('KP', u"Korea, Democratic People's Republic of"),
('KR', u'Korea, Republic of'),
('KW', u'Kuwait'),
('KG', u'Kyrgyzstan'),
('LA', u"Lao People's Democratic Republic"),
('LV', u'Latvia'),
('LB', u'Lebanon'),
('LS', u'Lesotho'),
('LR', u'Liberia'),
('LY', u'Libya'),
('LI', u'Liechtenstein'),
('LT', u'Lithuania'),
('LU', u'Luxembourg'),
('MO', u'Macao'),
('MK', u'Macedonia, The Former Yugoslav Republic of'),
('MG', u'Madagascar'),
('MW', u'Malawi'),
('MY', u'Malaysia'),
('MV', u'Maldives'),
('ML', u'Mali'),
('MT', u'Malta'),
('MH', u'Marshall Islands'),
('MQ', u'Martinique'),
('MR', u'Mauritania'),
('MU', u'Mauritius'),
('YT', u'Mayotte'),
('MX', u'Mexico'),
('FM', u'Micronesia, Federated States of'),
('MD', u'Moldova, Republic of'),
('MC', u'Monaco'),
('MN', u'Mongolia'),
('ME', u'Montenegro'),
('MS', u'Montserrat'),
('MA', u'Morocco'),
('MZ', u'Mozambique'),
('MM', u'Myanmar'),
('NA', u'Namibia'),
('NR', u'Nauru'),
('NP', u'Nepal'),
('NL', u'Netherlands'),
('NC', u'New Caledonia'),
('NZ', u'New Zealand'),
('NI', u'Nicaragua'),
('NE', u'Niger'),
('NG', u'Nigeria'),
('NU', u'Niue'),
('NF', u'Norfolk Island'),
('MP', u'Northern Mariana Islands'),
('NO', u'Norway'),
('OM', u'Oman'),
('PK', u'Pakistan'),
('PW', u'Palau'),
('PS', u'Palestinian Territory, Occupied'),
('PA', u'Panama'),
('PG', u'Papua New Guinea'),
('PY', u'Paraguay'),
('PE', u'Peru'),
('PH', u'Philippines'),
('PN', u'Pitcairn'),
('PL', u'Poland'),
('PT', u'Portugal'),
('PR', u'Puerto Rico'),
('QA', u'Qatar'),
('RE', u'R\xe9union'),
('RO', u'Romania'),
('RU', u'Russian Federation'),
('RW', u'Rwanda'),
('BL', u'Saint Barth\xe9lemy'),
('SH', u'Saint Helena, Ascension and Tristan Da Cunha'),
('KN', u'Saint Kitts and Nevis'),
('LC', u'Saint Lucia'),
('MF', u'Saint Martin (French Part)'),
('PM', u'Saint Pierre and Miquelon'),
('VC', u'Saint Vincent and the Grenadines'),
('WS', u'Samoa'),
('SM', u'San Marino'),
('ST', u'Sao Tome and Principe'),
('SA', u'Saudi Arabia'),
('SN', u'Senegal'),
('RS', u'Serbia'),
('SC', u'Seychelles'),
('SL', u'Sierra Leone'),
('SG', u'Singapore'),
('SX', u'Sint Maarten (Dutch Part)'),
('SK', u'Slovakia'),
('SI', u'Slovenia'),
('SB', u'Solomon Islands'),
('SO', u'Somalia'),
('ZA', u'South Africa'),
('GS', u'South Georgia and the South Sandwich Islands'),
('SS', u'South Sudan'),
('ES', u'Spain'),
('LK', u'Sri Lanka'),
('SD', u'Sudan'),
('SR', u'Suriname'),
('SJ', u'Svalbard and Jan Mayen'),
('SZ', u'Swaziland'),
('SE', u'Sweden'),
('CH', u'Switzerland'),
('SY', u'Syrian Arab Republic'),
('TW', u'Taiwan, Province of China'),
('TJ', u'Tajikistan'),
('TZ', u'Tanzania, United Republic of'),
('TH', u'Thailand'),
('TL', u'Timor-leste'),
('TG', u'Togo'),
('TK', u'Tokelau'),
('TO', u'Tonga'),
('TT', u'Trinidad and Tobago'),
('TN', u'Tunisia'),
('TR', u'Turkey'),
('TM', u'Turkmenistan'),
('TC', u'Turks and Caicos Islands'),
('TV', u'Tuvalu'),
('UG', u'Uganda'),
('UA', u'Ukraine'),
('AE', u'United Arab Emirates'),
('GB', u'United Kingdom'),
('US', u'United States'),
('UM', u'United States Minor Outlying Islands'),
('UY', u'Uruguay'),
('UZ', u'Uzbekistan'),
('VU', u'Vanuatu'),
('VE', u'Venezuela, Bolivarian Republic of'),
('VN', u'Viet Nam'),
('VG', u'Virgin Islands, British'),
('VI', u'Virgin Islands, U.S.'),
('WF', u'Wallis and Futuna'),
('EH', u'Western Sahara'),
('YE', u'Yemen'),
('ZM', u'Zambia'),
('ZW', u'Zimbabwe')
]

88
rate_limit.py Normal file
View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
import time
import logging
from collections import namedtuple
RATE_NO_LIMIT = 0x00
RATE_GLOBAL = 0x01
RATE_NO_SILENCE = 0x02
RATE_INTERACTIVE = 0x04
RATE_CHAT = 0x08
RATE_URL = 0x10
RATE_EVENT = 0x20
RATE_FUN = 0x40
Bucket = namedtuple("BucketConfig", ["history", "period", "max_hist_len"])
buckets = {
# everything else
RATE_GLOBAL: Bucket(history=[], period=60, max_hist_len=10),
# bot writes with no visible stimuli
RATE_NO_SILENCE: Bucket(history=[], period=10, max_hist_len=5),
# interactive stuff like ping
RATE_INTERACTIVE: Bucket(history=[], period=30, max_hist_len=5),
# chitty-chat, master volume control
RATE_CHAT: Bucket(history=[], period=10, max_hist_len=5),
# reacting on URLs
RATE_URL: Bucket(history=[], period=10, max_hist_len=5),
# triggering events
RATE_EVENT: Bucket(history=[], period=60, max_hist_len=10),
# bot blames people, produces cake and entertains
RATE_FUN: Bucket(history=[], period=180, max_hist_len=5),
}
rate_limit_classes = buckets.keys()
def rate_limit(rate_class=RATE_GLOBAL):
"""
Remember N timestamps,
if N[0] newer than now()-T then do not output, do not append.
else pop(0); append()
:param rate_class: the type of message to verify
:return: False if blocked, True if allowed
"""
if rate_class not in rate_limit_classes:
return all(rate_limit(c) for c in rate_limit_classes if c & rate_class)
now = time.time()
bucket = buckets[rate_class]
logging.getLogger(__name__).debug(
"[ratelimit][bucket=%x][time=%s]%s",
rate_class, now, bucket.history
)
if len(bucket.history) >= bucket.max_hist_len and bucket.history[0] > (now - bucket.period):
# print("blocked")
return False
else:
if bucket.history and len(bucket.history) > bucket.max_hist_len:
bucket.history.pop(0)
bucket.history.append(now)
return True
def rate_limited(max_per_second):
"""
very simple flow control context manager
:param max_per_second: how many events per second may be executed - more are delayed
:return:
"""
min_interval = 1.0 / float(max_per_second)
def decorate(func):
lasttimecalled = [0.0]
def ratelimitedfunction(*args, **kargs):
elapsed = time.clock() - lasttimecalled[0]
lefttowait = min_interval - elapsed
if lefttowait > 0:
time.sleep(lefttowait)
ret = func(*args, **kargs)
lasttimecalled[0] = time.clock()
return ret
return ratelimitedfunction
return decorate

View File

@@ -1,963 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# retrieved from http://pages.cs.wisc.edu/~ballard/bofh/excuses
excuses = '''
clock speed
solar flares
electromagnetic radiation from satellite debris
static from nylon underwear
static from plastic slide rules
global warming
poor power conditioning
static buildup
doppler effect
hardware stress fractures
magnetic interference from money/credit cards
dry joints on cable plug
we're waiting for [the phone company] to fix that line
sounds like a Windows problem, try calling Microsoft support
temporary routing anomaly
somebody was calculating pi on the server
fat electrons in the lines
excess surge protection
floating point processor overflow
divide-by-zero error
POSIX compliance problem
monitor resolution too high
improperly oriented keyboard
network packets travelling uphill (use a carrier pigeon)
Decreasing electron flux
first Saturday after first full moon in Winter
radiosity depletion
CPU radiator broken
It works the way the Wang did, what's the problem
positron router malfunction
cellular telephone interference
techtonic stress
piezo-electric interference
(l)user error
working as designed
dynamic software linking table corrupted
heavy gravity fluctuation, move computer to floor rapidly
secretary plugged hairdryer into UPS
terrorist activities
not enough memory, go get system upgrade
interrupt configuration error
spaghetti cable cause packet failure
boss forgot system password
bank holiday - system operating credits not recharged
virus attack, luser responsible
waste water tank overflowed onto computer
Complete Transient Lockout
bad ether in the cables
Bogon emissions
Change in Earth's rotational speed
Cosmic ray particles crashed through the hard disk platter
Smell from unhygienic janitorial staff wrecked the tape heads
Little hamster in running wheel had coronary; waiting for replacement to be Fedexed from Wyoming
Evil dogs hypnotised the night shift
Plumber mistook routing panel for decorative wall fixture
Electricians made popcorn in the power supply
Groundskeepers stole the root password
high pressure system failure
failed trials, system needs redesigned
system has been recalled
not approved by the FCC
need to wrap system in aluminum foil to fix problem
not properly grounded, please bury computer
CPU needs recalibration
system needs to be rebooted
bit bucket overflow
descramble code needed from software company
only available on a need to know basis
knot in cables caused data stream to become twisted and kinked
nesting roaches shorted out the ether cable
The file system is full of it
Satan did it
Daemons did it
You're out of memory
There isn't any problem
Unoptimized hard drive
Typo in the code
Yes, yes, its called a design limitation
Look, buddy: Windows 3.1 IS A General Protection Fault.
That's a great computer you have there; have you considered how it would work as a BSD machine?
Please excuse me, I have to circuit an AC line through my head to get this database working.
Yeah, yo mama dresses you funny and you need a mouse to delete files.
Support staff hung over, send aspirin and come back LATER.
Someone is standing on the ethernet cable, causing a kink in the cable
Windows 95 undocumented "feature"
Runt packets
Password is too complex to decrypt
Boss' kid fucked up the machine
Electromagnetic energy loss
Budget cuts
Mouse chewed through power cable
Stale file handle (next time use Tupperware(tm)!)
Feature not yet implemented
Internet outage
Pentium FDIV bug
Vendor no longer supports the product
Small animal kamikaze attack on power supplies
The vendor put the bug there.
SIMM crosstalk.
IRQ dropout
Collapsed Backbone
Power company testing new voltage spike (creation) equipment
operators on strike due to broken coffee machine
backup tape overwritten with copy of system manager's favourite CD
UPS interrupted the server's power
The electrician didn't know what the yellow cable was so he yanked the ethernet out.
The keyboard isn't plugged in
The air conditioning water supply pipe ruptured over the machine room
The electricity substation in the car park blew up.
The rolling stones concert down the road caused a brown out
The salesman drove over the CPU board.
The monitor is plugged into the serial port
Root nameservers are out of sync
electro-magnetic pulses from French above ground nuke testing.
your keyboard's space bar is generating spurious keycodes.
the real ttys became pseudo ttys and vice-versa.
the printer thinks its a router.
the router thinks its a printer.
evil hackers from Serbia.
we just switched to FDDI.
halon system went off and killed the operators.
because Bill Gates is a Jehovah's witness and so nothing can work on St. Swithin's day.
user to computer ratio too high.
user to computer ration too low.
we just switched to Sprint.
it has Intel Inside
Sticky bits on disk.
Power Company having EMP problems with their reactor
The ring needs another token
new management
telnet: Unable to connect to remote host: Connection refused
SCSI Chain overterminated
It's not plugged in.
because of network lag due to too many people playing deathmatch
You put the disk in upside down.
Daemons loose in system.
User was distributing pornography on server; system seized by FBI.
BNC (brain not connected)
UBNC (user brain not connected)
LBNC (luser brain not connected)
disks spinning backwards - toggle the hemisphere jumper.
new guy cross-connected phone lines with ac power bus.
had to use hammer to free stuck disk drive heads.
Too few computrons available.
Flat tire on station wagon with tapes. ("Never underestimate the bandwidth of a station wagon full of tapes hurling down the highway" Andrew S. Tannenbaum)
Communications satellite used by the military for star wars.
Party-bug in the Aloha protocol.
Insert coin for new game
Dew on the telephone lines.
Arcserve crashed the server again.
Some one needed the powerstrip, so they pulled the switch plug.
My pony-tail hit the on/off switch on the power strip.
Big to little endian conversion error
You can tune a file system, but you can't tune a fish (from most tunefs man pages)
Dumb terminal
Zombie processes haunting the computer
Incorrect time synchronization
Defunct processes
Stubborn processes
non-redundant fan failure
monitor VLF leakage
bugs in the RAID
no "any" key on keyboard
root rot
Backbone Scoliosis
/pub/lunch
excessive collisions & not enough packet ambulances
le0: no carrier: transceiver cable problem?
broadcast packets on wrong frequency
popper unable to process jumbo kernel
NOTICE: alloc: /dev/null: filesystem full
pseudo-user on a pseudo-terminal
Recursive traversal of loopback mount points
Backbone adjustment
OS swapped to disk
vapors from evaporating sticky-note adhesives
sticktion
short leg on process table
multicasts on broken packets
ether leak
Atilla the Hub
endothermal recalibration
filesystem not big enough for Jumbo Kernel Patch
loop found in loop in redundant loopback
system consumed all the paper for paging
permission denied
Reformatting Page. Wait...
..disk or the processor is on fire.
SCSI's too wide.
Proprietary Information.
Just type 'mv * /dev/null'.
runaway cat on system.
Did you pay the new Support Fee?
We only support a 1200 bps connection.
We only support a 28000 bps connection.
Me no internet, only janitor, me just wax floors.
I'm sorry a pentium won't do, you need an SGI to connect with us.
Post-it Note Sludge leaked into the monitor.
the curls in your keyboard cord are losing electricity.
The monitor needs another box of pixels.
RPC_PMAP_FAILURE
kernel panic: write-only-memory (/dev/wom0) capacity exceeded.
Write-only-memory subsystem too slow for this machine. Contact your local dealer.
Just pick up the phone and give modem connect sounds. "Well you said we should get more lines so we don't have voice lines."
Quantum dynamics are affecting the transistors
Police are examining all internet packets in the search for a narco-net-trafficker
We are currently trying a new concept of using a live mouse. Unfortunately, one has yet to survive being hooked up to the computer.....please bear with us.
Your mail is being routed through Germany ... and they're censoring us.
Only people with names beginning with 'A' are getting mail this week (a la Microsoft)
We didn't pay the Internet bill and it's been cut off.
Lightning strikes.
Of course it doesn't work. We've performed a software upgrade.
Change your language to Finnish.
Fluorescent lights are generating negative ions. If turning them off doesn't work, take them out and put tin foil on the ends.
High nuclear activity in your area.
What office are you in? Oh, that one. Did you know that your building was built over the universities first nuclear research site? And wow, aren't you the lucky one, your office is right over where the core is buried!
The MGs ran out of gas.
The UPS doesn't have a battery backup.
Recursivity. Call back if it happens again.
Someone thought The Big Red Button was a light switch.
The mainframe needs to rest. It's getting old, you know.
I'm not sure. Try calling the Internet's head office -- it's in the book.
The lines are all busy (busied out, that is -- why let them in to begin with?).
Jan 9 16:41:27 huber su: 'su root' succeeded for .... on /dev/pts/1
It's those computer people in X {city of world}. They keep stuffing things up.
A star wars satellite accidently blew up the WAN.
Fatal error right in front of screen
That function is not currently supported, but Bill Gates assures us it will be featured in the next upgrade.
wrong polarity of neutron flow
Lusers learning curve appears to be fractal
We had to turn off that service to comply with the CDA Bill.
Ionization from the air-conditioning
TCP/IP UDP alarm threshold is set too low.
Someone is broadcasting pygmy packets and the router doesn't know how to deal with them.
The new frame relay network hasn't bedded down the software loop transmitter yet.
Fanout dropping voltage too much, try cutting some of those little traces
Plate voltage too low on demodulator tube
You did wha... oh _dear_....
CPU needs bearings repacked
Too many little pins on CPU confusing it, bend back and forth until 10-20% are neatly removed. Do _not_ leave metal bits visible!
_Rosin_ core solder? But...
Software uses US measurements, but the OS is in metric...
The computer fleetly, mouse and all.
Your cat tried to eat the mouse.
The Borg tried to assimilate your system. Resistance is futile.
It must have been the lightning storm we had (yesterday) (last week) (last month)
Due to Federal Budget problems we have been forced to cut back on the number of users able to access the system at one time. (namely none allowed....)
Too much radiation coming from the soil.
Unfortunately we have run out of bits/bytes/whatever. Don't worry, the next supply will be coming next week.
Program load too heavy for processor to lift.
Processes running slowly due to weak power supply
Our ISP is having {switching,routing,SMDS,frame relay} problems
We've run out of licenses
Interference from lunar radiation
Standing room only on the bus.
You need to install an RTFM interface.
That would be because the software doesn't work.
That's easy to fix, but I can't be bothered.
Someone's tie is caught in the printer, and if anything else gets printed, he'll be in it too.
We're upgrading /dev/null
The Usenet news is out of date
Our POP server was kidnapped by a weasel.
It's stuck in the Web.
Your modem doesn't speak English.
The mouse escaped.
All of the packets are empty.
The UPS is on strike.
Neutrino overload on the nameserver
Melting hard drives
Someone has messed up the kernel pointers
The kernel license has expired
Netscape has crashed
The cord jumped over and hit the power switch.
It was OK before you touched it.
Bit rot
U.S. Postal Service
Your Flux Capacitor has gone bad.
The Dilithium Crystals need to be rotated.
The static electricity routing is acting up...
Traceroute says that there is a routing problem in the backbone. It's not our problem.
The co-locator cannot verify the frame-relay gateway to the ISDN server.
High altitude condensation from U.S.A.F prototype aircraft has contaminated the primary subnet mask. Turn off your computer for 9 days to avoid damaging it.
Lawn mower blade in your fan need sharpening
Electrons on a bender
Telecommunications is upgrading.
Telecommunications is downgrading.
Telecommunications is downshifting.
Hard drive sleeping. Let it wake up on it's own...
Interference between the keyboard and the chair.
The CPU has shifted, and become decentralized.
Due to the CDA, we no longer have a root account.
We ran out of dial tone and we're and waiting for the phone company to deliver another bottle.
You must've hit the wrong any key.
PCMCIA slave driver
The Token fell out of the ring. Call us when you find it.
The hardware bus needs a new token.
Too many interrupts
Not enough interrupts
The data on your hard drive is out of balance.
Digital Manipulator exceeding velocity parameters
appears to be a Slow/Narrow SCSI-0 Interface problem
microelectronic Riemannian curved-space fault in write-only file system
fractal radiation jamming the backbone
routing problems on the neural net
IRQ-problems with the Un-Interruptible-Power-Supply
CPU-angle has to be adjusted because of vibrations coming from the nearby road
emissions from GSM-phones
CD-ROM server needs recalibration
firewall needs cooling
asynchronous inode failure
transient bus protocol violation
incompatible bit-registration operators
your process is not ISO 9000 compliant
You need to upgrade your VESA local bus to a MasterCard local bus.
The recent proliferation of Nuclear Testing
Elves on strike. (Why do they call EMAG Elf Magic)
Internet exceeded Luser level, please wait until a luser logs off before attempting to log back on.
Your EMAIL is now being delivered by the USPS.
Your computer hasn't been returning all the bits it gets from the Internet.
You've been infected by the Telescoping Hubble virus.
Scheduled global CPU outage
Your Pentium has a heating problem - try cooling it with ice cold water.(Do not turn off your computer, you do not want to cool down the Pentium Chip while he isn't working, do you?)
Your processor has processed too many instructions. Turn it off immediately, do not type any commands!!
Your packets were eaten by the terminator
Your processor does not develop enough heat.
We need a licensed electrician to replace the light bulbs in the computer room.
The POP server is out of Coke
Fiber optics caused gas main leak
Server depressed, needs Prozac
quantum decoherence
those damn raccoons!
suboptimal routing experience
A plumber is needed, the network drain is clogged
50% of the manual is in .pdf readme files
the AA battery in the wallclock sends magnetic interference
the xy axis in the trackball is coordinated with the summer solstice
the butane lighter causes the pincushioning
old inkjet cartridges emanate barium-based fumes
manager in the cable duct
We'll fix that in the next (upgrade, update, patch release, service pack).
HTTPD Error 666 : BOFH was here
HTTPD Error 4004 : very old Intel cpu - insufficient processing power
The ATM board has run out of 10 pound notes. We are having a whip round to refill it, care to contribute ?
Network failure - call NBC
Having to manually track the satellite.
Your/our computer(s) had suffered a memory leak, and we are waiting for them to be topped up.
The rubber band broke
We're on Token Ring, and it looks like the token got loose.
Stray Alpha Particles from memory packaging caused Hard Memory Error on Server.
paradigm shift...without a clutch
PEBKAC (Problem Exists Between Keyboard And Chair)
The cables are not the same length.
Second-system effect.
Chewing gum on /dev/sd3c
Boredom in the Kernel.
the daemons! the daemons! the terrible daemons!
I'd love to help you -- it's just that the Boss won't let me near the computer.
struck by the Good Times virus
YOU HAVE AN I/O ERROR -> Incompetent Operator error
Your parity check is overdrawn and you're out of cache.
Communist revolutionaries taking over the server room and demanding all the computers in the building or they shoot the sysadmin. Poor misguided fools.
Plasma conduit breach
Out of cards on drive D:
Sand fleas eating the Internet cables
parallel processors running perpendicular today
ATM cell has no roaming feature turned on, notebooks can't connect
Webmasters kidnapped by evil cult.
Failure to adjust for daylight savings time.
Virus transmitted from computer to sysadmins.
Virus due to computers having unsafe sex.
Incorrectly configured static routes on the corerouters.
Forced to support NT servers; sysadmins quit.
Suspicious pointer corrupted virtual machine
It's the InterNIC's fault.
Root name servers corrupted.
Budget cuts forced us to sell all the power cords for the servers.
Someone hooked the twisted pair wires into the answering machine.
Operators killed by year 2000 bug bite.
We've picked COBOL as the language of choice.
Operators killed when huge stack of backup tapes fell over.
Robotic tape changer mistook operator's tie for a backup tape.
Someone was smoking in the computer room and set off the halon systems.
Your processor has taken a ride to Heaven's Gate on the UFO behind Hale-Bopp's comet.
it's an ID-10-T error
Dyslexics retyping hosts file on servers
The Internet is being scanned for viruses.
Your computer's union contract is set to expire at midnight.
Bad user karma.
/dev/clue was linked to /dev/null
Increased sunspot activity.
We already sent around a notice about that.
It's union rules. There's nothing we can do about it. Sorry.
Interference from the Van Allen Belt.
Jupiter is aligned with Mars.
Redundant ACLs.
Mail server hit by UniSpammer.
T-1's congested due to porn traffic to the news server.
Data for intranet got routed through the extranet and landed on the internet.
We are a 100% Microsoft Shop.
We are Microsoft. What you are experiencing is not a problem; it is an undocumented feature.
Sales staff sold a product we don't offer.
Secretary sent chain letter to all 5000 employees.
Sysadmin didn't hear pager go off due to loud music from bar-room speakers.
Sysadmin accidentally destroyed pager with a large hammer.
Sysadmins unavailable because they are in a meeting talking about why they are unavailable so much.
Bad cafeteria food landed all the sysadmins in the hospital.
Route flapping at the NAP.
Computers under water due to SYN flooding.
The vulcan-death-grip ping has been applied.
Electrical conduits in machine room are melting.
Traffic jam on the Information Superhighway.
Radial Telemetry Infiltration
Cow-tippers tipped a cow onto the server.
tachyon emissions overloading the system
Maintenance window broken
We're out of slots on the server
Computer room being moved. Our systems are down for the weekend.
Sysadmins busy fighting SPAM.
Repeated reboots of the system failed to solve problem
Feature was not beta tested
Domain controller not responding
Someone else stole your IP address, call the Internet detectives!
It's not RFC-822 compliant.
operation failed because: there is no message for this error (#1014)
stop bit received
internet is needed to catch the etherbunny
network down, IP packets delivered via UPS
Firmware update in the coffee machine
Temporal anomaly
Mouse has out-of-cheese-error
Borg implants are failing
Borg nanites have infested the server
error: one bad user found in front of screen
Please state the nature of the technical emergency
Internet shut down due to maintenance
Daemon escaped from pentagram
crop circles in the corn shell
sticky bit has come loose
Hot Java has gone cold
Cache miss - please take better aim next time
Hash table has woodworm
Trojan horse ran out of hay
Zombie processes detected, machine is haunted.
overflow error in /dev/null
Browser's cookie is corrupted -- someone's been nibbling on it.
Mailer-daemon is busy burning your message in hell.
According to Microsoft, it's by design
vi needs to be upgraded to vii
greenpeace free'd the mallocs
Terrorists crashed an airplane into the server room, have to remove /bin/laden. (rm -rf /bin/laden)
astropneumatic oscillations in the water-cooling
Somebody ran the operating system through a spelling checker.
Rhythmic variations in the voltage reaching the power supply.
Keyboard Actuator Failure. Order and Replace.
Packet held up at customs.
Propagation delay.
High line impedance.
Someone set us up the bomb.
Power surges on the Underground.
Don't worry; it's been deprecated. The new one is worse.
Excess condensation in cloud network
It is a layer 8 problem
The math co-processor had an overflow error that leaked out and shorted the RAM
Leap second overloaded RHEL6 servers
DNS server drank too much and had a hiccup
'''.split('\n')[1:-1]
moin_strings_hi = [
'Hi',
'Guten Morgen', 'Morgen',
'Moin',
'Tag', 'Tach',
'NAbend', 'Abend',
'Hallo', 'Hello'
]
moin_strings_bye = [
'Nacht', 'gN8', 'N8',
'bye',
]
cakes = [
"No cake for you!",
("The Enrichment Center is required to remind you "
"that you will be baked, and then there will be cake."),
"The cake is a lie!",
("This is your fault. I'm going to kill you. "
"And all the cake is gone. You don't even care, do you?"),
"Quit now and cake will be served immediately.",
("Enrichment Center regulations require both hands to be "
"empty before any cake..."),
("Uh oh. Somebody cut the cake. I told them to wait for "
"you, but they did it anyway. There is still some left, "
"though, if you hurry back."),
"I'm going to kill you, and all the cake is gone.",
"Who's gonna make the cake when I'm gone? You?"
]
languages = [
('aa', 'Afar'),
('ab', 'Abkhazian'),
('af', 'Afrikaans'),
('ak', 'Akan'),
('sq', 'Albanian'),
('am', 'Amharic'),
('ar', 'Arabic'),
('an', 'Aragonese'),
('hy', 'Armenian'),
('as', 'Assamese'),
('av', 'Avaric'),
('ae', 'Avestan'),
('ay', 'Aymara'),
('az', 'Azerbaijani'),
('ba', 'Bashkir'),
('bm', 'Bambara'),
('eu', 'Basque'),
('be', 'Belarusian'),
('bn', 'Bengali'),
('bh', 'Bihari languages'),
('bi', 'Bislama'),
('bo', 'Tibetan'),
('bs', 'Bosnian'),
('br', 'Breton'),
('bg', 'Bulgarian'),
('my', 'Burmese'),
('ca', 'Catalan; Valencian'),
('cs', 'Czech'),
('ch', 'Chamorro'),
('ce', 'Chechen'),
('zh', 'Chinese'),
('cu', 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic'),
('cv', 'Chuvash'),
('kw', 'Cornish'),
('co', 'Corsican'),
('cr', 'Cree'),
('cy', 'Welsh'),
('cs', 'Czech'),
('da', 'Danish'),
('de', 'German'),
('dv', 'Divehi; Dhivehi; Maldivian'),
('nl', 'Dutch; Flemish'),
('dz', 'Dzongkha'),
('el', 'Greek, Modern (1453-)'),
('en', 'English'),
('eo', 'Esperanto'),
('et', 'Estonian'),
('eu', 'Basque'),
('ee', 'Ewe'),
('fo', 'Faroese'),
('fa', 'Persian'),
('fj', 'Fijian'),
('fi', 'Finnish'),
('fr', 'French'),
('fr', 'French'),
('fy', 'Western Frisian'),
('ff', 'Fulah'),
('Ga', 'Georgian'),
('de', 'German'),
('gd', 'Gaelic; Scottish Gaelic'),
('ga', 'Irish'),
('gl', 'Galician'),
('gv', 'Manx'),
('el', 'Greek, Modern (1453-)'),
('gn', 'Guarani'),
('gu', 'Gujarati'),
('ht', 'Haitian; Haitian Creole'),
('ha', 'Hausa'),
('he', 'Hebrew'),
('hz', 'Herero'),
('hi', 'Hindi'),
('ho', 'Hiri Motu'),
('hr', 'Croatian'),
('hu', 'Hungarian'),
('hy', 'Armenian'),
('ig', 'Igbo'),
('is', 'Icelandic'),
('io', 'Ido'),
('ii', 'Sichuan Yi; Nuosu'),
('iu', 'Inuktitut'),
('ie', 'Interlingue; Occidental'),
('ia', 'Interlingua (International Auxiliary Language Association)'),
('id', 'Indonesian'),
('ik', 'Inupiaq'),
('is', 'Icelandic'),
('it', 'Italian'),
('jv', 'Javanese'),
('ja', 'Japanese'),
('kl', 'Kalaallisut; Greenlandic'),
('kn', 'Kannada'),
('ks', 'Kashmiri'),
('ka', 'Georgian'),
('kr', 'Kanuri'),
('kk', 'Kazakh'),
('km', 'Central Khmer'),
('ki', 'Kikuyu; Gikuyu'),
('rw', 'Kinyarwanda'),
('ky', 'Kirghiz; Kyrgyz'),
('kv', 'Komi'),
('kg', 'Kongo'),
('ko', 'Korean'),
('kj', 'Kuanyama; Kwanyama'),
('ku', 'Kurdish'),
('lo', 'Lao'),
('la', 'Latin'),
('lv', 'Latvian'),
('li', 'Limburgan; Limburger; Limburgish'),
('ln', 'Lingala'),
('lt', 'Lithuanian'),
('lb', 'Luxembourgish; Letzeburgesch'),
('lu', 'Luba-Katanga'),
('lg', 'Ganda'),
('mk', 'Macedonian'),
('mh', 'Marshallese'),
('ml', 'Malayalam'),
('mi', 'Maori'),
('mr', 'Marathi'),
('ms', 'Malay'),
('Mi', 'Micmac'),
('mk', 'Macedonian'),
('mg', 'Malagasy'),
('mt', 'Maltese'),
('mn', 'Mongolian'),
('mi', 'Maori'),
('ms', 'Malay'),
('my', 'Burmese'),
('na', 'Nauru'),
('nv', 'Navajo; Navaho'),
('nr', 'Ndebele, South; South Ndebele'),
('nd', 'Ndebele, North; North Ndebele'),
('ng', 'Ndonga'),
('ne', 'Nepali'),
('nl', 'Dutch; Flemish'),
('nn', 'Norwegian Nynorsk; Nynorsk, Norwegian'),
('nb', 'Bokmål, Norwegian; Norwegian Bokmål'),
('no', 'Norwegian'),
('oc', 'Occitan (post 1500)'),
('oj', 'Ojibwa'),
('or', 'Oriya'),
('om', 'Oromo'),
('os', 'Ossetian; Ossetic'),
('pa', 'Panjabi; Punjabi'),
('fa', 'Persian'),
('pi', 'Pali'),
('pl', 'Polish'),
('pt', 'Portuguese'),
('ps', 'Pushto; Pashto'),
('qu', 'Quechua'),
('rm', 'Romansh'),
('ro', 'Romanian; Moldavian; Moldovan'),
('ro', 'Romanian; Moldavian; Moldovan'),
('rn', 'Rundi'),
('ru', 'Russian'),
('sg', 'Sango'),
('sa', 'Sanskrit'),
('si', 'Sinhala; Sinhalese'),
('sk', 'Slovak'),
('sk', 'Slovak'),
('sl', 'Slovenian'),
('se', 'Northern Sami'),
('sm', 'Samoan'),
('sn', 'Shona'),
('sd', 'Sindhi'),
('so', 'Somali'),
('st', 'Sotho, Southern'),
('es', 'Spanish; Castilian'),
('sq', 'Albanian'),
('sc', 'Sardinian'),
('sr', 'Serbian'),
('ss', 'Swati'),
('su', 'Sundanese'),
('sw', 'Swahili'),
('sv', 'Swedish'),
('ty', 'Tahitian'),
('ta', 'Tamil'),
('tt', 'Tatar'),
('te', 'Telugu'),
('tg', 'Tajik'),
('tl', 'Tagalog'),
('th', 'Thai'),
('bo', 'Tibetan'),
('ti', 'Tigrinya'),
('to', 'Tonga (Tonga Islands)'),
('tn', 'Tswana'),
('ts', 'Tsonga'),
('tk', 'Turkmen'),
('tr', 'Turkish'),
('tw', 'Twi'),
('ug', 'Uighur; Uyghur'),
('uk', 'Ukrainian'),
('ur', 'Urdu'),
('uz', 'Uzbek'),
('ve', 'Venda'),
('vi', 'Vietnamese'),
('vo', 'Volapük'),
('cy', 'Welsh'),
('wa', 'Walloon'),
('wo', 'Wolof'),
('xh', 'Xhosa'),
('yi', 'Yiddish'),
('yo', 'Yoruba'),
('za', 'Zhuang; Chuang'),
('zh', 'Chinese'),
('zu', 'Zulu')
]
countries = [
('AF', u'Afghanistan'),
('AX', u'\xc5land Islands'),
('AL', u'Albania'),
('DZ', u'Algeria'),
('AS', u'American Samoa'),
('AD', u'Andorra'),
('AO', u'Angola'),
('AI', u'Anguilla'),
('AQ', u'Antarctica'),
('AG', u'Antigua and Barbuda'),
('AR', u'Argentina'),
('AM', u'Armenia'),
('AW', u'Aruba'),
('AU', u'Australia'),
('AT', u'Austria'),
('AZ', u'Azerbaijan'),
('BS', u'Bahamas'),
('BH', u'Bahrain'),
('BD', u'Bangladesh'),
('BB', u'Barbados'),
('BY', u'Belarus'),
('BE', u'Belgium'),
('BZ', u'Belize'),
('BJ', u'Benin'),
('BM', u'Bermuda'),
('BT', u'Bhutan'),
('BO', u'Bolivia, Plurinational State of'),
('BQ', u'Bonaire, Sint Eustatius and Saba'),
('BA', u'Bosnia and Herzegovina'),
('BW', u'Botswana'),
('BV', u'Bouvet Island'),
('BR', u'Brazil'),
('IO', u'British Indian Ocean Territory'),
('BN', u'Brunei Darussalam'),
('BG', u'Bulgaria'),
('BF', u'Burkina Faso'),
('BI', u'Burundi'),
('KH', u'Cambodia'),
('CM', u'Cameroon'),
('CA', u'Canada'),
('CV', u'Cape Verde'),
('KY', u'Cayman Islands'),
('CF', u'Central African Republic'),
('TD', u'Chad'),
('CL', u'Chile'),
('CN', u'China'),
('CX', u'Christmas Island'),
('CC', u'Cocos (Keeling Islands)'),
('CO', u'Colombia'),
('KM', u'Comoros'),
('CG', u'Congo'),
('CD', u'Congo, The Democratic Republic of the'),
('CK', u'Cook Islands'),
('CR', u'Costa Rica'),
('CI', u"C\xf4te D'ivoire"),
('HR', u'Croatia'),
('CU', u'Cuba'),
('CW', u'Cura\xe7ao'),
('CY', u'Cyprus'),
('CZ', u'Czech Republic'),
('DK', u'Denmark'),
('DJ', u'Djibouti'),
('DM', u'Dominica'),
('DO', u'Dominican Republic'),
('EC', u'Ecuador'),
('EG', u'Egypt'),
('SV', u'El Salvador'),
('GQ', u'Equatorial Guinea'),
('ER', u'Eritrea'),
('EE', u'Estonia'),
('ET', u'Ethiopia'),
('FK', u'Falkland Islands (Malvinas)'),
('FO', u'Faroe Islands'),
('FJ', u'Fiji'),
('FI', u'Finland'),
('FR', u'France'),
('GF', u'French Guiana'),
('PF', u'French Polynesia'),
('TF', u'French Southern Territories'),
('GA', u'Gabon'),
('GM', u'Gambia'),
('GE', u'Georgia'),
('DE', u'Germany'),
('GH', u'Ghana'),
('GI', u'Gibraltar'),
('GR', u'Greece'),
('GL', u'Greenland'),
('GD', u'Grenada'),
('GP', u'Guadeloupe'),
('GU', u'Guam'),
('GT', u'Guatemala'),
('GG', u'Guernsey'),
('GN', u'Guinea'),
('GW', u'Guinea-bissau'),
('GY', u'Guyana'),
('HT', u'Haiti'),
('HM', u'Heard Island and McDonald Islands'),
('VA', u'Holy See (Vatican City State)'),
('HN', u'Honduras'),
('HK', u'Hong Kong'),
('HU', u'Hungary'),
('IS', u'Iceland'),
('IN', u'India'),
('ID', u'Indonesia'),
('IR', u'Iran, Islamic Republic of'),
('IQ', u'Iraq'),
('IE', u'Ireland'),
('IM', u'Isle of Man'),
('IL', u'Israel'),
('IT', u'Italy'),
('JM', u'Jamaica'),
('JP', u'Japan'),
('JE', u'Jersey'),
('JO', u'Jordan'),
('KZ', u'Kazakhstan'),
('KE', u'Kenya'),
('KI', u'Kiribati'),
('KP', u"Korea, Democratic People's Republic of"),
('KR', u'Korea, Republic of'),
('KW', u'Kuwait'),
('KG', u'Kyrgyzstan'),
('LA', u"Lao People's Democratic Republic"),
('LV', u'Latvia'),
('LB', u'Lebanon'),
('LS', u'Lesotho'),
('LR', u'Liberia'),
('LY', u'Libya'),
('LI', u'Liechtenstein'),
('LT', u'Lithuania'),
('LU', u'Luxembourg'),
('MO', u'Macao'),
('MK', u'Macedonia, The Former Yugoslav Republic of'),
('MG', u'Madagascar'),
('MW', u'Malawi'),
('MY', u'Malaysia'),
('MV', u'Maldives'),
('ML', u'Mali'),
('MT', u'Malta'),
('MH', u'Marshall Islands'),
('MQ', u'Martinique'),
('MR', u'Mauritania'),
('MU', u'Mauritius'),
('YT', u'Mayotte'),
('MX', u'Mexico'),
('FM', u'Micronesia, Federated States of'),
('MD', u'Moldova, Republic of'),
('MC', u'Monaco'),
('MN', u'Mongolia'),
('ME', u'Montenegro'),
('MS', u'Montserrat'),
('MA', u'Morocco'),
('MZ', u'Mozambique'),
('MM', u'Myanmar'),
('NA', u'Namibia'),
('NR', u'Nauru'),
('NP', u'Nepal'),
('NL', u'Netherlands'),
('NC', u'New Caledonia'),
('NZ', u'New Zealand'),
('NI', u'Nicaragua'),
('NE', u'Niger'),
('NG', u'Nigeria'),
('NU', u'Niue'),
('NF', u'Norfolk Island'),
('MP', u'Northern Mariana Islands'),
('NO', u'Norway'),
('OM', u'Oman'),
('PK', u'Pakistan'),
('PW', u'Palau'),
('PS', u'Palestinian Territory, Occupied'),
('PA', u'Panama'),
('PG', u'Papua New Guinea'),
('PY', u'Paraguay'),
('PE', u'Peru'),
('PH', u'Philippines'),
('PN', u'Pitcairn'),
('PL', u'Poland'),
('PT', u'Portugal'),
('PR', u'Puerto Rico'),
('QA', u'Qatar'),
('RE', u'R\xe9union'),
('RO', u'Romania'),
('RU', u'Russian Federation'),
('RW', u'Rwanda'),
('BL', u'Saint Barth\xe9lemy'),
('SH', u'Saint Helena, Ascension and Tristan Da Cunha'),
('KN', u'Saint Kitts and Nevis'),
('LC', u'Saint Lucia'),
('MF', u'Saint Martin (French Part)'),
('PM', u'Saint Pierre and Miquelon'),
('VC', u'Saint Vincent and the Grenadines'),
('WS', u'Samoa'),
('SM', u'San Marino'),
('ST', u'Sao Tome and Principe'),
('SA', u'Saudi Arabia'),
('SN', u'Senegal'),
('RS', u'Serbia'),
('SC', u'Seychelles'),
('SL', u'Sierra Leone'),
('SG', u'Singapore'),
('SX', u'Sint Maarten (Dutch Part)'),
('SK', u'Slovakia'),
('SI', u'Slovenia'),
('SB', u'Solomon Islands'),
('SO', u'Somalia'),
('ZA', u'South Africa'),
('GS', u'South Georgia and the South Sandwich Islands'),
('SS', u'South Sudan'),
('ES', u'Spain'),
('LK', u'Sri Lanka'),
('SD', u'Sudan'),
('SR', u'Suriname'),
('SJ', u'Svalbard and Jan Mayen'),
('SZ', u'Swaziland'),
('SE', u'Sweden'),
('CH', u'Switzerland'),
('SY', u'Syrian Arab Republic'),
('TW', u'Taiwan, Province of China'),
('TJ', u'Tajikistan'),
('TZ', u'Tanzania, United Republic of'),
('TH', u'Thailand'),
('TL', u'Timor-leste'),
('TG', u'Togo'),
('TK', u'Tokelau'),
('TO', u'Tonga'),
('TT', u'Trinidad and Tobago'),
('TN', u'Tunisia'),
('TR', u'Turkey'),
('TM', u'Turkmenistan'),
('TC', u'Turks and Caicos Islands'),
('TV', u'Tuvalu'),
('UG', u'Uganda'),
('UA', u'Ukraine'),
('AE', u'United Arab Emirates'),
('GB', u'United Kingdom'),
('US', u'United States'),
('UM', u'United States Minor Outlying Islands'),
('UY', u'Uruguay'),
('UZ', u'Uzbekistan'),
('VU', u'Vanuatu'),
('VE', u'Venezuela, Bolivarian Republic of'),
('VN', u'Viet Nam'),
('VG', u'Virgin Islands, British'),
('VI', u'Virgin Islands, U.S.'),
('WF', u'Wallis and Futuna'),
('EH', u'Western Sahara'),
('YE', u'Yemen'),
('ZM', u'Zambia'),
('ZW', u'Zimbabwe')
]
# from https://docs.google.com/document/d/1cSvjxVVlOVnP0NqhuiHfleUeHBrzYctGm5HJ-F7iY-s/edit?pli=1 # from https://docs.google.com/document/d/1cSvjxVVlOVnP0NqhuiHfleUeHBrzYctGm5HJ-F7iY-s/edit?pli=1
fillers_de = [ fillers_de = [
"aber", "aber",

View File

@@ -11,25 +11,16 @@ from lxml import etree
import requests import requests
from common import ( from plugin_system import plugin_storage, ptypes, plugin_enabled_get
rate_limit_classes, from rate_limit import rate_limit_classes, RATE_GLOBAL, RATE_CHAT, RATE_EVENT, rate_limit
RATE_GLOBAL,
RATE_CHAT, import events
RATE_EVENT,
rate_limit, from common import get_nick_from_object, else_command
get_nick_from_object)
from config import runtimeconf_set from config import runtimeconf_set
from idlebot import IdleBot, start from idlebot import IdleBot, start
from plugins import (
plugins as plugin_storage,
ptypes_COMMAND,
plugin_enabled_get,
ptypes_PARSE,
ptypes_MUC_ONLINE,
register_event,
register_active_event,
else_command,
)
import config import config
@@ -223,7 +214,7 @@ class UrlBot(IdleBot):
reply_user = get_nick_from_object(msg_obj) reply_user = get_nick_from_object(msg_obj)
for plugin in plugin_storage[ptypes_MUC_ONLINE]: for plugin in plugin_storage[ptypes.MUC_ONLINE]:
if not plugin_enabled_get(plugin): if not plugin_enabled_get(plugin):
continue continue
@@ -265,15 +256,15 @@ class UrlBot(IdleBot):
# TODO: check how several commands/plugins # TODO: check how several commands/plugins
# in a single message behave (also with rate limiting) # in a single message behave (also with rate limiting)
reacted = False reacted = False
for plugin in filter(lambda p: p.plugin_name == words[1], plugin_storage[ptypes_COMMAND]): for plugin in filter(lambda p: p.plugin_name == words[1], plugin_storage[ptypes.COMMAND]):
if not plugin_enabled_get(plugin): if not plugin_enabled_get(plugin):
continue continue
ret = plugin( ret = plugin(
data=data, data=data,
cmd_list=[pl.plugin_name for pl in plugin_storage[ptypes_COMMAND]], cmd_list=[pl.plugin_name for pl in plugin_storage[ptypes.COMMAND]],
parser_list=[pl.plugin_name for pl in plugin_storage[ptypes_PARSE]], parser_list=[pl.plugin_name for pl in plugin_storage[ptypes.PARSE]],
reply_user=reply_user, reply_user=reply_user,
msg_obj=msg_obj, msg_obj=msg_obj,
argv=words[2:] if len(words) > 1 else [], argv=words[2:] if len(words) > 1 else [],
@@ -296,7 +287,7 @@ class UrlBot(IdleBot):
reply_user = get_nick_from_object(msg_obj) reply_user = get_nick_from_object(msg_obj)
reacted = False reacted = False
for plugin in plugin_storage[ptypes_PARSE]: for plugin in plugin_storage[ptypes.PARSE]:
if not plugin_enabled_get(plugin): if not plugin_enabled_get(plugin):
continue continue
@@ -332,7 +323,7 @@ class UrlBot(IdleBot):
posttext = postelement.xpath('{}//div[@class="content"]/text()'.format(post_path)) posttext = postelement.xpath('{}//div[@class="content"]/text()'.format(post_path))
print(user, '\n'.join(posttext)) print(user, '\n'.join(posttext))
summary_action = {'msg': '{} posted {} words'.format(user, len('\n'.join(posttext).split()))} summary_action = {'msg': '{} posted {} words'.format(user, len('\n'.join(posttext).split()))}
self._run_action(summary_action, plugin=plugin_storage[ptypes_COMMAND][0], msg_obj=msg_obj) self._run_action(summary_action, plugin=plugin_storage[ptypes.COMMAND][0], msg_obj=msg_obj)
return return
def _run_action(self, action, plugin, msg_obj): def _run_action(self, action, plugin, msg_obj):
@@ -345,13 +336,13 @@ class UrlBot(IdleBot):
if 'event' in action and action["event"] is not None: if 'event' in action and action["event"] is not None:
event = action["event"] event = action["event"]
if 'msg' in event: if 'msg' in event:
register_event(event["time"], self.send_reply, [event['msg'], msg_obj]) events.register_event(event["time"], self.send_reply, [event['msg'], msg_obj])
elif 'command' in event: elif 'command' in event:
command = event["command"] command = event["command"]
if rate_limit(RATE_EVENT): if rate_limit(RATE_EVENT):
# register_event(t=event["time"], callback=command[0], args=command[1]) # register_event(t=event["time"], callback=command[0], args=command[1])
# kind of ugly.. # kind of ugly..
register_active_event( events.register_active_event(
t=event['time'], t=event['time'],
callback=command[0], callback=command[0],
args=command[1], args=command[1],