summaryrefslogtreecommitdiffstats
path: root/Reaktor
diff options
context:
space:
mode:
authorroot <root@flap>2014-05-06 10:00:33 -0400
committerroot <root@flap>2014-05-06 10:00:33 -0400
commit4d8016064edd5e5dc1d194ea5ec0fce4f07b8f2a (patch)
treed8ecba8651604e51d6f887449641ac627844ae63 /Reaktor
parentf44c8529e6d04b557d93cc862599b956ed21f0de (diff)
parentd0367082a5c1296cefed641b4eda736b29a3ad69 (diff)
Merge branch 'master' of https://github.com/krebscode/painload
Diffstat (limited to 'Reaktor')
-rw-r--r--Reaktor/IRC/getconf.py7
-rw-r--r--Reaktor/IRC/ircasy.py51
-rwxr-xr-x[-rw-r--r--]Reaktor/IRC/reaktor.py80
-rwxr-xr-xReaktor/auth.lst1
-rwxr-xr-xReaktor/commands/caps5
-rwxr-xr-xReaktor/commands/identify22
-rwxr-xr-xReaktor/commands/tell-on_join19
-rwxr-xr-xReaktor/commands/tell-on_privmsg17
l---------Reaktor/commands/visit-page1
-rwxr-xr-xReaktor/commands/whatweb2
-rw-r--r--Reaktor/config.py69
-rw-r--r--Reaktor/etc/conf.d/reaktor (renamed from Reaktor/startup/conf.d/reaktor)0
-rwxr-xr-xReaktor/etc/init.d/reaktor-debian (renamed from Reaktor/startup/init.d/reaktor-debian)0
-rw-r--r--Reaktor/etc/supervisor/Reaktor.conf (renamed from Reaktor/startup/supervisor/Reaktor.conf)0
-rw-r--r--Reaktor/etc/systemd/system/Reaktor.service14
-rwxr-xr-xReaktor/index2
m---------Reaktor/repos/view-website0
m---------Reaktor/repos/whatweb0
-rwxr-xr-xReaktor/titlebot/commands/clear12
-rwxr-xr-xReaktor/titlebot/commands/down2
-rwxr-xr-xReaktor/titlebot/commands/help11
-rwxr-xr-xReaktor/titlebot/commands/highest30
-rwxr-xr-xReaktor/titlebot/commands/list27
-rwxr-xr-xReaktor/titlebot/commands/new19
-rw-r--r--Reaktor/titlebot/commands/poll.py23
-rwxr-xr-xReaktor/titlebot/commands/undo31
-rwxr-xr-xReaktor/titlebot/commands/up33
-rw-r--r--Reaktor/titlebot/titlebot.py77
28 files changed, 506 insertions, 49 deletions
diff --git a/Reaktor/IRC/getconf.py b/Reaktor/IRC/getconf.py
index f9cd4404..168c908c 100644
--- a/Reaktor/IRC/getconf.py
+++ b/Reaktor/IRC/getconf.py
@@ -9,14 +9,17 @@ import os
def make_getconf(filename):
- def getconf(prop):
+ def getconf(prop, default_value=None):
prop_split = prop.split('.')
string = ''
config = load_config(filename)
#imp.reload(config)
tmp = config.__dict__
for pr in prop_split:
- tmp = tmp[pr]
+ if pr in tmp:
+ tmp = tmp[pr]
+ else:
+ return default_value
return tmp
return getconf
diff --git a/Reaktor/IRC/ircasy.py b/Reaktor/IRC/ircasy.py
index 7821305f..9a7f44f3 100644
--- a/Reaktor/IRC/ircasy.py
+++ b/Reaktor/IRC/ircasy.py
@@ -22,10 +22,10 @@ class asybot(asychat):
asychat.__init__(self)
#logger magic
self.log = logging.getLogger('asybot_' + nickname)
- hdlr = logging.handlers.SysLogHandler(facility=logging.handlers.SysLogHandler.LOG_DAEMON)
- formatter = logging.Formatter( '%(filename)s: %(levelname)s: %(message)s')
- hdlr.setFormatter(formatter)
- self.log.addHandler(hdlr)
+ #hdlr = logging.handlers.SysLogHandler(facility=logging.handlers.SysLogHandler.LOG_DAEMON)
+ #formatter = logging.Formatter( '%(filename)s: %(levelname)s: %(message)s')
+ #hdlr.setFormatter(formatter)
+ #self.log.addHandler(hdlr)
logging.basicConfig(level = loglevel)
self.nickname = nickname
@@ -45,7 +45,7 @@ class asybot(asychat):
else:
self.hostname = nickname
- self.retry = False
+ self.retry = True
self.server = server
self.port = port
self.channels = channels
@@ -85,7 +85,10 @@ class asybot(asychat):
alarm(self.hammer_interval)
def collect_incoming_data(self, data):
- self.data += data.decode()
+ try:
+ self.data += data.decode()
+ except Exception as e:
+ print('error decoding message: ' + str(e));
def found_terminator(self):
self.log.debug('<< %s' % self.data)
@@ -107,13 +110,15 @@ class asybot(asychat):
elif command == 'INVITE':
self.on_invite(prefix, command, params, rest)
+ elif command == 'KICK':
+ self.on_kick(prefix, command, params, rest)
+
+ elif command == 'JOIN':
+ self.on_join(prefix, command, params, rest)
+
elif command == '433':
# ERR_NICKNAMEINUSE, retry with another name
- _, nickname, int, _ = split('^.*[^0-9]([0-9]+)$', self.nickname) \
- if search('[0-9]$', self.nickname) \
- else ['', self.nickname, 0, '']
- self.nickname = nickname + str(int + 1)
- self.handle_connect()
+ self.on_nickinuse(prefix, command, params, rest)
elif command == '376':
self.on_welcome(prefix, command, params, rest)
@@ -158,11 +163,29 @@ class asybot(asychat):
def ME(self, target, text):
self.PRIVMSG(target, ('ACTION ' + text + ''))
- def on_privmsg(self, prefix, command, params, rest):
- pass
-
def on_welcome(self, prefix, command, params, rest):
self.push('JOIN %s' % ','.join(self.channels))
+ def on_kick(self, prefix, command, params, rest):
+ self.log.debug(params)
+ if params[-1] == self.nickname:
+ for chan in params[:-1]:
+ self.channels.remove(chan)
+
+ def on_join(self, prefix, command, params, rest):
+ pass
+
+ def on_privmsg(self, prefix, command, params, rest):
+ pass
+
def on_invite(self, prefix, command, params, rest):
pass
+
+ def on_nickinuse(self, prefix, command, params, rest):
+ regex = search('(\d+)$', self.nickname)
+ if regex:
+ theint = int(regex.group(0))
+ self.nickname = self.nickname.strip(str(theint)) + str(theint + 1)
+ else:
+ self.nickname = self.nickname + '0'
+ self.handle_connect()
diff --git a/Reaktor/IRC/reaktor.py b/Reaktor/IRC/reaktor.py
index 990d47e5..f9f25e57 100644..100755
--- a/Reaktor/IRC/reaktor.py
+++ b/Reaktor/IRC/reaktor.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
import os
from ircasy import asybot
from asyncore import loop
@@ -5,29 +6,53 @@ from translate_colors import translate_colors
import shlex
from re import split, search, match
-config_filename = './config.py'
+default_config = './config.py'
from getconf import make_getconf
-getconf = make_getconf(config_filename)
+getconf = None
import logging,logging.handlers
log = logging.getLogger('asybot')
-hdlr = logging.handlers.SysLogHandler(facility=logging.handlers.SysLogHandler.LOG_DAEMON)
-formatter = logging.Formatter( '%(filename)s: %(levelname)s: %(message)s')
-hdlr.setFormatter(formatter)
-log.addHandler(hdlr)
-logging.basicConfig(level = logging.DEBUG if getconf('debug') else logging.INFO)
+#hdlr = logging.handlers.SysLogHandler(address='/dev/log', facility=logging.handlers.SysLogHandler.LOG_DAEMON)
+#formatter = logging.Formatter( '%(filename)s: %(levelname)s: %(message)s')
+#hdlr.setFormatter(formatter)
+#log.addHandler(hdlr)
+
class Reaktor(asybot):
- def __init__(self):
+ def __init__(self,config=default_config):
+ self.config = config
+ log.info("using config file %s"%(config))
asybot.__init__(self, getconf('irc_server'), getconf('irc_port'), getconf('irc_nickname'), getconf('irc_channels'), hammer_interval=getconf('irc_hammer_interval'), alarm_timeout=getconf('irc_alarm_timeout'), kill_timeout=getconf('irc_kill_timeout'))
+ def is_admin(self,prefix):
+ try:
+ with open(getconf('auth_file')) as f:
+ for line in f:
+ if line.strip() == prefix:
+ return True
+ except Exception as e:
+ log.info(e)
+ return False
+
+ def on_join(self, prefix, command, params, rest):
+ for command in getconf('on_join', []):
+ self.execute_command(command, None, prefix, params)
+
def on_privmsg(self, prefix, command, params, rest):
for command in getconf('commands'):
y = match(command['pattern'], rest)
if y:
- self.execute_command(command, y, prefix, params)
- break
+ if not self.is_admin(prefix):
+ self.PRIVMSG(params,'unauthorized!')
+ else:
+ return self.execute_command(command, y, prefix, params)
+
+ for command in getconf('public_commands'):
+ y = match(command['pattern'], rest)
+ if y:
+ return self.execute_command(command, y, prefix, params)
+
def execute_command(self, command, match, prefix, target):
from os.path import realpath, dirname, join
@@ -37,16 +62,33 @@ class Reaktor(asybot):
#TODO: allow only commands below ./commands/
exe = join(dirname(realpath(dirname(__file__))), command['argv'][0])
myargv = [exe] + command['argv'][1:]
- if match.groupdict().get('args',None):
- myargv += shlex.split(match.groupdict()['args'])
+ try:
+ if match and match.groupdict().get('args', None):
+ myargv += shlex.split(match.groupdict()['args'])
+ except:
+ log.info("cannot parse args!")
- env = {}
+ cwd = getconf('workdir')
+ if not os.access(cwd,os.W_OK):
+ log.error("Workdir '%s' is not Writable! Falling back to root dir"%cwd)
+ cwd = "/"
+
+ env = command.get('env', {})
+ env['_prefix'] = prefix
env['_from'] = prefix.split('!', 1)[0]
- env['config_filename'] = os.path.abspath(config_filename)
+
+ log.debug('self:' +self.nickname)
+ # when receiving /query, answer to the user, not to self
+ if self.nickname in target:
+ target.remove(self.nickname)
+ target.append(env['_from'])
+ log.debug('target:' +str(target))
+
start = time()
try:
- p = popen(myargv, bufsize=1, stdout=PIPE, stderr=PIPE, env=env)
- except (OSError, Exception) as error:
+ print(myargv)
+ p = popen(myargv, bufsize=1, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd)
+ except Exception as error:
self.ME(target, 'brain damaged')
log.error('OSError@%s: %s' % (myargv, error))
return
@@ -67,5 +109,9 @@ class Reaktor(asybot):
self.ME(target, 'mimimi')
if __name__ == "__main__":
- Reaktor()
+ import sys
+ conf = sys.argv[1] if len(sys.argv) == 2 else default_config
+ getconf = make_getconf(conf)
+ logging.basicConfig(level = logging.DEBUG if getconf('debug') else logging.INFO)
+ Reaktor(conf)
loop()
diff --git a/Reaktor/auth.lst b/Reaktor/auth.lst
new file mode 100755
index 00000000..8b137891
--- /dev/null
+++ b/Reaktor/auth.lst
@@ -0,0 +1 @@
+
diff --git a/Reaktor/commands/caps b/Reaktor/commands/caps
index c47319f5..ac8cc66d 100755
--- a/Reaktor/commands/caps
+++ b/Reaktor/commands/caps
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
import imp
import os
@@ -9,4 +9,5 @@ def load_config(filename):
return imp.load_module(modname, file, pathname, description)
config = load_config(os.environ['config_filename'])
-print(' '.join(filter(None,[ x.get('capname',None) for x in config.commands])))
+print('Private: '+' '.join(filter(None,[ x.get('capname',None) for x in config.commands])))
+print('Public: '+' '.join(filter(None,[ x.get('capname',None) for x in config.public_commands])))
diff --git a/Reaktor/commands/identify b/Reaktor/commands/identify
new file mode 100755
index 00000000..c2fb2c58
--- /dev/null
+++ b/Reaktor/commands/identify
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import imp
+import os,sys
+
+def load_config(filename):
+ dirname = os.path.dirname(filename)
+ modname, ext = os.path.splitext(os.path.basename(filename))
+ file, pathname, description = imp.find_module(modname, [ dirname ])
+ return imp.load_module(modname, file, pathname, description)
+
+config = load_config(os.environ['config_filename'])
+
+with open(config.admin_file) as f:
+ for line in f:
+ nick,secret = line.split()
+ if sys.argv[1] == secret:
+ print("identified you as %s!"%nick)
+ with open(config.auth_file,'a+') as g:
+ g.write(os.environ['_prefix'] +"\n")
+ sys.exit(0)
+
+print("unable to identify you, sorry")
diff --git a/Reaktor/commands/tell-on_join b/Reaktor/commands/tell-on_join
new file mode 100755
index 00000000..2dbff41a
--- /dev/null
+++ b/Reaktor/commands/tell-on_join
@@ -0,0 +1,19 @@
+#! /bin/sh
+set -euf
+
+# require flock from util-linux
+if test "${FLOCK-}" != "$state_file"; then
+ exec env FLOCK="$state_file" flock "$state_file" "$0" "$@"
+fi
+
+to="$_from"
+
+# print messages
+sed -n '/^'"$to"' /{
+ s/^\([^ ]\+\) \([^ ]\+\) <\([^>]\+\)> \(.*\)/\1: \4 2-- \2, \3/p
+}' "$state_file"
+
+# delete messages
+sed -i '/^'"$to"' /{
+ d
+}' "$state_file"
diff --git a/Reaktor/commands/tell-on_privmsg b/Reaktor/commands/tell-on_privmsg
new file mode 100755
index 00000000..5d0aff41
--- /dev/null
+++ b/Reaktor/commands/tell-on_privmsg
@@ -0,0 +1,17 @@
+#! /bin/sh
+set -euf
+
+# require flock from util-linux
+if test "${FLOCK-}" != "$state_file"; then
+ exec env FLOCK="$state_file" flock "$state_file" "$0" "$@"
+fi
+
+from="$_prefix"
+to="$1"; shift
+msg="$*"
+date=$(date)
+
+# TODO tell now, if already joined
+printf '%s %s <%s> %s\n' "$to" "$from" "$date" "$msg" >> "$state_file"
+
+echo 'Consider it noted.' # that's what lambdabot says...
diff --git a/Reaktor/commands/visit-page b/Reaktor/commands/visit-page
new file mode 120000
index 00000000..8723336b
--- /dev/null
+++ b/Reaktor/commands/visit-page
@@ -0,0 +1 @@
+../repos/view-website/runner.sh \ No newline at end of file
diff --git a/Reaktor/commands/whatweb b/Reaktor/commands/whatweb
index afe20360..68f8aa38 100755
--- a/Reaktor/commands/whatweb
+++ b/Reaktor/commands/whatweb
@@ -4,4 +4,4 @@ here=$(dirname `readlink -f $0`)
whatweb_bin="$here/../repos/whatweb/whatweb"
[ ! -e "$whatweb_bin" ] && echo "!! Whatweb app does not exist" && exit 1
[ -z "${1:-}" ] && echo "!! no host given" && exit 1
-exec $whatweb_bin -a 3 "$1" 2>&1
+exec $whatweb_bin --user-agent="Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0" -a 3 "$1" 2>&1
diff --git a/Reaktor/config.py b/Reaktor/config.py
index 2dd6ac2f..b1158271 100644
--- a/Reaktor/config.py
+++ b/Reaktor/config.py
@@ -1,37 +1,82 @@
+from os.path import abspath, expanduser
+import re
-debug = True
+debug = False
-# CAVEAT name should not contains regex magic
name = 'crabmanner'
+#workdir = expanduser('~') + '/state'
+workdir = '/home/reaktor/state'
+
irc_alarm_timeout = 300
irc_hammer_interval = 10
irc_kill_timeout = 360
irc_nickname = name
irc_server = 'irc.freenode.org'
irc_port = 6667
+irc_restart_timeout = 5
irc_channels = [
'#krebs'
]
+admin_file=workdir+'/admin.lst'
+auth_file=workdir+'/auth.lst'
+
+config_filename = abspath(__file__)
+
+# me is used, so name cannot kill our patterns below
+me = '\\b' + re.escape(name) + '\\b'
+me_or_us = '(?:' + me + '|\\*)'
-def default_command(cmd):
+def default_command(cap, cmd=None, env=None):
+ if not env: env = {}
+ if cmd == None: cmd=cap
return {
- 'capname': cmd,
- 'pattern': '^(?:' + name + '|\\*):\\s*' + cmd + '\\s*(?:\\s+(?P<args>.*))?$',
- 'argv': [ 'commands/' + cmd ] }
+ 'capname': cap,
+ 'pattern': '^' + me_or_us + ':\\s*' + cap + '\\s*(?:\\s+(?P<args>.*))?$',
+ 'argv': [ 'commands/' + cmd ],
+ 'env': env
+ }
-commands = [
- default_command('caps'),
+def simple_command(cap, cmd=None, env={}):
+ if cmd == None: cmd=cap
+ return {
+ 'capname': cap,
+ 'pattern': '^' + cap + '\\s*(?:\\s+(?P<args>.*))?$',
+ 'argv' : [ 'commands/' + cmd ],
+ 'env': env
+ }
+
+public_commands = [
+ default_command('caps', env={
+ 'config_filename': config_filename
+ }),
default_command('hello'),
- default_command('reload'),
default_command('badcommand'),
default_command('rev'),
default_command('uptime'),
default_command('nocommand'),
+ default_command('tell', cmd='tell-on_privmsg', env={
+ 'state_file': workdir + '/tell.txt'
+ }),
# command not found
- { 'pattern': '^(?:' + name + '|\\*):.*',
+ { 'pattern': '^' + me_or_us + ':.*',
'argv': [ 'commands/respond','You are made of stupid!'] },
# "highlight"
- { 'pattern': '.*\\b' + name + '\\b.*',
- 'argv': [ 'commands/say', 'I\'m famous' ] }
+ { 'pattern': '.*' + me + '.*',
+ 'argv': [ 'commands/say', 'I\'m famous' ] },
+ # identify via direct connect
+ simple_command('identify', env={
+ 'config_filename': config_filename
+ })
+]
+commands = [
+ default_command('reload')
+]
+
+on_join = [
+ {
+ 'capname': 'tell',
+ 'argv': [ 'commands/tell-on_join' ],
+ 'env': { 'state_file': workdir + '/tell.txt' }
+ }
]
diff --git a/Reaktor/startup/conf.d/reaktor b/Reaktor/etc/conf.d/reaktor
index a4f3f8e1..a4f3f8e1 100644
--- a/Reaktor/startup/conf.d/reaktor
+++ b/Reaktor/etc/conf.d/reaktor
diff --git a/Reaktor/startup/init.d/reaktor-debian b/Reaktor/etc/init.d/reaktor-debian
index a94384f4..a94384f4 100755
--- a/Reaktor/startup/init.d/reaktor-debian
+++ b/Reaktor/etc/init.d/reaktor-debian
diff --git a/Reaktor/startup/supervisor/Reaktor.conf b/Reaktor/etc/supervisor/Reaktor.conf
index 497066e9..497066e9 100644
--- a/Reaktor/startup/supervisor/Reaktor.conf
+++ b/Reaktor/etc/supervisor/Reaktor.conf
diff --git a/Reaktor/etc/systemd/system/Reaktor.service b/Reaktor/etc/systemd/system/Reaktor.service
new file mode 100644
index 00000000..6bb3e550
--- /dev/null
+++ b/Reaktor/etc/systemd/system/Reaktor.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Reaktor for user %i
+After=network.target nss-lookup.target
+
+[Service]
+Type=normal
+#TODO - make reaktor path variable
+User=reaktor
+ExecStart=/krebs/painload/Reaktor/index
+Restart=always
+RestartSec=3
+
+[Install]
+WantedBy=multi-user.target
diff --git a/Reaktor/index b/Reaktor/index
index ac647ca3..fc59cd73 100755
--- a/Reaktor/index
+++ b/Reaktor/index
@@ -4,4 +4,4 @@ set -euf
# cd //Reaktor
cd $(dirname $(readlink -f $0))
-exec IRC/index
+exec IRC/index "$@"
diff --git a/Reaktor/repos/view-website b/Reaktor/repos/view-website
new file mode 160000
+Subproject a3892837aabd5d95e997c0fd2526096f685669f
diff --git a/Reaktor/repos/whatweb b/Reaktor/repos/whatweb
-Subproject 0918a0d9b75df77f9c3e9eb360b6b22824582a2
+Subproject 362145cf80ccd82d4c32e15b37eeff745e0ba66
diff --git a/Reaktor/titlebot/commands/clear b/Reaktor/titlebot/commands/clear
new file mode 100755
index 00000000..e3558194
--- /dev/null
+++ b/Reaktor/titlebot/commands/clear
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+import json
+from os import environ
+import sys
+import os
+# krebs polling
+import poll
+
+f = 'suggestions.json'
+title=" ".join(sys.argv[1:])
+db = poll.save_db(f,[])
+print("cleared database")
diff --git a/Reaktor/titlebot/commands/down b/Reaktor/titlebot/commands/down
new file mode 100755
index 00000000..8964382d
--- /dev/null
+++ b/Reaktor/titlebot/commands/down
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "not implemented"
diff --git a/Reaktor/titlebot/commands/help b/Reaktor/titlebot/commands/help
new file mode 100755
index 00000000..f04e43b7
--- /dev/null
+++ b/Reaktor/titlebot/commands/help
@@ -0,0 +1,11 @@
+#!/bin/sh
+cat <<EOF
+BGT Title Poll Bot:
+ .new TITLE - suggest a new title
+ .list <(age|votes)> - list all suggestions
+ .highest <NUM> - lists the NUM highest voted suggestions
+ .up NUM (NUM ...) - upvote one or more suggestions from .list
+ .undo NUM (NUM ...) - undo an upvote
+ .clear - clear the poll (auth required)
+EOF
+
diff --git a/Reaktor/titlebot/commands/highest b/Reaktor/titlebot/commands/highest
new file mode 100755
index 00000000..d0408ac0
--- /dev/null
+++ b/Reaktor/titlebot/commands/highest
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+import json
+from os import environ
+import sys
+import os
+import poll
+
+f = 'suggestions.json'
+title=" ".join(sys.argv[1:])
+db = poll.load_db(f)
+# only print the last N values (default 1)
+limit = int(sys.argv[1]) if len(sys.argv) > 1 else 1
+num = 0
+last_vote = 9001
+# stolen from http://stackoverflow.com/questions/9647202/ordinal-numbers-replacement
+suffixes = ["th", "st", "nd", "rd", ] + ["th"] * 16
+
+for entry in poll.sort_by_votes(db):
+ # if two entries have the same number of upvotes, do not increment the rank
+ current_vote = sum(entry['votes'].values())
+ if current_vote < last_vote:
+ num = num + 1
+ last_vote = current_vote
+ # exit if we are above the limit
+ if num > limit:
+ sys.exit(0)
+
+ suffixed_num = str(num) + suffixes[num % 100]
+ print("%s: '%s' (%d votes)" %
+ (suffixed_num,entry['title'],sum(entry['votes'].values())))
diff --git a/Reaktor/titlebot/commands/list b/Reaktor/titlebot/commands/list
new file mode 100755
index 00000000..cee4b8a8
--- /dev/null
+++ b/Reaktor/titlebot/commands/list
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+import json
+from os import environ
+import sys
+import os
+import poll
+
+f = 'suggestions.json'
+title=" ".join(sys.argv[1:])
+db = poll.load_db(f)
+if len(sys.argv) > 1 and ("-h" in sys.argv[1] or "usage" == sys.argv[1]):
+ print("""usage: list <(age|votes)>
+ sort by age or by votes (default: age)
+""")
+ sys.exit(0)
+
+if len(sys.argv) > 1 and ("votes" in sys.argv[1]):
+ use = poll.sort_by_votes(db)
+elif len(sys.argv) > 1 and ("age" in sys.argv[1]) or len(sys.argv) == 1:
+ use = db
+else:
+ print("unknown sorting method")
+ sys.exit(1)
+
+for entry in use:
+ print("#%d %s (votes: %d)" %
+ (db.index(entry),entry['title'],sum(entry['votes'].values())))
diff --git a/Reaktor/titlebot/commands/new b/Reaktor/titlebot/commands/new
new file mode 100755
index 00000000..7246a2b2
--- /dev/null
+++ b/Reaktor/titlebot/commands/new
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+import json
+from os import environ
+import sys
+import os
+# krebs polling
+import poll
+
+f = 'suggestions.json'
+title=" ".join(sys.argv[1:])
+db = poll.load_db(f)
+
+suggester = environ['_from']
+if not poll.title_in_db(title,db):
+ db.append( { 'by': suggester,
+ 'votes':{},'title': title})
+ print("Thank you for your suggestion '%s'!"%environ["_from"])
+ print("To vote type '.up %d'"%(len(db)-1))
+poll.save_db(f,db)
diff --git a/Reaktor/titlebot/commands/poll.py b/Reaktor/titlebot/commands/poll.py
new file mode 100644
index 00000000..595ab269
--- /dev/null
+++ b/Reaktor/titlebot/commands/poll.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+import json
+def load_db(f):
+ try:
+ with open(f) as fl:
+ return json.load(fl)
+ except:
+ #default db is []
+ return []
+
+def title_in_db(t,d):
+ for index,entry in enumerate(d):
+ if t == entry['title']:
+ print("Title is already in list.")
+ print("To vote for this type '.up %d'" %index)
+ return True
+ return False
+def save_db(f,db):
+ with open(f,"w") as x:
+ json.dump(db,x)
+
+def sort_by_votes(db):
+ return sorted(db,key=lambda entry:sum(entry['votes'].values()),reverse=True)
diff --git a/Reaktor/titlebot/commands/undo b/Reaktor/titlebot/commands/undo
new file mode 100755
index 00000000..a66de67f
--- /dev/null
+++ b/Reaktor/titlebot/commands/undo
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+import json
+from os import environ
+import sys
+import os
+# krebs polling
+import poll
+
+f = 'suggestions.json'
+db = poll.load_db(f)
+votes = []
+try:
+ votes = sys.argv[1:]
+except:
+ print("""usage: undo number (...)
+ undos vote of one or more entries based on .list""")
+ sys.exit(1)
+voter = environ['_prefix']
+voter_name = environ['_from']
+for vote in votes:
+ try:
+ vote = int(vote)
+ if not voter in db[vote]['votes']:
+ print("%s, you never voted for '%s'!"%(voter_name,db[vote]['title']))
+ else:
+ del(db[vote]['votes'][voter] )
+ print("%s undid vote for '%s'" %(voter_name,db[vote]['title'] ))
+ except:
+ print("%s undo voting for #%d failed" %(voter_name,vote))
+
+poll.save_db(f,db)
diff --git a/Reaktor/titlebot/commands/up b/Reaktor/titlebot/commands/up
new file mode 100755
index 00000000..0a48bdb0
--- /dev/null
+++ b/Reaktor/titlebot/commands/up
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+import json
+from os import environ
+import sys
+import os
+# krebs polling
+import poll
+
+f = 'suggestions.json'
+db = poll.load_db(f)
+votes = []
+votes = sys.argv[1:]
+if not votes:
+ print("""usage: up number (...)
+ upvotes one or more entries based on .list""")
+ sys.exit(1)
+
+voter = environ['_prefix']
+voter_name =environ['_from']
+for vote in votes:
+ try:
+ vote = int(vote)
+ if vote < 0:
+ raise Exception()
+ if voter in db[vote]['votes']:
+ print("%s, you already have voted for '%s'"%(voter_name,db[vote]['title']) )
+ else:
+ db[vote]['votes'][voter] = 1
+ print("%s voted for '%s'"%(voter_name,db[vote]['title']))
+ except:
+ print("%s, voting for #%s failed" %(voter_name,vote))
+
+poll.save_db(f,db)
diff --git a/Reaktor/titlebot/titlebot.py b/Reaktor/titlebot/titlebot.py
new file mode 100644
index 00000000..c1eac3b0
--- /dev/null
+++ b/Reaktor/titlebot/titlebot.py
@@ -0,0 +1,77 @@
+from os import environ,mkdir
+from os.path import abspath, expanduser
+import re
+debug = False
+
+# CAVEAT name should not contains regex magic
+name = 'bgt_titlebot'
+
+workdir = '/tmp/state'
+
+try:
+ mkdir(workdir)
+except: pass
+
+irc_alarm_timeout = 300
+irc_hammer_interval = 10
+irc_kill_timeout = 360
+irc_nickname = name
+irc_server = 'irc.freenode.org'
+irc_port = 6667
+irc_restart_timeout = 5
+irc_channels = [
+ '#binaergewitter'
+]
+admin_file=workdir+'/admin.lst'
+auth_file=workdir+'/auth.lst'
+
+config_filename = abspath(__file__)
+
+try:
+ with open(admin_file,"x"): pass
+except: pass
+
+# me is used, so name cannot kill our patterns below
+me = '\\b' + re.escape(name) + '\\b'
+me_or_us = '(?:' + me + '|\\*)'
+
+def default_command(cmd, env=None):
+ if not env: env = {}
+ return {
+ 'capname': cmd,
+ 'pattern': '^' + me_or_us + ':\\s*' + cmd + '\\s*(?:\\s+(?P<args>.*))?$',
+ 'argv': [ 'commands/' + cmd ],
+ 'env': env
+ }
+def titlebot_cmd(cmd):
+ return {
+ 'capname': cmd,
+ 'pattern': '^\\.' + cmd + '\\s*(?:\\s+(?P<args>.*))?$',
+ 'argv': [ 'titlebot/commands/' + cmd ] }
+
+public_commands = [
+ default_command('caps', env={
+ 'config_filename': config_filename
+ }),
+ default_command('hello'),
+ default_command('badcommand'),
+ default_command('rev'),
+ default_command('uptime'),
+ default_command('nocommand'),
+ titlebot_cmd('list'),
+ titlebot_cmd('help'),
+ titlebot_cmd('highest'),
+ titlebot_cmd('up'),
+ titlebot_cmd('new'),
+ titlebot_cmd('undo'),
+ titlebot_cmd('down'),
+ # identify via direct connect
+ { 'capname': 'identify',
+ 'pattern': '^identify' + '\\s*(?:\\s+(?P<args>.*))?$',
+ 'argv' : [ 'commands/identify' ]}
+]
+commands = [
+ default_command('reload'),
+ titlebot_cmd('clear')
+]
+