Compare commits
13 Commits
d9d87f114a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98e9efe682 | ||
|
|
abb6494acf | ||
|
|
8ab4e8cdd4 | ||
|
|
d92a177aa8 | ||
|
|
bddc3034d5 | ||
|
|
3b506d737c | ||
|
|
f00555de90 | ||
|
|
c3f5371fe3 | ||
|
|
e0385a7db3 | ||
|
|
cde081dad2 | ||
|
|
492ca7a7c8 | ||
|
|
877de9b5c3 | ||
|
|
f8373a61c4 |
11
events.py
11
events.py
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import time
|
||||
import sched
|
||||
import threading
|
||||
@@ -8,7 +9,7 @@ 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):
|
||||
def register_active_event(t, callback, args, action_runner, plugin, msg_obj, mutex=None):
|
||||
"""
|
||||
Execute a callback at a given time and react on the output
|
||||
|
||||
@@ -24,10 +25,14 @@ def register_active_event(t, callback, args, action_runner, plugin, msg_obj):
|
||||
action = callback(*func_args)
|
||||
if action:
|
||||
action_runner(action=action, plugin=plugin, msg_obj=msg_obj)
|
||||
event_list.enterabs(t, 0, func, args)
|
||||
register_event(t, func, args, mutex=mutex)
|
||||
|
||||
|
||||
def register_event(t, callback, args):
|
||||
def register_event(t, callback, args, **kwargs):
|
||||
for pending_event in event_list.queue:
|
||||
if kwargs.get('mutex') and pending_event.kwargs.get('mutex', None) == kwargs.get('mutex'):
|
||||
logging.debug("Dropped event: %s", kwargs.get('mutex'))
|
||||
return
|
||||
event_list.enterabs(t, 0, callback, args)
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class IdleBot(ClientXMPP):
|
||||
self.add_event_handler('session_start', self.session_start)
|
||||
self.add_event_handler('groupchat_message', self.muc_message)
|
||||
self.add_event_handler('disconnected', self.disconnected)
|
||||
self.add_event_handler('presence_error', self.disconnected)
|
||||
self.priority = 0
|
||||
self.status = None
|
||||
self.show = None
|
||||
@@ -29,7 +30,7 @@ class IdleBot(ClientXMPP):
|
||||
self.add_event_handler('muc::%s::got_offline' % room, self.muc_offline)
|
||||
|
||||
def disconnected(self, _):
|
||||
exit(0)
|
||||
self.disconnect(wait=True)
|
||||
|
||||
def session_start(self, _):
|
||||
self.get_roster()
|
||||
@@ -81,8 +82,7 @@ class IdleBot(ClientXMPP):
|
||||
"""
|
||||
disconnect and exit
|
||||
"""
|
||||
self.disconnect()
|
||||
sys.exit(1)
|
||||
self.disconnect(wait=True)
|
||||
|
||||
|
||||
def start(botclass, active=False):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
import re
|
||||
import events
|
||||
import json
|
||||
import random
|
||||
@@ -279,15 +280,158 @@ def command_dice(argv, **args):
|
||||
}
|
||||
|
||||
|
||||
@pluginfunction('xchoose', 'chooses randomly between nested option groups', ptypes.COMMAND, ratelimit_class=RATE_INTERACTIVE)
|
||||
def command_xchoose(argv, **args):
|
||||
|
||||
class ChooseTree():
|
||||
def __init__(self, item=None):
|
||||
self.item = item
|
||||
self.tree = None
|
||||
self.closed = False
|
||||
|
||||
# opening our root node
|
||||
if self.item is None:
|
||||
self.open()
|
||||
|
||||
def open(self):
|
||||
if self.tree is None:
|
||||
self.tree = []
|
||||
elif self.closed:
|
||||
raise Exception("cannot re-open group for item '%s'" % (self.item))
|
||||
|
||||
def close(self):
|
||||
if self.tree is None:
|
||||
raise Exception("close on unopened bracket")
|
||||
elif len(self.tree) == 0:
|
||||
raise Exception("item '%s' has a group without sub options" % (self.item))
|
||||
else:
|
||||
self.closed = True
|
||||
|
||||
def last(self):
|
||||
return self.tree[-1]
|
||||
|
||||
def choose(self):
|
||||
if self.item:
|
||||
yield self.item
|
||||
|
||||
if self.tree:
|
||||
sel = random.choice(self.tree)
|
||||
for sub in sel.choose():
|
||||
yield sub
|
||||
|
||||
def add(self, item):
|
||||
self.tree.append( ChooseTree(item) )
|
||||
|
||||
# because of error handling we're nesting this function here
|
||||
def xchoose(line):
|
||||
item = ''
|
||||
quote = None
|
||||
choose_tree = ChooseTree()
|
||||
choose_stack = [ choose_tree ]
|
||||
bracket_stack = []
|
||||
|
||||
for pos, c in enumerate(line, 1):
|
||||
try:
|
||||
if quote:
|
||||
if c == quote:
|
||||
quote = None
|
||||
else:
|
||||
item += c
|
||||
|
||||
elif c == ' ':
|
||||
if item:
|
||||
choose_stack[-1].add(item)
|
||||
item = ''
|
||||
|
||||
elif c in ('(', '[', '{', '<'):
|
||||
if item:
|
||||
choose_stack[-1].add(item)
|
||||
item = ''
|
||||
|
||||
try:
|
||||
last = choose_stack[-1].last()
|
||||
last.open()
|
||||
choose_stack.append(last)
|
||||
bracket_stack.append(c)
|
||||
except IndexError:
|
||||
raise Exception("cannot open group without preceding option")
|
||||
|
||||
elif c in (')', ']', '}', '>'):
|
||||
if not bracket_stack:
|
||||
raise Exception("missing leading bracket for '%s'" % (c))
|
||||
|
||||
opening_bracket = bracket_stack.pop(-1)
|
||||
wanted_closing_bracket = { '(':')', '[':']', '{':'}', '<':'>' }[opening_bracket]
|
||||
if c != wanted_closing_bracket:
|
||||
raise Exception("bracket mismatch, wanted bracket '%s' but got '%s'" % (
|
||||
wanted_closing_bracket, c))
|
||||
|
||||
if item:
|
||||
choose_stack[-1].add(item)
|
||||
item = ''
|
||||
|
||||
choose_stack[-1].close()
|
||||
choose_stack.pop(-1)
|
||||
|
||||
elif c in ('"', "'"):
|
||||
quote = c
|
||||
|
||||
else:
|
||||
item += c
|
||||
|
||||
except Exception as e:
|
||||
raise Exception("%s (at pos %d)" % (e, pos))
|
||||
|
||||
if bracket_stack:
|
||||
raise Exception("missing closing bracket for '%s'" % (bracket_stack[-1]))
|
||||
|
||||
if quote:
|
||||
raise Exception("missing closing quote (%s)" % (quote))
|
||||
|
||||
if item:
|
||||
choose_stack[-1].add(item)
|
||||
|
||||
return ' '.join(choose_tree.choose())
|
||||
|
||||
|
||||
# start of command_xchoose
|
||||
line = re.sub('.*xchoose *', '', args['data'])
|
||||
if not line:
|
||||
return {
|
||||
'msg': '%s: %s' % (args['reply_user'], 'missing options')
|
||||
}
|
||||
try:
|
||||
return {
|
||||
'msg': '%s: %s' % (args['reply_user'], xchoose(line))
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'msg': '%s: %s' % (args['reply_user'], str(e))
|
||||
}
|
||||
|
||||
|
||||
@pluginfunction('choose', 'chooses randomly between arguments', ptypes.COMMAND, ratelimit_class=RATE_INTERACTIVE)
|
||||
def command_choose(argv, **args):
|
||||
alternatives = argv
|
||||
binary = ['Yes', 'No', 'Maybe']
|
||||
binary = (
|
||||
(('Yes.', 'Yeah!', 'Ok!', 'Aye!', 'Great!'), 4),
|
||||
(('No.', 'Naah..', 'Meh.', 'Nay.', 'You stupid?'), 4),
|
||||
(('Maybe.', 'Dunno.', 'I don\'t care.'), 2)
|
||||
)
|
||||
|
||||
def weighted_choice(choices):
|
||||
total = sum(w for c, w in choices)
|
||||
r = random.uniform(0, total)
|
||||
upto = 0
|
||||
for c, w in choices:
|
||||
if upto + w >= r:
|
||||
return c
|
||||
upto += w
|
||||
|
||||
# single or no choice
|
||||
if len(alternatives) < 2:
|
||||
return {
|
||||
'msg': '{}: {}.'.format(args['reply_user'], random.choice(binary))
|
||||
'msg': '{}: {}'.format(args['reply_user'], random.choice(weighted_choice(binary)))
|
||||
}
|
||||
elif 'choose' not in alternatives:
|
||||
choice = random.choice(alternatives)
|
||||
@@ -302,14 +446,14 @@ def command_choose(argv, **args):
|
||||
for item in options:
|
||||
if item == 'choose':
|
||||
if len(current_choices) < 2:
|
||||
responses.append(random.choice(binary))
|
||||
responses.append(random.choice(weighted_choice(binary)))
|
||||
else:
|
||||
responses.append(random.choice(current_choices))
|
||||
current_choices = []
|
||||
else:
|
||||
current_choices.append(item)
|
||||
if len(current_choices) < 2:
|
||||
responses.append(random.choice(binary))
|
||||
responses.append(random.choice(weighted_choice(binary)))
|
||||
else:
|
||||
responses.append(random.choice(current_choices))
|
||||
return responses
|
||||
@@ -350,7 +494,8 @@ def command_teatimer(argv, **args):
|
||||
),
|
||||
'event': {
|
||||
'time': ready,
|
||||
'msg': (args['reply_user'] + ': Your tea is ready!')
|
||||
'msg': (args['reply_user'] + ': Your tea is ready!'),
|
||||
'mutex': 'teatimer_{}'.format(args['reply_user'])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,9 +761,11 @@ def command_dsa_watcher(argv=None, **_):
|
||||
msg = 'next crawl set to %s' % time.strftime('%Y-%m-%d %H:%M', time.localtime(crawl_at))
|
||||
out.append(msg)
|
||||
return {
|
||||
# 'msg': out,
|
||||
'event': {
|
||||
'time': crawl_at,
|
||||
'command': (command_dsa_watcher, ([],))
|
||||
'command': (command_dsa_watcher, ([],)),
|
||||
'mutex': 'dsa'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
176
plugins/morse.py
Normal file
176
plugins/morse.py
Normal file
@@ -0,0 +1,176 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import re
|
||||
|
||||
from plugin_system import pluginfunction, ptypes
|
||||
from rate_limit import RATE_FUN, RATE_GLOBAL
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# copy from https://de.wikipedia.org/wiki/Morsezeichen
|
||||
raw_wiki_copy = """
|
||||
A· −
|
||||
B− · · ·
|
||||
C− · − ·
|
||||
D− · ·
|
||||
E·
|
||||
F· · − ·
|
||||
G− − ·
|
||||
H· · · ·
|
||||
I· ·
|
||||
J· − − −
|
||||
K− · −
|
||||
L· − · ·
|
||||
M− −
|
||||
N− ·
|
||||
O− − −
|
||||
P· − − ·
|
||||
Q− − · −
|
||||
R· − ·
|
||||
S· · ·
|
||||
T−
|
||||
U· · −
|
||||
V· · · −
|
||||
W· − −
|
||||
X− · · −
|
||||
Y− · − −
|
||||
Z− − · ·
|
||||
1· − − − −
|
||||
2· · − − −
|
||||
3· · · − −
|
||||
4· · · · −
|
||||
5· · · · ·
|
||||
6− · · · ·
|
||||
7− − · · ·
|
||||
8− − − · ·
|
||||
9− − − − ·
|
||||
0− − − − −
|
||||
"""
|
||||
|
||||
|
||||
# machen dictionary aus wikipaste
|
||||
def wiki_paste_to_morse_dict(wikicopy):
|
||||
wikicopy = wikicopy.replace(' ', '')
|
||||
morse_dict = {l[0]: l[1:] for l in wikicopy.splitlines() if l}
|
||||
return morse_dict
|
||||
|
||||
|
||||
ascii_morse = wiki_paste_to_morse_dict(raw_wiki_copy)
|
||||
morse_ascii = {v: k for k, v in ascii_morse.items()}
|
||||
|
||||
|
||||
# return a dictionary of possible morse-chars as key
|
||||
# and their count as value
|
||||
def possible_morse_chars(string):
|
||||
"""
|
||||
returns dit,dah or None
|
||||
"""
|
||||
stats = {}
|
||||
|
||||
for c in re.sub("[\w\d ]", '', string):
|
||||
try:
|
||||
stats[c] += 1
|
||||
except KeyError:
|
||||
stats[c] = 1
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
# return morse-encoded string
|
||||
def morse_encode(string, dot='·', dash='−', sep=' ', ignore_unknown=False):
|
||||
morse_codes = []
|
||||
|
||||
for char in string.upper():
|
||||
try:
|
||||
morse_codes.append(ascii_morse[char].replace('·', dot).replace('−', dash))
|
||||
except KeyError:
|
||||
if not ignore_unknown:
|
||||
morse_codes.append(char)
|
||||
|
||||
return sep.join(morse_codes)
|
||||
|
||||
|
||||
# return morse-decoded string with number of errors as tuple
|
||||
# -> (decoded string, num errors)
|
||||
def morse_decode(string, dot=None, dash=None):
|
||||
"""
|
||||
decode a "morse string" to ascii text
|
||||
uses \s{2,} as word separator
|
||||
"""
|
||||
# dot and dash given, just decode
|
||||
if dot and dash:
|
||||
errors = 0
|
||||
|
||||
words = []
|
||||
# drawback: does not allow single characters.
|
||||
for match in re.finditer('([{dit}{dah}]+((?:\\s)[{dit}{dah}]+)+|\w+)'.format(dit=dot, dah=dash), string):
|
||||
word = match.group()
|
||||
log.debug("morse word: ", word)
|
||||
if any([dot in word, dash in word]):
|
||||
w = []
|
||||
for morse_character in word.split():
|
||||
try:
|
||||
character = morse_ascii[morse_character.replace(dot, '·').replace(dash, '−')]
|
||||
print("Converted \t{} \tto {}".format(morse_character, character))
|
||||
except KeyError:
|
||||
character = morse_character
|
||||
errors += 1
|
||||
w.append(character)
|
||||
words.append(''.join(w))
|
||||
# words.append(''.join([morse_ascii[x.replace(dot, '·').replace(dash, '−')] for x in word.split()]))
|
||||
else:
|
||||
words.append(word)
|
||||
return ' '.join(words), errors
|
||||
|
||||
# dot/dash given, search for dash/dot
|
||||
else:
|
||||
if not dash:
|
||||
dash_stats = {x: string.count(x) for x in '-−_'}
|
||||
dash = max(dash_stats, key=dash_stats.get)
|
||||
if not dot:
|
||||
dot_stats = {x: string.count(x) for x in '.·*'}
|
||||
dot = max(dot_stats, key=dot_stats.get)
|
||||
|
||||
return morse_decode(string, dot=dot, dash=dash)
|
||||
|
||||
|
||||
@pluginfunction('morse-encode', 'encode string to morse', ptypes.COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
|
||||
def command_morse_encode(argv, **args):
|
||||
if not argv:
|
||||
return {
|
||||
'msg': args['reply_user'] + "usage: morse-encode <string>"
|
||||
}
|
||||
|
||||
if len(argv) == 1 and argv[0] == 'that':
|
||||
message_stack = args['stack']
|
||||
if not message_stack[-1]:
|
||||
return
|
||||
message = message_stack[-1]['body']
|
||||
else:
|
||||
message = ' '.join(argv)
|
||||
|
||||
return {
|
||||
'msg': args['reply_user'] + ': %s' % morse_encode(message)
|
||||
}
|
||||
|
||||
|
||||
@pluginfunction('morse-decode', 'decode morse encoded string', ptypes.COMMAND, ratelimit_class=RATE_FUN | RATE_GLOBAL)
|
||||
def command_morse_decode(argv, **args):
|
||||
if not argv:
|
||||
return {
|
||||
'msg': args['reply_user'] + "usage: morse-decode <string>"
|
||||
}
|
||||
|
||||
if len(argv) == 1 and argv[0] == 'that':
|
||||
message_stack = args['stack']
|
||||
if not message_stack[-1]:
|
||||
return
|
||||
message = message_stack[-1]['body']
|
||||
else:
|
||||
message = ' '.join(argv)
|
||||
|
||||
decoded, errors = morse_decode(message, dot='·', dash='-')
|
||||
|
||||
return {
|
||||
'msg': args['reply_user'] + ': %s (%d errors)' % (decoded, errors)
|
||||
}
|
||||
@@ -39,6 +39,14 @@ def parse_mental_ill(**args):
|
||||
}
|
||||
|
||||
|
||||
@pluginfunction('woof', '*puts sunglasses on*', ptypes.PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
|
||||
def command_woof(**args):
|
||||
if 'who let the bots out' in args['data']:
|
||||
return {
|
||||
'msg': 'beeep! beep! beep! beep! beep!'
|
||||
}
|
||||
|
||||
|
||||
@pluginfunction('debbug', 'parse Debian bug numbers', ptypes.PARSE, ratelimit_class=RATE_NO_SILENCE | RATE_GLOBAL)
|
||||
def parse_debbug(**args):
|
||||
bugs = re.findall(r'#(\d{4,})', args['data'])
|
||||
|
||||
13
urlbot.py
13
urlbot.py
@@ -12,7 +12,9 @@ from collections import deque
|
||||
from lxml import etree
|
||||
|
||||
import requests
|
||||
from sleekxmpp.plugins import PluginNotFound
|
||||
|
||||
import plugins # force initialization
|
||||
from plugin_system import plugin_storage, ptypes, plugin_enabled_get
|
||||
from rate_limit import rate_limit_classes, RATE_GLOBAL, RATE_CHAT, RATE_EVENT, rate_limit
|
||||
|
||||
@@ -44,6 +46,9 @@ class UrlBot(IdleBot):
|
||||
for room in self.rooms:
|
||||
self.add_event_handler('muc::%s::got_online' % room, self.muc_online)
|
||||
|
||||
dsa_plugin = list(filter(lambda x: x.plugin_name == 'dsa-watcher', plugin_storage[ptypes.COMMAND]))[0]
|
||||
self._run_action(dsa_plugin(), dsa_plugin, None)
|
||||
|
||||
def muc_message(self, msg_obj):
|
||||
"""
|
||||
Handle muc messages, return if irrelevant content or die by hangup.
|
||||
@@ -109,7 +114,10 @@ class UrlBot(IdleBot):
|
||||
other_bots = config.runtimeconf_get("other_bots")
|
||||
if not other_bots:
|
||||
return False
|
||||
users = self.plugin['xep_0045'].getRoster(room)
|
||||
try:
|
||||
users = self.plugin['xep_0045'].getRoster(room)
|
||||
except PluginNotFound:
|
||||
users = []
|
||||
return set(users).intersection(set(other_bots))
|
||||
|
||||
def _prevent_panic(message, room):
|
||||
@@ -349,7 +357,8 @@ class UrlBot(IdleBot):
|
||||
args=command[1],
|
||||
action_runner=self._run_action,
|
||||
plugin=plugin,
|
||||
msg_obj=msg_obj
|
||||
msg_obj=msg_obj,
|
||||
mutex=event.get('mutex')
|
||||
)
|
||||
|
||||
if 'msg' in action and rate_limit(RATE_CHAT | plugin.ratelimit_class):
|
||||
|
||||
Reference in New Issue
Block a user