diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 20e8101..5fa7446 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ persist config +config.ssl gitflow *.db *.log diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 8810f2f..5495bc9 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -22,6 +22,7 @@ frdmn We are using code from the following projects: ./plugins/mlia.py - https://github.com/infinitylabs/UguuBot ./plugins/horoscope.py - https://github.com/infinitylabs/UguuBot +color section in ./plugins/utility.py - https://github.com/hitzler/homero Special Thanks: Rmmh (created skybot!) diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 906ead3..06d64ac --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ CloudBot is a Python IRC bot based on [Skybot](http://git.io/skybot) by [rmmh](h ## Getting and using CloudBot -### Download +### Download Get CloudBot at [https://github.com/ClouDev/CloudBot/zipball/develop](https://github.com/ClouDev/CloudBot/zipball/develop "Get CloudBot from Github!"). @@ -20,6 +20,9 @@ Before you can run the bot, you need to install a few Python dependencies. LXML These can be installed with `pip` (The Python package manager): [sudo] pip install -r requirements.txt + +If you use `pip`, you will also need the following packages on linux or `pip` will fail to install the requirements. + ```python, python-dev, libenchant-dev, libenchant1c2a, libxslt-dev, libxml2-dev.``` #### How to install `pip` diff --git a/bot.py b/cloudbot.py similarity index 69% rename from bot.py rename to cloudbot.py index adbb1f2..91515db 100755 --- a/bot.py +++ b/cloudbot.py @@ -1,47 +1,28 @@ #!/usr/bin/env python -__author__ = "ClouDev" -__authors__ = ["Lukeroge", "neersighted"] -__copyright__ = "Copyright 2012-2013, ClouDev" -__credits__ = ["thenoodle", "_frozen", "rmmh"] -__license__ = "GPL v3" -__version__ = "DEV" -__maintainer__ = "ClouDev" -__email__ = "cloudev@neersighted.com" -__status__ = "Development" - import os import Queue import sys import time -import platform import re -sys.path += ['plugins', 'lib'] # so 'import hook' works without duplication +sys.path += ['plugins', 'lib'] # add stuff to the sys.path for easy imports os.chdir(sys.path[0] or '.') # do stuff relative to the install directory class Bot(object): pass +print 'CloudBot DEV ' -print 'CloudBot %s (%s) ' % (__version__, __status__) - -# print debug info -opsys = platform.platform() -python_imp = platform.python_implementation() -python_ver = platform.python_version() -architecture = ' '.join(platform.architecture()) - -print "Operating System: %s, Python " \ - "Version: %s %s, Architecture: %s" \ - "" % (opsys, python_imp, python_ver, architecture) - +# create new bot object bot = Bot() bot.vars = {} + +# record start time for the uptime command bot.start_time = time.time() -print 'Loading plugins...' +print 'Begin Plugin Loading.' # bootstrap the reloader eval(compile(open(os.path.join('core', 'reload.py'), 'U').read(), diff --git a/config.default b/config.default index 00b2446..4bda1b0 100644 --- a/config.default +++ b/config.default @@ -1,65 +1,77 @@ { - "connections": - { - "esper": - { - "server": "irc.esper.net", - "nick": "MyCloudBot", - "user": "cloudbot", - "realname": "CloudBot - http://git.io/cloudbotirc", - "mode": "", - "nickserv_password": "", - "nickserv_user": "", - "channels": ["#cloudbot", "#cloudbot2"], - "invite_join": true, - "auto_rejoin": false, - "command_prefix": "." - } - }, - "disabled_plugins": [], - "disabled_commands": [], - "acls": {}, - "api_keys": - { - "tvdb": "", - "wolframalpha": "", - "lastfm": "", - "rottentomatoes": "", - "soundcloud": "", - "twitter_consumer_key": "", - "twitter_consumer_secret": "", - "twitter_access_token": "", - "twitter_access_secret": "", - "wunderground": "", - "googletranslate": "", - "rdio_key": "", - "rdio_secret": "", - "steam_key": "" - }, - "permissions": { - "admins": { - "perms": ["adminonly", "addfactoid", "delfactoid", "ignore", "botcontrol", "permissions_users", "op"], - "users": ["examplea!user@example.com", "exampleb!user@example.com"] + "connections": { + "hackint": { + "server": "irc.hackint.eu", + "nick": "antibot", + "user": "antibot", + "realname": "CloudBot - http://git.io/cloudbotirc", + "mode": "", + "_nickserv_password": "", + "-nickserv_user": "", + "channels": [ + "#ChaosChemnitz", + "#logbot" + ], + "invite_join": true, + "auto_rejoin": false, + "command_prefix": "." + } }, - "moderators": { - "perms": ["addfactoid", "delfactoid", "ignore"], - "users": ["examplec!user@example.com"] - } - }, - "plugins": - { - "factoids": - { - "prefix": false + "disabled_plugins": [], + "disabled_commands": [], + "acls": {}, + "api_keys": { + "tvdb": "", + "wolframalpha": "", + "lastfm": "", + "rottentomatoes": "", + "soundcloud": "", + "twitter_consumer_key": "", + "twitter_consumer_secret": "", + "twitter_access_token": "", + "twitter_access_secret": "", + "wunderground": "", + "googletranslate": "", + "rdio_key": "", + "rdio_secret": "" }, - "ignore": - { - "ignored": [] - } - }, - "censored_strings": - [ - "mypass", - "mysecret" - ] + "permissions": { + "admins": { + "perms": [ + "adminonly", + "addfactoid", + "delfactoid", + "ignore", + "botcontrol", + "permissions_users", + "op" + ], + "users": [ + "examplea!user@example.com", + "exampleb!user@example.com" + ] + }, + "moderators": { + "perms": [ + "addfactoid", + "delfactoid", + "ignore" + ], + "users": [ + "stummi!~Stummi@stummi.org" + ] + } + }, + "plugins": { + "factoids": { + "prefix": false + }, + "ignore": { + "ignored": [] + } + }, + "censored_strings": [ + "mypass", + "mysecret" + ] } diff --git a/core/config.py b/core/config.py old mode 100755 new mode 100644 index b81fb4d..c813ea5 --- a/core/config.py +++ b/core/config.py @@ -24,4 +24,4 @@ def config(): print 'error: malformed config', e -bot._config_mtime = 0 \ No newline at end of file +bot._config_mtime = 0 diff --git a/core/db.py b/core/db.py old mode 100755 new mode 100644 diff --git a/core/irc.py b/core/irc.py old mode 100755 new mode 100644 index e075d4b..40831e3 --- a/core/irc.py +++ b/core/irc.py @@ -44,7 +44,16 @@ class crlf_tcp(object): return socket.socket(socket.AF_INET, socket.TCP_NODELAY) def run(self): - self.socket.connect((self.host, self.port)) + noerror = 0 + while 1: + try: + self.socket.connect((self.host, self.port)) + break + except socket.gaierror as e: + time.sleep(5) + except socket.timeout as e: + time.sleep(5) + thread.start_new_thread(self.recv_loop, ()) thread.start_new_thread(self.send_loop, ()) @@ -55,17 +64,25 @@ class crlf_tcp(object): return socket.timeout def handle_receive_exception(self, error, last_timestamp): + print("Receive exception: %s" % (error)) if time.time() - last_timestamp > self.timeout: + print("Receive timeout. Restart connection.") self.iqueue.put(StopIteration) self.socket.close() return True return False + def handle_send_exception(self, error): + print("Send exception: %s" % (error)) + self.iqueue.put(StopIteration) + self.socket.close() + return True + def recv_loop(self): last_timestamp = time.time() while True: try: - data = self.recv_from_socket(4096) + data = self.recv_from_socket(4096) self.ibuffer += data if data: last_timestamp = time.time() @@ -79,6 +96,8 @@ class crlf_tcp(object): if self.handle_receive_exception(e, last_timestamp): return continue + except AttributeError: + return while '\r\n' in self.ibuffer: line, self.ibuffer = self.ibuffer.split('\r\n', 1) @@ -86,13 +105,19 @@ class crlf_tcp(object): def send_loop(self): while True: - line = self.oqueue.get().splitlines()[0][:500] - print ">>> %r" % line - self.obuffer += line.encode('utf-8', 'replace') + '\r\n' - while self.obuffer: - sent = self.socket.send(self.obuffer) - self.obuffer = self.obuffer[sent:] + try: + line = self.oqueue.get().splitlines()[0][:500] + if line == StopIteration: + return + print ">>> %r" % line + self.obuffer += line.encode('utf-8', 'replace') + '\r\n' + while self.obuffer: + sent = self.socket.send(self.obuffer) + self.obuffer = self.obuffer[sent:] + except socket.error as e: + self.handle_send_exception(e) + return class crlf_ssl_tcp(crlf_tcp): """Handles ssl tcp connetions that consist of utf-8 lines ending with crlf""" @@ -114,10 +139,13 @@ class crlf_ssl_tcp(crlf_tcp): def handle_receive_exception(self, error, last_timestamp): # this is terrible - if not "timed out" in error.args[0]: - raise + #if not "timed out" in error.args[0]: + # raise return crlf_tcp.handle_receive_exception(self, error, last_timestamp) + def handle_send_exception(self, error): + return crlf_tcp.handle_send_exception(self, error) + irc_prefix_rem = re.compile(r'(.*?) (.*?) (.*)').match irc_noprefix_rem = re.compile(r'()(.*?) (.*)').match @@ -135,6 +163,7 @@ class IRC(object): self.server = server self.port = port self.nick = nick + self.history = {} self.vars = {} self.out = Queue.Queue() # responses from the server are placed here @@ -205,13 +234,18 @@ class IRC(object): self.channels.remove(channel) def msg(self, target, text): - """ makes the bot send a message to a user """ + """ makes the bot send a PRIVMSG to a target """ self.cmd("PRIVMSG", [target, text]) + def ctcp(self, target, ctcp_type, text): + """ makes the bot send a PRIVMSG CTCP to a target """ + out = u"\x01{} {}\x01".format(ctcp_type, text) + self.cmd("PRIVMSG", [target, out]) + def cmd(self, command, params=None): if params: - params[-1] = ':' + params[-1] - self.send(command + ' ' + ' '.join(map(censor, params))) + params[-1] = u':' + params[-1] + self.send(u"{} {}".format(command, ' '.join(params))) else: self.send(command) diff --git a/core/main.py b/core/main.py old mode 100755 new mode 100644 index 1b8609d..0054b0a --- a/core/main.py +++ b/core/main.py @@ -13,29 +13,34 @@ class Input(dict): if chan == conn.nick.lower(): # is a PM chan = nick - def say(msg, chan=chan): - conn.msg(chan, msg) + def message(message, target=chan): + """sends a message to a specific or current channel/user""" + conn.msg(target, message) - def pm(msg, nick=nick): - conn.msg(nick, msg) - - def reply(msg, chan=chan): - if chan == nick: # PMs don't need prefixes - conn.msg(chan, msg) + def reply(message, target=chan): + """sends a message to the current channel/user with a prefix""" + if target == nick: + conn.msg(target, message) else: - conn.msg(chan, '(' + nick + ') ' + msg) + conn.msg(target, u"({}) {}".format(nick, message)) - def me(msg, chan=chan): - conn.msg(chan, "\x01{} {}\x01".format("ACTION", msg)) + def action(message, target=chan): + """sends an action to the current channel/user or a specific channel/user""" + conn.ctcp(target, "ACTION", message) - def notice(msg, nick=nick): - conn.cmd('NOTICE', [nick, msg]) + def ctcp(message, ctcp_type, target=chan): + """sends an ctcp to the current channel/user or a specific channel/user""" + conn.ctcp(target, ctcp_type, message) + + def notice(message, target=nick): + """sends a notice to the current channel/user or a specific channel/user""" + conn.cmd('NOTICE', [target, message]) dict.__init__(self, conn=conn, raw=raw, prefix=prefix, command=command, params=params, nick=nick, user=user, host=host, mask=mask, paraml=paraml, msg=msg, server=conn.server, chan=chan, - notice=notice, say=say, reply=reply, pm=pm, bot=bot, - me=me, lastparam=paraml[-1]) + notice=notice, message=message, reply=reply, bot=bot, + action=action, ctcp=ctcp, lastparam=paraml[-1]) # make dict keys accessible as attributes def __getattr__(self, key): diff --git a/core/reload.py b/core/reload.py old mode 100755 new mode 100644 diff --git a/plugins/attacks.py b/disabled_stuff/attacks.py similarity index 64% rename from plugins/attacks.py rename to disabled_stuff/attacks.py index 7c778b9..feb00b8 100644 --- a/plugins/attacks.py +++ b/disabled_stuff/attacks.py @@ -1,14 +1,12 @@ -from util import hook import random +from util import hook + + with open("plugins/data/larts.txt") as f: larts = [line.strip() for line in f.readlines() if not line.startswith("//")] -with open("plugins/data/kills.txt") as f: - kills = [line.strip() for line in f.readlines() - if not line.startswith("//")] - with open("plugins/data/insults.txt") as f: insults = [line.strip() for line in f.readlines() if not line.startswith("//")] @@ -17,8 +15,9 @@ with open("plugins/data/flirts.txt") as f: flirts = [line.strip() for line in f.readlines() if not line.startswith("//")] + @hook.command -def lart(inp, me=None, nick=None, conn=None, notice=None): +def lart(inp, action=None, nick=None, conn=None, notice=None): """lart -- LARTs .""" target = inp.strip() @@ -34,31 +33,11 @@ def lart(inp, me=None, nick=None, conn=None, notice=None): phrase = random.choice(larts) # act out the message - me(phrase.format(**values)) + action(phrase.format(**values)) @hook.command -def kill(inp, me=None, nick=None, conn=None, notice=None): - """kill -- Makes the bot kill .""" - target = inp.strip() - - if " " in target: - notice("Invalid username!") - return - - # if the user is trying to make the bot slap itself, slap them - if target.lower() == conn.nick.lower() or target.lower() == "itself": - target = nick - - values = {"user": target} - phrase = random.choice(kills) - - # act out the message - me(phrase.format(**values)) - - -@hook.command -def insult(inp, nick=None, me=None, conn=None, notice=None): +def insult(inp, nick=None, action=None, conn=None, notice=None): """insult -- Makes the bot insult .""" target = inp.strip() @@ -72,11 +51,11 @@ def insult(inp, nick=None, me=None, conn=None, notice=None): target = inp out = 'insults {}... "{}"'.format(target, random.choice(insults)) - me(out) + action(out) @hook.command -def flirt(inp, me=None, conn=None, notice=None): +def flirt(inp, action=None, conn=None, notice=None): """flirt -- Make the bot flirt with .""" target = inp.strip() @@ -90,4 +69,4 @@ def flirt(inp, me=None, conn=None, notice=None): target = inp out = 'flirts with {}... "{}"'.format(target, random.choice(flirts)) - me(out) + action(out) diff --git a/plugins/brainfuck.py b/disabled_stuff/brainfuck.py old mode 100755 new mode 100644 similarity index 95% rename from plugins/brainfuck.py rename to disabled_stuff/brainfuck.py index 1c68b57..a7dc12e --- a/plugins/brainfuck.py +++ b/disabled_stuff/brainfuck.py @@ -1,5 +1,5 @@ -'''brainfuck interpreter adapted from (public domain) code at -http://brainfuck.sourceforge.net/brain.py''' +"""brainfuck interpreter adapted from (public domain) code at +http://brainfuck.sourceforge.net/brain.py""" import re import random diff --git a/plugins/choose.py b/disabled_stuff/choose.py old mode 100755 new mode 100644 similarity index 90% rename from plugins/choose.py rename to disabled_stuff/choose.py index 4e7df10..f478328 --- a/plugins/choose.py +++ b/disabled_stuff/choose.py @@ -6,7 +6,7 @@ from util import hook @hook.command def choose(inp): - """choose , [choice2], [choice3], [choice4], ... -- + """choose , [choice2], [choice3], [choice4], ... -- Randomly picks one of the given choices.""" c = re.findall(r'([^,]+)', inp) @@ -15,4 +15,4 @@ def choose(inp): if len(c) == 1: return 'The decision is up to you!' - return random.choice(c).strip() \ No newline at end of file + return random.choice(c).strip() diff --git a/plugins/cleverbot.py b/disabled_stuff/cleverbot.py similarity index 100% rename from plugins/cleverbot.py rename to disabled_stuff/cleverbot.py diff --git a/plugins/coin.py b/disabled_stuff/coin.py old mode 100755 new mode 100644 similarity index 61% rename from plugins/coin.py rename to disabled_stuff/coin.py index 78668bb..7cc2a2a --- a/plugins/coin.py +++ b/disabled_stuff/coin.py @@ -1,9 +1,10 @@ -from util import hook import random +from util import hook + @hook.command(autohelp=False) -def coin(inp, me=None): +def coin(inp, action=None): """coin [amount] -- Flips [amount] of coins.""" if inp: @@ -15,10 +16,10 @@ def coin(inp, me=None): amount = 1 if amount == 1: - me("flips a coin and gets {}.".format(random.choice(["heads", "tails"]))) + action("flips a coin and gets {}.".format(random.choice(["heads", "tails"]))) elif amount == 0: - me("makes a coin flipping motion with its hands.") + action("makes a coin flipping motion with its hands.") else: heads = int(random.normalvariate(.5 * amount, (.75 * amount) ** .5)) tails = amount - heads - me("flips {} coins and gets {} heads and {} tails.".format(amount, heads, tails)) + action("flips {} coins and gets {} heads and {} tails.".format(amount, heads, tails)) diff --git a/disabled_stuff/correction.py b/disabled_stuff/correction.py new file mode 100644 index 0000000..7617e11 --- /dev/null +++ b/disabled_stuff/correction.py @@ -0,0 +1,37 @@ +from util import hook + +import re + +CORRECTION_RE = r'^(s|S)/.*/.*/?\S*$' + + +@hook.regex(CORRECTION_RE) +def correction(match, input=None, conn=None, message=None): + split = input.msg.split("/") + + if len(split) == 4: + nick = split[3].lower() + else: + nick = None + + find = split[1] + replace = split[2] + + for item in conn.history[input.chan].__reversed__(): + name, timestamp, msg = item + if msg.startswith("s/"): + # don't correct corrections, it gets really confusing + continue + if nick: + if nick != name.lower(): + continue + if find in msg: + if "\x01ACTION" in msg: + msg = msg.replace("\x01ACTION ", "/me ").replace("\x01", "") + message(u"Correction, <{}> {}".format(name, msg.replace(find, "\x02" + replace + "\x02"))) + return + else: + continue + + return u"Did not find {} in any recent messages.".format(find) + diff --git a/disabled_stuff/cryptocoins.py b/disabled_stuff/cryptocoins.py new file mode 100644 index 0000000..42d5945 --- /dev/null +++ b/disabled_stuff/cryptocoins.py @@ -0,0 +1,60 @@ +from util import http, hook + +## CONSTANTS + +exchanges = { + "blockchain": { + "api_url": "https://blockchain.info/ticker", + "func": lambda data: u"Blockchain // Buy: \x0307${:,.2f}\x0f -" + u" Sell: \x0307${:,.2f}\x0f".format(data["USD"]["buy"], data["USD"]["sell"]) + }, + "coinbase": { + "api_url": "https://coinbase.com/api/v1/prices/spot_rate", + "func": lambda data: u"Coinbase // Current: \x0307${:,.2f}\x0f".format(float(data['amount'])) + }, + "bitpay": { + "api_url": "https://bitpay.com/api/rates", + "func": lambda data: u"Bitpay // Current: \x0307${:,.2f}\x0f".format(data[0]['rate']) + }, + "bitstamp": { + "api_url": "https://www.bitstamp.net/api/ticker/", + "func": lambda data: u"BitStamp // Current: \x0307${:,.2f}\x0f - High: \x0307${:,.2f}\x0f -" + u" Low: \x0307${:,.2f}\x0f - Volume: {:,.2f} BTC".format(float(data['last']), + float(data['high']), + float(data['low']), + float(data['volume'])) + } +} + + +## HOOK FUNCTIONS + +@hook.command("btc", autohelp=False) +@hook.command(autohelp=False) +def bitcoin(inp): + """bitcoin -- Gets current exchange rate for bitcoins from several exchanges, default is Blockchain. + Supports MtGox, Bitpay, Coinbase and BitStamp.""" + inp = inp.lower() + + if inp: + if inp in exchanges: + exchange = exchanges[inp] + else: + return "Invalid Exchange" + else: + exchange = exchanges["blockchain"] + + data = http.get_json(exchange["api_url"]) + func = exchange["func"] + return func(data) + + +@hook.command("ltc", autohelp=False) +@hook.command(autohelp=False) +def litecoin(inp, message=None): + """litecoin -- gets current exchange rate for litecoins from BTC-E""" + data = http.get_json("https://btc-e.com/api/2/ltc_usd/ticker") + ticker = data['ticker'] + message("Current: \x0307${:,.2f}\x0f - High: \x0307${:,.2f}\x0f" + " - Low: \x0307${:,.2f}\x0f - Volume: {:,.2f} LTC".format(ticker['buy'], ticker['high'], ticker['low'], + ticker['vol_cur'])) diff --git a/plugins/cypher.py b/disabled_stuff/cypher.py old mode 100755 new mode 100644 similarity index 86% rename from plugins/cypher.py rename to disabled_stuff/cypher.py index 1527778..b54248a --- a/plugins/cypher.py +++ b/disabled_stuff/cypher.py @@ -1,4 +1,5 @@ import base64 + from util import hook @@ -10,9 +11,10 @@ def encode(key, clear): enc.append(enc_c) return base64.urlsafe_b64encode("".join(enc)) + def decode(key, enc): dec = [] - enc = base64.urlsafe_b64decode(enc.encode('ascii','ignore')) + enc = base64.urlsafe_b64decode(enc.encode('ascii', 'ignore')) for i in range(len(enc)): key_c = key[i % len(key)] dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256) @@ -26,7 +28,7 @@ def cypher(inp): passwd = inp.split(" ")[0] inp = " ".join(inp.split(" ")[1:]) - return encode(passwd,inp) + return encode(passwd, inp) @hook.command @@ -34,4 +36,4 @@ def decypher(inp): """decypher -- Decyphers with .""" passwd = inp.split(" ")[0] inp = " ".join(inp.split(" ")[1:]) - return decode(passwd,inp) + return decode(passwd, inp) diff --git a/plugins/data/8ball_responses.txt b/disabled_stuff/data/8ball_responses.txt similarity index 100% rename from plugins/data/8ball_responses.txt rename to disabled_stuff/data/8ball_responses.txt diff --git a/disabled_stuff/data/GeoLiteCity.dat b/disabled_stuff/data/GeoLiteCity.dat new file mode 100644 index 0000000..e94f60e Binary files /dev/null and b/disabled_stuff/data/GeoLiteCity.dat differ diff --git a/plugins/data/flirts.txt b/disabled_stuff/data/flirts.txt old mode 100755 new mode 100644 similarity index 87% rename from plugins/data/flirts.txt rename to disabled_stuff/data/flirts.txt index f5eed69..6490da8 --- a/plugins/data/flirts.txt +++ b/disabled_stuff/data/flirts.txt @@ -44,4 +44,11 @@ Do you live on a chicken farm? Because you sure know how to raise cocks. Are you wearing space pants? Because your ass is out of this world. Nice legs. What time do they open? Are you lost? Because it’s so strange to see an angel so far from heaven. -Your daddy must have been a baker, because you've got a nice set of buns. \ No newline at end of file +Your daddy must have been a baker, because you've got a nice set of buns. +You're so beautiful that last night you made me forget my pickup line. +I've never seen such dark eyes with so much light in them. +I think we should just be friends with sexual tension. +Whenever I see you I feel like a dog dying to get out of the car. +If I'd have held you any closer I'd be in back of you. +I wish I were on Facebook so I could poke you. +I want you like JFK wanted a car with a roof. diff --git a/plugins/data/fortunes.txt b/disabled_stuff/data/fortunes.txt old mode 100755 new mode 100644 similarity index 100% rename from plugins/data/fortunes.txt rename to disabled_stuff/data/fortunes.txt diff --git a/plugins/data/geoip_regions.json b/disabled_stuff/data/geoip_regions.json similarity index 100% rename from plugins/data/geoip_regions.json rename to disabled_stuff/data/geoip_regions.json diff --git a/plugins/data/insults.txt b/disabled_stuff/data/insults.txt old mode 100755 new mode 100644 similarity index 100% rename from plugins/data/insults.txt rename to disabled_stuff/data/insults.txt diff --git a/disabled_stuff/data/itemids.txt b/disabled_stuff/data/itemids.txt new file mode 100644 index 0000000..4f0ce1d --- /dev/null +++ b/disabled_stuff/data/itemids.txt @@ -0,0 +1,620 @@ + 1 Stone + 1:1 Granite + 1:2 Polished Granite + 1:3 Diorite + 1:4 Polished Diorite + 1:5 Andesite + 1:6 Polished Andesite + 2 Grass + 3 Dirt + 3:1 Dirt (No Grass) + 3:2 Podzol + 4 Cobblestone + 5 Wooden Plank (Oak) + 5:1 Wooden Plank (Spruce) + 5:2 Wooden Plank (Birch) + 5:3 Wooden Plank (Jungle) + 5:4 Wooden Plank (Acacia) + 5:5 Wooden Plank (Dark Oak) + 6 Sapling (Oak) + 6:1 Sapling (Spruce) + 6:2 Sapling (Birch) + 6:3 Sapling (Jungle) + 6:4 Sapling (Acacia) + 6:5 Sapling (Dark Oak) + 7 Bedrock + 8 Water + 9 Water (No Spread) + 10 Lava + 11 Lava (No Spread) + 12 Sand + 12:1 Red Sand + 13 Gravel + 14 Gold Ore + 15 Iron Ore + 16 Coal Ore + 17 Wood (Oak) + 17:1 Wood (Spruce) + 17:2 Wood (Birch) + 17:3 Wood (Jungle) + 17:4 Wood (Oak 4) + 17:5 Wood (Oak 5) + 18 Leaves (Oak) + 18:1 Leaves (Spruce) + 18:2 Leaves (Birch) + 18:3 Leaves (Jungle) + 19 Sponge + 20 Glass + 21 Lapis Lazuli Ore + 22 Lapis Lazuli Block + 23 Dispenser + 24 Sandstone + 24:1 Sandstone (Chiseled) + 24:2 Sandstone (Smooth) + 25 Note Block + 26 Bed (Block) + 27 Rail (Powered) + 28 Rail (Detector) + 29 Sticky Piston + 30 Cobweb + 31 Tall Grass (Dead Shrub) + 31:1 Tall Grass + 31:2 Tall Grass (Fern) + 32 Dead Shrub + 33 Piston + 34 Piston (Head) + 35 Wool + 35:1 Orange Wool + 35:2 Magenta Wool + 35:3 Light Blue Wool + 35:4 Yellow Wool + 35:5 Lime Wool + 35:6 Pink Wool + 35:7 Gray Wool + 35:8 Light Gray Wool + 35:9 Cyan Wool + 35:10 Purple Wool + 35:11 Blue Wool + 35:12 Brown Wool + 35:13 Green Wool + 35:14 Red Wool + 35:15 Black Wool + 36 Piston (Moving) + 37 Dandelion + 38 Poppy + 38:1 Blue Orchid + 38:2 Allium + 38:4 Red Tulip + 38:5 Orange Tulip + 38:6 White Tulip + 38:7 Pink Tulip + 38:8 Oxeye Daisy + 39 Brown Mushroom + 40 Red Mushroom + 41 Block of Gold + 42 Block of Iron + 43 Stone Slab (Double) + 43:1 Sandstone Slab (Double) + 43:2 Wooden Slab (Double) + 43:3 Cobblestone Slab (Double) + 43:4 Brick Slab (Double) + 43:5 Stone Brick Slab (Double) + 43:6 Nether Brick Slab (Double) + 43:7 Quartz Slab (Double) + 43:8 Smooth Stone Slab (Double) + 43:9 Smooth Sandstone Slab (Double) + 44 Stone Slab + 44:1 Sandstone Slab + 44:2 Wooden Slab + 44:3 Cobblestone Slab + 44:4 Brick Slab + 44:5 Stone Brick Slab + 44:6 Nether Brick Slab + 44:7 Quartz Slab + 45 Brick + 46 TNT + 47 Bookshelf + 48 Moss Stone + 49 Obsidian + 50 Torch + 51 Fire + 52 Mob Spawner + 53 Wooden Stairs (Oak) + 54 Chest + 55 Redstone Wire + 56 Diamond Ore + 57 Block of Diamond + 58 Workbench + 59 Wheat (Crop) + 60 Farmland + 61 Furnace + 62 Furnace (Smelting) + 63 Sign (Block) + 64 Wood Door (Block) + 65 Ladder + 66 Rail + 67 Cobblestone Stairs + 68 Sign (Wall Block) + 69 Lever + 70 Stone Pressure Plate + 71 Iron Door (Block) + 72 Wooden Pressure Plate + 73 Redstone Ore + 74 Redstone Ore (Glowing) + 75 Redstone Torch (Off) + 76 Redstone Torch + 77 Button (Stone) + 78 Snow + 79 Ice + 80 Snow Block + 81 Cactus + 82 Clay Block + 83 Sugar Cane (Block) + 84 Jukebox + 85 Fence + 86 Pumpkin + 87 Netherrack + 88 Soul Sand + 89 Glowstone + 90 Portal + 91 Jack-O-Lantern + 92 Cake (Block) + 93 Redstone Repeater (Block Off) + 94 Redstone Repeater (Block On) + 95 Stained Glass (White) + 95:1 Stained Glass (Orange) + 95:2 Stained Glass (Magenta) + 95:3 Stained Glass (Light Blue) + 95:4 Stained Glass (Yellow) + 95:5 Stained Glass (Lime) + 95:6 Stained Glass (Pink) + 95:7 Stained Glass (Gray) + 95:8 Stained Glass (Light Grey) + 95:9 Stained Glass (Cyan) + 95:10 Stained Glass (Purple) + 95:11 Stained Glass (Blue) + 95:12 Stained Glass (Brown) + 95:13 Stained Glass (Green) + 95:14 Stained Glass (Red) + 95:15 Stained Glass (Black) + 96 Trapdoor + 97 Monster Egg (Stone) + 97:1 Monster Egg (Cobblestone) + 97:2 Monster Egg (Stone Brick) + 97:3 Monster Egg (Mossy Stone Brick) + 97:4 Monster Egg (Cracked Stone) + 97:5 Monster Egg (Chiseled Stone) + 98 Stone Bricks + 98:1 Mossy Stone Bricks + 98:2 Cracked Stone Bricks + 98:3 Chiseled Stone Brick + 99 Brown Mushroom (Block) + 100 Red Mushroom (Block) + 101 Iron Bars + 102 Glass Pane + 103 Melon (Block) + 104 Pumpkin Vine + 105 Melon Vine + 106 Vines + 107 Fence Gate + 108 Brick Stairs + 109 Stone Brick Stairs + 110 Mycelium + 111 Lily Pad + 112 Nether Brick + 113 Nether Brick Fence + 114 Nether Brick Stairs + 115 Nether Wart + 116 Enchantment Table + 117 Brewing Stand (Block) + 118 Cauldron (Block) + 119 End Portal + 120 End Portal Frame + 121 End Stone + 122 Dragon Egg + 123 Redstone Lamp + 124 Redstone Lamp (On) + 125 Oak-Wood Slab (Double) + 125:1 Spruce-Wood Slab (Double) + 125:2 Birch-Wood Slab (Double) + 125:3 Jungle-Wood Slab (Double) + 125:4 Acacia Wood Slab (Double) + 125:5 Dark Oak Wood Slab (Double) + 126 Oak-Wood Slab + 126:1 Spruce-Wood Slab + 126:2 Birch-Wood Slab + 126:3 Jungle-Wood Slab + 126:4 Acacia Wood Slab + 126:5 Dark Oak Wood Slab + 127 Cocoa Plant + 128 Sandstone Stairs + 129 Emerald Ore + 130 Ender Chest + 131 Tripwire Hook + 132 Tripwire + 133 Block of Emerald + 134 Wooden Stairs (Spruce) + 135 Wooden Stairs (Birch) + 136 Wooden Stairs (Jungle) + 137 Command Block + 138 Beacon + 139 Cobblestone Wall + 139:1 Mossy Cobblestone Wall + 140 Flower Pot (Block) + 141 Carrot (Crop) + 142 Potatoes (Crop) + 143 Button (Wood) + 144 Head Block (Skeleton) + 144:1 Head Block (Wither) + 144:2 Head Block (Zombie) + 144:3 Head Block (Steve) + 144:4 Head Block (Creeper) + 145 Anvil + 145:1 Anvil (Slightly Damaged) + 145:2 Anvil (Very Damaged) + 146 Trapped Chest + 147 Weighted Pressure Plate (Light) + 148 Weighted Pressure Plate (Heavy) + 149 Redstone Comparator (Off) + 150 Redstone Comparator (On) + 151 Daylight Sensor + 152 Block of Redstone + 153 Nether Quartz Ore + 154 Hopper + 155 Quartz Block + 155:1 Chiseled Quartz Block + 155:2 Pillar Quartz Block + 156 Quartz Stairs + 157 Rail (Activator) + 158 Dropper + 159 Stained Clay (White) + 159:1 Stained Clay (Orange) + 159:2 Stained Clay (Magenta) + 159:3 Stained Clay (Light Blue) + 159:4 Stained Clay (Yellow) + 159:5 Stained Clay (Lime) + 159:6 Stained Clay (Pink) + 159:7 Stained Clay (Gray) + 159:8 Stained Clay (Light Gray) + 159:9 Stained Clay (Cyan) + 159:10 Stained Clay (Purple) + 159:11 Stained Clay (Blue) + 159:12 Stained Clay (Brown) + 159:13 Stained Clay (Green) + 159:14 Stained Clay (Red) + 159:15 Stained Clay (Black) + 160 Stained Glass Pane (White) + 160:1 Stained Glass Pane (Orange) + 160:2 Stained Glass Pane (Magenta) + 160:3 Stained Glass Pane (Light Blue) + 160:4 Stained Glass Pane (Yellow) + 160:5 Stained Glass Pane (Lime) + 160:6 Stained Glass Pane (Pink) + 160:7 Stained Glass Pane (Gray) + 160:8 Stained Glass Pane (Light Gray) + 160:9 Stained Glass Pane (Cyan) + 160:10 Stained Glass Pane (Purple) + 160:11 Stained Glass Pane (Blue) + 160:12 Stained Glass Pane (Brown) + 160:13 Stained Glass Pane (Green) + 160:14 Stained Glass Pane (Red) + 160:15 Stained Glass Pane (Black) + 162 Wood (Acacia Oak) + 162:1 Wood (Dark Oak) + 163 Wooden Stairs (Acacia) + 164 Wooden Stairs (Dark Oak) + 165 Slime Block + 170 Hay Bale + 171 Carpet (White) + 171:1 Carpet (Orange) + 171:2 Carpet (Magenta) + 171:3 Carpet (Light Blue) + 171:4 Carpet (Yellow) + 171:5 Carpet (Lime) + 171:6 Carpet (Pink) + 171:7 Carpet (Grey) + 171:8 Carpet (Light Gray) + 171:9 Carpet (Cyan) + 171:10 Carpet (Purple) + 171:11 Carpet (Blue) + 171:12 Carpet (Brown) + 171:13 Carpet (Green) + 171:14 Carpet (Red) + 171:15 Carpet (Black) + 172 Hardened Clay + 173 Block of Coal + 174 Packed Ice + 175 Sunflower + 175:1 Lilac + 175:2 Double Tallgrass + 175:3 Large Fern + 175:4 Rose Bush + 175:5 Peony + 256 Iron Shovel + 257 Iron Pickaxe + 258 Iron Axe + 259 Flint and Steel + 260 Apple + 261 Bow + 262 Arrow + 263 Coal + 263:1 Charcoal + 264 Diamond Gem + 265 Iron Ingot + 266 Gold Ingot + 267 Iron Sword + 268 Wooden Sword + 269 Wooden Shovel + 270 Wooden Pickaxe + 271 Wooden Axe + 272 Stone Sword + 273 Stone Shovel + 274 Stone Pickaxe + 275 Stone Axe + 276 Diamond Sword + 277 Diamond Shovel + 278 Diamond Pickaxe + 279 Diamond Axe + 280 Stick + 281 Bowl + 282 Mushroom Stew + 283 Gold Sword + 284 Gold Shovel + 285 Gold Pickaxe + 286 Gold Axe + 287 String + 288 Feather + 289 Gunpowder + 290 Wooden Hoe + 291 Stone Hoe + 292 Iron Hoe + 293 Diamond Hoe + 294 Gold Hoe + 295 Wheat Seeds + 296 Wheat + 297 Bread + 298 Leather Helmet + 299 Leather Chestplate + 300 Leather Leggings + 301 Leather Boots + 302 Chainmail Helmet + 303 Chainmail Chestplate + 304 Chainmail Leggings + 305 Chainmail Boots + 306 Iron Helmet + 307 Iron Chestplate + 308 Iron Leggings + 309 Iron Boots + 310 Diamond Helmet + 311 Diamond Chestplate + 312 Diamond Leggings + 313 Diamond Boots + 314 Gold Helmet + 315 Gold Chestplate + 316 Gold Leggings + 317 Gold Boots + 318 Flint + 319 Raw Porkchop + 320 Cooked Porkchop + 321 Painting + 322 Golden Apple + 322:1 Enchanted Golden Apple + 323 Sign + 324 Wooden Door + 325 Bucket + 326 Bucket (Water) + 327 Bucket (Lava) + 328 Minecart + 329 Saddle + 330 Iron Door + 331 Redstone Dust + 332 Snowball + 333 Boat + 334 Leather + 335 Bucket (Milk) + 336 Clay Brick + 337 Clay + 338 Sugar Cane + 339 Paper + 340 Book + 341 Slime Ball + 342 Minecart (Storage) + 343 Minecart (Powered) + 344 Egg + 345 Compass + 346 Fishing Rod + 347 Watch + 348 Glowstone Dust + 349 Raw Fish + 349:1 Raw Salmon + 349:2 Clownfish + 349:3 Pufferfish + 350 Cooked Fish + 350:1 Cooked Salmon + 350:2 Clownfish + 350:3 Pufferfish + 351 Ink Sack + 351:1 Rose Red Dye + 351:2 Cactus Green Dye + 351:3 Cocoa Bean + 351:4 Lapis Lazuli + 351:5 Purple Dye + 351:6 Cyan Dye + 351:7 Light Gray Dye + 351:8 Gray Dye + 351:9 Pink Dye + 351:10 Lime Dye + 351:11 Dandelion Yellow Dye + 351:12 Light Blue Dye + 351:13 Magenta Dye + 351:14 Orange Dye + 351:15 Bone Meal + 352 Bone + 353 Sugar + 354 Cake + 355 Bed + 356 Redstone Repeater + 357 Cookie + 358 Map + 359 Shears + 360 Melon (Slice) + 361 Pumpkin Seeds + 362 Melon Seeds + 363 Raw Beef + 364 Steak + 365 Raw Chicken + 366 Cooked Chicken + 367 Rotten Flesh + 368 Ender Pearl + 369 Blaze Rod + 370 Ghast Tear + 371 Gold Nugget + 372 Nether Wart Seeds + 373 Water Bottle + 373:16 Awkward Potion + 373:32 Thick Potion + 373:64 Mundane Potion + 373:8193 Regeneration Potion (0:45) + 373:8194 Swiftness Potion (3:00) + 373:8195 Fire Resistance Potion (3:00) + 373:8196 Poison Potion (0:45) + 373:8197 Healing Potion + 373:8198 Night Vision Potion (3:00) + 373:8200 Weakness Potion (1:30) + 373:8201 Strength Potion (3:00) + 373:8202 Slowness Potion (1:30) + 373:8204 Harming Potion + 373:8205 Water Breathing Potion (3:00) + 373:8206 Invisibility Potion (3:00) + 373:8225 Regeneration Potion II (0:22) + 373:8226 Swiftness Potion II (1:30) + 373:8228 Poison Potion II (0:22) + 373:8229 Healing Potion II + 373:8233 Strength Potion II (1:30) + 373:8236 Harming Potion II + 373:8257 Regeneration Potion (2:00) + 373:8258 Swiftness Potion (8:00) + 373:8259 Fire Resistance Potion (8:00) + 373:8260 Poison Potion (2:00) + 373:8262 Night Vision Potion (8:00) + 373:8264 Weakness Potion (4:00) + 373:8265 Strength Potion (8:00) + 373:8266 Slowness Potion (4:00) + 373:8269 Water Breathing Potion (8:00) + 373:8270 Invisibility Potion (8:00) + 373:8289 Regeneration Potion II (1:00) + 373:8290 Swiftness Potion II (4:00) + 373:8292 Poison Potion II (1:00) + 373:8297 Strength Potion II (4:00) + 373:16385 Regeneration Splash (0:33) + 373:16386 Swiftness Splash (2:15) + 373:16387 Fire Resistance Splash (2:15) + 373:16388 Poison Splash (0:33) + 373:16389 Healing Splash + 373:16390 Night Vision Splash (2:15) + 373:16392 Weakness Splash (1:07) + 373:16393 Strength Splash (2:15) + 373:16394 Slowness Splash (1:07) + 373:16396 Harming Splash + 373:16397 Breathing Splash (2:15) + 373:16398 Invisibility Splash (2:15) + 373:16417 Regeneration Splash II (0:16) + 373:16418 Swiftness Splash II (1:07) + 373:16420 Poison Splash II (0:16) + 373:16421 Healing Splash II + 373:16425 Strength Splash II (1:07) + 373:16428 Harming Splash II + 373:16449 Regeneration Splash (1:30) + 373:16450 Swiftness Splash (6:00) + 373:16451 Fire Resistance Splash (6:00) + 373:16452 Poison Splash (1:30) + 373:16454 Night Vision Splash (6:00) + 373:16456 Weakness Splash (3:00) + 373:16457 Strength Splash (6:00) + 373:16458 Slowness Splash (3:00) + 373:16461 Breathing Splash (6:00) + 373:16462 Invisibility Splash (6:00) + 373:16481 Regeneration Splash II (0:45) + 373:16482 Swiftness Splash II (3:00) + 373:16484 Poison Splash II (0:45) + 373:16489 Strength Splash II (3:00) + 374 Glass Bottle + 375 Spider Eye + 376 Fermented Spider Eye + 377 Blaze Powder + 378 Magma Cream + 379 Brewing Stand + 380 Cauldron + 381 Eye of Ender + 382 Glistering Melon (Slice) + 383:50 Spawn Egg (Creeper) + 383:51 Spawn Egg (Skeleton) + 383:52 Spawn Egg (Spider) + 383:54 Spawn Egg (Zombie) + 383:55 Spawn Egg (Slime) + 383:56 Spawn Egg (Ghast) + 383:57 Spawn Egg (Zombie Pigmen) + 383:58 Spawn Egg (Endermen) + 383:59 Spawn Egg (Cave Spider) + 383:60 Spawn Egg (Silverfish) + 383:61 Spawn Egg (Blaze) + 383:62 Spawn Egg (Magma Cube) + 383:65 Spawn Egg (Bat) + 383:66 Spawn Egg (Witch) + 383:90 Spawn Egg (Pig) + 383:91 Spawn Egg (Sheep) + 383:92 Spawn Egg (Cow) + 383:93 Spawn Egg (Chicken) + 383:94 Spawn Egg (Squid) + 383:95 Spawn Egg (Wolf) + 383:96 Spawn Egg (Mooshroom) + 383:98 Spawn Egg (Ocelot) + 383:100 Spawn Egg (Horse) + 383:120 Spawn Egg (Villager) + 384 Bottle of Enchanting + 385 Fire Charge + 386 Book and Quill + 387 Written Book + 388 Emerald + 389 Item Frame + 390 Flower Pot + 391 Carrot + 392 Potato + 393 Baked Potato + 394 Poisonous Potato + 395 Empty Map + 396 Golden Carrot + 397 Head (Skeleton) + 397:1 Head (Wither) + 397:2 Head (Zombie) + 397:3 Head (Steve) + 397:4 Head (Creeper) + 398 Carrot on a Stick + 399 Nether Star + 400 Pumpkin Pie + 401 Firework Rocket + 402 Firework Star + 403 Enchanted Book + 404 Redstone Comparator + 405 Nether Brick (Item) + 406 Nether Quartz + 407 Minecart (TNT) + 408 Minecart (Hopper) + 417 Iron Horse Armor + 418 Gold Horse Armor + 419 Diamond Horse Armor + 420 Lead + 421 Name Tag + 422 Minecart (Command Block) + 2256 Music Disk (13) + 2257 Music Disk (Cat) + 2258 Music Disk (Blocks) + 2259 Music Disk (Chirp) + 2260 Music Disk (Far) + 2261 Music Disk (Mall) + 2262 Music Disk (Mellohi) + 2263 Music Disk (Stal) + 2264 Music Disk (Strad) + 2265 Music Disk (Ward) + 2266 Music Disk (11) + 2267 Music Disk (Wait) diff --git a/disabled_stuff/data/kills.json b/disabled_stuff/data/kills.json new file mode 100644 index 0000000..5f6d046 --- /dev/null +++ b/disabled_stuff/data/kills.json @@ -0,0 +1,79 @@ +{ + "templates": [ + "rips off {user}'s {limbs} and leaves them to die.", + "grabs {user}'s head and rips it clean off their body.", + "grabs a {gun} and riddles {user}'s body with bullets.", + "gags and ties {user} then throws them off a {tall_thing}.", + "crushes {user} with a huge spiked {spiked_thing}.", + "glares at {user} until they die of boredom.", + "stabs {user} in the heart a few times with a {weapon_stab}.", + "rams a {weapon_explosive} up {user}'s ass and lets off a few rounds.", + "crushes {user}'s skull in with a {weapon_crush}.", + "unleashes the armies of Isengard on {user}.", + "gags and ties {user} then throws them off a {tall_thing} to their death.", + "reaches out and punches right through {user}'s chest.", + "slices {user}'s limbs off with a {weapon_slice}.", + "throws {user} to Cthulu and watches them get ripped to shreds.", + "feeds {user} to an owlbear who then proceeds to maul them violently.", + "turns {user} into a snail and covers then in salt.", + "snacks on {user}'s dismembered body.", + "stuffs {bomb} up {user}'s ass and waits for it to go off.", + "puts {user} into a sack, throws the sack in the river, and hurls the river into space.", + "goes bowling with {user}'s bloody disembodied head.", + "sends {user} to /dev/null!", + "feeds {user} coke and mentos till they violently explode." + ], + "parts": { + "gun": [ + "AK47", + "machine gun", + "automatic pistol", + "Uzi" + ], + "limbs": [ + "legs", + "arms", + "limbs" + ], + "weapon_stab": [ + "knife", + "shard of glass", + "sword blade", + "butchers knife", + "corkscrew" + ], + "weapon_slice": [ + "sharpened katana", + "chainsaw", + "polished axe" + ], + "weapon_crush": [ + "spiked mace", + "baseball bat", + "wooden club", + "massive steel ball", + "heavy iron rod" + ], + "weapon_explosive": [ + "rocket launcher", + "grenade launcher", + "napalm launcher" + ], + "tall_thing": [ + "bridge", + "tall building", + "cliff", + "mountain" + ], + "spiked_thing": [ + "boulder", + "rock", + "barrel of rocks" + ], + "bomb": [ + "a bomb", + "some TNT", + "a bunch of C4" + ] + } +} diff --git a/plugins/data/kills.txt b/disabled_stuff/data/kills.txt old mode 100755 new mode 100644 similarity index 100% rename from plugins/data/kills.txt rename to disabled_stuff/data/kills.txt diff --git a/plugins/data/larts.txt b/disabled_stuff/data/larts.txt old mode 100755 new mode 100644 similarity index 100% rename from plugins/data/larts.txt rename to disabled_stuff/data/larts.txt diff --git a/plugins/data/name_files/dragons.json b/disabled_stuff/data/name_files/dragons.json similarity index 100% rename from plugins/data/name_files/dragons.json rename to disabled_stuff/data/name_files/dragons.json diff --git a/plugins/data/name_files/dwarves.json b/disabled_stuff/data/name_files/dwarves.json similarity index 100% rename from plugins/data/name_files/dwarves.json rename to disabled_stuff/data/name_files/dwarves.json diff --git a/plugins/data/name_files/elves_female.json b/disabled_stuff/data/name_files/elves_female.json similarity index 100% rename from plugins/data/name_files/elves_female.json rename to disabled_stuff/data/name_files/elves_female.json diff --git a/plugins/data/name_files/elves_male.json b/disabled_stuff/data/name_files/elves_male.json similarity index 100% rename from plugins/data/name_files/elves_male.json rename to disabled_stuff/data/name_files/elves_male.json diff --git a/plugins/data/name_files/fantasy.json b/disabled_stuff/data/name_files/fantasy.json similarity index 100% rename from plugins/data/name_files/fantasy.json rename to disabled_stuff/data/name_files/fantasy.json diff --git a/plugins/data/name_files/female.json b/disabled_stuff/data/name_files/female.json similarity index 100% rename from plugins/data/name_files/female.json rename to disabled_stuff/data/name_files/female.json diff --git a/plugins/data/name_files/general.json b/disabled_stuff/data/name_files/general.json similarity index 100% rename from plugins/data/name_files/general.json rename to disabled_stuff/data/name_files/general.json diff --git a/plugins/data/name_files/hobbits.json b/disabled_stuff/data/name_files/hobbits.json similarity index 100% rename from plugins/data/name_files/hobbits.json rename to disabled_stuff/data/name_files/hobbits.json diff --git a/plugins/data/name_files/inns.json b/disabled_stuff/data/name_files/inns.json similarity index 100% rename from plugins/data/name_files/inns.json rename to disabled_stuff/data/name_files/inns.json diff --git a/plugins/data/name_files/items.json b/disabled_stuff/data/name_files/items.json similarity index 100% rename from plugins/data/name_files/items.json rename to disabled_stuff/data/name_files/items.json diff --git a/plugins/data/name_files/male.json b/disabled_stuff/data/name_files/male.json similarity index 100% rename from plugins/data/name_files/male.json rename to disabled_stuff/data/name_files/male.json diff --git a/plugins/data/name_files/narn.json b/disabled_stuff/data/name_files/narn.json similarity index 100% rename from plugins/data/name_files/narn.json rename to disabled_stuff/data/name_files/narn.json diff --git a/plugins/data/name_files/warrior_cats.json b/disabled_stuff/data/name_files/warrior_cats.json similarity index 100% rename from plugins/data/name_files/warrior_cats.json rename to disabled_stuff/data/name_files/warrior_cats.json diff --git a/plugins/data/recipes.txt b/disabled_stuff/data/recipes.txt old mode 100755 new mode 100644 similarity index 68% rename from plugins/data/recipes.txt rename to disabled_stuff/data/recipes.txt index 14ce8cf..2b0e1db --- a/plugins/data/recipes.txt +++ b/disabled_stuff/data/recipes.txt @@ -1,7 +1,7 @@ //Minecraft Recipes List //Created by _303 //Obtained from https://github.com/ClouDev/CloudBot/blob/develop/plugins/data/recipes.txt -//Edited by _frozen +//Edited by CHCMATT for Minecraft version: 1.7.4 // //Summary of Use: Each column is seperated by a comma (,) and rows by a vertical bar (|). Order of Recipes & Categories taken from //www.minecraftwiki.net/wiki/Crafting for easier updating in the future (The Future!) @@ -21,7 +21,10 @@ 1x Block of Gold: Gold Ingot, Gold Ingot, Gold Ingot | Gold Ingot, Gold Ingot, Gold Ingot | Gold Ingot, Gold Ingot, Gold Ingot 1x Block of Iron: Iron Ingot, Iron Ingot, Iron Ingot | Iron Ingot, Iron Ingot, Iron Ingot | Iron Ingot, Iron Ingot, Iron Ingot 1x Block of Diamond: Diamond, Diamond, Diamond | Diamond, Diamond, Diamond | Diamond, Diamond, Diamond +1x Block of Coal: Coal, Coal, Coal | Coal, Coal, Coal | Coal, Coal, Coal +1x Block of Redstone: Redstone Dust, Redstone Dust, Redstone Dust | Redstone Dust, Redstone Dust, Redstone Dust | Redstone Dust, Redstone Dust, Redstone Dust 1x Lapis Lazuli Block: Lapis Lazuli, Lapis Lazuli, Lapis Lazuli | Lapis Lazuli, Lapis Lazuli, Lapis Lazuli | Lapis Lazuli, Lapis Lazuli, Lapis Lazuli +1x Emerald Block: Emerald, Emerald, Emerald | Emerald, Emerald, Emerald | Emerald, Emerald, Emerald 1x Glowstone: Glowstone Dust, Glowstone Dust | Glowstone Dust, Glowstone Dust 1x Wool: String, String | String, String 1x TNT: Gunpowder, Sand, Gunpowder | Sand, Gunpowder, Sand | Gunpowder, Sand, Gunpowder @@ -117,6 +120,7 @@ 6x Powered Rail: Gold Ingot, None, Gold Ingot | Gold Ingot, Stick, Gold Ingot | Gold Ingot, Redstone, Gold Ingot 6x Detector Rail: Iron Ingot, None, Iron Ingot | Iron Ingot, Pressure Plate, Iron Ingot | Iron Ingot, Redstone, Iron Ingot 1x Boat: Wooden Planks, None, Wooden Planks | Wooden Planks, Wooden Planks, Wooden Planks +1x Carrot On A Stick: Fishing Rod | None, Carrot // //Mechanism Recipes // @@ -125,7 +129,8 @@ 2x Trapdoor: Wooden Planks, Wooden Planks, Wooden Planks | Wooden Planks, Wooden Planks, Wooden Planks 1x Stone Pressure Plate: Stone, Stone 1x Wooden Pressure Plate: Wooden Planks, Wooden Planks -1x Button: Stone | Stone +1x Stone Button: Stone +1x Wooden Button: Wooden Planks 1x Redstone Torch: Redstone | Stick 1x Lever: Stick | Cobblestone 1x Note Block: Wooden Planks, Wooden Planks, Wooden Planks | Wooden Planks, Redstone, Wooden Planks | Wooden Planks, Wooden Planks, Wooden Planks @@ -133,8 +138,13 @@ 1x Dispenser: Cobblestone, Cobblestone, Cobblestone | Cobblestone, Bow, Cobblestone | Cobblestone, Redstone, Cobblestone 1x Redstone Repeater: Redstone Torch, Redstone, Redstone Torch | Stone, Stone, Stone 1x Piston: Wooden Planks, Wooden Planks, Wooden Planks | Cobblestone, Iron Ingot, Cobblestone | Cobblestone, Redstone, Cobblestone -1x Sticky Piston: none, slime ball, none | none, piston, none -1x Redstone Lamp: none, redstone dust, none | redstone dust, glowstone block, redstone | none, redstone dust, none +1x Sticky Piston: Slime Ball | Piston +1x Redstone Lamp: None, Redstone Dust, None | Redstone Dust, Glowstone Block, Redstone Dust | None, Redstone Dust, None +1x Trapped Chest: Chest, Tripwire Hook +1x Dropper: Cobblestone, Cobblestone, Cobblestone | Cobblestone, None, Cobblestone | Cobblestone, Redstone Dust, Cobblestone +1x Weighted Pressure Plate (Heavy): Iron Ingot, Iron Ingot +1x Weighted Pressure Plate (Light): Gold Ingot, Gold Ingot +2x Tripwire Hook: Iron Ingot | Stick | Wooden Planks // //Food Recipes // @@ -169,6 +179,11 @@ 9x Gold Nugget: Gold Ingot 1x Gold Ingot: Gold Nugget, Gold Nugget, Gold Nugget | Gold Nugget, Gold Nugget, Gold Nugget | Gold Nugget, Gold Nugget, Gold Nugget 1x Eye of Ender: Ender Pearl | Blaze Powder +1x Item Frame: Stick, Stick, Stick | Stick, Leather, Stick | Stick, Stick, Stick +1x Anvil: Block of Iron, Block of Iron, Block of Iron | None, Iron Ingot, None | Iron Ingot, Iron Ingot, Iron Ingot +1x Ender Chest: Obsidian, Obsidian, Obsidian | Osbidian, Eye of Ender, Obsidian | Obsidian, Obsidian, Obsidian +1x Flower Pot: Brick, None, Brick | None, Brick, None +2x Lead: None, String, String | None, Slime Ball, String | String, None, None // //Dye Recipes // @@ -214,4 +229,41 @@ 1x Fermented Spider Eye: Spider Eye | Brown Mushroom, Sugar 1x Glistering Melon: Melon Slice, Gold Nugget 9x Gold Nugget: Gold Ingot -1x Enchantment Table: None, Book, None | Diamond, Obsidian, Diamond | Obsidian, Obsidian, Obsidian \ No newline at end of file +1x Enchantment Table: None, Book, None | Diamond, Obsidian, Diamond | Obsidian, Obsidian, Obsidian +// +//Stained Glass Recipes +// +8x White Stained Glass: Glass, Glass, Glass | Glass, Bone Meal, Glass | Glass, Glass, Glass +8x Orange Stained Glass: Glass, Glass, Glass | Glass, Orange Dye, Glass | Glass, Glass, Glass +8x Magenta Stained Glass: Glass, Glass, Glass | Glass, Magenta Dye, Glass | Glass, Glass, Glass +8x Light Blue Stained Glass: Glass, Glass, Glass | Glass, Light Blue Dye, Glass | Glass, Glass, Glass +8x Yellow Stained Glass: Glass, Glass, Glass | Glass, Dandelion Yellow, Glass | Glass, Glass, Glass +8x Lime Stained Glass: Glass, Glass, Glass | Glass, Lime Dye, Glass | Glass, Glass, Glass +8x Pink Stained Glass: Glass, Glass, Glass | Glass, Pink Dye, Glass | Glass, Glass, Glass +8x Gray Stained Glass: Glass, Glass, Glass | Glass, Gray Dye, Glass | Glass, Glass, Glass +8x Light Gray Stained Glass: Glass, Glass, Glass | Glass, Light Gray Dye, Glass | Glass, Glass, Glass +8x Cyan Stained Glass: Glass, Glass, Glass | Glass, Cyan Dye, Glass | Glass, Glass, Glass +8x Purple Stained Glass: Glass, Glass, Glass | Glass, Purple Dye, Glass | Glass, Glass, Glass +8x Blue Stained Glass: Glass, Glass, Glass | Glass, Lapis Lazuli, Glass | Glass, Glass, Glass +8x Brown Stained Glass: Glass, Glass, Glass | Glass, Cocoa Beans, Glass | Glass, Glass, Glass +8x Green Stained Glass: Glass, Glass, Glass | Glass, Cactus Green, Glass | Glass, Glass, Glass +8x Red Stained Glass: Glass, Glass, Glass | Glass, Rose Red, Glass | Glass, Glass, Glass +8x Black Stained Glass: Glass, Glass, Glass | Glass, Inc Sac, Glass | Glass, Glass, Glass +// +//Stained Glass Panes +// +16x White Stained Glass Panes: White Stained Glass, White Stained Glass, White Stained Glass | White Stained Glass, White Stained Glass, White Stained Glass +16x Orange Stained Glass Panes: Orange Stained Glass, Orange Stained Glass, Orange Stained Glass | Orange Stained Glass, Orange Stained Glass, Orange Stained Glass +16x Magenta Stained Glass Panes: Magenta Stained Glass, Magenta Stained Glass, Magenta Stained Glass | Magenta Stained Glass, Magenta Stained Glass, Magenta Stained Glass +16x Light Blue Stained Glass Panes: Light Blue Stained Glass, Light Blue Stained Glass, Light Blue Stained Glass | Light Blue Stained Glass, Light Blue Stained Glass, Light Blue Stained Glass +16x Yellow Stained Glass Panes: Yellow Stained Glass, Yellow Stained Glass, Yellow Stained Glass | Yellow Stained Glass, Yellow Stained Glass, Yellow Stained Glass +16x Lime Stained Glass Panes: Lime Stained Glass, Lime Stained Glass, Lime Stained Glass | Lime Stained Glass, Lime Stained Glass, Lime Stained Glass +16x Pink Stained Glass Panes: Pink Stained Glass, Pink Stained Glass, Pink Stained Glass | Pink Stained Glass, Pink Stained Glass, Pink Stained Glass +16x Gray Stained Glass Panes: Gray Stained Glass, Gray Stained Glass, Gray Stained Glass | Gray Stained Glass, Gray Stained Glass, Gray Stained Glass +16x Light Gray Stained Glass Panes: Light Gray Stained Glass, Light Gray Stained Glass, Light Gray Stained Glass | Light Gray Stained Glass, Light Gray Stained Glass, Light Gray Stained Glass +16x Cyan Stained Glass Panes: Cyan Stained Glass, Cyan Stained Glass, Cyan Stained Glass | Cyan Stained Glass, Cyan Stained Glass, Cyan Stained Glass +16x Purple Stained Glass Panes: Purple Stained Glass, Purple Stained Glass, Purple Stained Glass | Purple Stained Glass, Purple Stained Glass, Purple Stained Glass +16x Blue Stained Glass Panes: Blue Stained Glass, Blue Stained Glass, Blue Stained Glass | Blue Stained Glass, Blue Stained Glass, Blue Stained Glass +16x Brown Stained Glass Panes: Brown Stained Glass, Brown Stained Glass, Brown Stained Glass | Brown Stained Glass, Brown Stained Glass, Brown Stained Glass +16x Green Stained Glass Panes: Green Stained Glass, Green Stained Glass, Green Stained Glass | Green Stained Glass, Green Stained Glass, Green Stained Glass +16x Black Stained Glass Panes: Black Stained Glass, Black Stained Glass, Black Stained Glass | Black Stained Glass, Black Stained Glass, Black Stained Glass diff --git a/plugins/data/slaps.json b/disabled_stuff/data/slaps.json similarity index 87% rename from plugins/data/slaps.json rename to disabled_stuff/data/slaps.json index d446cef..6ec0166 100644 --- a/plugins/data/slaps.json +++ b/disabled_stuff/data/slaps.json @@ -9,10 +9,10 @@ "sits on {user}'s face while slamming a {item} into their crotch.", "starts slapping {user} silly with a {item}.", "holds {user} down and repeatedly {hits} them with a {item}.", - "prods {user} with a {mod} {item}.", + "prods {user} with a {item}.", "picks up a {item} and {hits} {user} with it.", "ties {user} to a chair and {throws} a {item} at them.", - "{hits} {user} on the head with a {item}.", + "{hits} {user} {where} with a {item}.", "ties {user} to a pole and whips them with a {item}." ], "parts": { @@ -29,6 +29,8 @@ "diamond sword", "baguette", "physics textbook", + "toaster", + "portrait of Richard Stallman", "television", "mau5head", "five ton truck", @@ -58,10 +60,10 @@ "slaps", "smacks" ], - "mod": [ - "flaming", - "sticky", - "dripping" + "where": [ + "in the chest", + "on the head", + "on the bum" ] } -} \ No newline at end of file +} diff --git a/plugins/data/slogans.txt b/disabled_stuff/data/slogans.txt old mode 100755 new mode 100644 similarity index 100% rename from plugins/data/slogans.txt rename to disabled_stuff/data/slogans.txt diff --git a/plugins/dice.py b/disabled_stuff/dice.py old mode 100755 new mode 100644 similarity index 82% rename from plugins/dice.py rename to disabled_stuff/dice.py index 94e0f46..a89f3d5 --- a/plugins/dice.py +++ b/disabled_stuff/dice.py @@ -14,7 +14,7 @@ sign_re = re.compile(r'[+-]?(?:\d*d)?(?:\d+|F)', re.I) split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I) -def nrolls(count, n): +def n_rolls(count, n): """roll an n-sided die count times""" if n == "F": return [random.randint(-1, 1) for x in xrange(min(count, 100))] @@ -36,7 +36,7 @@ def nrolls(count, n): #@hook.regex(valid_diceroll, re.I) @hook.command def dice(inp): - """dice -- Simulates dicerolls. Example of : + """dice -- Simulates dice rolls. Example of : 'dice 2d20-d5+4 roll 2'. D20s, subtract 1D5, add 4""" try: # if inp is a re.match object... @@ -49,7 +49,7 @@ def dice(inp): spec = whitespace_re.sub('', inp) if not valid_diceroll_re.match(spec): - return "Invalid diceroll" + return "Invalid dice roll" groups = sign_re.findall(spec) total = 0 @@ -59,7 +59,7 @@ def dice(inp): count, side = split_re.match(roll).groups() count = int(count) if count not in " +-" else 1 if side.upper() == "F": # fudge dice are basically 1d3-2 - for fudge in nrolls(count, "F"): + for fudge in n_rolls(count, "F"): if fudge == 1: rolls.append("\x033+\x0F") elif fudge == -1: @@ -73,14 +73,15 @@ def dice(inp): side = int(side) try: if count > 0: - dice = nrolls(count, side) - rolls += map(str, dice) - total += sum(dice) + d = n_rolls(count, side) + rolls += map(str, d) + total += sum(d) else: - dice = nrolls(-count, side) - rolls += [str(-x) for x in dice] - total -= sum(dice) + d = n_rolls(-count, side) + rolls += [str(-x) for x in d] + total -= sum(d) except OverflowError: + # I have never seen this happen. If you make this happen, you win a cookie return "Thanks for overflowing a float, jerk >:[" if desc: diff --git a/plugins/dictionary.py b/disabled_stuff/dictionary.py old mode 100755 new mode 100644 similarity index 92% rename from plugins/dictionary.py rename to disabled_stuff/dictionary.py index ca74c36..5b4123b --- a/plugins/dictionary.py +++ b/disabled_stuff/dictionary.py @@ -1,5 +1,6 @@ # Plugin by GhettoWizard and Scaevolus import re + from util import hook from util import http @@ -18,10 +19,10 @@ def define(inp): '//div[@class="example"]') if not definition: - return 'No results for ' + inp + ' :(' + return u'No results for {} :('.format(inp) def format_output(show_examples): - result = '{}: '.format(h.xpath('//dt[@class="title-word"]/a/text()')[0]) + result = u'{}: '.format(h.xpath('//dt[@class="title-word"]/a/text()')[0]) correction = h.xpath('//span[@class="correct-word"]/text()') if correction: @@ -76,7 +77,7 @@ def etymology(inp): etym = h.xpath('//dl') if not etym: - return 'No etymology found for {} :('.format(inp) + return u'No etymology found for {} :('.format(inp) etym = etym[0].text_content() diff --git a/plugins/domainr.py b/disabled_stuff/domainr.py similarity index 69% rename from plugins/domainr.py rename to disabled_stuff/domainr.py index 804d237..e853bfa 100644 --- a/plugins/domainr.py +++ b/disabled_stuff/domainr.py @@ -9,10 +9,10 @@ def domainr(inp): except (http.URLError, http.HTTPError) as e: return "Unable to get data for some reason. Try again later." if data['query'] == "": - return "An error occurrred: {status} - {message}".format(**data['error']) + return "An error occurred: {status} - {message}".format(**data['error']) domains = "" for domain in data['results']: domains += ("\x034" if domain['availability'] == "taken" else ( - "\x033" if domain['availability'] == "available" else "\x031")) + domain['domain'] + "\x0f" + domain[ - 'path'] + ", " + "\x033" if domain['availability'] == "available" else "\x031")) + domain['domain'] + "\x0f" + domain[ + 'path'] + ", " return "Domains: " + domains diff --git a/plugins/down.py b/disabled_stuff/down.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/down.py rename to disabled_stuff/down.py diff --git a/plugins/drama.py b/disabled_stuff/drama.py old mode 100755 new mode 100644 similarity index 99% rename from plugins/drama.py rename to disabled_stuff/drama.py index 351c5c8..d348cba --- a/plugins/drama.py +++ b/disabled_stuff/drama.py @@ -1,6 +1,8 @@ -from util import hook, http, text import re +from util import hook, http, text + + api_url = "http://encyclopediadramatica.se/api.php?action=opensearch" ed_url = "http://encyclopediadramatica.se/" diff --git a/plugins/eightball.py b/disabled_stuff/eightball.py old mode 100755 new mode 100644 similarity index 84% rename from plugins/eightball.py rename to disabled_stuff/eightball.py index 141c464..8d91303 --- a/plugins/eightball.py +++ b/disabled_stuff/eightball.py @@ -1,6 +1,8 @@ import random + from util import hook, text + color_codes = { "": "\x02\x0305", "": "\x02\x0303", @@ -13,9 +15,9 @@ with open("plugins/data/8ball_responses.txt") as f: @hook.command('8ball') -def eightball(input, me=None): +def eightball(inp, action=None): """8ball -- The all knowing magic eight ball, in electronic form. Ask and it shall be answered!""" magic = text.multiword_replace(random.choice(responses), color_codes) - me("shakes the magic 8 ball... {}".format(magic)) + action("shakes the magic 8 ball... {}".format(magic)) diff --git a/disabled_stuff/encrypt.py b/disabled_stuff/encrypt.py new file mode 100644 index 0000000..e391a04 --- /dev/null +++ b/disabled_stuff/encrypt.py @@ -0,0 +1,105 @@ +import os +import base64 +import json +import hashlib + +from Crypto import Random +from Crypto.Cipher import AES +from Crypto.Protocol.KDF import PBKDF2 + +from util import hook + + +# helper functions to pad and unpad a string to a specified block size +# +BS = AES.block_size +pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) +unpad = lambda s: s[0:-ord(s[-1])] + +# helper functions to encrypt and encode a string with AES and base64 +encode_aes = lambda c, s: base64.b64encode(c.encrypt(pad(s))) +decode_aes = lambda c, s: unpad(c.decrypt(base64.b64decode(s))) + +db_ready = False + + +def db_init(db): + """check to see that our db has the the encryption table.""" + global db_ready + if not db_ready: + db.execute("create table if not exists encryption(encrypted, iv, " + "primary key(encrypted))") + db.commit() + db_ready = True + + +def get_salt(bot): + """generate an encryption salt if none exists, then returns the salt""" + if not bot.config.get("random_salt", False): + bot.config["random_salt"] = hashlib.md5(os.urandom(16)).hexdigest() + json.dump(bot.config, open('config', 'w'), sort_keys=True, indent=2) + return bot.config["random_salt"] + + +@hook.command +def encrypt(inp, bot=None, db=None, notice=None): + """encrypt -- Encrypts with . ( can only be decrypted using this bot)""" + db_init(db) + + split = inp.split(" ") + + # if there is only one argument, return the help message + if len(split) == 1: + notice(encrypt.__doc__) + return + + # generate the key from the password and salt + password = split[0] + salt = get_salt(bot) + key = PBKDF2(password, salt) + + # generate the IV and encode it to store in the database + iv = Random.new().read(AES.block_size) + iv_encoded = base64.b64encode(iv) + + # create the AES cipher and encrypt/encode the text with it + text = " ".join(split[1:]) + cipher = AES.new(key, AES.MODE_CBC, iv) + encoded = encode_aes(cipher, text) + + # store the encoded text and IV in the DB for decoding later + db.execute("insert or replace into encryption(encrypted, iv)" + "values(?,?)", (encoded, iv_encoded)) + db.commit() + + return encoded + + +@hook.command +def decrypt(inp, bot=None, db=None, notice=None): + """decrypt -- Decrypts with . (can only decrypt strings encrypted on this bot)""" + if not db_ready: + db_init(db) + + split = inp.split(" ") + + # if there is only one argument, return the help message + if len(split) == 1: + notice(decrypt.__doc__) + return + + # generate the key from the password and salt + password = split[0] + salt = get_salt(bot) + key = PBKDF2(password, salt) + + text = " ".join(split[1:]) + + # get the encoded IV from the database and decode it + iv_encoded = db.execute("select iv from encryption where" + " encrypted=?", (text,)).fetchone()[0] + iv = base64.b64decode(iv_encoded) + + # create AES cipher, decode text, decrypt text, and unpad it + cipher = AES.new(key, AES.MODE_CBC, iv) + return decode_aes(cipher, text) diff --git a/plugins/fact.py b/disabled_stuff/fact.py old mode 100755 new mode 100644 similarity index 80% rename from plugins/fact.py rename to disabled_stuff/fact.py index ab05f17..1d48ae7 --- a/plugins/fact.py +++ b/disabled_stuff/fact.py @@ -2,7 +2,7 @@ from util import hook, http, web @hook.command(autohelp=False) -def fact(inp, say=False, nick=False): +def fact(inp): """fact -- Gets a random fact from OMGFACTS.""" attempts = 0 @@ -20,10 +20,10 @@ def fact(inp, say=False, nick=False): response = soup.find('a', {'class': 'surprise'}) link = response['href'] - fact = ''.join(response.find(text=True)) + fact_data = ''.join(response.find(text=True)) - if fact: - fact = fact.strip() + if fact_data: + fact_data = fact_data.strip() break else: if attempts > 2: @@ -34,4 +34,4 @@ def fact(inp, say=False, nick=False): url = web.try_isgd(link) - return "{} - {}".format(fact, url) + return "{} - {}".format(fact_data, url) diff --git a/plugins/factoids.py b/disabled_stuff/factoids.py old mode 100755 new mode 100644 similarity index 83% rename from plugins/factoids.py rename to disabled_stuff/factoids.py index a42ed56..403e6f5 --- a/plugins/factoids.py +++ b/disabled_stuff/factoids.py @@ -1,10 +1,14 @@ # Written by Scaevolus 2010 -from util import hook, http, text, execute import string import re +from util import hook, http, text, pyexec + + re_lineends = re.compile(r'[\r\n]*') +db_ready = False + # some simple "shortcodes" for formatting purposes shortcodes = { '[b]': '\x02', @@ -16,9 +20,12 @@ shortcodes = { def db_init(db): - db.execute("create table if not exists mem(word, data, nick," - " primary key(word))") - db.commit() + global db_ready + if not db_ready: + db.execute("create table if not exists mem(word, data, nick," + " primary key(word))") + db.commit() + db_ready = True def get_memory(db, word): @@ -105,8 +112,8 @@ def info(inp, notice=None, db=None): @hook.regex(r'^\? ?(.+)') -def factoid(inp, say=None, db=None, bot=None, me=None, conn=None, input=None): - "? -- Shows what data is associated with ." +def factoid(inp, message=None, db=None, bot=None, action=None, conn=None, input=None): + """? -- Shows what data is associated with .""" try: prefix_on = bot.config["plugins"]["factoids"].get("prefix", False) except KeyError: @@ -130,9 +137,9 @@ def factoid(inp, say=None, db=None, bot=None, me=None, conn=None, input=None): if data.startswith(""): code = data[4:].strip() variables = 'input="""{}"""; nick="{}"; chan="{}"; bot_nick="{}";'.format(arguments.replace('"', '\\"'), - input.nick, input.chan, - input.conn.nick) - result = execute.eval_py(variables + code) + input.nick, input.chan, + input.conn.nick) + result = pyexec.eval_py(variables + code) else: result = data @@ -141,15 +148,15 @@ def factoid(inp, say=None, db=None, bot=None, me=None, conn=None, input=None): if result.startswith(""): result = result[5:].strip() - me(result) + action(result) elif result.startswith(""): url = result[5:].strip() try: - say(http.get(url)) + message(http.get(url)) except http.HttpError: - say("Could not fetch URL.") + message("Could not fetch URL.") else: if prefix_on: - say("\x02[{}]:\x02 {}".format(factoid_id, result)) + message("\x02[{}]:\x02 {}".format(factoid_id, result)) else: - say(result) + message(result) diff --git a/plugins/fishbans.py b/disabled_stuff/fishbans.py similarity index 98% rename from plugins/fishbans.py rename to disabled_stuff/fishbans.py index 26d86c5..aa76676 100644 --- a/plugins/fishbans.py +++ b/disabled_stuff/fishbans.py @@ -1,6 +1,8 @@ -from util import hook, http from urllib import quote_plus +from util import hook, http + + api_url = "http://api.fishbans.com/stats/{}/" @@ -51,6 +53,5 @@ def bancount(inp): if not out: return "The user \x02{}\x02 has no bans.".format(user) else: - # dat string. return "Bans for \x02{}\x02: ".format(user) + ", ".join(out) + ". More info " \ "at {}".format(user_url) diff --git a/plugins/fmylife.py b/disabled_stuff/fmylife.py old mode 100755 new mode 100644 similarity index 81% rename from plugins/fmylife.py rename to disabled_stuff/fmylife.py index a25b1a4..1d8c0fa --- a/plugins/fmylife.py +++ b/disabled_stuff/fmylife.py @@ -8,9 +8,9 @@ def refresh_cache(): soup = http.get_soup('http://www.fmylife.com/random/') for e in soup.find_all('div', {'class': 'post article'}): - id = int(e['id']) + fml_id = int(e['id']) text = ''.join(e.find('p').find_all(text=True)) - fml_cache.append((id, text)) + fml_cache.append((fml_id, text)) # do an initial refresh of the cache refresh_cache() @@ -21,9 +21,9 @@ def fml(inp, reply=None): """fml -- Gets a random quote from fmyfife.com.""" # grab the last item in the fml cache and remove it - id, text = fml_cache.pop() + fml_id, text = fml_cache.pop() # reply with the fml we grabbed - reply('(#{}) {}'.format(id, text)) + reply('(#{}) {}'.format(fml_id, text)) # refresh fml cache if its getting empty if len(fml_cache) < 3: refresh_cache() diff --git a/plugins/fortune.py b/disabled_stuff/fortune.py old mode 100755 new mode 100644 similarity index 99% rename from plugins/fortune.py rename to disabled_stuff/fortune.py index c32efcb..5f1c478 --- a/plugins/fortune.py +++ b/disabled_stuff/fortune.py @@ -1,6 +1,8 @@ -from util import hook import random +from util import hook + + with open("plugins/data/fortunes.txt") as f: fortunes = [line.strip() for line in f.readlines() if not line.startswith("//")] diff --git a/disabled_stuff/freddy.py b/disabled_stuff/freddy.py new file mode 100644 index 0000000..c77fa5a --- /dev/null +++ b/disabled_stuff/freddy.py @@ -0,0 +1,13 @@ +from util import hook, http, web +from subprocess import check_output, CalledProcessError + +@hook.command +def freddycode(inp): + """freddycode - Check if the Freddy Fresh code is correct.""" + + try: + return "Freddy: '%s' ist %s" % (inp, \ + check_output(["/bin/freddycheck", inp])) + except CalledProcessError as err: + return "Freddy: Skript returned %s" % (str(err)) + diff --git a/plugins/geoip.py b/disabled_stuff/geoip.py old mode 100755 new mode 100644 similarity index 92% rename from plugins/geoip.py rename to disabled_stuff/geoip.py index a4fab57..b7ca61d --- a/plugins/geoip.py +++ b/disabled_stuff/geoip.py @@ -1,17 +1,19 @@ -from util import hook, http import os.path -import pygeoip import json import gzip from StringIO import StringIO +import pygeoip + +from util import hook, http + # load region database with open("./plugins/data/geoip_regions.json", "rb") as f: regions = json.loads(f.read()) if os.path.isfile(os.path.abspath("./plugins/data/GeoLiteCity.dat")): - # initalise geolocation database + # initialise geolocation database geo = pygeoip.GeoIP(os.path.abspath("./plugins/data/GeoLiteCity.dat")) else: download = http.get("http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz") @@ -49,4 +51,4 @@ def geoip(inp): data["cc"] = record["country_code"] or "N/A" data["country"] = record["country_name"] or "Unknown" data["city"] = record["city"] or "Unknown" - return "\x02Country:\x02 {country} ({cc}), \x02City:\x02 {city}{region}".format(**data) + return u"\x02Country:\x02 {country} ({cc}), \x02City:\x02 {city}{region}".format(**data) diff --git a/plugins/github.py b/disabled_stuff/github.py similarity index 92% rename from plugins/github.py rename to disabled_stuff/github.py index 00ee88d..18033ef 100644 --- a/plugins/github.py +++ b/disabled_stuff/github.py @@ -1,12 +1,14 @@ -from util import hook, http import json import urllib2 +from util import hook, http + + shortcuts = {"cloudbot": "ClouDev/CloudBot"} def truncate(msg): - nmsg = msg.split(" ") + nmsg = msg.split() out = None x = 0 for i in nmsg: @@ -50,8 +52,8 @@ def ghissues(inp): return "Repo has no open issues" except ValueError: return "Invalid data returned. Check arguments (.github issues username/repo [number]" - fmt = "Issue: #{} ({}) by {}: {} | {} {}".format(number, state, user.login, title, truncate(body), gitio.gitio(data.url)) - fmt1 = "Issue: #{} ({}) by {}: {} {}".format(number, state, user.login, title, gitio.gitio(data.url)) + fmt = "Issue: #%s (%s) by %s: %s | %s %s" # (number, state, user.login, title, truncate(body), gitio.gitio(data.url)) + fmt1 = "Issue: #%s (%s) by %s: %s %s" # (number, state, user.login, title, gitio.gitio(data.url)) number = data["number"] if data["state"] == "open": state = u"\x033\x02OPEN\x02\x0f" diff --git a/plugins/google.py b/disabled_stuff/google.py old mode 100755 new mode 100644 similarity index 92% rename from plugins/google.py rename to disabled_stuff/google.py index 74b0c18..fe9e288 --- a/plugins/google.py +++ b/disabled_stuff/google.py @@ -1,4 +1,5 @@ import random + from util import hook, http, text @@ -20,8 +21,7 @@ def googleimage(inp): raise IOError('error searching for images: {}: {}'.format(parsed['responseStatus'], '')) if not parsed['responseData']['results']: return 'no images found' - return random.choice(parsed['responseData']['results'][:10]) \ - ['unescapedUrl'] + return random.choice(parsed['responseData']['results'][:10])['unescapedUrl'] @hook.command('search') @@ -48,6 +48,4 @@ def google(inp): content = http.html.fromstring(content).text_content() content = text.truncate_str(content, 150) - out = '{} -- \x02{}\x02: "{}"'.format(result['unescapedUrl'], title, content) - - return out + return u'{} -- \x02{}\x02: "{}"'.format(result['unescapedUrl'], title, content) diff --git a/plugins/google_translate.py b/disabled_stuff/google_translate.py similarity index 99% rename from plugins/google_translate.py rename to disabled_stuff/google_translate.py index 8a05b9d..a9d4ea3 100644 --- a/plugins/google_translate.py +++ b/disabled_stuff/google_translate.py @@ -8,6 +8,7 @@ import re from util import hook, http + max_length = 100 @@ -164,4 +165,4 @@ lang_pairs = [ ("vi", "Vietnamese"), ("cy", "Welsh"), ("yi", "Yiddish") -] \ No newline at end of file +] diff --git a/disabled_stuff/googleurlparse.py b/disabled_stuff/googleurlparse.py new file mode 100644 index 0000000..cbea897 --- /dev/null +++ b/disabled_stuff/googleurlparse.py @@ -0,0 +1,22 @@ +from util import hook +from urllib import unquote + +@hook.command(autohelp=False) +def googleurl(inp, db=None, nick=None): + """googleurl [nickname] - Converts Google urls (google.com/url) to normal urls + where possible, in the specified nickname's last message. If nickname isn't provided, + action will be performed on user's last message""" + if not inp: + inp = nick + last_message = db.execute("select name, quote from seen_user where name" + " like ? and chan = ?", (inp.lower(), input.chan.lower())).fetchone() + if last_message: + msg = last_message[1] + out = ", ".join([(unquote(a[4:]) if a[:4] == "url=" else "") for a in msg.split("&")])\ + .replace(", ,", "").strip() + return out if out else "No matches in your last message." + else: + if inp == nick: + return "You haven't said anything in this channel yet!" + else: + return "That user hasn't said anything in this channel yet!" diff --git a/disabled_stuff/history.py b/disabled_stuff/history.py new file mode 100644 index 0000000..c703bcf --- /dev/null +++ b/disabled_stuff/history.py @@ -0,0 +1,89 @@ +from collections import deque +from util import hook, timesince +import time +import re + +db_ready = [] + + +def db_init(db, conn_name): + """check to see that our db has the the seen table (connection name is for caching the result per connection)""" + global db_ready + if db_ready.count(conn_name) < 1: + db.execute("create table if not exists seen_user(name, time, quote, chan, host, " + "primary key(name, chan))") + db.commit() + db_ready.append(conn_name) + + +def track_seen(input, message_time, db, conn): + """ Tracks messages for the .seen command """ + db_init(db, conn) + # keep private messages private + if input.chan[:1] == "#" and not re.findall('^s/.*/.*/$', input.msg.lower()): + db.execute("insert or replace into seen_user(name, time, quote, chan, host)" + "values(?,?,?,?,?)", (input.nick.lower(), message_time, input.msg, + input.chan, input.mask)) + db.commit() + + +def track_history(input, message_time, conn): + try: + history = conn.history[input.chan] + except KeyError: + conn.history[input.chan] = deque(maxlen=100) + history = conn.history[input.chan] + + data = (input.nick, message_time, input.msg) + history.append(data) + + +@hook.singlethread +@hook.event('PRIVMSG', ignorebots=False) +def chat_tracker(paraml, input=None, db=None, conn=None): + message_time = time.time() + track_seen(input, message_time, db, conn) + track_history(input, message_time, conn) + + +@hook.command(autohelp=False) +def resethistory(inp, input=None, conn=None): + """resethistory - Resets chat history for the current channel""" + try: + conn.history[input.chan].clear() + return "Reset chat history for current channel." + except KeyError: + # wat + return "There is no history for this channel." + +"""seen.py: written by sklnd in about two beers July 2009""" + +@hook.command +def seen(inp, nick='', chan='', db=None, input=None, conn=None): + """seen -- Tell when a nickname was last in active in one of this bot's channels.""" + + if input.conn.nick.lower() == inp.lower(): + return "You need to get your eyes checked." + + if inp.lower() == nick.lower(): + return "Have you looked in a mirror lately?" + + if not re.match("^[A-Za-z0-9_|.\-\]\[]*$", inp.lower()): + return "I can't look up that name, its impossible to use!" + + db_init(db, conn.name) + + last_seen = db.execute("select name, time, quote from seen_user where name" + " like ? and chan = ?", (inp, chan)).fetchone() + + if last_seen: + reltime = timesince.timesince(last_seen[1]) + if last_seen[0] != inp.lower(): # for glob matching + inp = last_seen[0] + if last_seen[2][0:1] == "\x01": + return '{} was last seen {} ago: * {} {}'.format(inp, reltime, inp, + last_seen[2][8:-1]) + else: + return '{} was last seen {} ago saying: {}'.format(inp, reltime, last_seen[2]) + else: + return "I've never seen {} talking in this channel.".format(inp) diff --git a/plugins/horoscope.py b/disabled_stuff/horoscope.py similarity index 75% rename from plugins/horoscope.py rename to disabled_stuff/horoscope.py index 4b05bc5..e4404cf 100644 --- a/plugins/horoscope.py +++ b/disabled_stuff/horoscope.py @@ -7,17 +7,17 @@ db_ready = False def db_init(db): """check to see that our db has the horoscope table and return a connection.""" - db.execute("create table if not exists horoscope(nick primary key, sign)") - db.commit() - db_ready = True + global db_ready + if not db_ready: + db.execute("create table if not exists horoscope(nick primary key, sign)") + db.commit() + db_ready = True @hook.command(autohelp=False) def horoscope(inp, db=None, notice=None, nick=None): """horoscope -- Get your horoscope.""" - - if not db_ready: - db_init(db) + db_init(db) # check if the user asked us not to save his details dontsave = inp.endswith(" dontsave") @@ -32,16 +32,16 @@ def horoscope(inp, db=None, notice=None, nick=None): sign = db.execute("select sign from horoscope where nick=lower(?)", (nick,)).fetchone() if not sign: - notice(horoscope.__doc__) + notice("horoscope -- Get your horoscope") return sign = sign[0] - url = "http://my.horoscope.com/astrology/free-daily-horoscope-%s.html" % sign + url = "http://my.horoscope.com/astrology/free-daily-horoscope-{}.html".format(sign) soup = http.get_soup(url) title = soup.find_all('h1', {'class': 'h1b'})[1] - horoscope = soup.find('div', {'class': 'fontdef1'}) - result = "\x02%s\x02 %s" % (title, horoscope) + horoscope_text = soup.find('div', {'class': 'fontdef1'}) + result = u"\x02%s\x02 %s" % (title, horoscope_text) result = text.strip_html(result) #result = unicode(result, "utf8").replace('flight ','') @@ -50,7 +50,7 @@ def horoscope(inp, db=None, notice=None, nick=None): if inp and not dontsave: db.execute("insert or replace into horoscope(nick, sign) values (?,?)", - (nick.lower(), sign)) + (nick.lower(), sign)) db.commit() - return result \ No newline at end of file + return result diff --git a/plugins/hulu.py b/disabled_stuff/hulu.py similarity index 85% rename from plugins/hulu.py rename to disabled_stuff/hulu.py index 0b69788..74e6b00 100644 --- a/plugins/hulu.py +++ b/disabled_stuff/hulu.py @@ -1,7 +1,9 @@ -from util import hook, http, timeformat from urllib import urlencode import re +from util import hook, http, timeformat + + hulu_re = (r'(.*://)(www.hulu.com|hulu.com)(.*)', re.I) @@ -10,7 +12,7 @@ def hulu_url(match): data = http.get_json("http://www.hulu.com/api/oembed.json?url=http://www.hulu.com" + match.group(3)) showname = data['title'].split("(")[-1].split(")")[0] title = data['title'].split(" (")[0] - return "{}: {} - {}".format(showname, title, timeformat.timeformat(int(data['duration']))) + return "{}: {} - {}".format(showname, title, timeformat.format_time(int(data['duration']))) @hook.command('hulu') @@ -21,7 +23,7 @@ def hulu_search(inp): data = result.find('results').find('videos').find('video') showname = data.find('show').find('name').text title = data.find('title').text - duration = timeformat.timeformat(int(float(data.find('duration').text))) + duration = timeformat.format_time(int(float(data.find('duration').text))) description = data.find('description').text rating = data.find('content-rating').text return "{}: {} - {} - {} ({}) {}".format(showname, title, description, duration, rating, diff --git a/plugins/imdb.py b/disabled_stuff/imdb.py old mode 100755 new mode 100644 similarity index 91% rename from plugins/imdb.py rename to disabled_stuff/imdb.py index f7a0223..0272248 --- a/plugins/imdb.py +++ b/disabled_stuff/imdb.py @@ -1,8 +1,10 @@ # IMDb lookup plugin by Ghetto Wizard (2011) and blha303 (2013) -from util import hook, http, text import re +from util import hook, http, text + + id_re = re.compile("tt\d+") imdb_re = (r'(.*:)//(imdb.com|www.imdb.com)(:[0-9]+)?(.*)', re.I) @@ -37,10 +39,10 @@ def imdb(inp): @hook.regex(*imdb_re) def imdb_url(match): - id = match.group(4).split('/')[-1] - if id == "": - id = match.group(4).split('/')[-2] - content = http.get_json("http://www.omdbapi.com/", i=id) + imdb_id = match.group(4).split('/')[-1] + if imdb_id == "": + imdb_id = match.group(4).split('/')[-2] + content = http.get_json("http://www.omdbapi.com/", i=imdb_id) if content.get('Error', None) == 'Movie not found!': return 'Movie not found!' elif content['Response'] == 'True': diff --git a/disabled_stuff/imgur.py b/disabled_stuff/imgur.py new file mode 100644 index 0000000..320bc6e --- /dev/null +++ b/disabled_stuff/imgur.py @@ -0,0 +1,82 @@ +import re +import random + +from util import hook, http, web + + +base_url = "http://reddit.com/r/{}/.json" +imgur_re = re.compile(r'http://(?:i\.)?imgur\.com/(a/)?(\w+\b(?!/))\.?\w?') + +album_api = "https://api.imgur.com/3/album/{}/images.json" + + +def is_valid(data): + if data["domain"] in ["i.imgur.com", "imgur.com"]: + return True + else: + return False + + +@hook.command(autohelp=False) +def imgur(inp): + """imgur [subreddit] -- Gets the first page of imgur images from [subreddit] and returns a link to them. + If [subreddit] is undefined, return any imgur images""" + if inp: + # see if the input ends with "nsfw" + show_nsfw = inp.endswith(" nsfw") + + # remove "nsfw" from the input string after checking for it + if show_nsfw: + inp = inp[:-5].strip().lower() + + url = base_url.format(inp.strip()) + else: + url = "http://www.reddit.com/domain/imgur.com/.json" + show_nsfw = False + + try: + data = http.get_json(url, user_agent=http.ua_chrome) + except Exception as e: + return "Error: " + str(e) + + data = data["data"]["children"] + random.shuffle(data) + + # filter list to only have imgur links + filtered_posts = [i["data"] for i in data if is_valid(i["data"])] + + if not filtered_posts: + return "No images found." + + items = [] + + headers = { + "Authorization": "Client-ID b5d127e6941b07a" + } + + # loop over the list of posts + for post in filtered_posts: + if post["over_18"] and not show_nsfw: + continue + + match = imgur_re.search(post["url"]) + if match.group(1) == 'a/': + # post is an album + url = album_api.format(match.group(2)) + images = http.get_json(url, headers=headers)["data"] + + # loop over the images in the album and add to the list + for image in images: + items.append(image["id"]) + + elif match.group(2) is not None: + # post is an image + items.append(match.group(2)) + + if not items: + return "No images found (use .imgur nsfw to show explicit content)" + + if show_nsfw: + return "{} \x02NSFW\x02".format(web.isgd("http://imgur.com/" + ','.join(items))) + else: + return web.isgd("http://imgur.com/" + ','.join(items)) diff --git a/plugins/isup.py b/disabled_stuff/isup.py similarity index 92% rename from plugins/isup.py rename to disabled_stuff/isup.py index 3d16ccb..5fc95d6 100644 --- a/plugins/isup.py +++ b/disabled_stuff/isup.py @@ -5,7 +5,7 @@ from util import hook, http, urlnorm @hook.command def isup(inp): - "isup -- uses isup.me to see if a site is up or not" + """isup -- uses isup.me to see if a site is up or not""" # slightly overcomplicated, esoteric URL parsing scheme, auth, path, query, fragment = urlparse.urlsplit(inp.strip()) @@ -25,4 +25,4 @@ def isup(inp): elif "is up" in content: return "It's just you. {} is \x02\x033up\x02\x0f.".format(url) else: - return "Huh? That doesn't look like a site on the interweb." \ No newline at end of file + return "Huh? That doesn't look like a site on the interweb." diff --git a/plugins/kernel.py b/disabled_stuff/kernel.py similarity index 99% rename from plugins/kernel.py rename to disabled_stuff/kernel.py index 7ade46e..90cbed5 100644 --- a/plugins/kernel.py +++ b/disabled_stuff/kernel.py @@ -1,6 +1,7 @@ -from util import hook, http import re +from util import hook, http + @hook.command(autohelp=False) def kernel(inp, reply=None): diff --git a/disabled_stuff/kill.py b/disabled_stuff/kill.py new file mode 100644 index 0000000..d25228e --- /dev/null +++ b/disabled_stuff/kill.py @@ -0,0 +1,33 @@ +import json + +from util import hook, textgen + + +def get_generator(_json, variables): + data = json.loads(_json) + return textgen.TextGenerator(data["templates"], + data["parts"], variables=variables) + + +@hook.command +def kill(inp, action=None, nick=None, conn=None, notice=None): + """kill -- Makes the bot kill .""" + target = inp.strip() + + if " " in target: + notice("Invalid username!") + return + + # if the user is trying to make the bot kill itself, kill them + if target.lower() == conn.nick.lower() or target.lower() == "itself": + target = nick + + variables = { + "user": target + } + + with open("plugins/data/kills.json") as f: + generator = get_generator(f.read(), variables) + + # act out the message + action(generator.generate_string()) diff --git a/plugins/lastfm.py b/disabled_stuff/lastfm.py old mode 100755 new mode 100644 similarity index 87% rename from plugins/lastfm.py rename to disabled_stuff/lastfm.py index 4f56ee3..b928b1e --- a/plugins/lastfm.py +++ b/disabled_stuff/lastfm.py @@ -1,6 +1,8 @@ -from util import hook, http, timesince from datetime import datetime +from util import hook, http, timesince + + api_url = "http://ws.audioscrobbler.com/2.0/?format=json" @@ -34,10 +36,10 @@ def lastfm(inp, nick='', db=None, bot=None, notice=None): api_key=api_key, user=user, limit=1) if 'error' in response: - return "Error: {}.".format(response["message"]) + return u"Error: {}.".format(response["message"]) if not "track" in response["recenttracks"] or len(response["recenttracks"]["track"]) == 0: - return 'No recent tracks for user "{}" found.'.format(user) + return u'No recent tracks for user "{}" found.'.format(user) tracks = response["recenttracks"]["track"] @@ -64,18 +66,18 @@ def lastfm(inp, nick='', db=None, bot=None, notice=None): album = track["album"]["#text"] artist = track["artist"]["#text"] - out = '{} {} "{}"'.format(user, status, title) + out = u'{} {} "{}"'.format(user, status, title) if artist: - out += " by \x02{}\x0f".format(artist) + out += u" by \x02{}\x0f".format(artist) if album: - out += " from the album \x02{}\x0f".format(album) + out += u" from the album \x02{}\x0f".format(album) # append ending based on what type it was out += ending if inp and not dontsave: db.execute("insert or replace into lastfm(nick, acc) values (?,?)", - (nick.lower(), user)) + (nick.lower(), user)) db.commit() return out diff --git a/plugins/lmgtfy.py b/disabled_stuff/lmgtfy.py similarity index 80% rename from plugins/lmgtfy.py rename to disabled_stuff/lmgtfy.py index c8dcee3..768075f 100644 --- a/plugins/lmgtfy.py +++ b/disabled_stuff/lmgtfy.py @@ -6,7 +6,7 @@ from util import hook, web, http def lmgtfy(inp): """lmgtfy [phrase] - Posts a google link for the specified phrase""" - link = "http://lmgtfy.com/?q={}".format(http.quote_plus(inp)) + link = u"http://lmgtfy.com/?q={}".format(http.quote_plus(inp)) try: return web.isgd(link) diff --git a/plugins/log.py b/disabled_stuff/log.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/log.py rename to disabled_stuff/log.py diff --git a/plugins/lyrics.py b/disabled_stuff/lyrics.py similarity index 95% rename from plugins/lyrics.py rename to disabled_stuff/lyrics.py index 6a226e8..eabb84a 100644 --- a/plugins/lyrics.py +++ b/disabled_stuff/lyrics.py @@ -38,6 +38,6 @@ def lyrics(inp): else: lyricsum = " / ".join(lyricsum.strip().split("\n")[0:4]) # truncate, format return "\x02{}\x02 by \x02{}\x02 {}{} - {}".format(title, artist, web.try_isgd(link), pasteurl, - lyricsum[:-3]) + lyricsum[:-3]) else: return "No song results. " + url + inp.replace(" ", "+") diff --git a/plugins/metacritic.py b/disabled_stuff/metacritic.py old mode 100755 new mode 100644 similarity index 68% rename from plugins/metacritic.py rename to disabled_stuff/metacritic.py index 0168230..92d0933 --- a/plugins/metacritic.py +++ b/disabled_stuff/metacritic.py @@ -9,15 +9,13 @@ from util import hook, http @hook.command('mc') @hook.command def metacritic(inp): - """mc [all|movie|tv|album|x360|ps3|wii|pc|ds|3ds|vita] + """mc [all|movie|tv|album|x360|ps3|pc|gba|ds|3ds|wii|vita|wiiu|xone|ps4] <title> Gets rating for <title> from metacritic on the specified medium.""" - # if the results suck, it's metacritic's fault - args = inp.strip() - game_platforms = ('x360', 'ps3', 'pc', 'ds', 'wii', '3ds', 'gba', - 'psp', 'vita') + game_platforms = ('x360', 'ps3', 'pc', 'gba', 'ds', '3ds', 'wii', + 'vita', 'wiiu', 'xone', 'ps4') all_platforms = game_platforms + ('all', 'movie', 'tv', 'album') @@ -43,38 +41,7 @@ def metacritic(inp): except HTTPError: return 'error fetching results' - ''' result format: - -- game result, with score - -- subsequent results are the same structure, without first_result class - <li class="result first_result"> - <div class="result_type"> - <strong>Game</strong> - <span class="platform">WII</span> - </div> - <div class="result_wrap"> - <div class="basic_stats has_score"> - <div class="main_stats"> - <h3 class="product_title basic_stat">...</h3> - <div class="std_score"> - <div class="score_wrap"> - <span class="label">Metascore: </span> - <span class="data metascore score_favorable">87</span> - </div> - </div> - </div> - <div class="more_stats extended_stats">...</div> - </div> - </div> - </li> - - -- other platforms are the same basic layout - -- if it doesn't have a score, there is no div.basic_score - -- the <div class="result_type"> changes content for non-games: - <div class="result_type"><strong>Movie</strong></div> - ''' - # get the proper result element we want to pull data from - result = None if not doc.find_class('query_results'): @@ -128,7 +95,7 @@ def metacritic(inp): release = None try: - score = result.find_class('metascore')[0].text_content() + score = result.find_class('metascore_w')[0].text_content() except IndexError: score = None diff --git a/disabled_stuff/minecraft_bukget.py b/disabled_stuff/minecraft_bukget.py new file mode 100644 index 0000000..496f169 --- /dev/null +++ b/disabled_stuff/minecraft_bukget.py @@ -0,0 +1,154 @@ +import time +import random + +from util import hook, http, web, text + + +## CONSTANTS + +base_url = "http://api.bukget.org/3/" + +search_url = base_url + "search/plugin_name/like/{}" +random_url = base_url + "plugins/bukkit/?start={}&size=1" +details_url = base_url + "plugins/bukkit/{}" + +categories = http.get_json("http://api.bukget.org/3/categories") + +count_total = sum([cat["count"] for cat in categories]) +count_categories = {cat["name"].lower(): int(cat["count"]) for cat in categories} # dict comps! + + +class BukgetError(Exception): + def __init__(self, code, text): + self.code = code + self.text = text + + def __str__(self): + return self.text + + +## DATA FUNCTIONS + +def plugin_search(term): + """ searches for a plugin with the bukget API and returns the slug """ + term = term.lower().strip() + + search_term = http.quote_plus(term) + + try: + results = http.get_json(search_url.format(search_term)) + except (http.HTTPError, http.URLError) as e: + raise BukgetError(500, "Error Fetching Search Page: {}".format(e)) + + if not results: + raise BukgetError(404, "No Results Found") + + for result in results: + if result["slug"] == term: + return result["slug"] + + return results[0]["slug"] + + +def plugin_random(): + """ gets a random plugin from the bukget API and returns the slug """ + results = None + + while not results: + plugin_number = random.randint(1, count_total) + print "trying {}".format(plugin_number) + try: + results = http.get_json(random_url.format(plugin_number)) + except (http.HTTPError, http.URLError) as e: + raise BukgetError(500, "Error Fetching Search Page: {}".format(e)) + + return results[0]["slug"] + + +def plugin_details(slug): + """ takes a plugin slug and returns details from the bukget API """ + slug = slug.lower().strip() + + try: + details = http.get_json(details_url.format(slug)) + except (http.HTTPError, http.URLError) as e: + raise BukgetError(500, "Error Fetching Details: {}".format(e)) + return details + + +## OTHER FUNCTIONS + +def format_output(data): + """ takes plugin data and returns two strings representing information about that plugin """ + name = data["plugin_name"] + description = text.truncate_str(data['description'], 30) + url = data['website'] + authors = data['authors'][0] + authors = authors[0] + u"\u200b" + authors[1:] + stage = data['stage'] + + current_version = data['versions'][0] + + last_update = time.strftime('%d %B %Y %H:%M', + time.gmtime(current_version['date'])) + version_number = data['versions'][0]['version'] + + bukkit_versions = ", ".join(current_version['game_versions']) + link = web.try_isgd(current_version['link']) + + if description: + line_a = u"\x02{}\x02, by \x02{}\x02 - {} - ({}) \x02{}".format(name, authors, description, stage, url) + else: + line_a = u"\x02{}\x02, by \x02{}\x02 ({}) \x02{}".format(name, authors, stage, url) + + line_b = u"Last release: \x02v{}\x02 for \x02{}\x02 at {} \x02{}\x02".format(version_number, bukkit_versions, + last_update, link) + + return line_a, line_b + + +## HOOK FUNCTIONS + +@hook.command('plugin') +@hook.command +def bukget(inp, reply=None, message=None): + """bukget <slug/name> - Look up a plugin on dev.bukkit.org""" + # get the plugin slug using search + try: + slug = plugin_search(inp) + except BukgetError as e: + return e + + # get the plugin info using the slug + try: + data = plugin_details(slug) + except BukgetError as e: + return e + + # format the final message and send it to IRC + line_a, line_b = format_output(data) + + reply(line_a) + message(line_b) + + +@hook.command(autohelp=None) +def randomplugin(inp, reply=None, message=None): + """randomplugin - Gets a random plugin from dev.bukkit.org""" + # get a random plugin slug + try: + slug = plugin_random() + except BukgetError as e: + return e + + # get the plugin info using the slug + try: + data = plugin_details(slug) + except BukgetError as e: + return e + + # format the final message and send it to IRC + line_a, line_b = format_output(data) + + reply(line_a) + message(line_b) \ No newline at end of file diff --git a/plugins/minecraft_items.py b/disabled_stuff/minecraft_items.py old mode 100755 new mode 100644 similarity index 80% rename from plugins/minecraft_items.py rename to disabled_stuff/minecraft_items.py index 97a6420..f1e94f9 --- a/plugins/minecraft_items.py +++ b/disabled_stuff/minecraft_items.py @@ -1,9 +1,11 @@ """ plugin by _303 (?) """ -from util import hook import re +from util import hook + + pattern = re.compile(r'^(?P<count>\d+)x (?P<name>.+?): (?P<ingredients>.*)$') recipelist = [] @@ -42,29 +44,29 @@ with open("plugins/data/itemids.txt") as f: if line.startswith("//"): continue parts = line.strip().split() - id = parts[0] + itemid = parts[0] name = " ".join(parts[1:]) - ids.append((id, name)) + ids.append((itemid, name)) @hook.command("mcid") @hook.command -def mcitem(input, reply=None): +def mcitem(inp, reply=None): """mcitem <item/id> -- gets the id from an item or vice versa""" - input = input.lower().strip() + inp = inp.lower().strip() - if input == "": + if inp == "": reply("error: no input.") return results = [] - for id, name in ids: - if input == id: - results = ["\x02[{}]\x02 {}".format(id, name)] + for item_id, item_name in ids: + if inp == item_id: + results = ["\x02[{}]\x02 {}".format(item_id, item_name)] break - elif input in name.lower(): - results.append("\x02[{}]\x02 {}".format(id, name)) + elif inp in item_name.lower(): + results.append("\x02[{}]\x02 {}".format(item_id, item_name)) if not results: return "No matches found." @@ -80,12 +82,12 @@ def mcitem(input, reply=None): @hook.command("mccraft") @hook.command -def mcrecipe(input, reply=None): +def mcrecipe(inp, reply=None): """mcrecipe <item> -- gets the crafting recipe for an item""" - input = input.lower().strip() + inp = inp.lower().strip() results = [recipe.line for recipe in recipelist - if input in recipe.output] + if inp in recipe.output] if not results: return "No matches found." diff --git a/disabled_stuff/minecraft_ping.py b/disabled_stuff/minecraft_ping.py new file mode 100644 index 0000000..978ca19 --- /dev/null +++ b/disabled_stuff/minecraft_ping.py @@ -0,0 +1,232 @@ +import socket +import struct +import json +import traceback + +from util import hook + + +try: + import DNS + has_dns = True +except ImportError: + has_dns = False + + +mc_colors = [(u'\xa7f', u'\x0300'), (u'\xa70', u'\x0301'), (u'\xa71', u'\x0302'), (u'\xa72', u'\x0303'), + (u'\xa7c', u'\x0304'), (u'\xa74', u'\x0305'), (u'\xa75', u'\x0306'), (u'\xa76', u'\x0307'), + (u'\xa7e', u'\x0308'), (u'\xa7a', u'\x0309'), (u'\xa73', u'\x0310'), (u'\xa7b', u'\x0311'), + (u'\xa71', u'\x0312'), (u'\xa7d', u'\x0313'), (u'\xa78', u'\x0314'), (u'\xa77', u'\x0315'), + (u'\xa7l', u'\x02'), (u'\xa79', u'\x0310'), (u'\xa7o', u'\t'), (u'\xa7m', u'\x13'), + (u'\xa7r', u'\x0f'), (u'\xa7n', u'\x15')] + + +## EXCEPTIONS + + +class PingError(Exception): + def __init__(self, text): + self.text = text + + def __str__(self): + return self.text + + +class ParseError(Exception): + def __init__(self, text): + self.text = text + + def __str__(self): + return self.text + + +## MISC + + +def unpack_varint(s): + d = 0 + i = 0 + while True: + b = ord(s.recv(1)) + d |= (b & 0x7F) << 7 * i + i += 1 + if not b & 0x80: + return d + +pack_data = lambda d: struct.pack('>b', len(d)) + d +pack_port = lambda i: struct.pack('>H', i) + +## DATA FUNCTIONS + + +def mcping_modern(host, port): + """ pings a server using the modern (1.7+) protocol and returns data """ + try: + # connect to the server + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + s.connect((host, port)) + except socket.gaierror: + raise PingError("Invalid hostname") + except socket.timeout: + raise PingError("Request timed out") + + # send handshake + status request + s.send(pack_data("\x00\x00" + pack_data(host.encode('utf8')) + pack_port(port) + "\x01")) + s.send(pack_data("\x00")) + + # read response + unpack_varint(s) # Packet length + unpack_varint(s) # Packet ID + l = unpack_varint(s) # String length + + if not l > 1: + raise PingError("Invalid response") + + d = "" + while len(d) < l: + d += s.recv(1024) + + # Close our socket + s.close() + except socket.error: + raise PingError("Socket Error") + + # Load json and return + data = json.loads(d.decode('utf8')) + try: + version = data["version"]["name"] + try: + desc = u" ".join(data["description"]["text"].split()) + except TypeError: + desc = u" ".join(data["description"].split()) + max_players = data["players"]["max"] + online = data["players"]["online"] + except Exception as e: + # TODO: except Exception is bad + traceback.print_exc(e) + raise PingError("Unknown Error: {}".format(e)) + + output = { + "motd": format_colors(desc), + "motd_raw": desc, + "version": version, + "players": online, + "players_max": max_players + } + return output + + +def mcping_legacy(host, port): + """ pings a server using the legacy (1.6 and older) protocol and returns data """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + sock.connect((host, port)) + sock.send('\xfe\x01') + response = sock.recv(1) + except socket.gaierror: + raise PingError("Invalid hostname") + except socket.timeout: + raise PingError("Request timed out") + + if response[0] != '\xff': + raise PingError("Invalid response") + + length = struct.unpack('!h', sock.recv(2))[0] + values = sock.recv(length * 2).decode('utf-16be') + data = values.split(u'\x00') # try to decode data using new format + if len(data) == 1: + # failed to decode data, server is using old format + data = values.split(u'\xa7') + output = { + "motd": format_colors(" ".join(data[0].split())), + "motd_raw": data[0], + "version": None, + "players": data[1], + "players_max": data[2] + } + else: + # decoded data, server is using new format + output = { + "motd": format_colors(" ".join(data[3].split())), + "motd_raw": data[3], + "version": data[2], + "players": data[4], + "players_max": data[5] + } + sock.close() + return output + + +## FORMATTING/PARSING FUNCTIONS + +def check_srv(domain): + """ takes a domain and finds minecraft SRV records """ + DNS.DiscoverNameServers() + srv_req = DNS.Request(qtype='srv') + srv_result = srv_req.req('_minecraft._tcp.{}'.format(domain)) + + for getsrv in srv_result.answers: + if getsrv['typename'] == 'SRV': + data = [getsrv['data'][2], getsrv['data'][3]] + return data + + +def parse_input(inp): + """ takes the input from the mcping command and returns the host and port """ + inp = inp.strip().split(" ")[0] + if ":" in inp: + # the port is defined in the input string + host, port = inp.split(":", 1) + try: + port = int(port) + if port > 65535 or port < 0: + raise ParseError("The port '{}' is invalid.".format(port)) + except ValueError: + raise ParseError("The port '{}' is invalid.".format(port)) + return host, port + if has_dns: + # the port is not in the input string, but we have PyDNS so look for a SRV record + srv_data = check_srv(inp) + if srv_data: + return str(srv_data[1]), int(srv_data[0]) + # return default port + return inp, 25565 + + +def format_colors(motd): + for original, replacement in mc_colors: + motd = motd.replace(original, replacement) + motd = motd.replace(u"\xa7k", "") + return motd + + +def format_output(data): + if data["version"]: + return u"{motd}\x0f - {version}\x0f - {players}/{players_max}" \ + u" players.".format(**data).replace("\n", u"\x0f - ") + else: + return u"{motd}\x0f - {players}/{players_max}" \ + u" players.".format(**data).replace("\n", u"\x0f - ") + + +@hook.command +@hook.command("mcp") +def mcping(inp): + """mcping <server>[:port] - Ping a Minecraft server to check status.""" + try: + host, port = parse_input(inp) + except ParseError as e: + return "Could not parse input ({})".format(e) + + try: + data = mcping_modern(host, port) + except PingError: + try: + data = mcping_legacy(host, port) + except PingError as e: + return "Could not ping server, is it offline? ({})".format(e) + + return format_output(data) diff --git a/plugins/minecraft_status.py b/disabled_stuff/minecraft_status.py old mode 100755 new mode 100644 similarity index 51% rename from plugins/minecraft_status.py rename to disabled_stuff/minecraft_status.py index 98fce4a..4ca67d3 --- a/plugins/minecraft_status.py +++ b/disabled_stuff/minecraft_status.py @@ -1,6 +1,7 @@ -from util import hook, http import json +from util import hook, http + @hook.command(autohelp=False) def mcstatus(inp): @@ -17,37 +18,27 @@ def mcstatus(inp): out = [] # use a loop so we don't have to update it if they add more servers - yes = [] - no = [] + green = [] + yellow = [] + red = [] for server, status in data.items(): if status == "green": - yes.append(server) + green.append(server) + elif status == "yellow": + yellow.append(server) else: - no.append(server) - if yes: - out = "\x033\x02Online\x02\x0f: " + ", ".join(yes) - if no: + red.append(server) + + if green: + out = "\x033\x02Online\x02\x0f: " + ", ".join(green) + if yellow: out += " " - if no: - out += "\x034\x02Offline\x02\x0f: " + ", ".join(no) + if yellow: + out += "\x02Issues\x02: " + ", ".join(yellow) + if red: + out += " " + if red: + out += "\x034\x02Offline\x02\x0f: " + ", ".join(red) return "\x0f" + out.replace(".mojang.com", ".mj") \ .replace(".minecraft.net", ".mc") - - -@hook.command("haspaid") -@hook.command -def mcpaid(inp): - """mcpaid <username> -- Checks if <username> has a premium Minecraft account.""" - - user = inp.strip() - - try: - status = http.get("http://www.minecraft.net/haspaid.jsp", user=user) - except (http.URLError, http.HTTPError) as e: - return "Unable to get user registration status: {}".format(e) - - if "true" in status: - return 'The account "{}" is a premium Minecraft account!'.format(inp) - else: - return 'The account "{}" is not a premium Minecraft account!'.format(inp) diff --git a/disabled_stuff/minecraft_user.py b/disabled_stuff/minecraft_user.py new file mode 100644 index 0000000..4026994 --- /dev/null +++ b/disabled_stuff/minecraft_user.py @@ -0,0 +1,101 @@ +import json +from util import hook, http + +NAME_URL = "https://account.minecraft.net/buy/frame/checkName/{}" +PAID_URL = "http://www.minecraft.net/haspaid.jsp" + + +class McuError(Exception): + pass + + +def get_status(name): + """ takes a name and returns status """ + try: + name_encoded = http.quote_plus(name) + response = http.get(NAME_URL.format(name_encoded)) + except (http.URLError, http.HTTPError) as e: + raise McuError("Could not get name status: {}".format(e)) + + if "OK" in response: + return "free" + elif "TAKEN" in response: + return "taken" + elif "invalid characters" in response: + return "invalid" + + +def get_profile(name): + profile = {} + + # form the profile request + request = { + "name": name, + "agent": "minecraft" + } + + # submit the profile request + try: + headers = {"Content-Type": "application/json"} + r = http.get_json( + 'https://api.mojang.com/profiles/page/1', + post_data=json.dumps(request), + headers=headers + ) + except (http.URLError, http.HTTPError) as e: + raise McuError("Could not get profile status: {}".format(e)) + + user = r["profiles"][0] + profile["name"] = user["name"] + profile["id"] = user["id"] + + profile["legacy"] = user.get("legacy", False) + + try: + response = http.get(PAID_URL, user=name) + except (http.URLError, http.HTTPError) as e: + raise McuError("Could not get payment status: {}".format(e)) + + if "true" in response: + profile["paid"] = True + else: + profile["paid"] = False + + return profile + + +@hook.command("haspaid") +@hook.command("mcpaid") +@hook.command +def mcuser(inp): + """mcpaid <username> -- Gets information about the Minecraft user <account>.""" + user = inp.strip() + + try: + # get status of name (does it exist?) + name_status = get_status(user) + except McuError as e: + return e + + if name_status == "taken": + try: + # get information about user + profile = get_profile(user) + except McuError as e: + return "Error: {}".format(e) + + profile["lt"] = ", legacy" if profile["legacy"] else "" + + if profile["paid"]: + return u"The account \x02{name}\x02 ({id}{lt}) exists. It is a \x02paid\x02" \ + u" account.".format(**profile) + else: + return u"The account \x02{name}\x02 ({id}{lt}) exists. It \x034\x02is NOT\x02\x0f a paid" \ + u" account.".format(**profile) + elif name_status == "free": + return u"The account \x02{}\x02 does not exist.".format(user) + elif name_status == "invalid": + return u"The name \x02{}\x02 contains invalid characters.".format(user) + else: + # if you see this, panic + return "Unknown Error." \ No newline at end of file diff --git a/disabled_stuff/minecraft_wiki.py b/disabled_stuff/minecraft_wiki.py new file mode 100644 index 0000000..072a8ac --- /dev/null +++ b/disabled_stuff/minecraft_wiki.py @@ -0,0 +1,51 @@ +import re + +from util import hook, http, text + + +api_url = "http://minecraft.gamepedia.com/api.php?action=opensearch" +mc_url = "http://minecraft.gamepedia.com/" + + +@hook.command +def mcwiki(inp): + """mcwiki <phrase> -- Gets the first paragraph of + the Minecraft Wiki article on <phrase>.""" + + try: + j = http.get_json(api_url, search=inp) + except (http.HTTPError, http.URLError) as e: + return "Error fetching search results: {}".format(e) + except ValueError as e: + return "Error reading search results: {}".format(e) + + if not j[1]: + return "No results found." + + # we remove items with a '/' in the name, because + # gamepedia uses sub-pages for different languages + # for some stupid reason + items = [item for item in j[1] if not "/" in item] + + if items: + article_name = items[0].replace(' ', '_').encode('utf8') + else: + # there are no items without /, just return a / one + article_name = j[1][0].replace(' ', '_').encode('utf8') + + url = mc_url + http.quote(article_name, '') + + try: + page = http.get_html(url) + except (http.HTTPError, http.URLError) as e: + return "Error fetching wiki page: {}".format(e) + + for p in page.xpath('//div[@class="mw-content-ltr"]/p'): + if p.text_content(): + summary = " ".join(p.text_content().splitlines()) + summary = re.sub("\[\d+\]", "", summary) + summary = text.truncate_str(summary, 200) + return u"{} :: {}".format(summary, url) + + # this shouldn't happen + return "Unknown Error." diff --git a/plugins/mlia.py b/disabled_stuff/mlia.py similarity index 85% rename from plugins/mlia.py rename to disabled_stuff/mlia.py index c155a3e..feea642 100644 --- a/plugins/mlia.py +++ b/disabled_stuff/mlia.py @@ -1,6 +1,7 @@ # Plugin by Infinity - <https://github.com/infinitylabs/UguuBot> import random + from util import hook, http @@ -23,11 +24,11 @@ refresh_cache() @hook.command(autohelp=False) def mlia(inp, reply=None): - "mlia -- Gets a random quote from MyLifeIsAverage.com." + """mlia -- Gets a random quote from MyLifeIsAverage.com.""" # grab the last item in the mlia cache and remove it - id, text = mlia_cache.pop() + mlia_id, text = mlia_cache.pop() # reply with the mlia we grabbed - reply('({}) {}'.format(id, text)) + reply('({}) {}'.format(mlia_id, text)) # refresh mlia cache if its getting empty if len(mlia_cache) < 3: refresh_cache() diff --git a/disabled_stuff/mtg.py b/disabled_stuff/mtg.py old mode 100755 new mode 100644 diff --git a/disabled_stuff/mygengo_translate.py b/disabled_stuff/mygengo_translate.py old mode 100755 new mode 100644 index d61ab14..6e7b006 --- a/disabled_stuff/mygengo_translate.py +++ b/disabled_stuff/mygengo_translate.py @@ -1,7 +1,7 @@ # BING translation plugin by Lukeroge and neersighted from util import hook from util import http -import re +import re import htmlentitydefs import mygengo diff --git a/plugins/namegen.py b/disabled_stuff/namegen.py old mode 100755 new mode 100644 similarity index 82% rename from plugins/namegen.py rename to disabled_stuff/namegen.py index 410620d..7a1f0e6 --- a/plugins/namegen.py +++ b/disabled_stuff/namegen.py @@ -1,6 +1,7 @@ -# Plugin by Lukeroge +import json +import os + from util import hook, text, textgen -import json, os GEN_DIR = "./plugins/data/name_files/" @@ -8,14 +9,14 @@ GEN_DIR = "./plugins/data/name_files/" def get_generator(_json): data = json.loads(_json) - return textgen.TextGenerator(data["name"], data["templates"], - data["default_templates"], data["parts"]) + return textgen.TextGenerator(data["templates"], + data["parts"], default_templates=data["default_templates"]) @hook.command(autohelp=False) def namegen(inp, notice=None): - "namegen [generator] -- Generates some names using the chosen generator. " \ - "'namegen list' will display a list of all generators." + """namegen [generator] -- Generates some names using the chosen generator. + 'namegen list' will display a list of all generators.""" # clean up the input inp = inp.strip().lower() diff --git a/disabled_stuff/newegg.py b/disabled_stuff/newegg.py new file mode 100644 index 0000000..68d604d --- /dev/null +++ b/disabled_stuff/newegg.py @@ -0,0 +1,95 @@ +import json +import re + +from util import hook, http, text, web + + +## CONSTANTS + +ITEM_URL = "http://www.newegg.com/Product/Product.aspx?Item={}" + +API_PRODUCT = "http://www.ows.newegg.com/Products.egg/{}/ProductDetails" +API_SEARCH = "http://www.ows.newegg.com/Search.egg/Advanced" + +NEWEGG_RE = (r"(?:(?:www.newegg.com|newegg.com)/Product/Product\.aspx\?Item=)([-_a-zA-Z0-9]+)", re.I) + + +## OTHER FUNCTIONS + +def format_item(item, show_url=True): + """ takes a newegg API item object and returns a description """ + title = text.truncate_str(item["Title"], 50) + + # format the rating nicely if it exists + if not item["ReviewSummary"]["TotalReviews"] == "[]": + rating = "Rated {}/5 ({} ratings)".format(item["ReviewSummary"]["Rating"], + item["ReviewSummary"]["TotalReviews"][1:-1]) + else: + rating = "No Ratings" + + if not item["FinalPrice"] == item["OriginalPrice"]: + price = "{FinalPrice}, was {OriginalPrice}".format(**item) + else: + price = item["FinalPrice"] + + tags = [] + + if item["Instock"]: + tags.append("\x02Stock Available\x02") + else: + tags.append("\x02Out Of Stock\x02") + + if item["FreeShippingFlag"]: + tags.append("\x02Free Shipping\x02") + + if item["IsFeaturedItem"]: + tags.append("\x02Featured\x02") + + if item["IsShellShockerItem"]: + tags.append(u"\x02SHELL SHOCKER\u00AE\x02") + + # join all the tags together in a comma separated string ("tag1, tag2, tag3") + tag_text = u", ".join(tags) + + if show_url: + # create the item URL and shorten it + url = web.try_isgd(ITEM_URL.format(item["NeweggItemNumber"])) + return u"\x02{}\x02 ({}) - {} - {} - {}".format(title, price, rating, + tag_text, url) + else: + return u"\x02{}\x02 ({}) - {} - {}".format(title, price, rating, + tag_text) + + +## HOOK FUNCTIONS + +@hook.regex(*NEWEGG_RE) +def newegg_url(match): + item_id = match.group(1) + item = http.get_json(API_PRODUCT.format(item_id)) + return format_item(item, show_url=False) + + +@hook.command +def newegg(inp): + """newegg <item name> -- Searches newegg.com for <item name>""" + + # form the search request + request = { + "Keyword": inp, + "Sort": "FEATURED" + } + + # submit the search request + r = http.get_json( + 'http://www.ows.newegg.com/Search.egg/Advanced', + post_data=json.dumps(request) + ) + + # get the first result + if r["ProductListItems"]: + return format_item(r["ProductListItems"][0]) + else: + return "No results found." + + diff --git a/plugins/newgrounds.py b/disabled_stuff/newgrounds.py similarity index 99% rename from plugins/newgrounds.py rename to disabled_stuff/newgrounds.py index 7bfdb97..b26ffe4 100644 --- a/plugins/newgrounds.py +++ b/disabled_stuff/newgrounds.py @@ -1,6 +1,8 @@ import re + from util import hook, http + newgrounds_re = (r'(.*:)//(www.newgrounds.com|newgrounds.com)(:[0-9]+)?(.*)', re.I) valid = set('0123456789') diff --git a/plugins/notes.py b/disabled_stuff/notes.py similarity index 93% rename from plugins/notes.py rename to disabled_stuff/notes.py index 6c6e450..070a9fb 100644 --- a/plugins/notes.py +++ b/disabled_stuff/notes.py @@ -1,16 +1,18 @@ -from util import hook import re -db_inited = False +from util import hook -def cleanSQL(sql): +db_ready = False + + +def clean_sql(sql): return re.sub(r'\s+', " ", sql).strip() def db_init(db): - global db_inited - if db_inited: + global db_ready + if db_ready: return exists = db.execute(""" @@ -20,7 +22,7 @@ def db_init(db): """).fetchone()[0] == 1 if not exists: - db.execute(cleanSQL(""" + db.execute(clean_sql(""" create virtual table todos using fts4( user, text, @@ -30,7 +32,7 @@ def db_init(db): db.commit() - db_inited = True + db_ready = True def db_getall(db, nick, limit=-1): @@ -44,14 +46,14 @@ def db_getall(db, nick, limit=-1): """, (nick, limit)) -def db_get(db, nick, id): +def db_get(db, nick, note_id): return db.execute(""" select added, text from todos where lower(user) = lower(?) order by added desc limit 1 offset ? - """, (nick, id)).fetchone() + """, (nick, note_id)).fetchone() def db_del(db, nick, limit='all'): @@ -91,7 +93,7 @@ def db_search(db, nick, query): @hook.command("notes") @hook.command def note(inp, nick='', chan='', db=None, notice=None, bot=None): - "note(s) <add|del|list|search> args -- Manipulates your list of notes." + """note(s) <add|del|list|search> args -- Manipulates your list of notes.""" db_init(db) diff --git a/disabled_stuff/osrc.py b/disabled_stuff/osrc.py new file mode 100644 index 0000000..99cba1c --- /dev/null +++ b/disabled_stuff/osrc.py @@ -0,0 +1,29 @@ +from bs4 import BeautifulSoup + +from util import hook, http, web + + +user_url = "http://osrc.dfm.io/{}" + + +@hook.command +def osrc(inp): + """osrc <github user> -- Gets an Open Source Report Card for <github user>""" + + user_nick = inp.strip() + url = user_url.format(user_nick) + + try: + soup = http.get_soup(url) + except (http.HTTPError, http.URLError): + return "Couldn't find any stats for this user." + + report = soup.find("div", {"id": "description"}).find("p").get_text() + + # Split and join to remove all the excess whitespace, slice the + # string to remove the trailing full stop. + report = " ".join(report.split())[:-1] + + short_url = web.try_isgd(url) + + return "{} - {}".format(report, short_url) diff --git a/plugins/password.py b/disabled_stuff/password.py old mode 100755 new mode 100644 similarity index 89% rename from plugins/password.py rename to disabled_stuff/password.py index 6878515..34a379b --- a/plugins/password.py +++ b/disabled_stuff/password.py @@ -1,8 +1,10 @@ -# based on password generation code by TheNoodle -from util import hook +# TODO: Add some kind of pronounceable password generation +# TODO: Improve randomness import string import random +from util import hook + @hook.command def password(inp, notice=None): @@ -39,10 +41,10 @@ def password(inp, notice=None): if not okay: okay = okay + list(string.ascii_lowercase) - password = "" + pw = "" # generates password for x in range(length): - password = password + random.choice(okay) + pw = pw + random.choice(okay) - notice(password) + notice(pw) diff --git a/plugins/plpaste.py b/disabled_stuff/plpaste.py similarity index 100% rename from plugins/plpaste.py rename to disabled_stuff/plpaste.py diff --git a/plugins/potato.py b/disabled_stuff/potato.py old mode 100755 new mode 100644 similarity index 95% rename from plugins/potato.py rename to disabled_stuff/potato.py index 8d03e2b..9987e18 --- a/plugins/potato.py +++ b/disabled_stuff/potato.py @@ -1,8 +1,10 @@ # coding=utf-8 -from util import hook import re import random +from util import hook + + potatoes = ['AC Belmont', 'AC Blue Pride', 'AC Brador', 'AC Chaleur', 'AC Domino', 'AC Dubuc', 'AC Glacier Chip', 'AC Maple Gold', 'AC Novachip', 'AC Peregrine Red', 'AC Ptarmigan', 'AC Red Island', 'AC Saguenor', 'AC Stampede Russet', 'AC Sunbury', 'Abeille', 'Abnaki', 'Acadia', 'Acadia Russet', 'Accent', @@ -37,7 +39,7 @@ potatoes = ['AC Belmont', 'AC Blue Pride', 'AC Brador', 'AC Chaleur', 'AC Domino @hook.command -def potato(inp, me=None, input=None): +def potato(inp, action=None): """potato <user> - Makes <user> a tasty little potato.""" inp = inp.strip() @@ -50,5 +52,5 @@ def potato(inp, me=None, input=None): method = random.choice(['bakes', 'fries', 'boils', 'roasts']) side_dish = random.choice(['side salad', 'dollop of sour cream', 'piece of chicken', 'bowl of shredded bacon']) - me("{} a {} {} {} potato for {} and serves it with a small {}!".format(method, flavor, size, potato_type, inp, - side_dish)) + action("{} a {} {} {} potato for {} and serves it with a small {}!".format(method, flavor, size, potato_type, inp, + side_dish)) diff --git a/disabled_stuff/pre.py b/disabled_stuff/pre.py new file mode 100644 index 0000000..f4e61a3 --- /dev/null +++ b/disabled_stuff/pre.py @@ -0,0 +1,38 @@ +import datetime + +from util import hook, http, timesince + + +@hook.command("scene") +@hook.command +def pre(inp): + """pre <query> -- searches scene releases using orlydb.com""" + + try: + h = http.get_html("http://orlydb.com/", q=inp) + except http.HTTPError as e: + return 'Unable to fetch results: {}'.format(e) + + results = h.xpath("//div[@id='releases']/div/span[@class='release']/..") + + if not results: + return "No results found." + + result = results[0] + + date = result.xpath("span[@class='timestamp']/text()")[0] + section = result.xpath("span[@class='section']//text()")[0] + name = result.xpath("span[@class='release']/text()")[0] + + # parse date/time + date = datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S") + date_string = date.strftime("%d %b %Y") + since = timesince.timesince(date) + + size = result.xpath("span[@class='inforight']//text()") + if size: + size = ' - ' + size[0].split()[0] + else: + size = '' + + return '{} - {}{} - {} ({} ago)'.format(section, name, size, date_string, since) diff --git a/plugins/pyexec.py b/disabled_stuff/python.py old mode 100755 new mode 100644 similarity index 80% rename from plugins/pyexec.py rename to disabled_stuff/python.py index 98fecc2..5cdc524 --- a/plugins/pyexec.py +++ b/disabled_stuff/python.py @@ -1,5 +1,5 @@ from util import hook -from util.execute import eval_py +from util.pyexec import eval_py @hook.command diff --git a/plugins/qr.py b/disabled_stuff/qrcode.py similarity index 83% rename from plugins/qr.py rename to disabled_stuff/qrcode.py index f14437f..9d481e0 100644 --- a/plugins/qr.py +++ b/disabled_stuff/qrcode.py @@ -8,9 +8,9 @@ def qrcode(inp): """qrcode [link] returns a link for a QR code.""" args = { - "cht": "qr", # chart type + "cht": "qr", # chart type (QR) "chs": "200x200", # dimensions - "chl": inp + "chl": inp # data } link = http.prepare_url("http://chart.googleapis.com/chart", args) diff --git a/plugins/quote.py b/disabled_stuff/quote.py old mode 100755 new mode 100644 similarity index 98% rename from plugins/quote.py rename to disabled_stuff/quote.py index 25c614d..6beefc5 --- a/plugins/quote.py +++ b/disabled_stuff/quote.py @@ -8,8 +8,8 @@ from util import hook def format_quote(q, num, n_quotes): """Returns a formatted string of a quote""" ctime, nick, msg = q - return "[{}/{}] <{}> {}".format((num, n_quotes, - nick, msg)) + return "[{}/{}] <{}> {}".format(num, n_quotes, + nick, msg) def create_table_if_not_exists(db): diff --git a/plugins/rdio.py b/disabled_stuff/rdio.py similarity index 93% rename from plugins/rdio.py rename to disabled_stuff/rdio.py index 6333ab1..2677090 100644 --- a/plugins/rdio.py +++ b/disabled_stuff/rdio.py @@ -1,9 +1,11 @@ import urllib import json import re -from util import hook + import oauth2 as oauth +from util import hook + def getdata(inp, types, api_key, api_secret): consumer = oauth.Consumer(api_key, api_secret) @@ -27,18 +29,18 @@ def rdio(inp, bot=None): except IndexError: return "No results." if 'name' in info: - if 'artist' in info and 'album' in info: #Track + if 'artist' in info and 'album' in info: # Track name = info['name'] artist = info['artist'] album = info['album'] url = info['shortUrl'] return u"\x02{}\x02 by \x02{}\x02 - {} {}".format(name, artist, album, url) - elif 'artist' in info and not 'album' in info: #Album + elif 'artist' in info and not 'album' in info: # Album name = info['name'] artist = info['artist'] url = info['shortUrl'] return u"\x02{}\x02 by \x02{}\x02 - {}".format(name, artist, url) - else: #Artist + else: # Artist name = info['name'] url = info['shortUrl'] return u"\x02{}\x02 - {}".format(name, url) @@ -115,15 +117,15 @@ def rdio_url(match, bot=None): data = json.loads(response[1]) info = data['result'] if 'name' in info: - if 'artist' in info and 'album' in info: #Track + if 'artist' in info and 'album' in info: # Track name = info['name'] artist = info['artist'] album = info['album'] return u"Rdio track: \x02{}\x02 by \x02{}\x02 - {}".format(name, artist, album) - elif 'artist' in info and not 'album' in info: #Album + elif 'artist' in info and not 'album' in info: # Album name = info['name'] artist = info['artist'] return u"Rdio album: \x02{}\x02 by \x02{}\x02".format(name, artist) - else: #Artist + else: # Artist name = info['name'] return u"Rdio artist: \x02{}\x02".format(name) diff --git a/disabled_stuff/recipe.py b/disabled_stuff/recipe.py new file mode 100644 index 0000000..0e04572 --- /dev/null +++ b/disabled_stuff/recipe.py @@ -0,0 +1,106 @@ +import random + +from util import hook, http, web + +metadata_url = "http://omnidator.appspot.com/microdata/json/?url={}" + +base_url = "http://www.cookstr.com" +search_url = base_url + "/searches" +random_url = search_url + "/surprise" + +# set this to true to censor this plugin! +censor = True +phrases = [ + u"EAT SOME FUCKING \x02{}\x02", + u"YOU WON'T NOT MAKE SOME FUCKING \x02{}\x02", + u"HOW ABOUT SOME FUCKING \x02{}?\x02", + u"WHY DON'T YOU EAT SOME FUCKING \x02{}?\x02", + u"MAKE SOME FUCKING \x02{}\x02", + u"INDUCE FOOD COMA WITH SOME FUCKING \x02{}\x02" +] + +clean_key = lambda i: i.split("#")[1] + + +class ParseError(Exception): + pass + + +def get_data(url): + """ Uses the omnidator API to parse the metadata from the provided URL """ + try: + omni = http.get_json(metadata_url.format(url)) + except (http.HTTPError, http.URLError) as e: + raise ParseError(e) + schemas = omni["@"] + for d in schemas: + if d["a"] == "<http://schema.org/Recipe>": + data = {clean_key(key): value for (key, value) in d.iteritems() + if key.startswith("http://schema.org/Recipe")} + return data + raise ParseError("No recipe data found") + + +@hook.command(autohelp=False) +def recipe(inp): + """recipe [term] - Gets a recipe for [term], or ets a random recipe if [term] is not provided""" + if inp: + # get the recipe URL by searching + try: + search = http.get_soup(search_url, query=inp.strip()) + except (http.HTTPError, http.URLError) as e: + return "Could not get recipe: {}".format(e) + + # find the list of results + result_list = search.find('div', {'class': 'found_results'}) + + if result_list: + results = result_list.find_all('div', {'class': 'recipe_result'}) + else: + return "No results" + + # pick a random front page result + result = random.choice(results) + + # extract the URL from the result + url = base_url + result.find('div', {'class': 'image-wrapper'}).find('a')['href'] + + else: + # get a random recipe URL + try: + page = http.open(random_url) + except (http.HTTPError, http.URLError) as e: + return "Could not get recipe: {}".format(e) + url = page.geturl() + + # use get_data() to get the recipe info from the URL + try: + data = get_data(url) + except ParseError as e: + return "Could not parse recipe: {}".format(e) + + name = data["name"].strip() + return u"Try eating \x02{}!\x02 - {}".format(name, web.try_isgd(url)) + + +@hook.command(autohelp=False) +def dinner(inp): + """dinner - WTF IS FOR DINNER""" + try: + page = http.open(random_url) + except (http.HTTPError, http.URLError) as e: + return "Could not get recipe: {}".format(e) + url = page.geturl() + + try: + data = get_data(url) + except ParseError as e: + return "Could not parse recipe: {}".format(e) + + name = data["name"].strip().upper() + text = random.choice(phrases).format(name) + + if censor: + text = text.replace("FUCK", "F**K") + + return u"{} - {}".format(text, web.try_isgd(url)) diff --git a/disabled_stuff/reddit.py b/disabled_stuff/reddit.py new file mode 100644 index 0000000..80fcb76 --- /dev/null +++ b/disabled_stuff/reddit.py @@ -0,0 +1,79 @@ +from datetime import datetime +import re +import random + +from util import hook, http, text, timesince + + +reddit_re = (r'.*(((www\.)?reddit\.com/r|redd\.it)[^ ]+)', re.I) + +base_url = "http://reddit.com/r/{}/.json" +short_url = "http://redd.it/{}" + + +@hook.regex(*reddit_re) +def reddit_url(match): + thread = http.get_html(match.group(0)) + + title = thread.xpath('//title/text()')[0] + upvotes = thread.xpath("//span[@class='upvotes']/span[@class='number']/text()")[0] + downvotes = thread.xpath("//span[@class='downvotes']/span[@class='number']/text()")[0] + author = thread.xpath("//div[@id='siteTable']//a[contains(@class,'author')]/text()")[0] + timeago = thread.xpath("//div[@id='siteTable']//p[@class='tagline']/time/text()")[0] + comments = thread.xpath("//div[@id='siteTable']//a[@class='comments']/text()")[0] + + return u'\x02{}\x02 - posted by \x02{}\x02 {} ago - {} upvotes, {} downvotes - {}'.format( + title, author, timeago, upvotes, downvotes, comments) + + +@hook.command(autohelp=False) +def reddit(inp): + """reddit <subreddit> [n] -- Gets a random post from <subreddit>, or gets the [n]th post in the subreddit.""" + id_num = None + + if inp: + # clean and split the input + parts = inp.lower().strip().split() + + # find the requested post number (if any) + if len(parts) > 1: + url = base_url.format(parts[0].strip()) + try: + id_num = int(parts[1]) - 1 + except ValueError: + return "Invalid post number." + else: + url = base_url.format(parts[0].strip()) + else: + url = "http://reddit.com/.json" + + try: + data = http.get_json(url, user_agent=http.ua_chrome) + except Exception as e: + return "Error: " + str(e) + data = data["data"]["children"] + + # get the requested/random post + if id_num is not None: + try: + item = data[id_num]["data"] + except IndexError: + length = len(data) + return "Invalid post number. Number must be between 1 and {}.".format(length) + else: + item = random.choice(data)["data"] + + item["title"] = text.truncate_str(item["title"], 50) + item["link"] = short_url.format(item["id"]) + + raw_time = datetime.fromtimestamp(int(item["created_utc"])) + item["timesince"] = timesince.timesince(raw_time) + + if item["over_18"]: + item["warning"] = " \x02NSFW\x02" + else: + item["warning"] = "" + + return u"\x02{title} : {subreddit}\x02 - posted by \x02{author}\x02" \ + " {timesince} ago - {ups} upvotes, {downs} downvotes -" \ + " {link}{warning}".format(**item) diff --git a/disabled_stuff/regex_chans.py b/disabled_stuff/regex_chans.py new file mode 100644 index 0000000..c16c250 --- /dev/null +++ b/disabled_stuff/regex_chans.py @@ -0,0 +1,128 @@ +from util import hook + + +# Default value. +# If True, all channels without a setting will have regex enabled +# If False, all channels without a setting will have regex disabled +default_enabled = True + +db_ready = False + + +def db_init(db): + global db_ready + if not db_ready: + db.execute("CREATE TABLE IF NOT EXISTS regexchans(channel PRIMARY KEY, status)") + db.commit() + db_ready = True + + +def get_status(db, channel): + row = db.execute("SELECT status FROM regexchans WHERE channel = ?", [channel]).fetchone() + if row: + return row[0] + else: + return None + + +def set_status(db, channel, status): + row = db.execute("REPLACE INTO regexchans (channel, status) VALUES(?, ?)", [channel, status]) + db.commit() + + +def delete_status(db, channel): + row = db.execute("DELETE FROM regexchans WHERE channel = ?", [channel]) + db.commit() + + +def list_status(db): + row = db.execute("SELECT * FROM regexchans").fetchall() + result = None + for values in row: + if result: + result += u", {}: {}".format(values[0], values[1]) + else: + result = u"{}: {}".format(values[0], values[1]) + return result + + +@hook.sieve +def sieve_regex(bot, inp, func, kind, args): + db = bot.get_db_connection(inp.conn) + db_init(db) + if kind == 'regex' and inp.chan.startswith("#") and func.__name__ != 'factoid': + chanstatus = get_status(db, inp.chan) + if chanstatus != "ENABLED" and (chanstatus == "DISABLED" or not default_enabled): + print u"Denying input.raw={}, kind={}, args={} from {}".format(inp.raw, kind, args, inp.chan) + return None + print u"Allowing input.raw={}, kind={}, args={} from {}".format(inp.raw, kind, args, inp.chan) + + return inp + + +@hook.command(permissions=["botcontrol"]) +def enableregex(inp, db=None, message=None, notice=None, chan=None, nick=None): + db_init(db) + inp = inp.strip().lower() + if not inp: + channel = chan + elif inp.startswith("#"): + channel = inp + else: + channel = u"#{}".format(inp) + + message(u"Enabling regex matching (youtube, etc) (issued by {})".format(nick), target=channel) + notice(u"Enabling regex matching (youtube, etc) in channel {}".format(channel)) + set_status(db, channel, "ENABLED") + + +@hook.command(permissions=["botcontrol"]) +def disableregex(inp, db=None, message=None, notice=None, chan=None, nick=None): + db_init(db) + inp = inp.strip().lower() + if not inp: + channel = chan + elif inp.startswith("#"): + channel = inp + else: + channel = u"#{}".format(inp) + + message(u"Disabling regex matching (youtube, etc) (issued by {})".format(nick), target=channel) + notice(u"Disabling regex matching (youtube, etc) in channel {}".format(channel)) + set_status(db, channel, "DISABLED") + + +@hook.command(permissions=["botcontrol"]) +def resetregex(inp, db=None, message=None, notice=None, chan=None, nick=None): + db_init(db) + inp = inp.strip().lower() + if not inp: + channel = chan + elif inp.startswith("#"): + channel = inp + else: + channel = u"#{}".format(inp) + + message(u"Resetting regex matching setting (youtube, etc) (issued by {})".format(nick), target=channel) + notice(u"Resetting regex matching setting (youtube, etc) in channel {}".format(channel)) + delete_status(db, channel) + + +@hook.command(permissions=["botcontrol"]) +def regexstatus(inp, db=None, chan=None): + db_init(db) + inp = inp.strip().lower() + if not inp: + channel = chan + elif inp.startswith("#"): + channel = inp + else: + channel = u"#{}".format(inp) + + return u"Regex status for {}: {}".format(channel, get_status(db, channel)) + + +@hook.command(permissions=["botcontrol"]) +def listregex(inp, db=None): + db_init(db) + return list_status(db) diff --git a/disabled_stuff/religion.py b/disabled_stuff/religion.py new file mode 100644 index 0000000..552b23f --- /dev/null +++ b/disabled_stuff/religion.py @@ -0,0 +1,38 @@ +from util import hook, http + + +@hook.command('god') +@hook.command +def bible(inp): + """.bible <passage> -- gets <passage> from the Bible (ESV)""" + + base_url = ('http://www.esvapi.org/v2/rest/passageQuery?key=IP&' + 'output-format=plain-text&include-heading-horizontal-lines&' + 'include-headings=false&include-passage-horizontal-lines=false&' + 'include-passage-references=false&include-short-copyright=false&' + 'include-footnotes=false&line-length=0&' + 'include-heading-horizontal-lines=false') + + text = http.get(base_url, passage=inp) + + text = ' '.join(text.split()) + + if len(text) > 400: + text = text[:text.rfind(' ', 0, 400)] + '...' + + return text + + +@hook.command('allah') +@hook.command +def koran(inp): # Koran look-up plugin by Ghetto Wizard + """.koran <chapter.verse> -- gets <chapter.verse> from the Koran""" + + url = 'http://quod.lib.umich.edu/cgi/k/koran/koran-idx?type=simple' + + results = http.get_html(url, q1=inp).xpath('//li') + + if not results: + return 'No results for ' + inp + + return results[0].text_content() diff --git a/disabled_stuff/repaste.py b/disabled_stuff/repaste.py old mode 100755 new mode 100644 diff --git a/plugins/rottentomatoes.py b/disabled_stuff/rottentomatoes.py similarity index 81% rename from plugins/rottentomatoes.py rename to disabled_stuff/rottentomatoes.py index b3381c0..2d7af38 100644 --- a/plugins/rottentomatoes.py +++ b/disabled_stuff/rottentomatoes.py @@ -21,7 +21,7 @@ def rottentomatoes(inp, bot=None): movie = results['movies'][0] title = movie['title'] - id = movie['id'] + movie_id = movie['id'] critics_score = movie['ratings']['critics_score'] audience_score = movie['ratings']['audience_score'] url = movie['links']['alternate'] @@ -29,11 +29,11 @@ def rottentomatoes(inp, bot=None): if critics_score == -1: return - reviews = http.get_json(movie_reviews_url % id, apikey=api_key, review_type='all') + reviews = http.get_json(movie_reviews_url % movie_id, apikey=api_key, review_type='all') review_count = reviews['total'] fresh = critics_score * review_count / 100 rotten = review_count - fresh return u"{} - Critics Rating: \x02{}%\x02 ({} liked, {} disliked) " \ - "Audience Rating: \x02{}%\x02 - {}".format(title, critics_score, fresh, rotten, audience_score, url) \ No newline at end of file + "Audience Rating: \x02{}%\x02 - {}".format(title, critics_score, fresh, rotten, audience_score, url) diff --git a/plugins/rss.py b/disabled_stuff/rss.py similarity index 89% rename from plugins/rss.py rename to disabled_stuff/rss.py index 734db0f..f7ed1c4 100644 --- a/plugins/rss.py +++ b/disabled_stuff/rss.py @@ -3,7 +3,7 @@ from util import hook, http, web, text @hook.command("feed") @hook.command -def rss(inp, say=None): +def rss(inp, message=None): """rss <feed> -- Gets the first three items from the RSS feed <feed>.""" limit = 3 @@ -31,10 +31,10 @@ def rss(inp, say=None): link = web.isgd(row["link"]) except (web.ShortenError, http.HTTPError, http.URLError): link = row["link"] - say(u"{} - {}".format(title, link)) + message(u"{} - {}".format(title, link)) @hook.command(autohelp=False) -def rb(inp, say=None): +def rb(inp, message=None): """rb -- Shows the latest Craftbukkit recommended build""" - rss("bukkit", say) + rss("bukkit", message) diff --git a/plugins/shorten.py b/disabled_stuff/shorten.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/shorten.py rename to disabled_stuff/shorten.py diff --git a/plugins/slap.py b/disabled_stuff/slap.py similarity index 79% rename from plugins/slap.py rename to disabled_stuff/slap.py index b887ab3..37dfbbd 100644 --- a/plugins/slap.py +++ b/disabled_stuff/slap.py @@ -1,5 +1,6 @@ -from util import hook, text, textgen -import json, os +import json + +from util import hook, textgen def get_generator(_json, variables): @@ -9,7 +10,7 @@ def get_generator(_json, variables): @hook.command -def slap(inp, me=None, nick=None, conn=None, notice=None): +def slap(inp, action=None, nick=None, conn=None, notice=None): """slap <user> -- Makes the bot slap <user>.""" target = inp.strip() @@ -22,11 +23,11 @@ def slap(inp, me=None, nick=None, conn=None, notice=None): target = nick variables = { - "user": target + "user": target } with open("plugins/data/slaps.json") as f: generator = get_generator(f.read(), variables) # act out the message - me(generator.generate_string()) \ No newline at end of file + action(generator.generate_string()) diff --git a/plugins/slogan.py b/disabled_stuff/slogan.py old mode 100755 new mode 100644 similarity index 99% rename from plugins/slogan.py rename to disabled_stuff/slogan.py index 50ccbc1..279c41d --- a/plugins/slogan.py +++ b/disabled_stuff/slogan.py @@ -1,6 +1,7 @@ -from util import hook, text import random +from util import hook, text + with open("plugins/data/slogans.txt") as f: slogans = [line.strip() for line in f.readlines() diff --git a/plugins/snopes.py b/disabled_stuff/snopes.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/snopes.py rename to disabled_stuff/snopes.py diff --git a/plugins/soundcloud.py b/disabled_stuff/soundcloud.py similarity index 93% rename from plugins/soundcloud.py rename to disabled_stuff/soundcloud.py index f2e3a5d..d31f103 100644 --- a/plugins/soundcloud.py +++ b/disabled_stuff/soundcloud.py @@ -1,7 +1,9 @@ -from util import hook, http, web, text from urllib import urlencode import re +from util import hook, http, web, text + + sc_re = (r'(.*:)//(www.)?(soundcloud.com)(.*)', re.I) api_url = "http://api.soundcloud.com" sndsc_re = (r'(.*:)//(www.)?(snd.sc)(.*)', re.I) @@ -21,7 +23,7 @@ def soundcloud(url, api_key): url = web.try_isgd(data['permalink_url']) - return u"SoundCloud track: \x02{}\x02 by \x02{}user\x02 {}{}- {} plays, {} downloads, {} comments - {}".format( + return u"SoundCloud track: \x02{}\x02 by \x02{}\x02 {}{}- {} plays, {} downloads, {} comments - {}".format( data['title'], data['user']['username'], desc, genre, data['playback_count'], data['download_count'], data['comment_count'], url) diff --git a/plugins/spellcheck.py b/disabled_stuff/spellcheck.py old mode 100755 new mode 100644 similarity index 90% rename from plugins/spellcheck.py rename to disabled_stuff/spellcheck.py index 7af8ab2..1630a0d --- a/plugins/spellcheck.py +++ b/disabled_stuff/spellcheck.py @@ -1,8 +1,9 @@ -from util import hook from enchant.checker import SpellChecker - import enchant +from util import hook + + locale = "en_US" @@ -14,12 +15,12 @@ def spell(inp): return "Could not find dictionary: {}".format(locale) if len(inp.split(" ")) > 1: - chkr = SpellChecker(locale) - chkr.set_text(inp) + # input is a sentence + checker = SpellChecker(locale) + checker.set_text(inp) offset = 0 - - for err in chkr: + for err in checker: # find the location of the incorrect word start = err.wordpos + offset finish = start + len(err.word) @@ -31,9 +32,9 @@ def spell(inp): offset = (offset + len(s_string)) - len(err.word) # replace the word with the suggestions inp = inp[:start] + s_string + inp[finish:] - return inp else: + # input is a word dictionary = enchant.Dict(locale) is_correct = dictionary.check(inp) suggestions = dictionary.suggest(inp) diff --git a/plugins/spotify.py b/disabled_stuff/spotify.py similarity index 88% rename from plugins/spotify.py rename to disabled_stuff/spotify.py index c113a06..9897235 100644 --- a/plugins/spotify.py +++ b/disabled_stuff/spotify.py @@ -1,7 +1,7 @@ import re +from urllib import urlencode from util import hook, http, web -from urllib import urlencode gateway = 'http://open.spotify.com/{}/{}' # http spotify gw address spuri = 'spotify:{}:{}' @@ -45,7 +45,7 @@ def spotify(inp): except IndexError: return "Could not find track." url = sptfy(gateway.format(type, id)) - return u"\x02{}\x02 by \x02{}\x02 - \x02{}\x02".format(data["tracks"][0]["name"], + return u"\x02{}\x02 by \x02{}\x02 - {}".format(data["tracks"][0]["name"], data["tracks"][0]["artists"][0]["name"], url) @@ -62,7 +62,7 @@ def spalbum(inp): except IndexError: return "Could not find album." url = sptfy(gateway.format(type, id)) - return u"\x02{}\x02 by \x02{}\x02 - \x02{}\x02".format(data["albums"][0]["name"], + return u"\x02{}\x02 by \x02{}\x02 - {}".format(data["albums"][0]["name"], data["albums"][0]["artists"][0]["name"], url) @@ -79,7 +79,7 @@ def spartist(inp): except IndexError: return "Could not find artist." url = sptfy(gateway.format(type, id)) - return u"\x02{}\x02 - \x02{}\x02".format(data["artists"][0]["name"], url) + return u"\x02{}\x02 - {}".format(data["artists"][0]["name"], url) @hook.regex(*http_re) @@ -94,13 +94,13 @@ def spotify_url(match): name = data["track"]["name"] artist = data["track"]["artists"][0]["name"] album = data["track"]["album"]["name"] - return u"Spotify Track: \x02{}\x02 by \x02{}\x02 from the album \x02{}\x02 - \x02{}\x02".format(name, artist, + return u"Spotify Track: \x02{}\x02 by \x02{}\x02 from the album \x02{}\x02 - {}".format(name, artist, album, sptfy( gateway.format(type, spotify_id))) elif type == "artist": - return u"Spotify Artist: \x02{}\x02 - \x02{}\x02".format(data["artist"]["name"], + return u"Spotify Artist: \x02{}\x02 - {}".format(data["artist"]["name"], sptfy(gateway.format(type, spotify_id))) elif type == "album": - return u"Spotify Album: \x02{}\x02 - \x02{}\x02 - \x02{}\x02".format(data["album"]["artist"], + return u"Spotify Album: \x02{}\x02 - \x02{}\x02 - {}".format(data["album"]["artist"], data["album"]["name"], sptfy(gateway.format(type, spotify_id))) diff --git a/disabled_stuff/status.py b/disabled_stuff/status.py new file mode 100644 index 0000000..977ac8e --- /dev/null +++ b/disabled_stuff/status.py @@ -0,0 +1,53 @@ +from util import hook +import re +import time +from subprocess import check_output + +def getstatus(): + try: + return check_output("sudo /bin/chch-status", shell=True).strip("\n").decode("utf-8") + except: + return "unbekannt" + +@hook.command("status", autohelp=False) +def cmd_status(inp, reply=None): + """status - Return the door status""" + reply("Chaostreff Status: %s" % (getstatus())) + +@hook.event("TOPIC") +def topic_update(info, conn=None, chan=None): + """topic_update -- Update the topic on TOPIC command""" + status = getstatus() + + topic = info[-1] + + sstr = "Status: %s" % (status) + if sstr in topic: + return + + if 'Status: ' in topic: + new_topic = re.sub("Status: [^ ]*", sstr, topic) + else: + new_topic = "%s | %s" % (topic.rstrip(' |'), sstr) + + if new_topic != topic: + conn.send("TOPIC %s :%s" % (chan, new_topic)) + +@hook.event("332") +def e332_update(info, conn=None, chan=None): + """e332_update -- run after current topic was requested""" + chan = info[1] + topic_update(info, conn=conn, chan=chan) + +@hook.singlethread +@hook.event("353") +def e353_update(info, conn=None, chan=None): + """e353_update -- runs after a channel was joined""" + chan = info[2] + if chan.lower() == "#chaoschemnitz": + conn.send("PRIVMSG Chanserv :op #chaoschemnitz") + + while True: + conn.send("TOPIC %s" % (chan)) + time.sleep(60) + diff --git a/disabled_stuff/steam.py b/disabled_stuff/steam.py new file mode 100644 index 0000000..f3814db --- /dev/null +++ b/disabled_stuff/steam.py @@ -0,0 +1,75 @@ +import re + +from bs4 import BeautifulSoup, NavigableString, Tag + +from util import hook, http, web +from util.text import truncate_str + + +steam_re = (r'(.*:)//(store.steampowered.com)(:[0-9]+)?(.*)', re.I) + + +def get_steam_info(url): + page = http.get(url) + soup = BeautifulSoup(page, 'lxml', from_encoding="utf-8") + + data = {} + + data["name"] = soup.find('div', {'class': 'apphub_AppName'}).text + data["desc"] = truncate_str(soup.find('meta', {'name': 'description'})['content'].strip(), 80) + + # get the element details_block + details = soup.find('div', {'class': 'details_block'}) + + # loop over every <b></b> tag in details_block + for b in details.findAll('b'): + # get the contents of the <b></b> tag, which is our title + title = b.text.lower().replace(":", "") + if title == "languages": + # we have all we need! + break + + # find the next element directly after the <b></b> tag + next_element = b.nextSibling + if next_element: + # if the element is some text + if isinstance(next_element, NavigableString): + text = next_element.string.strip() + if text: + # we found valid text, save it and continue the loop + data[title] = text + continue + else: + # the text is blank - sometimes this means there are + # useless spaces or tabs between the <b> and <a> tags. + # so we find the next <a> tag and carry on to the next + # bit of code below + next_element = next_element.find_next('a', href=True) + + # if the element is an <a></a> tag + if isinstance(next_element, Tag) and next_element.name == 'a': + text = next_element.string.strip() + if text: + # we found valid text (in the <a></a> tag), + # save it and continue the loop + data[title] = text + continue + + data["price"] = soup.find('div', {'class': 'game_purchase_price price'}).text.strip() + + return u"\x02{name}\x02: {desc}, \x02Genre\x02: {genre}, \x02Release Date\x02: {release date}," \ + u" \x02Price\x02: {price}".format(**data) + + +@hook.regex(*steam_re) +def steam_url(match): + return get_steam_info("http://store.steampowered.com" + match.group(4)) + + +@hook.command +def steam(inp): + """steam [search] - Search for specified game/trailer/DLC""" + page = http.get("http://store.steampowered.com/search/?term=" + inp) + soup = BeautifulSoup(page, 'lxml', from_encoding="utf-8") + result = soup.find('a', {'class': 'search_result_row'}) + return get_steam_info(result['href']) + " - " + web.isgd(result['href']) diff --git a/plugins/steam_calc.py b/disabled_stuff/steam_calc.py similarity index 68% rename from plugins/steam_calc.py rename to disabled_stuff/steam_calc.py index d9ff67d..6684eba 100644 --- a/plugins/steam_calc.py +++ b/disabled_stuff/steam_calc.py @@ -1,14 +1,21 @@ -from util import hook, http, text import csv import StringIO +from util import hook, http, text + + gauge_url = "http://www.mysteamgauge.com/search?username={}" api_url = "http://mysteamgauge.com/user/{}.csv" steam_api_url = "http://steamcommunity.com/id/{}/?xml=1" -def refresh_data(name): http.get(gauge_url.format(name), timeout=25, get_method='HEAD') -def get_data(name): return http.get(api_url.format(name)) + +def refresh_data(name): + http.get(gauge_url.format(name), timeout=25, get_method='HEAD') + + +def get_data(name): + return http.get(api_url.format(name)) def is_number(s): @@ -31,12 +38,14 @@ def steamcalc(inp, reply=None): """steamcalc <username> [currency] - Gets value of steam account and total hours played. Uses steamcommunity.com/id/<nickname>. """ - name = inp.strip() + # check if the user asked us to force reload + force_reload = inp.endswith(" forcereload") + if force_reload: + name = inp[:-12].strip().lower() + else: + name = inp.strip() - try: - request = get_data(name) - do_refresh = True - except (http.HTTPError, http.URLError): + if force_reload: try: reply("Collecting data, this may take a while.") refresh_data(name) @@ -44,8 +53,20 @@ def steamcalc(inp, reply=None): do_refresh = False except (http.HTTPError, http.URLError): return "Could not get data for this user." + else: + try: + request = get_data(name) + do_refresh = True + except (http.HTTPError, http.URLError): + try: + reply("Collecting data, this may take a while.") + refresh_data(name) + request = get_data(name) + do_refresh = False + except (http.HTTPError, http.URLError): + return "Could not get data for this user." - csv_data = StringIO.StringIO(request) # we use StringIO because CSV can't read a string + csv_data = StringIO.StringIO(request) # we use StringIO because CSV can't read a string reader = unicode_dictreader(csv_data) # put the games in a list @@ -63,7 +84,7 @@ def steamcalc(inp, reply=None): except AttributeError: return "Could not get data for this user." - online_state = online_state.replace("<br/>", ": ") # will make this pretty later + online_state = online_state.replace("<br/>", ": ") # will make this pretty later data["state"] = text.strip_html(online_state) # work out the average metascore for all games @@ -91,10 +112,9 @@ def steamcalc(inp, reply=None): data["size"] = "{0:.1f}".format(total_size) - - reply("{name} ({state}) has {games} games with a total value of ${value}" \ - " and a total size of {size}GB! The average metascore for these" \ - " games is {average_metascore}.".format(**data)) + reply("{name} ({state}) has {games} games with a total value of ${value}" + " and a total size of {size}GB! The average metascore for these" + " games is {average_metascore}.".format(**data)) if do_refresh: refresh_data(name) diff --git a/disabled_stuff/stock.py b/disabled_stuff/stock.py new file mode 100644 index 0000000..aedf051 --- /dev/null +++ b/disabled_stuff/stock.py @@ -0,0 +1,30 @@ +from util import hook, web + + +@hook.command +def stock(inp): + """stock <symbol> -- gets stock information""" + sym = inp.strip().lower() + + query = "SELECT * FROM yahoo.finance.quote WHERE symbol=@symbol LIMIT 1" + quote = web.query(query, {"symbol": sym}).one() + + # if we don't get a company name back, the symbol doesn't match a company + if quote['Change'] is None: + return "Unknown ticker symbol: {}".format(sym) + + change = float(quote['Change']) + price = float(quote['LastTradePriceOnly']) + + if change < 0: + quote['color'] = "5" + else: + quote['color'] = "3" + + quote['PercentChange'] = 100 * change / (price - change) + print quote + + return u"\x02{Name}\x02 (\x02{symbol}\x02) - {LastTradePriceOnly} " \ + "\x03{color}{Change} ({PercentChange:.2f}%)\x03 " \ + "Day Range: {DaysRange} " \ + "MCAP: {MarketCapitalization}".format(**quote) diff --git a/disabled_stuff/suggest.py b/disabled_stuff/suggest.py old mode 100755 new mode 100644 index 92f9405..ec66144 --- a/disabled_stuff/suggest.py +++ b/disabled_stuff/suggest.py @@ -1,33 +1,19 @@ -import json -import random -import re - -from util import hook, http +from util import hook, http, text +from bs4 import BeautifulSoup @hook.command -def suggest(inp, inp_unstripped=''): - ".suggest [#n] <phrase> -- gets a random/the nth suggested google search" +def suggest(inp): + """suggest <phrase> -- Gets suggested phrases for a google search""" + suggestions = http.get_json('http://suggestqueries.google.com/complete/search', client='firefox', q=inp)[1] - inp = inp_unstripped - m = re.match('^#(\d+) (.+)$', inp) - if m: - num, inp = m.groups() - num = int(num) - if num > 10: - return 'I can only get the first ten suggestions.' - else: - num = 0 - - page = http.get('http://google.com/complete/search', output='json', client='hp', q=inp) - page_json = page.split('(', 1)[1][:-1] - suggestions = json.loads(page_json)[1] if not suggestions: - return 'No suggestions found.' - if num: - if len(suggestions) + 1 <= num: - return 'I only got %d suggestions.' % len(suggestions) - out = suggestions[num - 1] - else: - out = random.choice(suggestions) - return '#%d: %s' % (int(out[2][0]) + 1, out[0].replace('<b>', '').replace('</b>', '')) \ No newline at end of file + return 'no suggestions found' + + out = u", ".join(suggestions) + + # defuckify text (might not be needed now, but I'll keep it) + soup = BeautifulSoup(out) + out = soup.get_text() + + return text.truncate_str(out, 200) diff --git a/plugins/system.py b/disabled_stuff/system.py old mode 100755 new mode 100644 similarity index 99% rename from plugins/system.py rename to disabled_stuff/system.py index e8489d1..08891fd --- a/plugins/system.py +++ b/disabled_stuff/system.py @@ -2,9 +2,10 @@ import os import re import time import platform -from util import hook from datetime import timedelta +from util import hook + def convert_kilobytes(kilobytes): if kilobytes >= 1024: diff --git a/plugins/tell.py b/disabled_stuff/tell.py old mode 100755 new mode 100644 similarity index 76% rename from plugins/tell.py rename to disabled_stuff/tell.py index 5e54c77..52a0aa1 --- a/plugins/tell.py +++ b/disabled_stuff/tell.py @@ -6,15 +6,18 @@ import re from util import hook, timesince +db_ready = [] -def db_init(db): - """check to see that our db has the tell table and return a dbection.""" - db.execute("create table if not exists tell" - "(user_to, user_from, message, chan, time," - "primary key(user_to, message))") - db.commit() - return db +def db_init(db, conn): + """Check that our db has the tell table, create it if not.""" + global db_ready + if not conn.name in db_ready: + db.execute("create table if not exists tell" + "(user_to, user_from, message, chan, time," + "primary key(user_to, message))") + db.commit() + db_ready.append(conn.name) def get_tells(db, user_to): @@ -25,11 +28,11 @@ def get_tells(db, user_to): @hook.singlethread @hook.event('PRIVMSG') -def tellinput(paraml, input=None, notice=None, db=None, bot=None, nick=None, conn=None): +def tellinput(inp, input=None, notice=None, db=None, nick=None, conn=None): if 'showtells' in input.msg.lower(): return - db_init(db) + db_init(db, conn) tells = get_tells(db, nick) @@ -38,7 +41,7 @@ def tellinput(paraml, input=None, notice=None, db=None, bot=None, nick=None, con reltime = timesince.timesince(time) reply = "{} sent you a message {} ago from {}: {}".format(user_from, reltime, chan, - message) + message) if len(tells) > 1: reply += " (+{} more, {}showtells to view)".format(len(tells) - 1, conn.conf["command_prefix"]) @@ -49,10 +52,10 @@ def tellinput(paraml, input=None, notice=None, db=None, bot=None, nick=None, con @hook.command(autohelp=False) -def showtells(inp, nick='', chan='', notice=None, db=None): - "showtells -- View all pending tell messages (sent in a notice)." +def showtells(inp, nick='', chan='', notice=None, db=None, conn=None): + """showtells -- View all pending tell messages (sent in a notice).""" - db_init(db) + db_init(db, conn) tells = get_tells(db, nick) @@ -71,7 +74,7 @@ def showtells(inp, nick='', chan='', notice=None, db=None): @hook.command -def tell(inp, nick='', chan='', db=None, input=None, notice=None): +def tell(inp, nick='', chan='', db=None, input=None, notice=None, conn=None): """tell <nick> <message> -- Relay <message> to <nick> when <nick> is around.""" query = inp.split(' ', 1) @@ -91,15 +94,15 @@ def tell(inp, nick='', chan='', db=None, input=None, notice=None): return if user_to.lower() == input.conn.nick.lower(): - # user is looking for us, being a smartass + # user is looking for us, being a smart-ass notice("Thanks for the message, {}!".format(user_from)) return if not re.match("^[A-Za-z0-9_|.\-\]\[]*$", user_to.lower()): - notice("I cant send a message to that user!") + notice("I can't send a message to that user!") return - db_init(db) + db_init(db, conn) if db.execute("select count() from tell where user_to=?", (user_to,)).fetchone()[0] >= 10: diff --git a/plugins/time.py b/disabled_stuff/time_plugin.py old mode 100755 new mode 100644 similarity index 89% rename from plugins/time.py rename to disabled_stuff/time_plugin.py index b508522..885208b --- a/plugins/time.py +++ b/disabled_stuff/time_plugin.py @@ -1,7 +1,9 @@ -from util import hook, http import time + +from util import hook, http from util.text import capitalize_first + api_url = 'http://api.wolframalpha.com/v2/query?format=plaintext' @@ -16,10 +18,10 @@ def time_command(inp, bot=None): return "error: no wolfram alpha api key set" request = http.get_xml(api_url, input=query, appid=api_key) - time = " ".join(request.xpath("//pod[@title='Result']/subpod/plaintext/text()")) - time = time.replace(" | ", ", ") + current_time = " ".join(request.xpath("//pod[@title='Result']/subpod/plaintext/text()")) + current_time = current_time.replace(" | ", ", ") - if time: + if current_time: # nice place name for UNIX time if inp.lower() == "unix": place = "Unix Epoch" @@ -27,7 +29,7 @@ def time_command(inp, bot=None): place = capitalize_first(" ".join(request.xpath("//pod[@" "title='Input interpretation']/subpod/plaintext/text()"))[ 16:]) - return "{} - \x02{}\x02".format(time, place) + return "{} - \x02{}\x02".format(current_time, place) else: return "Could not get the time for '{}'.".format(inp) diff --git a/plugins/title.py b/disabled_stuff/title.py old mode 100755 new mode 100644 similarity index 79% rename from plugins/title.py rename to disabled_stuff/title.py index 1e3b403..4264188 --- a/plugins/title.py +++ b/disabled_stuff/title.py @@ -1,6 +1,7 @@ -from util import hook, http, urlnorm from bs4 import BeautifulSoup +from util import hook, http, urlnorm + @hook.command def title(inp): @@ -14,9 +15,9 @@ def title(inp): except (http.HTTPError, http.URLError): return "Could not fetch page." - title = soup.find('title').contents[0] + page_title = soup.find('title').contents[0] - if not title: + if not page_title: return "Could not find title." - return u"{} [{}]".format(title, real_url) + return u"{} [{}]".format(page_title, real_url) diff --git a/plugins/tvdb.py b/disabled_stuff/tvdb.py old mode 100755 new mode 100644 similarity index 77% rename from plugins/tvdb.py rename to disabled_stuff/tvdb.py index 22a5cd7..b5fa12f --- a/plugins/tvdb.py +++ b/disabled_stuff/tvdb.py @@ -1,14 +1,5 @@ -""" -TV information, written by Lurchington 2010 -modified by rmmh 2010 -""" - import datetime -from urllib2 import URLError -from zipfile import ZipFile -from cStringIO import StringIO -from lxml import etree from util import hook, http @@ -16,21 +7,12 @@ base_url = "http://thetvdb.com/api/" api_key = "469B73127CA0C411" -def get_zipped_xml(*args, **kwargs): - try: - path = kwargs.pop("path") - except KeyError: - raise KeyError("must specify a path for the zipped file to be read") - zip_buffer = StringIO(http.get(*args, **kwargs)) - return etree.parse(ZipFile(zip_buffer, "r").open(path)) - - -def get_episodes_for_series(seriesname, api_key): +def get_episodes_for_series(series_name, api_key): res = {"error": None, "ended": False, "episodes": None, "name": None} # http://thetvdb.com/wiki/index.php/API:GetSeries try: - query = http.get_xml(base_url + 'GetSeries.php', seriesname=seriesname) - except URLError: + query = http.get_xml(base_url + 'GetSeries.php', seriesname=series_name) + except http.URLError: res["error"] = "error contacting thetvdb.com" return res @@ -43,9 +25,8 @@ def get_episodes_for_series(seriesname, api_key): series_id = series_id[0] try: - series = get_zipped_xml(base_url + '%s/series/%s/all/en.zip' % - (api_key, series_id), path="en.xml") - except URLError: + series = http.get_xml(base_url + '%s/series/%s/all/en.xml' % (api_key, series_id)) + except http.URLError: res["error"] = "Error contacting thetvdb.com." return res @@ -63,7 +44,7 @@ def get_episode_info(episode, api_key): first_aired = episode.findtext("FirstAired") try: - airdate = datetime.date(*map(int, first_aired.split('-'))) + air_date = datetime.date(*map(int, first_aired.split('-'))) except (ValueError, TypeError): return None @@ -79,7 +60,7 @@ def get_episode_info(episode, api_key): episode_desc = '{}'.format(episode_num) if episode_name: episode_desc += ' - {}'.format(episode_name) - return first_aired, airdate, episode_desc + return first_aired, air_date, episode_desc @hook.command @@ -111,15 +92,15 @@ def tv_next(inp, bot=None): if ep_info is None: continue - (first_aired, airdate, episode_desc) = ep_info + (first_aired, air_date, episode_desc) = ep_info - if airdate > today: + if air_date > today: next_eps = ['{} ({})'.format(first_aired, episode_desc)] - elif airdate == today: + elif air_date == today: next_eps = ['Today ({})'.format(episode_desc)] + next_eps else: - #we're iterating in reverse order with newest episodes last - #so, as soon as we're past today, break out of loop + # we're iterating in reverse order with newest episodes last + # so, as soon as we're past today, break out of loop break if not next_eps: @@ -158,9 +139,9 @@ def tv_last(inp, bot=None): if ep_info is None: continue - (first_aired, airdate, episode_desc) = ep_info + (first_aired, air_date, episode_desc) = ep_info - if airdate < today: + if air_date < today: #iterating in reverse order, so the first episode encountered #before today was the most recently aired prev_ep = '{} ({})'.format(first_aired, episode_desc) diff --git a/plugins/twitch.py b/disabled_stuff/twitch.py similarity index 93% rename from plugins/twitch.py rename to disabled_stuff/twitch.py index 842057d..7e1a56a 100644 --- a/plugins/twitch.py +++ b/disabled_stuff/twitch.py @@ -1,7 +1,9 @@ import re -from util import hook, http from HTMLParser import HTMLParser +from util import hook, http + + twitch_re = (r'(.*:)//(twitch.tv|www.twitch.tv)(:[0-9]+)?(.*)', re.I) multitwitch_re = (r'(.*:)//(www.multitwitch.tv|multitwitch.tv)/(.*)', re.I) @@ -92,8 +94,9 @@ def twitch_lookup(location): views = views + "s" if not views[0:2] == "1 " else views return h.unescape(fmt.format(title, channel, playing, views)) else: - data = http.get_json("http://api.justin.tv/api/stream/list.json?channel=" + channel)[0] - if data: + data = http.get_json("http://api.justin.tv/api/stream/list.json?channel=" + channel) + if data and len(data) >= 1: + data = data[0] title = data['title'] playing = data['meta_game'] viewers = "\x033\x02Online now!\x02\x0f " + str(data["channel_count"]) + " viewer" @@ -102,7 +105,10 @@ def twitch_lookup(location): print viewers return h.unescape(fmt.format(title, channel, playing, viewers)) else: - data = http.get_json("https://api.twitch.tv/kraken/channels/" + channel) + try: + data = http.get_json("https://api.twitch.tv/kraken/channels/" + channel) + except: + return title = data['status'] playing = data['game'] viewers = "\x034\x02Offline\x02\x0f" diff --git a/plugins/twitter.py b/disabled_stuff/twitter.py old mode 100755 new mode 100644 similarity index 67% rename from plugins/twitter.py rename to disabled_stuff/twitter.py index c6e9cde..c83ea67 --- a/plugins/twitter.py +++ b/disabled_stuff/twitter.py @@ -1,16 +1,16 @@ -from util import hook, timesince -import tweepy import re import random from datetime import datetime +import tweepy -@hook.command("tw") -@hook.command("twatter") -@hook.command -def twitter(inp, bot=None): - "twitter <user> [n] -- Gets last/[n]th tweet from <user>" +from util import hook, timesince + +TWITTER_RE = (r"(?:(?:www.twitter.com|twitter.com)/(?:[-_a-zA-Z0-9]+)/status/)([0-9]+)", re.I) + + +def get_api(bot): consumer_key = bot.config.get("api_keys", {}).get("twitter_consumer_key") consumer_secret = bot.config.get("api_keys", {}).get("twitter_consumer_secret") @@ -18,12 +18,51 @@ def twitter(inp, bot=None): oauth_secret = bot.config.get("api_keys", {}).get("twitter_access_secret") if not consumer_key: - return "Error: No Twitter API details." + return False auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(oauth_token, oauth_secret) - api = tweepy.API(auth) + return tweepy.API(auth) + + +@hook.regex(*TWITTER_RE) +def twitter_url(match, bot=None): + # Find the tweet ID from the URL + tweet_id = match.group(1) + + # Get the tweet using the tweepy API + api = get_api(bot) + if not api: + return + try: + tweet = api.get_status(tweet_id) + user = tweet.user + except tweepy.error.TweepError: + return + + # Format the return the text of the tweet + text = " ".join(tweet.text.split()) + + if user.verified: + prefix = u"\u2713" + else: + prefix = "" + + time = timesince.timesince(tweet.created_at, datetime.utcnow()) + + return u"{}@\x02{}\x02 ({}): {} ({} ago)".format(prefix, user.screen_name, user.name, text, time) + + +@hook.command("tw") +@hook.command("twatter") +@hook.command +def twitter(inp, bot=None): + """twitter <user> [n] -- Gets last/[n]th tweet from <user>""" + + api = get_api(bot) + if not api: + return "Error: No Twitter API details." if re.match(r'^\d+$', inp): # user is getting a tweet by id @@ -35,7 +74,7 @@ def twitter(inp, bot=None): if e[0][0]['code'] == 34: return "Could not find tweet." else: - return "Error {}: {}".format(e[0][0]['code'], e[0][0]['message']) + return u"Error {}: {}".format(e[0][0]['code'], e[0][0]['message']) user = tweet.user @@ -59,21 +98,21 @@ def twitter(inp, bot=None): if e[0][0]['code'] == 34: return "Could not find user." else: - return "Error {}: {}".format(e[0][0]['code'], e[0][0]['message']) + return u"Error {}: {}".format(e[0][0]['code'], e[0][0]['message']) # get the users tweets user_timeline = api.user_timeline(id=user.id, count=tweet_number + 1) # if the timeline is empty, return an error if not user_timeline: - return "The user \x02{}\x02 has no tweets.".format(user.screen_name) + return u"The user \x02{}\x02 has no tweets.".format(user.screen_name) # grab the newest tweet from the users timeline try: tweet = user_timeline[tweet_number] except IndexError: tweet_count = len(user_timeline) - return "The user \x02{}\x02 only has \x02{}\x02 tweets.".format(user.screen_name, tweet_count) + return u"The user \x02{}\x02 only has \x02{}\x02 tweets.".format(user.screen_name, tweet_count) elif re.match(r'^#\w+$', inp): # user is searching by hashtag @@ -84,11 +123,15 @@ def twitter(inp, bot=None): tweet = random.choice(search) user = tweet.user + else: + # ??? + return "Invalid Input" + # Format the return the text of the tweet text = " ".join(tweet.text.split()) if user.verified: - prefix = "+" + prefix = u"\u2713" else: prefix = "" @@ -102,20 +145,10 @@ def twitter(inp, bot=None): def twuser(inp, bot=None): """twuser <user> -- Get info on the Twitter user <user>""" - consumer_key = bot.config.get("api_keys", {}).get("twitter_consumer_key") - consumer_secret = bot.config.get("api_keys", {}).get("twitter_consumer_secret") - - oauth_token = bot.config.get("api_keys", {}).get("twitter_access_token") - oauth_secret = bot.config.get("api_keys", {}).get("twitter_access_secret") - - if not consumer_key: + api = get_api(bot) + if not api: return "Error: No Twitter API details." - auth = tweepy.OAuthHandler(consumer_key, consumer_secret) - auth.set_access_token(oauth_token, oauth_secret) - - api = tweepy.API(auth) - try: # try to get user by username user = api.get_user(inp) @@ -126,17 +159,17 @@ def twuser(inp, bot=None): return "Unknown error" if user.verified: - prefix = "+" + prefix = u"\u2713" else: prefix = "" if user.location: - loc_str = " is located in \x02{}\x02 and".format(user.location) + loc_str = u" is located in \x02{}\x02 and".format(user.location) else: loc_str = "" if user.description: - desc_str = " The users description is \"{}\"".format(user.description) + desc_str = u" The users description is \"{}\"".format(user.description) else: desc_str = "" diff --git a/disabled_stuff/update.py b/disabled_stuff/update.py new file mode 100644 index 0000000..67e55f2 --- /dev/null +++ b/disabled_stuff/update.py @@ -0,0 +1,43 @@ +from git import Repo + + +from util import hook, web + +@hook.command +def update(inp, bot=None): + repo = Repo() + git = repo.git + try: + pull = git.pull() + except Exception as e: + return e + if "\n" in pull: + return web.haste(pull) + else: + return pull + + +@hook.command +def version(inp, bot=None): + repo = Repo() + + # get origin and fetch it + origin = repo.remotes.origin + info = origin.fetch() + + # get objects + head = repo.head + origin_head = info[0] + current_commit = head.commit + remote_commit = origin_head.commit + + if current_commit == remote_commit: + in_sync = True + else: + in_sync = False + + # output + return "Local \x02{}\x02 is at commit \x02{}\x02, remote \x02{}\x02 is at commit \x02{}\x02." \ + " You {} running the latest version.".format(head, current_commit.name_rev[:7], + origin_head, remote_commit.name_rev[:7], + "are" if in_sync else "are not") diff --git a/disabled_stuff/urban.py b/disabled_stuff/urban.py new file mode 100644 index 0000000..48da433 --- /dev/null +++ b/disabled_stuff/urban.py @@ -0,0 +1,66 @@ +import re +import random + +from util import hook, http, text + + +base_url = 'http://api.urbandictionary.com/v0' +define_url = base_url + "/define" +random_url = base_url + "/random" + +@hook.command('u', autohelp=False) +@hook.command(autohelp=False) +def urban(inp): + """urban <phrase> [id] -- Looks up <phrase> on urbandictionary.com.""" + + if inp: + # clean and split the input + inp = inp.lower().strip() + parts = inp.split() + + # if the last word is a number, set the ID to that number + if parts[-1].isdigit(): + id_num = int(parts[-1]) + # remove the ID from the input string + del parts[-1] + inp = " ".join(parts) + else: + id_num = 1 + + # fetch the definitions + page = http.get_json(define_url, term=inp, referer="http://m.urbandictionary.com") + + if page['result_type'] == 'no_results': + return 'Not found.' + else: + # get a random definition! + page = http.get_json(random_url, referer="http://m.urbandictionary.com") + id_num = None + + definitions = page['list'] + + if id_num: + # try getting the requested definition + try: + definition = definitions[id_num - 1] + + def_text = " ".join(definition['definition'].split()) # remove excess spaces + def_text = text.truncate_str(def_text, 200) + except IndexError: + return 'Not found.' + + url = definition['permalink'] + output = u"[%i/%i] %s :: %s" % \ + (id_num, len(definitions), def_text, url) + + else: + definition = random.choice(definitions) + + def_text = " ".join(definition['definition'].split()) # remove excess spaces + def_text = text.truncate_str(def_text, 200) + + name = definition['word'] + url = definition['permalink'] + output = u"\x02{}\x02: {} :: {}".format(name, def_text, url) + + return output diff --git a/disabled_stuff/urlhistory.py b/disabled_stuff/urlhistory.py old mode 100755 new mode 100644 diff --git a/disabled_stuff/utility.py b/disabled_stuff/utility.py new file mode 100644 index 0000000..b0afa5b --- /dev/null +++ b/disabled_stuff/utility.py @@ -0,0 +1,197 @@ +import hashlib +import collections +import re + +from util import hook, text + + +# variables + +colors = collections.OrderedDict([ + ('red', '\x0304'), + ('orange', '\x0307'), + ('yellow', '\x0308'), + ('green', '\x0309'), + ('cyan', '\x0303'), + ('ltblue', '\x0310'), + ('rylblue', '\x0312'), + ('blue', '\x0302'), + ('magenta', '\x0306'), + ('pink', '\x0313'), + ('maroon', '\x0305') +]) + +# helper functions + +strip_re = re.compile("(\x03|\x02|\x1f)(?:,?\d{1,2}(?:,\d{1,2})?)?", re.UNICODE) + + +def strip(string): + return strip_re.sub('', string) + + +# basic text tools + + +## TODO: make this capitalize sentences correctly +@hook.command("capitalise") +@hook.command +def capitalize(inp): + """capitalize <string> -- Capitalizes <string>.""" + return inp.capitalize() + + +@hook.command +def upper(inp): + """upper <string> -- Convert string to uppercase.""" + return inp.upper() + + +@hook.command +def lower(inp): + """lower <string> -- Convert string to lowercase.""" + return inp.lower() + + +@hook.command +def titlecase(inp): + """title <string> -- Convert string to title case.""" + return inp.title() + + +@hook.command +def swapcase(inp): + """swapcase <string> -- Swaps the capitalization of <string>.""" + return inp.swapcase() + + +# encoding + + +@hook.command +def rot13(inp): + """rot13 <string> -- Encode <string> with rot13.""" + return inp.encode('rot13') + + +@hook.command +def base64(inp): + """base64 <string> -- Encode <string> with base64.""" + return inp.encode('base64') + + +@hook.command +def unbase64(inp): + """unbase64 <string> -- Decode <string> with base64.""" + return inp.decode('base64') + + +@hook.command +def checkbase64(inp): + try: + decoded = inp.decode('base64') + recoded = decoded.encode('base64').strip() + is_base64 = recoded == inp + except: + return '"{}" is not base64 encoded'.format(inp) + + if is_base64: + return '"{}" is base64 encoded'.format(recoded) + else: + return '"{}" is not base64 encoded'.format(inp) + + +@hook.command +def unescape(inp): + """unescape <string> -- Unescapes <string>.""" + try: + return inp.decode('unicode-escape') + except Exception as e: + return "Error: {}".format(e) + + +@hook.command +def escape(inp): + """escape <string> -- Escapes <string>.""" + try: + return inp.encode('unicode-escape') + except Exception as e: + return "Error: {}".format(e) + + +# length + + +@hook.command +def length(inp): + """length <string> -- gets the length of <string>""" + return "The length of that string is {} characters.".format(len(inp)) + + +# reverse + + +@hook.command +def reverse(inp): + """reverse <string> -- reverses <string>.""" + return inp[::-1] + + +# hashing + + +@hook.command("hash") +def hash_command(inp): + """hash <string> -- Returns hashes of <string>.""" + return ', '.join(x + ": " + getattr(hashlib, x)(inp).hexdigest() + for x in ['md5', 'sha1', 'sha256']) + + +# novelty + + +@hook.command +def munge(inp): + """munge <text> -- Munges up <text>.""" + return text.munge(inp) + + +# colors - based on code by Reece Selwood - <https://github.com/hitzler/homero> + + +@hook.command +def rainbow(inp): + inp = unicode(inp) + inp = strip(inp) + col = colors.items() + out = "" + l = len(colors) + for i, t in enumerate(inp): + if t == " ": + out += t + else: + out += col[i % l][1] + t + return out + + +@hook.command +def wrainbow(inp): + inp = unicode(inp) + col = colors.items() + inp = strip(inp).split(' ') + out = [] + l = len(colors) + for i, t in enumerate(inp): + out.append(col[i % l][1] + t) + return ' '.join(out) + + +@hook.command +def usa(inp): + inp = strip(inp) + c = [colors['red'], '\x0300', colors['blue']] + l = len(c) + out = '' + for i, t in enumerate(inp): + out += c[i % l] + t + return out diff --git a/plugins/validate.py b/disabled_stuff/validate.py old mode 100755 new mode 100644 similarity index 74% rename from plugins/validate.py rename to disabled_stuff/validate.py index 56a04fc..88022b7 --- a/plugins/validate.py +++ b/disabled_stuff/validate.py @@ -20,7 +20,7 @@ def validate(inp): status = info['x-w3c-validator-status'].lower() if status in ("valid", "invalid"): - errorcount = info['x-w3c-validator-errors'] - warningcount = info['x-w3c-validator-warnings'] + error_count = info['x-w3c-validator-errors'] + warning_count = info['x-w3c-validator-warnings'] return "{} was found to be {} with {} errors and {} warnings." \ - " see: {}".format(inp, status, errorcount, warningcount, url) + " see: {}".format(inp, status, error_count, warning_count, url) diff --git a/plugins/valuesounds.py b/disabled_stuff/valvesounds.py similarity index 91% rename from plugins/valuesounds.py rename to disabled_stuff/valvesounds.py index 572a545..88bc8ce 100644 --- a/plugins/valuesounds.py +++ b/disabled_stuff/valvesounds.py @@ -1,16 +1,17 @@ -from util import hook, http, web import json -from urllib2 import HTTPError +import urllib2 + +from util import hook, http, web def get_sound_info(game, search): search = search.replace(" ", "+") try: - data = http.get_json("http://p2sounds.blha303.com.au/search/%s/%s" % (game, search)) - except HTTPError as e: + data = http.get_json("http://p2sounds.blha303.com.au/search/%s/%s?format=json" % (game, search)) + except urllib2.HTTPError as e: return "Error: " + json.loads(e.read())["error"] items = [] - for item in data: + for item in data["items"]: if "music" in game: textsplit = item["text"].split('"') text = "" @@ -24,11 +25,11 @@ def get_sound_info(game, search): text = item["text"] items.append("{} - {} {}".format(item["who"], text if len(text) < 325 else text[:325] + "...", - item["listen"] ) ) + item["listen"])) if len(items) == 1: return items[0] else: - return "{} (and {} others: {})".format(items[0], len(items) - 1, web.haste("\n".join(items)) ) + return "{} (and {} others: {})".format(items[0], len(items) - 1, web.haste("\n".join(items))) @hook.command diff --git a/plugins/vimeo.py b/disabled_stuff/vimeo.py old mode 100755 new mode 100644 similarity index 91% rename from plugins/vimeo.py rename to disabled_stuff/vimeo.py index 5b21e87..0a55549 --- a/plugins/vimeo.py +++ b/disabled_stuff/vimeo.py @@ -8,7 +8,7 @@ def vimeo_url(match): % match.group(1)) if info: - info[0]["duration"] = timeformat.timeformat(info[0]["duration"]) + info[0]["duration"] = timeformat.format_time(info[0]["duration"]) info[0]["stats_number_of_likes"] = format( info[0]["stats_number_of_likes"], ",d") info[0]["stats_number_of_plays"] = format( diff --git a/plugins/weather.py b/disabled_stuff/weather.py old mode 100755 new mode 100644 similarity index 99% rename from plugins/weather.py rename to disabled_stuff/weather.py index 436a059..8a56046 --- a/plugins/weather.py +++ b/disabled_stuff/weather.py @@ -13,7 +13,7 @@ def weather(inp, reply=None, db=None, nick=None, bot=None, notice=None): if not api_key: return "Error: No wunderground API details." - # initalise weather DB + # initialise weather DB db.execute("create table if not exists weather(nick primary key, loc)") # if there is no input, try getting the users last location from the DB diff --git a/plugins/wikipedia.py b/disabled_stuff/wikipedia.py old mode 100755 new mode 100644 similarity index 85% rename from plugins/wikipedia.py rename to disabled_stuff/wikipedia.py index 58984cc..90461f4 --- a/plugins/wikipedia.py +++ b/disabled_stuff/wikipedia.py @@ -30,7 +30,7 @@ def wiki(inp): def extract(item): return [item.find(ns + x).text for x in - ('Text', 'Description', 'Url')] + ('Text', 'Description', 'Url')] title, desc, url = extract(items[0]) @@ -42,8 +42,8 @@ def wiki(inp): if title.lower() not in desc.lower(): desc = title + desc - desc = re.sub('\s+', ' ', desc).strip() # remove excess spaces + desc = u' '.join(desc.split()) # remove excess spaces desc = text.truncate_str(desc, 200) - return '{} :: {}'.format(desc, http.quote(url, ':/')) + return u'{} :: {}'.format(desc, http.quote(url, ':/')) diff --git a/plugins/wolframalpha.py b/disabled_stuff/wolframalpha.py old mode 100755 new mode 100644 similarity index 88% rename from plugins/wolframalpha.py rename to disabled_stuff/wolframalpha.py index 879b771..b20ffed --- a/plugins/wolframalpha.py +++ b/disabled_stuff/wolframalpha.py @@ -3,11 +3,12 @@ import re from util import hook, http, text, web +@hook.command('math') +@hook.command('calc') @hook.command('wa') @hook.command def wolframalpha(inp, bot=None): """wa <query> -- Computes <query> using Wolfram Alpha.""" - api_key = bot.config.get("api_keys", {}).get("wolframalpha", None) if not api_key: @@ -35,9 +36,9 @@ def wolframalpha(inp, bot=None): if subpod: results.append(subpod) if results: - pod_texts.append(title + ': ' + ', '.join(results)) + pod_texts.append(title + u': ' + u', '.join(results)) - ret = ' - '.join(pod_texts) + ret = u' - '.join(pod_texts) if not pod_texts: return 'No results.' @@ -54,4 +55,4 @@ def wolframalpha(inp, bot=None): if not ret: return 'No results.' - return "{} - {}".format(ret, short_url) + return u"{} - {}".format(ret, short_url) diff --git a/disabled_stuff/wordoftheday.py b/disabled_stuff/wordoftheday.py old mode 100755 new mode 100644 diff --git a/disabled_stuff/wrapper.old b/disabled_stuff/wrapper.old old mode 100755 new mode 100644 diff --git a/disabled_stuff/xkcd.py b/disabled_stuff/xkcd.py new file mode 100644 index 0000000..d7fad59 --- /dev/null +++ b/disabled_stuff/xkcd.py @@ -0,0 +1,43 @@ +import re + +from util import hook, http + + +xkcd_re = (r'(.*:)//(www.xkcd.com|xkcd.com)(.*)', re.I) +months = {1: 'January', 2: 'February', 3: 'March', 4: 'April', 5: 'May', 6: 'June', 7: 'July', 8: 'August', + 9: 'September', 10: 'October', 11: 'November', 12: 'December'} + + +def xkcd_info(xkcd_id, url=False): + """ takes an XKCD entry ID and returns a formatted string """ + data = http.get_json("http://www.xkcd.com/" + xkcd_id + "/info.0.json") + date = "%s %s %s" % (data['day'], months[int(data['month'])], data['year']) + if url: + url = " | http://xkcd.com/" + xkcd_id.replace("/", "") + return "xkcd: \x02%s\x02 (%s)%s" % (data['title'], date, url if url else "") + + +def xkcd_search(term): + search_term = http.quote_plus(term) + soup = http.get_soup("http://www.ohnorobot.com/index.pl?s={}&Search=Search&" + "comic=56&e=0&n=0&b=0&m=0&d=0&t=0".format(search_term)) + result = soup.find('li') + if result: + url = result.find('div', {'class': 'tinylink'}).text + xkcd_id = url[:-1].split("/")[-1] + print xkcd_id + return xkcd_info(xkcd_id, url=True) + else: + return "No results found!" + + +@hook.regex(*xkcd_re) +def xkcd_url(match): + xkcd_id = match.group(3).split(" ")[0].split("/")[1] + return xkcd_info(xkcd_id) + + +@hook.command +def xkcd(inp): + """xkcd <search term> - Search for xkcd comic matching <search term>""" + return xkcd_search(inp) diff --git a/plugins/yahooanswers.py b/disabled_stuff/yahooanswers.py similarity index 82% rename from plugins/yahooanswers.py rename to disabled_stuff/yahooanswers.py index 319502e..e28ed63 100644 --- a/plugins/yahooanswers.py +++ b/disabled_stuff/yahooanswers.py @@ -11,6 +11,6 @@ def answer(inp): short_url = web.try_isgd(result["Link"]) # we split the answer and .join() it to remove newlines/extra spaces - answer = text.truncate_str(' '.join(result["ChosenAnswer"].split()), 80) + answer_text = text.truncate_str(' '.join(result["ChosenAnswer"].split()), 80) - return u'\x02{}\x02 "{}" - {}'.format(result["Subject"], answer, short_url) + return u'\x02{}\x02 "{}" - {}'.format(result["Subject"], answer_text, short_url) diff --git a/disabled_stuff/youtube.py b/disabled_stuff/youtube.py new file mode 100644 index 0000000..e63bca3 --- /dev/null +++ b/disabled_stuff/youtube.py @@ -0,0 +1,136 @@ +import re +import time + +from util import hook, http, timeformat + + +youtube_re = (r'(?:youtube.*?(?:v=|/v/)|youtu\.be/|yooouuutuuube.*?id=)' + '([-_a-zA-Z0-9]+)', re.I) + +base_url = 'http://gdata.youtube.com/feeds/api/' +api_url = base_url + 'videos/{}?v=2&alt=jsonc' +search_api_url = base_url + 'videos?v=2&alt=jsonc&max-results=1' +video_url = "http://youtu.be/%s" + + +def plural(num=0, text=''): + return "{:,} {}{}".format(num, text, "s"[num == 1:]) + + +def get_video_description(video_id): + request = http.get_json(api_url.format(video_id)) + + if request.get('error'): + return + + data = request['data'] + + out = u'\x02{}\x02'.format(data['title']) + + if not data.get('duration'): + return out + + length = data['duration'] + out += u' - length \x02{}\x02'.format(timeformat.format_time(length, simple=True)) + + if 'ratingCount' in data: + likes = plural(int(data['likeCount']), "like") + dislikes = plural(data['ratingCount'] - int(data['likeCount']), "dislike") + + percent = 100 * float(data['likeCount']) / float(data['ratingCount']) + out += u' - {}, {} (\x02{:.1f}\x02%)'.format(likes, + dislikes, percent) + + if 'viewCount' in data: + views = data['viewCount'] + out += u' - \x02{:,}\x02 view{}'.format(views, "s"[views == 1:]) + + try: + uploader = http.get_json(base_url + "users/{}?alt=json".format(data["uploader"]))["entry"]["author"][0]["name"][ + "$t"] + except: + uploader = data["uploader"] + + upload_time = time.strptime(data['uploaded'], "%Y-%m-%dT%H:%M:%S.000Z") + out += u' - \x02{}\x02 on \x02{}\x02'.format(uploader, + time.strftime("%Y.%m.%d", upload_time)) + + if 'contentRating' in data: + out += u' - \x034NSFW\x02' + + return out + + +@hook.regex(*youtube_re) +def youtube_url(match): + return get_video_description(match.group(1)) + + +@hook.command('you') +@hook.command('yt') +@hook.command('y') +@hook.command +def youtube(inp): + """youtube <query> -- Returns the first YouTube search result for <query>.""" + request = http.get_json(search_api_url, q=inp) + + if 'error' in request: + return 'error performing search' + + if request['data']['totalItems'] == 0: + return 'no results found' + + video_id = request['data']['items'][0]['id'] + + return get_video_description(video_id) + u" - " + video_url % video_id + + +@hook.command('ytime') +@hook.command +def youtime(inp): + """youtime <query> -- Gets the total run time of the first YouTube search result for <query>.""" + request = http.get_json(search_api_url, q=inp) + + if 'error' in request: + return 'error performing search' + + if request['data']['totalItems'] == 0: + return 'no results found' + + video_id = request['data']['items'][0]['id'] + request = http.get_json(api_url.format(video_id)) + + if request.get('error'): + return + data = request['data'] + + if not data.get('duration'): + return + + length = data['duration'] + views = data['viewCount'] + total = int(length * views) + + length_text = timeformat.format_time(length, simple=True) + total_text = timeformat.format_time(total, accuracy=8) + + return u'The video \x02{}\x02 has a length of {} and has been viewed {:,} times for ' \ + u'a total run time of {}!'.format(data['title'], length_text, views, + total_text) + + +ytpl_re = (r'(.*:)//(www.youtube.com/playlist|youtube.com/playlist)(:[0-9]+)?(.*)', re.I) + + +@hook.regex(*ytpl_re) +def ytplaylist_url(match): + location = match.group(4).split("=")[-1] + try: + soup = http.get_soup("https://www.youtube.com/playlist?list=" + location) + except Exception: + return "\x034\x02Invalid response." + title = soup.find('title').text.split('-')[0].strip() + author = soup.find('img', {'class': 'channel-header-profile-image'})['title'] + num_videos = soup.find('ul', {'class': 'header-stats'}).findAll('li')[0].text.split(' ')[0] + views = soup.find('ul', {'class': 'header-stats'}).findAll('li')[1].text.split(' ')[0] + return u"\x02%s\x02 - \x02%s\x02 views - \x02%s\x02 videos - \x02%s\x02" % (title, views, num_videos, author) diff --git a/plugins/admin.py b/plugins/admin.py old mode 100755 new mode 100644 index c660615..dde067e --- a/plugins/admin.py +++ b/plugins/admin.py @@ -1,10 +1,12 @@ -from util import hook import os +import sys import re import json import time import subprocess +from util import hook + @hook.command(autohelp=False, permissions=["permissions_users"]) def permissions(inp, bot=None, notice=None): @@ -114,14 +116,18 @@ def stop(inp, nick=None, conn=None): @hook.command(autohelp=False, permissions=["botcontrol"]) -def restart(inp, nick=None, conn=None): +def restart(inp, nick=None, conn=None, bot=None): """restart [reason] -- Restarts the bot with [reason] as its quit message.""" - if inp: - conn.cmd("QUIT", ["Restarted by {} ({})".format(nick, inp)]) - else: - conn.cmd("QUIT", ["Restarted by {}.".format(nick)]) + for botcon in bot.conns: + if inp: + bot.conns[botcon].cmd("QUIT", ["Restarted by {} ({})".format(nick, inp)]) + else: + bot.conns[botcon].cmd("QUIT", ["Restarted by {}.".format(nick)]) time.sleep(5) - os.execl("./cloudbot", "cloudbot", "restart") + #os.execl("./cloudbot", "cloudbot", "restart") + args = sys.argv[:] + args.insert(0, sys.executable) + os.execv(sys.executable, args) @hook.command(autohelp=False, permissions=["botcontrol"]) @@ -133,8 +139,11 @@ def clearlogs(inp, input=None): @hook.command(permissions=["botcontrol"]) def join(inp, conn=None, notice=None): """join <channel> -- Joins <channel>.""" - notice("Attempting to join {}...".format(inp)) - conn.join(inp) + for target in inp.split(" "): + if not target.startswith("#"): + target = "#{}".format(target) + notice("Attempting to join {}...".format(target)) + conn.join(target) @hook.command(autohelp=False, permissions=["botcontrol"]) @@ -143,11 +152,14 @@ def part(inp, conn=None, chan=None, notice=None): If [channel] is blank the bot will leave the channel the command was used in.""" if inp: - target = inp + targets = inp else: - target = chan - notice("Attempting to leave {}...".format(target)) - conn.part(target) + targets = chan + for target in targets.split(" "): + if not target.startswith("#"): + target = "#{}".format(target) + notice("Attempting to leave {}...".format(target)) + conn.part(target) @hook.command(autohelp=False, permissions=["botcontrol"]) @@ -188,11 +200,11 @@ def say(inp, conn=None, chan=None): the command was used in.""" inp = inp.split(" ") if inp[0][0] == "#": - message = " ".join(inp[1:]) - out = "PRIVMSG {} :{}".format(inp[0], message) + message = u" ".join(inp[1:]) + out = u"PRIVMSG {} :{}".format(inp[0], message) else: - message = " ".join(inp[0:]) - out = "PRIVMSG {} :{}".format(chan, message) + message = u" ".join(inp[0:]) + out = u"PRIVMSG {} :{}".format(chan, message) conn.send(out) @@ -208,11 +220,11 @@ def me(inp, conn=None, chan=None): for x in inp[1:]: message = message + x + " " message = message[:-1] - out = "PRIVMSG {} :\x01ACTION {}\x01".format(inp[0], message) + out = u"PRIVMSG {} :\x01ACTION {}\x01".format(inp[0], message) else: message = "" for x in inp[0:]: message = message + x + " " message = message[:-1] - out = "PRIVMSG {} :\x01ACTION {}\x01".format(chan, message) + out = u"PRIVMSG {} :\x01ACTION {}\x01".format(chan, message) conn.send(out) diff --git a/plugins/bad_version.py b/plugins/bad_version.py new file mode 100644 index 0000000..9465f2d --- /dev/null +++ b/plugins/bad_version.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from util import hook +from thread import start_new_thread +from time import sleep + +def wait_and_send(conn, wait, msg): + sleep(wait) + conn.send(msg) + +@hook.command("check") +def check_nick(inp, conn=None): + conn.send("PRIVMSG %s :\x01VERSION\x01" % inp) + + +@hook.event("JOIN") +def handle_join(info, input=None, conn=None): + start_new_thread(wait_and_send, (conn, 5, "PRIVMSG %s :\x01VERSION\x01" % input.nick)) + +@hook.event("NOTICE") +def handle_ctcp_rply(info, input=None, conn=None, nick=None): + print "notice..." + print "-%s-" % input.lastparam + if input.lastparam == ("\1VERSION %s\1" % "mIRC v7.22 Khaled Mardam-Bey"): + for chan in conn.channels: + if chan != "#logbot": + conn.send("KICK %s %s :bad version" % (chan, nick)) + conn.send("MODE %s +b %s!*@*$#logbot" % (chan, nick)) + diff --git a/plugins/bandwidth.py b/plugins/bandwidth.py new file mode 100644 index 0000000..ea23333 --- /dev/null +++ b/plugins/bandwidth.py @@ -0,0 +1,21 @@ +from util import hook, http, web +from subprocess import check_output, CalledProcessError +from datetime import datetime + +@hook.command("bw", autohelp=False) +def bw(inp): + """bw - list last bandwidth measurement to the outside.""" + + try: + o = check_output("/bin/chch-bandwidth") + except CalledProcessError as err: + return "chch-bandwidth: returned %s" % (str(err)) + + os = o.split(",") + upl = int(os[-1])/1024.0/1024.0 + dl = int(os[-2])/1024.0/1024.0 + ts = os[0] + tsd = datetime.strptime(ts, "%Y%m%d%H%M%S") + + return "%s: upl = %f Mbit/s; dl = %f Mbit/s;" % (tsd, upl, dl) + diff --git a/plugins/bitcoin.py b/plugins/bitcoin.py deleted file mode 100755 index a644407..0000000 --- a/plugins/bitcoin.py +++ /dev/null @@ -1,16 +0,0 @@ -from util import http, hook - - -@hook.command(autohelp=False) -def bitcoin(inp, say=None): - """bitcoin -- gets current exchange rate for bitcoins from mtgox""" - data = http.get_json("https://data.mtgox.com/api/2/BTCUSD/money/ticker") - data = data['data'] - ticker = { - 'buy': data['buy']['display_short'].encode('ascii','ignore'), - 'high': data['high']['display_short'].encode('ascii','ignore'), - 'low': data['low']['display_short'].encode('ascii','ignore'), - 'vol': data['vol']['display_short'].encode('ascii','ignore'), - } - say("Current: \x0307{!s}\x0f - High: \x0307{!s}\x0f" - " - Low: \x0307{!s}\x0f - Volume: {!s}".format(ticker['buy'],ticker['high'],ticker['low'],ticker['vol'])) diff --git a/plugins/chch_worker.py b/plugins/chch_worker.py new file mode 100644 index 0000000..e57a9cc --- /dev/null +++ b/plugins/chch_worker.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +from util import hook +import re +import time +import requests +import urllib +from subprocess import check_output +import json +import socket +import struct + +#def run_ecmd(cmd): +## baseuri = "http://netio.chch.lan.ffc/ecmd?" +## baseuri = "http://10.8.128.35/ecmd?" +# baseuri = "http://127.0.0.1:4280/ecmd?" +# cmds = "%20".join(cmd) +# req = requests.get("%s%s" % (baseuri, cmds)) +# return req.text.strip() + +#def run_udp(cmd): +# ip="127.0.0.1" +# port=49152 +# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# # 100 ms timeout +# s.settimeout(0.1) +# s.connect((ip, port)) +# s.send(cmd) +# try: +# rec = s.recv(1024) +# except: +# rec = "" +# s.close() +# return rec + +## lamp_lounge handling +#@hook.command("lamp_lounge", autohelp=True) +#def cmd_lamp_lounge(inp, reply=None): +# """lamp_lounge color - set the lamp color""" +# args = inp.split(" ") +# if len(args) < 1: +# reply("lamp_lounge color - set the lamp color") +# return +# +# if len(args[0]) != 6: +# reply("lamp_lounge color - set the lamp color") +# return +# +# c = "a\x00\x03" + struct.pack('BBB', int(args[0][2:4], 16), int(args[0][0:2], 16), int(args[0][4:6], 16)) +# +# rep = run_tcp(c) +# +# if len(rep) < 3: +# reply("Error: no reply") +# return +# +# if rep[0] == 'a': +# reply("OK") +# elif rep[0] == 'e': +# reply("error: " + rep[3:]) +# else: +# reply("fatal error") + +@hook.command("corridor_light_toggle", autohelp=False) +def cmd_corridor_light_toggle(inp, reply=None): + """toggle corridor light modes""" + reply(check_output("echo corridor_light_toggle | ssh -q -p 2322 command@127.0.0.1", shell=True).strip("\n").decode("utf-8")) + +@hook.command("corridor_light", autohelp=False) +def cmd_corridor_light(inp, reply=None): + """set corridor light color""" + args = inp.split(" ") + if len(args) < 1: + reply("corridor_light color - set the light color") + return + + if len(args[0]) != 6: + reply("corridor_light color - set the light color") + return + + reply(check_output("echo corridor_light " + args[0] + " | ssh -q -p 2322 command@127.0.0.1", shell=True).strip("\n").decode("utf-8")) + +@hook.command("lounge_light_toggle", autohelp=False) +def cmd_lounge_light_toggle(inp, reply=None): + """toggle lounge light modes""" + reply(check_output("echo lounge_light_toggle | ssh -q -p 2322 command@127.0.0.1", shell=True).strip("\n").decode("utf-8")) + +@hook.command("lounge_light", autohelp=False) +def cmd_lounge_light(inp, reply=None): + """set lounge light color""" + args = inp.split(" ") + if len(args) < 1: + reply("lounge_light color - set the light color") + return + + if len(args[0]) != 6: + reply("lounge_light color - set the light color") + return + + reply(check_output("echo lounge_light " + args[0] + " | ssh -q -p 2322 command@127.0.0.1", shell=True).strip("\n").decode("utf-8")) + +@hook.command("elab_light_toggle", autohelp=False) +def cmd_elab_light_toggle(inp, reply=None): + """toggle e-lab light modes""" + reply(check_output("echo e-lab_light_toggle | ssh -q -p 2322 command@127.0.0.1", shell=True).strip("\n").decode("utf-8")) + +@hook.command("elab_light", autohelp=False) +def cmd_elab_light(inp, reply=None): + """set e-lab light color""" + args = inp.split(" ") + if len(args) < 1: + reply("e-lab_light color - set the light color") + return + + if len(args[0]) != 6: + reply("e-lab_light color - set the light color") + return + + reply(check_output("echo e-lab_light " + args[0] + " | ssh -q -p 2322 command@127.0.0.1", shell=True).strip("\n").decode("utf-8")) + +## Lamp handling +#@hook.command("lamp", autohelp=True) +#def cmd_lamp(inp, reply=None): +# """lamp color [mode] - set the lamp color""" +# args = inp.split(" ") +# if len(args) < 1: +# reply("""lamp color [mode] - set the lamp color""") +# return +# +# if len(args[0]) != 6: +# reply("""lamp color [mode] - set the lamp color""") +# return +# +# cmode = "s" +# if len(args) > 1: +# if args[1] == "s" or args[1] == "y" or args[1] == "f": +# cmode = args[1] +# +# c = [] +# c.append([5, int(args[0][0:2], 16)]) +# c.append([4, int(args[0][2:4], 16)]) +# c.append([3, int(args[0][4:6], 16)]) +# +# for ce in c: +# res = run_ecmd(["channel", str(ce[0]), str(ce[1]), cmode]) +# if res != "OK": +# return +# reply("OK") + +#@hook.command("lamp_fadestep", autohelp=True) +#def cmd_lamp_fadestep(inp, reply=None): +# """lamp_fadestep step - set the lamp fadestep""" +# args = inp.split(" ") +# +# if len(args) < 1: +# reply("""lamp_fadestep step - set the lamp fadestep""") +# return +# +# reply(run_ecmd(["fadestep", args[0]])) + +#@hook.command("lamp_fadestep_get", autohelp=False) +#def cmd_lamp_fadestep_get(inp, reply=None): +# """lamp_fadestep_get - get the lamp fadestep""" +# reply(run_ecmd(["fadestep"])) +# +#@hook.command("lamp_channels", autohelp=False) +#def cmd_lamp_channels(inp, reply=None): +# """lamp_chanels - get the lamp channel count""" +# reply(run_ecmd(["channels"])) + +# Wiki handling +def wiki_changes(cmd=False): + tmpfile = "/tmp/wikichanges.timestamp.txt" + basewikiuri = "https://www.chaoschemnitz.de/index.php?title=%s" + wikiapiuri = "https://www.chaoschemnitz.de/api.php?"\ + "action=query&list=recentchanges&format=json&"\ + "rcprop=user|userid|comment|parsedcomment|timestamp|"\ + "title|sha1|sizes|redirect|loginfo|tags|flags"\ + "&rclist=edit|external|new|log" + + try: + fdch = open(tmpfile, "rw") + timestamp = fdch.read() + fdch.close() + except IOError: + timestamp = None + + try: + r = requests.get(wikiapiuri, verify=False) + except: + return [] + + rarr = [] + changes = r.json()["query"]["recentchanges"] + ntimestamp = changes[0]["timestamp"] + for change in changes: + if change["timestamp"] == timestamp: + break + uri = basewikiuri % (urllib.quote(change["title"].encode("utf-8"), safe="")) + rarr.append("wiki: %s changed '%s' ( %s ) comment: %s" %\ + (change["user"], change["title"], uri,\ + change["comment"].strip("\r\n\t"))) + + if cmd == False: + fdch = open(tmpfile, "w+") + fdch.write("%s" % (ntimestamp)) + fdch.close() + + return rarr + +def print_wiki_changes(info, conn=None, chan=None): + """print_wiki_changes - print wiki changes, when the worker calls""" + ch = wiki_changes(cmd=False) + if len(ch) == 0: + return + for c in ch[::-1]: + conn.msg("#chaoschemnitz", c) + time.sleep(0.5) + +@hook.command("wikichanges", autohelp=False) +def cmd_wikichanges(inp, reply=None): + """wikichanges - Return new recent wiki changes""" + ch = wiki_changes(cmd=True) + if len(ch) == 0: + reply("No changes since the last call were made to the wiki.") + else: + for c in ch[::-1][-4:]: + reply(c) + time.sleep(0.5) + +# Status handling +def getstatus(): +# try: + fd = requests.get('http://www.chaoschemnitz.de/chch.json') + chch_info = fd.json() + if 'message' in chch_info['state']: + message = chch_info['state']['message'] + if " | " in message: + message = message.split(" | ", 1)[0] + else: + message = "" + + if chch_info['state']['open']: + state = "geöffnet".decode("utf-8") + else: + state = "geschlossen" + + return "%s (%s)" % (state, message) +# return check_output("sudo /bin/chch-status", shell=True).strip("\n").decode("utf-8") +# except: +# return "unbekannt" + +@hook.command("status", autohelp=False) +def cmd_status(inp, reply=None): + """status - Return the door status""" + reply("Chaostreff Status: %s" % (getstatus())) + +@hook.event("TOPIC") +def topic_update(info, conn=None, chan=None): + print("topic update") + """topic_update -- Update the topic on TOPIC command""" + if chan != "#ChaosChemnitz": + return + + status = getstatus() + print("status: %s" % (status.encode('utf8'))) + + topic = info[-1].split(" | ") + print("topic: %s" % ([ elem.encode('utf8') for elem in topic ])) + + sstr = "Status: %s" % (status) + print("sstr: %s" % (sstr.encode('utf8'))) + didset = False + i = 0 + while i < len(topic): + if sstr in topic[i]: + print("Found current status in topic.") + didset = True + break + if 'Status: ' in topic[i]: + print("Found Status field in topic.") + didset = True + topic[i] = sstr + i += 1 + if didset == False: + print("No topic fiel was found, appending.") + topic.append(sstr) + + newtopic = " | ".join(topic) + if newtopic != info[-1]: + conn.send("TOPIC %s :%s" % (chan, newtopic)) + +@hook.event("332") +def e332_update(info, conn=None, chan=None): + """e332_update -- run after current topic was requested, runs worker tasks too""" + chan = info[1] + topic_update(info, conn=conn, chan=chan) + print_wiki_changes(info, conn=conn, chan=chan) + +@hook.singlethread +@hook.event("353") +def e353_update(info, conn=None, chan=None): + """e353_update -- runs after a channel (#chaoschemnitz) was joined""" + chan = info[2] + if chan.lower() == "#chaoschemnitz": + conn.send("PRIVMSG Chanserv :op #chaoschemnitz") + + while True: + time.sleep(60) + conn.send("TOPIC %s" % (chan)) + diff --git a/plugins/core_ctcp.py b/plugins/core_ctcp.py old mode 100755 new mode 100644 index 4f399f6..3e7a200 --- a/plugins/core_ctcp.py +++ b/plugins/core_ctcp.py @@ -1,4 +1,5 @@ import time + from util import hook diff --git a/plugins/core_misc.py b/plugins/core_misc.py old mode 100755 new mode 100644 index bd71870..c1fafca --- a/plugins/core_misc.py +++ b/plugins/core_misc.py @@ -4,6 +4,7 @@ import re from util import hook + socket.setdefaulttimeout(10) nick_re = re.compile(":(.+?)!") diff --git a/plugins/core_sieve.py b/plugins/core_sieve.py old mode 100755 new mode 100644 index 3b00c4b..9d41c54 --- a/plugins/core_sieve.py +++ b/plugins/core_sieve.py @@ -1,7 +1,7 @@ import re +from fnmatch import fnmatch from util import hook -from fnmatch import fnmatch @hook.sieve @@ -38,30 +38,21 @@ def sieve_suite(bot, input, func, kind, args): groups = bot.config.get("permissions", []) allowed_permissions = args.get('permissions', []) - allowed_groups = [] - - # loop over every group - for key, value in groups.iteritems(): - # loop over every permission the command allows - for permission in allowed_permissions: - # see if the group has that permission - print value - if permission in value["perms"]: - # if so, add the group name to the allowed_groups list - allowed_groups.append(key) - - if not allowed_groups: - print "Something is wrong. A hook requires {} but" \ - " there are no groups with that permission!".format(str(allowed_permissions)) mask = input.mask.lower() - for group in allowed_groups: - group_users = bot.config.get("permissions", {}).get(group, [])["users"] - group_users = [_mask.lower() for _mask in group_users] - for pattern in group_users: - if fnmatch(mask, pattern): - return input + # loop over every group + for key, group in groups.iteritems(): + # loop over every permission the command allows + for permission in allowed_permissions: + # see if the group has that permission + if permission in group["perms"]: + # if so, check it + group_users = [_mask.lower() for _mask in group["users"]] + for pattern in group_users: + if fnmatch(mask, pattern): + print "Allowed group {}.".format(group) + return input input.notice("Sorry, you are not allowed to use this command.") return None diff --git a/plugins/correction.py b/plugins/correction.py deleted file mode 100644 index 0536eeb..0000000 --- a/plugins/correction.py +++ /dev/null @@ -1,30 +0,0 @@ -from util import hook - - -@hook.regex(r'^(s|S)/.*/.*/\S*$') -def correction(inp, say=None, input=None, notice=None, db=None): - splitinput = input.msg.split("/") - if splitinput[3]: - nick = splitinput[3] - else: - nick = input.nick - last_message = db.execute("select name, quote from seen_user where name" - " like ? and chan = ?", (nick.lower(), input.chan.lower())).fetchone() - - if last_message: - splitinput = input.msg.split("/") - find = splitinput[1] - replace = splitinput[2] - if find in last_message[1]: - if "\x01ACTION" in last_message[1]: - message = last_message[1].replace("\x01ACTION ", "/me ").replace("\x01", "") - else: - message = last_message[1] - say("{} meant to say: {}".format(nick, message.replace(find, "\x02" + replace + "\x02"))) - else: - notice("{} can't be found in your last message".format(find)) - else: - if nick == input.nick: - notice("I haven't seen you say anything here yet") - else: - notice("I haven't seen {} say anything here yet".format(nick)) diff --git a/plugins/data/itemids.txt b/plugins/data/itemids.txt deleted file mode 100755 index b4d991e..0000000 --- a/plugins/data/itemids.txt +++ /dev/null @@ -1,537 +0,0 @@ -1 Stone -2 Grass -3 Dirt -4 Cobblestone -5 Wooden Plank (Oak) -5:1 Wooden Plank (Spruce) -5:2 Wooden Plank (Birch) -5:3 Wooden Plank (Jungle) -6 Sapling (Oak) -6:1 Sapling (Spruce) -6:2 Sapling (Birch) -6:3 Sapling (Jungle) -7 Bedrock -8 Water -9 Water (No Spread) -10 Lava -11 Lava (No Spread) -12 Sand -13 Gravel -14 Gold Ore -15 Iron Ore -16 Coal Ore -17 Wood (Oak) -17:1 Wood (Spruce) -17:2 Wood (Birch) -17:3 Wood (Jungle) -18 Leaves (Oak) -18:1 Leaves (Spruce) -18:2 Leaves (Birch) -18:3 Leaves (Jungle) -19 Sponge -20 Glass -21 Lapis Lazuli Ore -22 Lapis Lazuli Block -23 Dispenser -24 Sandstone -24:1 Sandstone (Chiseled) -24:2 Sandstone (Smooth) -25 Note Block -26 Bed (Block) -27 Powered Rail -28 Detector Rail -29 Sticky Piston -30 Cobweb -31 Tall Grass (Dead Shrub) -31:1 Tall Grass -31:2 Tall Grass (Fern) -32 Dead Shrub -33 Piston -34 Piston (Head) -35 Wool -35:1 Orange Wool -35:2 Magenta Wool -35:3 Light Blue Wool -35:4 Yellow Wool -35:5 Lime Wool -35:6 Pink Wool -35:7 Gray Wool -35:8 Light Gray Wool -35:9 Cyan Wool -35:10 Purple Wool -35:11 Blue Wool -35:12 Brown Wool -35:13 Green Wool -35:14 Red Wool -35:15 Black Wool -36 Piston (Moving) -37 Dandelion -38 Rose -39 Brown Mushroom -40 Red Mushroom -41 Block of Gold -42 Block of Iron -43 Stone Slab (Double) -43:1 Sandstone Slab (Double) -43:2 Wooden Slab (Double) -43:3 Cobblestone Slab (Double) -43:4 Brick Slab (Double) -43:5 Stone Brick Slab (Double) -43:6 Nether Brick Slab (Double) -43:7 Quartz Slab (Double) -43:8 Smooth Stone Slab (Double) -43:9 Smooth Sandstone Slab (Double) -44 Stone Slab -44:1 Sandstone Slab -44:2 Wooden Slab -44:3 Cobblestone Slab -44:4 Brick Slab -44:5 Stone Brick Slab -44:6 Nether Brick Slab -44:7 Quartz Slab -45 Brick -46 TNT -47 Bookcase -48 Moss Stone -49 Obsidian -50 Torch -51 Fire -52 Mob Spawner -53 Wooden Stairs (Oak) -54 Chest -55 Redstone Wire -56 Diamond Ore -57 Block of Diamond -58 Workbench -59 Wheat (Crop) -60 Farmland -61 Furnace -62 Furnace (Smelting) -63 Sign (Block) -64 Wood Door (Block) -65 Ladder -66 Rail -67 Cobblestone Stairs -68 Sign (Wall Block) -69 Lever -70 Stone Pressure Plate -71 Iron Door (Block) -72 Wooden Pressure Plate -73 Redstone Ore -74 Redstone Ore (Glowing) -75 Redstone Torch (Off) -76 Redstone Torch -77 Button (Stone) -78 Snow -79 Ice -80 Snow Block -81 Cactus -82 Clay Block -83 Sugar Cane (Block) -84 Jukebox -85 Fence -86 Pumpkin -87 Netherrack -88 Soul Sand -89 Glowstone -90 Portal -91 Jack-O-Lantern -92 Cake (Block) -93 Redstone Repeater (Block Off) -94 Redstone Repeater (Block On) -95 Locked Chest -96 Trapdoor -97 Silverfish Stone -97:1 Silverfish Cobblestone -97:2 Silverfish Stone Brick -98 Stone Bricks -98:1 Mossy Stone Bricks -98:2 Cracked Stone Bricks -98:3 Chiseled Stone Brick -99 Brown Mushroom (Block) -100 Red Mushroom (Block) -101 Iron Bars -102 Glass Pane -103 Melon (Block) -104 Pumpkin Vine -105 Melon Vine -106 Vines -107 Fence Gate -108 Brick Stairs -109 Stone Brick Stairs -110 Mycelium -111 Lily Pad -112 Nether Brick -113 Nether Brick Fence -114 Nether Brick Stairs -115 Nether Wart -116 Enchantment Table -117 Brewing Stand (Block) -118 Cauldron (Block) -119 End Portal -120 End Portal Frame -121 End Stone -122 Dragon Egg -123 Redstone Lamp -124 Redstone Lamp (On) -125 Oak-Wood Slab (Double) -125:1 Spruce-Wood Slab (Double) -125:2 Birch-Wood Slab (Double) -125:3 Jungle-Wood Slab (Double) -126 Oak-Wood Slab -126:1 Spruce-Wood Slab -126:2 Birch-Wood Slab -126:3 Jungle-Wood Slab -127 Coca Plant -128 Sandstone Stairs -129 Emerald Ore -130 Ender Chest -131 Tripwire Hook -132 Tripwire -133 Block of Emerald -134 Wooden Stairs (Spruce) -135 Wooden Stairs (Birch) -136 Wooden Stairs (Jungle) -137 Command Block -138 Beacon -139 Cobblestone Wall -139:1 Mossy Cobblestone Wall -140 Flower Pot (Block) -141 Carrot (Crop) -142 Potatoes (Crop) -143 Button (Wood) -144 Head Block (Skeleton) -144:1 Head Block (Wither) -144:2 Head Block (Zombie) -144:3 Head Block (Steve) -144:4 Head Block (Creeper) -145 Anvil -145:1 Anvil (Slightly Damaged) -145:2 Anvil (Very Damaged) -146 Trapped Chest -147 Weighted Pressure Plate (Light) -148 Weighted Pressure Plate (Heavy) -149 Redstone Comparator (Off) -150 Redstone Comparator (On) -151 Daylight Sensor -152 Block of Redstone -153 Nether Quartz Ore -154 Hopper -155 Quartz Block -155:1 Chiseled Quartz Block -155:2 Pillar Quartz Block -156 Quartz Stairs -157 Activator Rail -158 Dropper -159 Stained Clay (White) -159:1 Stained Clay (Orange) -159:2 Stained Clay (Magenta) -159:3 Stained Clay (Light Blue) -159:4 Stained Clay (Yellow) -159:5 Stained Clay (Lime) -159:6 Stained Clay (Pink) -159:7 Stained Clay (Gray) -159:8 Stained Clay (Light Gray) -159:9 Stained Clay (Cyan) -159:10 Stained Clay (Purple) -159:11 Stained Clay (Blue) -159:12 Stained Clay (Brown) -159:13 Stained Clay (Green) -159:14 Stained Clay (Red) -159:15 Stained Clay (Black) -170 Hay Bale -171 Carpet (White) -171:1 Carpet (Orange) -171:2 Carpet (Magenta) -171:3 Carpet (Light Blue) -171:4 Carpet (Yellow) -171:5 Carpet (Lime) -171:6 Carpet (Pink) -171:7 Carpet (Grey) -171:8 Carpet (Light Gray) -171:9 Carpet (Cyan) -171:10 Carpet (Purple) -171:11 Carpet (Blue) -171:12 Carpet (Brown) -171:13 Carpet (Green) -171:14 Carpet (Red) -171:15 Carpet (Black) -172 Hardened Clay -173 Block of Coal -256 Iron Shovel -257 Iron Pickaxe -258 Iron Axe -259 Flint and Steel -260 Apple -261 Bow -262 Arrow -263 Coal -263:1 Charcoal -264 Diamond Gem -265 Iron Ingot -266 Gold Ingot -267 Iron Sword -268 Wooden Sword -269 Wooden Shovel -270 Wooden Pickaxe -271 Wooden Axe -272 Stone Sword -273 Stone Shovel -274 Stone Pickaxe -275 Stone Axe -276 Diamond Sword -277 Diamond Shovel -278 Diamond Pickaxe -279 Diamond Axe -280 Stick -281 Bowl -282 Mushroom Stew -283 Gold Sword -284 Gold Shovel -285 Gold Pickaxe -286 Gold Axe -287 String -288 Feather -289 Gunpowder -290 Wooden Hoe -291 Stone Hoe -292 Iron Hoe -293 Diamond Hoe -294 Gold Hoe -295 Wheat Seeds -296 Wheat -297 Bread -298 Leather Helmet -299 Leather Chestplate -300 Leather Leggings -301 Leather Boots -302 Chainmail Helmet -303 Chainmail Chestplate -304 Chainmail Leggings -305 Chainmail Boots -306 Iron Helmet -307 Iron Chestplate -308 Iron Leggings -309 Iron Boots -310 Diamond Helmet -311 Diamond Chestplate -312 Diamond Leggings -313 Diamond Boots -314 Gold Helmet -315 Gold Chestplate -316 Gold Leggings -317 Gold Boots -318 Flint -319 Raw Porkchop -320 Cooked Porkchop -321 Painting -322 Golden Apple -322:1 Enchanted Golden Apple -323 Sign -324 Wooden Door -325 Bucket -326 Bucket (Water) -327 Bucket (Lava) -328 Minecart -329 Saddle -330 Iron Door -331 Redstone Dust -332 Snowball -333 Boat -334 Leather -335 Bucket (Milk) -336 Clay Brick -337 Clay -338 Sugar Cane -339 Paper -340 Book -341 Slime Ball -342 Storage Minecart -343 Powered Minecart -344 Egg -345 Compass -346 Fishing Rod -347 Watch -348 Glowstone Dust -349 Raw Fish -350 Cooked Fish -351 Ink Sack -351:1 Rose Red Dye -351:2 Cactus Green Dye -351:3 Coca Bean -351:4 Lapis Lazuli -351:5 Purple Dye -351:6 Cyan Dye -351:7 Light Gray Dye -351:8 Gray Dye -351:9 Pink Dye -351:10 Lime Dye -351:11 Dandelion Yellow Dye -351:12 Light Blue Dye -351:13 Magenta Dye -351:14 Orange Dye -351:15 Bone Meal -352 Bone -353 Sugar -354 Cake -355 Bed -356 Redstone Repeater -357 Cookie -358 Map -359 Shears -360 Melon (Slice) -361 Pumpkin Seeds -362 Melon Seeds -363 Raw Beef -364 Steak -365 Raw Chicken -366 Cooked Chicken -367 Rotten Flesh -368 Ender Pearl -369 Blaze Rod -370 Ghast Tear -371 Gold Nugget -372 Nether Wart Seeds -373 Water Bottle -373:16 Awkward Potion -373:32 Thick Potion -373:64 Mundane Potion -373:8193 Regeneration Potion (0:45) -373:8194 Swiftness Potion (3:00) -373:8195 Fire Resistance Potion (3:00) -373:8196 Poison Potion (0:45) -373:8197 Healing Potion -373:8198 Night Vision Potion (3:00) -373:8200 Weakness Potion (1:30) -373:8201 Strength Potion (3:00) -373:8202 Slowness Potion (1:30) -373:8204 Harming Potion -373:8206 Invisibility Potion (3:00) -373:8225 Regeneration Potion II (0:22) -373:8226 Swiftness Potion II (1:30) -373:8228 Poison Potion II (0:22) -373:8229 Healing Potion II -373:8233 Strength Potion II (1:30) -373:8236 Harming Potion II -373:8257 Regeneration Potion (2:00) -373:8258 Swiftness Potion (8:00) -373:8259 Fire Resistance Potion (8:00) -373:8260 Poison Potion (2:00) -373:8262 Night Vision Potion (8:00) -373:8264 Weakness Potion (4:00) -373:8265 Strength Potion (8:00) -373:8266 Slowness Potion (4:00) -373:8270 Invisibility Potion (8:00) -373:8289 Regeneration Potion II (1:00) -373:8290 Swiftness Potion II (4:00) -373:8292 Poison Potion II (1:00) -373:8297 Strength Potion II (4:00) -373:16385 Regeneration Splash (0:33) -373:16386 Swiftness Splash (2:15) -373:16387 Fire Resistance Splash (2:15) -373:16388 Poison Splash (0:33) -373:16389 Healing Splash -373:16390 Night Vision Splash (2:15) -373:16392 Weakness Splash (1:07) -373:16393 Strength Splash (2:15) -373:16394 Slowness Splash (1:07) -373:16396 Harming Splash -373:16398 Invisibility Splash (2:15) -373:16417 Regeneration Splash II (0:16) -373:16418 Swiftness Splash II (1:07) -373:16420 Poison Splash II (0:16) -373:16421 Healing Splash II -373:16425 Strength Splash II (1:07) -373:16428 Harming Splash II -373:16449 Regeneration Splash (1:30) -373:16450 Swiftness Splash (6:00) -373:16451 Fire Resistance Splash (6:00) -373:16452 Poison Splash (1:30) -373:16454 Night Vision Splash (6:00) -373:16456 Weakness Splash (3:00) -373:16457 Strength Splash (6:00) -373:16458 Slowness Splash (3:00) -373:16462 Invisibility Splash (6:00) -373:16481 Regeneration Splash II (0:45) -373:16482 Swiftness Splash II (3:00) -373:16484 Poison Splash II (0:45) -373:16489 Strength Splash II (3:00) -374 Glass Bottle -375 Spider Eye -376 Fermented Spider Eye -377 Blaze Powder -378 Magma Cream -379 Brewing Stand -380 Cauldron -381 Eye of Ender -382 Glistering Melon (Slice) -383:50 Spawn Egg (Creeper) -383:51 Spawn Egg (Skeleton) -383:52 Spawn Egg (Spider) -383:54 Spawn Egg (Zombie) -383:55 Spawn Egg (Slime) -383:56 Spawn Egg (Ghast) -383:57 Spawn Egg (Zombie Pigmen) -383:58 Spawn Egg (Endermen) -383:59 Spawn Egg (Cave Spider) -383:60 Spawn Egg (Silverfish) -383:61 Spawn Egg (Blaze) -383:62 Spawn Egg (Magma Cube) -383:65 Spawn Egg (Bat) -383:66 Spawn Egg (Witch) -383:90 Spawn Egg (Pig) -383:91 Spawn Egg (Sheep) -383:92 Spawn Egg (Cow) -383:93 Spawn Egg (Chicken) -383:94 Spawn Egg (Squid) -383:95 Spawn Egg (Wolf) -383:96 Spawn Egg (Mooshroom) -383:98 Spawn Egg (Ocelot) -383:100 Spawn Egg (Horse) -383:120 Spawn Egg (Villager) -384 Bottle of Enchanting -385 Fire Charge -386 Book and Quill -387 Written Book -388 Emerald -389 Item Frame -390 Flower Pot -391 Carrot -392 Potato -393 Baked Potato -394 Poisonous Potato -395 Empty Map -396 Golden Carrot -397 Head (Skeleton) -397:1 Head (Wither) -397:2 Head (Zombie) -397:3 Head (Steve) -397:4 Head (Creeper) -398 Carrot on a Stick -399 Nether Star -400 Pumpkin Pie -401 Firework Rocket -402 Firework Star -403 Enchanted Book -404 Redstone Comparator -405 Nether Brick (Item) -406 Nether Quartz -407 TNT Minecart -408 Hopper Minecart -417 Iron Horse Armor -418 Gold Horse Armor -419 Diamond Horse Armor -420 Lead -421 Name Tag -2256 Music Disk (13) -2257 Music Disk (Cat) -2258 Music Disk (Blocks) -2259 Music Disk (Chirp) -2260 Music Disk (Far) -2261 Music Disk (Mall) -2262 Music Disk (Mellohi) -2263 Music Disk (Stal) -2264 Music Disk (Strad) -2265 Music Disk (Ward) -2266 Music Disk (11) -2267 Music Disk (Wait) diff --git a/plugins/gcalc.py b/plugins/gcalc.py deleted file mode 100755 index 9a2dbb5..0000000 --- a/plugins/gcalc.py +++ /dev/null @@ -1,19 +0,0 @@ -from util import hook, http - - -@hook.command("math") -@hook.command -def calc(inp): - """calc <term> -- Calculate <term> with Google Calc.""" - - soup = http.get_soup('http://www.google.com/search', q=inp) - - result = soup.find('h2', {'class': 'r'}) - exponent = result.find('sup') - if not result: - return "Could not calculate '{}'".format(inp) - - if not exponent: - return result.contents[0] - if exponent: - return "{}^{}".format(result.contents[0]*2) diff --git a/plugins/hash.py b/plugins/hash.py deleted file mode 100755 index 7db8109..0000000 --- a/plugins/hash.py +++ /dev/null @@ -1,9 +0,0 @@ -import hashlib -from util import hook - - -@hook.command -def hash(inp): - """hash <text> -- Returns hashes of <text>.""" - return ', '.join(x + ": " + getattr(hashlib, x)(inp).hexdigest() - for x in ['md5', 'sha1', 'sha256']) diff --git a/plugins/help.py b/plugins/help.py old mode 100755 new mode 100644 index f6b5143..d2b3d2e --- a/plugins/help.py +++ b/plugins/help.py @@ -1,9 +1,10 @@ import re + from util import hook -@hook.command(autohelp=False) -def help(inp, notice=None, input=None, conn=None, bot=None): +@hook.command("help", autohelp=False) +def help_command(inp, notice=None, conn=None, bot=None): """help -- Gives a list of commands/help for a command.""" funcs = {} @@ -46,3 +47,5 @@ def help(inp, notice=None, input=None, conn=None, bot=None): else: if inp in commands: notice(conn.conf["command_prefix"] + commands[inp].__doc__) + else: + notice("Command {}{} not found".format(conn.conf["command_prefix"], inp)) diff --git a/plugins/ignore.py b/plugins/ignore.py old mode 100755 new mode 100644 index dc3f382..3bafbfb --- a/plugins/ignore.py +++ b/plugins/ignore.py @@ -1,7 +1,8 @@ import json -from util import hook from fnmatch import fnmatch +from util import hook + @hook.sieve def ignore_sieve(bot, input, func, type, args): diff --git a/plugins/karma.py b/plugins/karma.py deleted file mode 100644 index 1a8941f..0000000 --- a/plugins/karma.py +++ /dev/null @@ -1,131 +0,0 @@ -from util import hook, timesince - -import time -import re - -db_ready = False - - -def db_init(db): - db.execute("""CREATE TABLE if not exists karma( - nick_vote TEXT PRIMARY KEY, - up_karma INTEGER, - down_karma INTEGER, - total_karma INTEGER)""") - - db.execute("""CREATE TABLE if not exists karma_voters( - voter TEXT, - votee TEXT, - epoch FLOAT, - PRIMARY KEY(voter, votee))""") - db_ready = True - - -def up(db, nick_vote): - db.execute("""UPDATE karma SET - up_karma = up_karma + 1, - total_karma = total_karma + 1 WHERE nick_vote=?""", (nick_vote.lower(),)) - db.commit() - - -def down(db, nick_vote): - db.execute("""UPDATE karma SET - down_karma = down_karma + 1, - total_karma = total_karma + 1 WHERE nick_vote=?""", (nick_vote.lower(),)) - db.commit() - - -def allowed(db, nick, nick_vote): - time_restriction = 3600 - db.execute("""DELETE FROM karma_voters WHERE ? - epoch >= 3600""", - (time.time(),)) - db.commit() - check = db.execute("""SELECT epoch FROM karma_voters WHERE voter=? AND votee=?""", - (nick.lower(), nick_vote.lower())).fetchone() - - if check: - check = check[0] - if time.time() - check >= time_restriction: - db.execute("""INSERT OR REPLACE INTO karma_voters( - voter, - votee, - epoch) values(?,?,?)""", (nick.lower(), nick_vote.lower(), time.time())) - db.commit() - return True, 0 - else: - return False, timesince.timeuntil(check, now=time.time()-time_restriction) - else: - db.execute("""INSERT OR REPLACE INTO karma_voters( - voter, - votee, - epoch) values(?,?,?)""", (nick.lower(), nick_vote.lower(), time.time())) - db.commit() - return True, 0 - - -# TODO Make this work on multiple matches in a string, right now it'll only -# work on one match. -# karma_re = ('((\S+)(\+\+|\-\-))+', re.I) -karma_re = ('(.+)(\+\+|\-\-)$', re.I) - -@hook.regex(*karma_re) -def karma_add(match, nick='', chan='', db=None, notice=None): - - if not db_ready: - db_init(db) - - nick_vote = match.group(1).strip().replace("+", "") - if nick.lower() == nick_vote.lower(): - notice("You can't vote on yourself!") - return - if len(nick_vote) < 3 or " " in nick_vote: - return # ignore anything below 3 chars in length or with spaces - - vote_allowed, when = allowed(db, nick, nick_vote) - if vote_allowed: - if match.group(2) == '++': - db.execute("""INSERT or IGNORE INTO karma( - nick_vote, - up_karma, - down_karma, - total_karma) values(?,?,?,?)""", (nick_vote.lower(),0,0,0)) - up(db, nick_vote) - notice("Gave {} 1 karma!".format(nick_vote)) - if match.group(2) == '--': - db.execute("""INSERT or IGNORE INTO karma( - nick_vote, - up_karma, - down_karma, - total_karma) values(?,?,?,?)""", (nick_vote.lower(),0,0,0)) - down(db, nick_vote) - notice("Took away 1 karma from {}.".format(nick_vote)) - else: - return - else: - notice("You are trying to vote too often. You can vote again in {}!".format(when)) - - return - - -@hook.command('k') -@hook.command -def karma(inp, nick='', chan='', db=None): - """k/karma <nick> -- returns karma stats for <nick>""" - - if not db_ready: - db_init(db) - - if not chan.startswith('#'): - return - - nick_vote = inp - out = db.execute("""SELECT * FROM karma WHERE nick_vote=?""", - (nick_vote.lower(),)).fetchall() - - if not out: - return "That user has no karma." - else: - out = out[0] - return "{} has {} karma points.".format(nick_vote, out[1]-out[2]) - - return diff --git a/plugins/length.py b/plugins/length.py deleted file mode 100644 index 9db7199..0000000 --- a/plugins/length.py +++ /dev/null @@ -1,7 +0,0 @@ -from util import hook - - -@hook.command -def length(inp): - """length <message> -- gets the length of <message>""" - return "The length of that message is {} characters.".format(len(inp)) diff --git a/plugins/minecraft_bukkit.py b/plugins/minecraft_bukkit.py deleted file mode 100644 index ad1a38c..0000000 --- a/plugins/minecraft_bukkit.py +++ /dev/null @@ -1,73 +0,0 @@ -from util import hook, http, web -import time -import json -from urllib2 import HTTPError -import random -from os import path - - -@hook.command('randomplugin') -@hook.command(autohelp=False) -def randombukkitplugin(inp, reply=None): - if not path.exists("plugins/data/bukgetplugins"): - with open("plugins/data/bukgetplugins", "w") as f: - f.write(http.get("http://api.bukget.org/3/plugins/bukkit")) - jsahn = json.loads(open("plugins/data/bukgetplugins", "r").read()) - pickslug = random.choice(jsahn)['slug'] - data = getplugininfo(pickslug) - name = data['plugin_name'] - description = data['description'] - url = data['website'] - authors = data['authors'][0] - authors = authors[0] + u"\u200b" + authors[1:] - stage = data['stage'] - lastUpdate = time.strftime('%d %B %Y %H:%M', - time.gmtime(data['versions'][0]['date'])) - lastVersion = data['versions'][0]['version'] - bukkitver = ", ".join(data['versions'][0]['game_versions']) - link = web.isgd(data['versions'][0]['link']) - if description != "": - reply("\x02{}\x02, by \x02{}\x02 - {} - ({}) \x02{}".format(name, authors, description, stage, url)) - else: - reply("\x02{}\x02, by \x02{}\x02 ({}) \x02{}".format(name, authors, stage, url)) - reply("Last release: \x02v{}\x02 for \x02{}\x02 at {} \x02{}\x02".format(lastVersion, bukkitver, lastUpdate, link)) - - -@hook.command('bplugin') -@hook.command('plugin') -@hook.command -def bukkitplugin(inp, reply=None): - """plugin <bukkit plugin slug> - Look up a plugin on dev.bukkit.org""" - data = getplugininfo(inp.lower()) - try: - name = data['plugin_name'] - except ValueError: - return data - description = data['description'] - url = data['website'] - authors = data['authors'][0] - authors = authors[0] + u"\u200b" + authors[1:] - stage = data['stage'] - lastUpdate = time.strftime('%d %B %Y %H:%M', - time.gmtime(data['versions'][0]['date'])) - lastVersion = data['versions'][0]['version'] - bukkitver = ", ".join(data['versions'][0]['game_versions']) - link = web.isgd(data['versions'][0]['link']) - if description != "": - reply("\x02{}\x02, by \x02{}\x02 - {} - ({}) \x02{}".format(name, authors, description, stage, url)) - else: - reply("\x02{}\x02, by \x02{}\x02 ({}) \x02{}".format(name, authors, stage, url)) - reply("Last release: \x02v{}\x02 for \x02{}\x02 at {} \x02{}\x02".format(lastVersion, bukkitver, lastUpdate, link)) - - -def getplugininfo(inp): - if len(inp.split(" ")) > 1: - slug = inp.split(" ")[0] - else: - slug = inp - try: - data = http.get_json("http://api.bukget.org/3/plugins/bukkit/%s/" - % slug) - except HTTPError as e: - return "Got error: {}".format(e) - return data diff --git a/plugins/minecraft_ping.py b/plugins/minecraft_ping.py deleted file mode 100644 index 63b56f4..0000000 --- a/plugins/minecraft_ping.py +++ /dev/null @@ -1,97 +0,0 @@ -from util import hook -import socket -import struct - -try: - import DNS - # Please remember to install the dependancy 'pydns' - pydns_installed = True -except ImportError: - pydns_installed = False - - -def format_motd(motd): - empty = "" - colors = [u"\x0300,\xa7f", u"\x0301,\xa70", u"\x0302,\xa71", u"\x0303,\xa72", u"\x0304,\xa7c", u"\x0305,\xa74", - u"\x0306,\xa75", u"\x0307,\xa76", u"\x0308,\xa7e", u"\x0309,\xa7a", u"\x0310,\xa73", u"\x0311,\xa7b", - u"\x0312,\xa71", u"\x0313,\xa7d", u"\x0314,\xa78", u"\x0315,\xa77", u"\x02,\xa7l", u"\x0310,\xa79", - u"\x09,\xa7o", u"\x13,\xa7m", u"\x0f,\xa7r", u"\x15,\xa7n"]; - for s in colors: - lcol = s.split(",") - motd = motd.replace(lcol[1], lcol[0]) - motd = motd.replace(u"\xa7k", empty) - return motd - - -def mcping_connect(host, port): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((host, port)) - sock.send('\xfe\x01') - response = sock.recv(1) - print response - - if response[0] != '\xff': - return "Server gave invalid response: " + repr(response) - length = struct.unpack('!h', sock.recv(2))[0] - - values = sock.recv(length * 2).decode('utf-16be') - - data = values.split(u'\x00') # try to decode data using new format - if len(data) == 1: - # failed to decode data, server is using old format - data = values.split(u'\xa7') - message = u"{} - {}/{} players".format(data[0], data[1], data[2]) - else: - # decoded data, server is using new format - message = u"{} \x0f- {} - {}/{} players".format(data[3], data[2], data[4], data[5]) - - sock.close() - return message - - except: - return "Error pinging {}:{}, is it up? Double-check your address!".format(host,str(port)) - - -def srvData(domain): - DNS.ParseResolvConf() - srv_req = DNS.Request(qtype='srv') - srv_result = srv_req.req('_minecraft._tcp.{}'.format(domain)) - - for getsrv in srv_result.answers: - if getsrv['typename'] == 'SRV': - data = [getsrv['data'][2], getsrv['data'][3]] - return data - - -@hook.command -def mcping(inp): - """mcping <server>[:port] - Ping a Minecraft server to check status.""" - inp = inp.strip().split(" ")[0] - - if ":" in inp: - host, port = inp.split(":", 1) - try: - port = int(port) - except: - return "error: invalid port!" - return format_motd(mcping_connect(host, port)) - - else: - host = inp - port = 25565 - rdata = format_motd(mcping_connect(host, port)) - - if 'is it up' in rdata: - if pydns_installed: - getdata = srvData(inp) - try: - host = str(getdata[1]) - port = int(getdata[0]) - return format_motd(mcping_connect(host, port)) - except: - return "Error pinging {}, is it up? Double-check your address!".format(inp) - else: - return "Error pinging {}, is it up? Double-check your address!".format(inp) - else: - return rdata diff --git a/plugins/minecraft_wiki.py b/plugins/minecraft_wiki.py deleted file mode 100755 index 210c3f4..0000000 --- a/plugins/minecraft_wiki.py +++ /dev/null @@ -1,29 +0,0 @@ -from util import hook, http, text -import re - -api_url = "http://minecraftwiki.net/api.php?action=opensearch" -mc_url = "http://minecraftwiki.net/wiki/" - - -@hook.command -def mcwiki(inp): - """mcwiki <phrase> -- Gets the first paragraph of - the Minecraft Wiki article on <phrase>.""" - - j = http.get_json(api_url, search=inp) - - if not j[1]: - return "No results found." - article_name = j[1][0].replace(' ', '_').encode('utf8') - - url = mc_url + http.quote(article_name, '') - page = http.get_html(url) - - for p in page.xpath('//div[@class="mw-content-ltr"]/p'): - if p.text_content(): - summary = " ".join(p.text_content().splitlines()) - summary = re.sub("\[\d+\]", "", summary) - summary = text.truncate_str(summary, 200) - return "{} :: {}".format(summary, url) - - return "Unknown Error." diff --git a/plugins/munge.py b/plugins/munge.py deleted file mode 100755 index 2bc3dc6..0000000 --- a/plugins/munge.py +++ /dev/null @@ -1,7 +0,0 @@ -from util import hook, text - - -@hook.command -def munge(inp): - """munge <text> -- Munges up <text>.""" - return text.munge(inp) diff --git a/plugins/op.py b/plugins/op.py old mode 100755 new mode 100644 index ed51f3f..695b74a --- a/plugins/op.py +++ b/plugins/op.py @@ -178,4 +178,4 @@ def unlock(inp, conn=None, chan=None, notice=None): """unlock [channel] -- Makes the bot unlock a channel.. If [channel] is blank the bot will mute the channel the command was used in.""" - mode_cmd_no_target("-i", "unlock", inp, chan, conn, notice) \ No newline at end of file + mode_cmd_no_target("-i", "unlock", inp, chan, conn, notice) diff --git a/plugins/osrc.py b/plugins/osrc.py deleted file mode 100644 index c1dd322..0000000 --- a/plugins/osrc.py +++ /dev/null @@ -1,27 +0,0 @@ -from util import hook, http, web -from bs4 import BeautifulSoup - -api_url = "http://osrc.dfm.io/{}/stats" -user_url = "http://osrc.dfm.io/{}" - - -@hook.command -def osrc(inp): - """osrc <github user> -- Gets an Open Source Report Card for <github user>""" - - user_nick = inp.strip() - url = api_url.format(user_nick) - - try: - response = http.get_json(url) - except (http.HTTPError, http.URLError): - return "Couldn't find any stats for this user." - - response["nick"] = user_nick - soup = BeautifulSoup(response["summary"]) - response["work_time"] = soup.find("a", {"href" : "#day"}).contents[0] - - response["short_url"] = web.try_isgd(user_url.format(user_nick)) - - return "{nick} is a {lang_user}. {nick} is a {hacker_type} " \ - "who seems to {work_time} - {short_url}".format(**response) diff --git a/plugins/ping.py b/plugins/ping.py old mode 100755 new mode 100644 index 746ea05..66286df --- a/plugins/ping.py +++ b/plugins/ping.py @@ -1,9 +1,11 @@ # ping plugin by neersighted -from util import hook import subprocess import re import os +from util import hook + + ping_regex = re.compile(r"(\d+.\d+)/(\d+.\d+)/(\d+.\d+)/(\d+.\d+)") @@ -13,11 +15,12 @@ def ping(inp, reply=None): if os.name == "nt": return "Sorry, this command is not supported on Windows systems." + # TODO: Rewrite this entire command to work on Windows, somehow args = inp.split(' ') host = args[0] - # check for a seccond argument and set the ping count + # check for a second argument and set the ping count if len(args) > 1: count = int(args[1]) if count > 20: diff --git a/plugins/puush.py b/plugins/puush.py deleted file mode 100644 index 015897d..0000000 --- a/plugins/puush.py +++ /dev/null @@ -1,49 +0,0 @@ -import urllib2 -import random -from util import hook - - -def make_string(): - stuff = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" - string = random.choice("123") - for x in range(4): - string += random.choice(stuff) - return string - - -def check_url(code): - try: - urllib2.urlopen(make_url(code)) - return True - except: - return False # sorry <3 - - -def make_url(code): - return "http://puu.sh/{}".format(code) - - -@hook.command(autohelp=False) -def puush(inp): - """puush [1-5] -- Returns a number of random puu.sh entries.""" - out = "" - num = 0 - - if not inp: - inp = "1" - if not inp.isdigit(): - out += "Defaulting to one: " - num = 1 - elif int(inp[0]) > 5: - out += "Five images max: " - num = 5 - else: - num = int(inp[0]) - - images = [] - for x in xrange(num): - ran = make_string() - while not check_url(ran): - ran = make_string() - images.append(make_url(ran)) - return out + " ".join(images) diff --git a/plugins/reddit.py b/plugins/reddit.py deleted file mode 100644 index 96f96b5..0000000 --- a/plugins/reddit.py +++ /dev/null @@ -1,19 +0,0 @@ -from util import hook, http -import re - -reddit_re = (r'.*((www\.)?reddit\.com/r[^ ]+)', re.I) - - -@hook.regex(*reddit_re) -def reddit_url(match): - thread = http.get_html(match.group(0)) - - title = thread.xpath('//title/text()')[0] - upvotes = thread.xpath("//span[@class='upvotes']/span[@class='number']/text()")[0] - downvotes = thread.xpath("//span[@class='downvotes']/span[@class='number']/text()")[0] - author = thread.xpath("//div[@id='siteTable']//a[contains(@class,'author')]/text()")[0] - timeago = thread.xpath("//div[@id='siteTable']//p[@class='tagline']/time/text()")[0] - comments = thread.xpath("//div[@id='siteTable']//a[@class='comments']/text()")[0] - - return '\x02{}\x02 - posted by \x02{}\x02 {} ago - {} upvotes, {} downvotes - {}'.format( - title, author, timeago, upvotes, downvotes, comments) diff --git a/plugins/seen.py b/plugins/seen.py deleted file mode 100755 index 640b227..0000000 --- a/plugins/seen.py +++ /dev/null @@ -1,61 +0,0 @@ -"""seen.py: written by sklnd in about two beers July 2009""" - -import time -import re - -from util import hook, timesince - -db_ready = False - - -def db_init(db): - """check to see that our db has the the seen table and return a connection.""" - db.execute("create table if not exists seen_user(name, time, quote, chan, host, " - "primary key(name, chan))") - db.commit() - db_ready = True - - -@hook.singlethread -@hook.event('PRIVMSG', ignorebots=False) -def seen_sieve(paraml, input=None, db=None): - if not db_ready: - db_init(db) - # keep private messages private - if input.chan[:1] == "#" and not re.findall('^s/.*/.*/$', input.msg.lower()): - db.execute("insert or replace into seen_user(name, time, quote, chan, host)" - "values(?,?,?,?,?)", (input.nick.lower(), time.time(), input.msg, - input.chan, input.mask)) - db.commit() - - -@hook.command -def seen(inp, nick='', chan='', db=None, input=None): - """seen <nick> <channel> -- Tell when a nickname was last in active in one of this bot's channels.""" - - if input.conn.nick.lower() == inp.lower(): - return "You need to get your eyes checked." - - if inp.lower() == nick.lower(): - return "Have you looked in a mirror lately?" - - if not re.match("^[A-Za-z0-9_|.\-\]\[]*$", inp.lower()): - return "I can't look up that name, its impossible to use!" - - if not db_ready: - db_init(db) - - last_seen = db.execute("select name, time, quote from seen_user where name" - " like ? and chan = ?", (inp, chan)).fetchone() - - if last_seen: - reltime = timesince.timesince(last_seen[1]) - if last_seen[0] != inp.lower(): # for glob matching - inp = last_seen[0] - if last_seen[2][0:1] == "\x01": - return '{} was last seen {} ago: * {} {}'.format(inp, reltime, inp, - last_seen[2][8:-1]) - else: - return '{} was last seen {} ago saying: {}'.format(inp, reltime, last_seen[2]) - else: - return "I've never seen {} talking in this channel.".format(inp) diff --git a/plugins/statuslog.py b/plugins/statuslog.py new file mode 100644 index 0000000..e70c7f8 --- /dev/null +++ b/plugins/statuslog.py @@ -0,0 +1,76 @@ +import os +import codecs +import time +import re + +from util import hook + +timestamp_format = '%H:%M:%S' + +formats = { + 'PRIVMSG': '<%(nick)s> %(msg)s', + 'PART': '-!- %(nick)s [%(user)s@%(host)s] has left %(chan)s', + 'JOIN': '-!- %(nick)s [%(user)s@%(host)s] has joined %(param0)s', + 'MODE': '-!- mode/%(chan)s [%(param_tail)s] by %(nick)s', + 'KICK': '-!- %(param1)s was kicked from %(chan)s by %(nick)s [%(msg)s]', + 'TOPIC': '-!- %(nick)s changed the topic of %(chan)s to: %(msg)s', + 'QUIT': '-!- %(nick)s has quit [%(msg)s]', + 'PING': '', + 'NOTICE': '-%(nick)s- %(msg)s' +} + +ctcp_formats = { + 'ACTION': '* %(nick)s %(ctcpmsg)s', + 'VERSION': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s', + 'PING': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s', + 'TIME': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s', + 'FINGER': '%(nick)s has requested CTCP %(ctcpcmd)s from %(chan)s: %(ctcpmsg)s' +} + +irc_color_re = re.compile(r'(\x03(\d+,\d+|\d)|[\x0f\x02\x16\x1f])') + + +def gmtime(format): + return time.strftime(format, time.gmtime()) + + +def beautify(input): + format = formats.get(input.command, '%(raw)s') + args = dict(input) + + leng = len(args['paraml']) + for n, p in enumerate(args['paraml']): + args['param' + str(n)] = p + args['param_' + str(abs(n - leng))] = p + + args['param_tail'] = ' '.join(args['paraml'][1:]) + args['msg'] = irc_color_re.sub('', args['msg']) + + if input.command == 'PRIVMSG' and input.msg.count('\x01') >= 2: + ctcp = input.msg.split('\x01', 2)[1].split(' ', 1) + if len(ctcp) == 1: + ctcp += [''] + args['ctcpcmd'], args['ctcpmsg'] = ctcp + format = ctcp_formats.get(args['ctcpcmd'], + '%(nick)s [%(user)s@%(host)s] requested unknown CTCP ' + '%(ctcpcmd)s from %(chan)s: %(ctcpmsg)s') + + return format % args + +@hook.singlethread +@hook.event('*') +def log(paraml, input=None, bot=None): + timestamp = gmtime(timestamp_format) + + if input.command == 'QUIT': # these are temporary fixes until proper + input.chan = 'quit' # presence tracking is implemented + if input.command == 'NICK': + input.chan = 'nick' + + beau = beautify(input) + + if beau == '': # don't log this + return + + print timestamp, input.chan, beau.encode('utf8', 'ignore') + diff --git a/plugins/steam.py b/plugins/steam.py deleted file mode 100644 index 842a6cb..0000000 --- a/plugins/steam.py +++ /dev/null @@ -1,39 +0,0 @@ -import re -from util import hook, http, web, text -from bs4 import BeautifulSoup - - -steam_re = (r'(.*:)//(store.steampowered.com)(:[0-9]+)?(.*)', re.I) - - -def get_steam_info(url): - # we get the soup manually because the steam pages have some odd encoding troubles - page = http.get(url) - soup = BeautifulSoup(page, 'lxml', from_encoding="utf-8") - - name = soup.find('div', {'class': 'apphub_AppName'}).text - desc = ": " + text.truncate_str(soup.find('div', {'class': 'game_description_snippet'}).text.strip()) - - # the page has a ton of returns and tabs - details = soup.find('div', {'class': 'glance_details'}).text.strip().split(u"\n\n\r\n\t\t\t\t\t\t\t\t\t") - genre = " - Genre: " + details[0].replace(u"Genre: ", u"") - date = " - Release date: " + details[1].replace(u"Release Date: ", u"") - price = "" - if not "Free to Play" in genre: - price = " - Price: " + soup.find('div', {'class': 'game_purchase_price price'}).text.strip() - - return name + desc + genre + date + price - - -@hook.regex(*steam_re) -def steam_url(match): - return get_steam_info("http://store.steampowered.com" + match.group(4)) - - -@hook.command -def steam(inp): - """steam [search] - Search for specified game/trailer/DLC""" - page = http.get("http://store.steampowered.com/search/?term=" + inp) - soup = BeautifulSoup(page, 'lxml', from_encoding="utf-8") - result = soup.find('a', {'class': 'search_result_row'}) - return get_steam_info(result['href']) + " - " + web.isgd(result['href']) diff --git a/plugins/stock.py b/plugins/stock.py deleted file mode 100755 index d2d40c5..0000000 --- a/plugins/stock.py +++ /dev/null @@ -1,48 +0,0 @@ -from util import hook, http - -import json - -url = 'http://www.google.com/ig/api' - - -@hook.command -def stock(inp): - """stock <symbol> -- Gets information about stock symbol <symbol>.""" - - parsed = http.get_xml(url, stock=inp) - - if len(parsed) != 1: - return "error getting stock info" - - # Stuff the results in a dict for easy string formatting - results = dict((el.tag, el.attrib['data']) - for el in parsed.xpath('//finance/*')) - - # if we dont get a company name back, the symbol doesn't match a company - if not "company" in results: - guess_data = json.loads(http.get("http://d.yimg.com/autoc.finance.yahoo.com/autoc", query=inp, - callback="YAHOO.Finance.SymbolSuggest.ssCallback")[39:-1]) - guess = guess_data['ResultSet']['Result'] - if len(guess) > 0: - guess = guess[0]["symbol"] - return stock(guess) - else: - return "error: unable to get stock info for '{}'".format(inp) - - if results['last'] == '0.00': - return "%(company)s - last known stock value was 0.00 %(currency)s" \ - " as of %(trade_timestamp)s" % results - - if results['change'][0] == '-': - results['color'] = "5" - else: - results['color'] = "3" - - ret = "%(company)s - %(last)s %(currency)s " \ - "\x03%(color)s%(change)s (%(perc_change)s%%)\x03 " \ - "as of %(trade_timestamp)s" % results - - if results['delay'] != '0': - ret += " (delayed %s minutes)" % results['delay'] - - return ret diff --git a/plugins/urban.py b/plugins/urban.py deleted file mode 100755 index 5158829..0000000 --- a/plugins/urban.py +++ /dev/null @@ -1,46 +0,0 @@ -from util import hook, http, text -import re - -base_url = 'http://www.urbandictionary.com/iphone/search/define' - - -@hook.command('u') -@hook.command -def urban(inp): - """urban <phrase> [id] -- Looks up <phrase> on urbandictionary.com.""" - - # clean and split the input - input = inp.lower().strip() - parts = input.split() - - # if the last word is a number, set the ID to that number - if parts[-1].isdigit(): - id = int(parts[-1]) - # remove the ID from the input string - del parts[-1] - input = " ".join(parts) - else: - id = 1 - - # fetch the definitions - page = http.get_json(base_url, term=input, referer="http://m.urbandictionary.com") - defs = page['list'] - print page - - if page['result_type'] == 'no_results': - return 'Not found.' - - # try getting the requested definition - try: - definition = defs[id - 1]['definition'].replace('\r\n', ' ') - definition = re.sub('\s+', ' ', definition).strip() # remove excess spaces - definition = text.truncate_str(definition, 200) - except IndexError: - return 'Not found.' - - url = defs[id - 1]['permalink'] - - output = u"[%i/%i] %s :: %s" % \ - (id, len(defs), definition, url) - - return output diff --git a/plugins/util/color.py b/plugins/util/color.py deleted file mode 100644 index d5501df..0000000 --- a/plugins/util/color.py +++ /dev/null @@ -1,35 +0,0 @@ -# Colors. Plugin by blha303, color/control id info from http://stackoverflow.com/a/13382032 - -colors = {'white': '0', 'black': '1', 'darkblue': '2', 'darkgreen': '3', - 'red': '4', 'darkred': '5', 'darkviolet': '6', 'orange': '7', - 'yellow': '8', 'lightgreen': '9', 'cyan': '10', 'lightcyan': '11', - 'blue': '12', 'violet': '13', 'darkgray': '14', 'lightgray': '15'} - -control = {'bold': '\x02', 'color': '\x03', 'italic': '\x09', - 'strikethrough': '\x13', 'reset': '\x0f', 'underline': '\x15', - 'underline2': '\x1f', 'reverse': '\x16'} - - -def color(color): - return control['color'] + colors[color] - -def bold(): - return control['bold'] - -def italic(): - return control['italic'] - -def strike(): - return control['strikethrough'] - -def reset(): - return control['reset'] - -def underline(other=False): - if other: - return control['underline2'] - else: - return control['underline'] - -def reverse(): - return control['reverse'] diff --git a/plugins/util/formatting.py b/plugins/util/formatting.py deleted file mode 100644 index 442adf5..0000000 --- a/plugins/util/formatting.py +++ /dev/null @@ -1,34 +0,0 @@ -def raw(format_string): - """Replace based irc formatting""" - stuff = {} - stuff['col'] = {'[white]':'\x030', - '[black]':'\x031', - '[dblue]':'\x032', - '[dgreen]':'\x033', - '[dred]':'\x034', - '[brown]':'\x035', - '[purple]':'\x036', - '[gold]':'\x037', - '[yellow]':'\x038', - '[green]':'\x039', - '[cyan]':'\x0310', - '[lblue]':'\x0311', - '[blue]':'\x0312', - '[pink]':'\x0313', - '[gray]':'\x0314', - '[lgray]':'\x0315', - '[err]':'\x034\x02' - '[/err]':'\x030\x02'} - stuff['style'] = {'[b]':'\x02', - '[clear]':'\x0f'} - stuff['sym'] = {'[point]':'\x07'} - stuff['text'] = {'[url]':'http://'} - final = {} - for x in stuff: - final.update(stuff[x]) - for x in final: - format_string = format_string.replace(x,final[x]) - return format_string -def err(format_string): - """Format the string with standard error styling""" - return "\x034\x02{}\x0f".format(format_string) \ No newline at end of file diff --git a/plugins/util/timeformat.py b/plugins/util/timeformat.py deleted file mode 100644 index 185fd36..0000000 --- a/plugins/util/timeformat.py +++ /dev/null @@ -1,14 +0,0 @@ -def timeformat(seconds): - days = seconds / 86400 - seconds -= 86400 * days - hours = seconds / 3600 - seconds -= 3600 * hours - minutes = seconds / 60 - seconds -= 60 * minutes - if days != 0: - return "%sd %sh %sm %ss" % (days, hours, minutes, seconds) - elif hours == 0 and minutes != 0: - return "%sm %ss" % (minutes, seconds) - elif hours == 0 and minutes == 0: - return "%ss" % seconds - return "%sh %sm %ss" % (hours, minutes, seconds) diff --git a/plugins/youtube.py b/plugins/youtube.py deleted file mode 100755 index 78f8b02..0000000 --- a/plugins/youtube.py +++ /dev/null @@ -1,102 +0,0 @@ -import re -import time - -from util import hook, http - - -youtube_re = (r'(?:youtube.*?(?:v=|/v/)|youtu\.be/|yooouuutuuube.*?id=)' - '([-_a-zA-Z0-9]+)', re.I) - -base_url = 'http://gdata.youtube.com/feeds/api/' -api_url = base_url + 'videos/{}?v=2&alt=jsonc' -search_api_url = base_url + 'videos?v=2&alt=jsonc&max-results=1' -video_url = "http://youtu.be/%s" - - -def get_video_description(video_id): - request = http.get_json(api_url.format(video_id)) - - if request.get('error'): - return - - data = request['data'] - - out = '\x02%s\x02' % data['title'] - - if not data.get('duration'): - return out - - out += ' - length \x02' - length = data['duration'] - if length / 3600: # > 1 hour - out += '%dh ' % (length / 3600) - if length / 60: - out += '%dm ' % (length / 60 % 60) - out += "%ds\x02" % (length % 60) - - if 'rating' in data: - out += ' - rated \x02%.2f/5.0\x02 (%d)' % (data['rating'], - data['ratingCount']) - - if 'viewCount' in data: - out += ' - \x02%s\x02 views' % format(data['viewCount'], ",d") - - upload_time = time.strptime(data['uploaded'], "%Y-%m-%dT%H:%M:%S.000Z") - out += ' - \x02%s\x02 on \x02%s\x02' % (data['uploader'], - time.strftime("%Y.%m.%d", upload_time)) - - if 'contentRating' in data: - out += ' - \x034NSFW\x02' - - return out - - -def GetInHMS(seconds): - hours = seconds / 3600 - seconds -= 3600 * hours - minutes = seconds / 60 - seconds -= 60 * minutes - if hours == 0: - return "%02d:%02d" % (minutes, seconds) - return "%02d:%02d:%02d" % (hours, minutes, seconds) - - -@hook.regex(*youtube_re) -def youtube_url(match): - return get_video_description(match.group(1)) - - -@hook.command('yt') -@hook.command('y') -@hook.command -def youtube(inp): - """youtube <query> -- Returns the first YouTube search result for <query>.""" - - request = http.get_json(search_api_url, q=inp) - - if 'error' in request: - return 'error performing search' - - if request['data']['totalItems'] == 0: - return 'no results found' - - video_id = request['data']['items'][0]['id'] - - return get_video_description(video_id) + " - " + video_url % video_id - - -ytpl_re = (r'(.*:)//(www.youtube.com/playlist|youtube.com/playlist)(:[0-9]+)?(.*)', re.I) - - -@hook.regex(*ytpl_re) -def ytplaylist_url(match): - location = match.group(4).split("=")[-1] - try: - soup = http.get_soup("https://www.youtube.com/playlist?list=" + location) - except Exception: - return "\x034\x02Invalid response." - title = soup.find('title').text.split('-')[0].strip() - author = soup.find('img', {'class': 'channel-header-profile-image'})['title'] - numvideos = soup.find('ul', {'class': 'header-stats'}).findAll('li')[0].text.split(' ')[0] - views = soup.find('ul', {'class': 'header-stats'}).findAll('li')[1].text.split(' ')[0] - return u"\x02%s\x02 - \x02%s\x02 views - \x02%s\x02 videos - \x02%s\x02" % (title, views, numvideos, author) diff --git a/requirements.txt b/requirements.txt index ae3c175..ada6c3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,10 @@ +GitPython==0.3.2.RC1 +oauth2 +pygeoip +tweepy BeautifulSoup==3.2.1 lxml==3.1beta1 pyenchant==1.6.5 pydns>=2.3.6 BeautifulSoup4 +pycrypto diff --git a/restartcloudbot.sh b/restartcloudbot.sh new file mode 100755 index 0000000..25f2324 --- /dev/null +++ b/restartcloudbot.sh @@ -0,0 +1,11 @@ +#!/bin/bash +cd $(dirname $0) + +( +while true +do + sudo -u cloudbot ./cloudbot.py 2>&1 | logger -s -t $(basename $0) + sleep 10 +done +) & +disown $! diff --git a/plugins/util/__init__.py b/util/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/util/__init__.py rename to util/__init__.py diff --git a/util/bucket.py b/util/bucket.py new file mode 100644 index 0000000..3a4a699 --- /dev/null +++ b/util/bucket.py @@ -0,0 +1,40 @@ +from time import time + + +class TokenBucket(object): + """An implementation of the token bucket algorithm. + + >>> bucket = TokenBucket(80, 0.5) + >>> print bucket.consume(10) + True + >>> print bucket.consume(90) + False + """ + def __init__(self, tokens, fill_rate): + """tokens is the total tokens in the bucket. fill_rate is the + rate in tokens/second that the bucket will be refilled.""" + self.capacity = float(tokens) + self._tokens = float(tokens) + self.fill_rate = float(fill_rate) + self.timestamp = time() + + def consume(self, tokens): + """Consume tokens from the bucket. Returns True if there were + sufficient tokens otherwise False.""" + if tokens <= self.tokens: + self._tokens -= tokens + else: + return False + return True + + def refill(self): + self._tokens = self.capacity + + def get_tokens(self): + now = time() + if self._tokens < self.capacity: + delta = self.fill_rate * (now - self.timestamp) + self._tokens = min(self.capacity, self._tokens + delta) + self.timestamp = now + return self._tokens + tokens = property(get_tokens) \ No newline at end of file diff --git a/plugins/util/hook.py b/util/hook.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/util/hook.py rename to util/hook.py diff --git a/plugins/util/http.py b/util/http.py old mode 100755 new mode 100644 similarity index 94% rename from plugins/util/http.py rename to util/http.py index 4409211..7f8bd66 --- a/plugins/util/http.py +++ b/util/http.py @@ -52,7 +52,7 @@ def get_json(*args, **kwargs): def open(url, query_params=None, user_agent=None, post_data=None, - referer=None, get_method=None, cookies=False, timeout=None, **kwargs): + referer=None, get_method=None, cookies=False, timeout=None, headers=None, **kwargs): if query_params is None: query_params = {} @@ -68,6 +68,10 @@ def open(url, query_params=None, user_agent=None, post_data=None, if get_method is not None: request.get_method = lambda: get_method + if headers is not None: + for header_key, header_value in headers.iteritems(): + request.add_header(header_key, header_value) + request.add_header('User-Agent', user_agent) if referer is not None: diff --git a/plugins/util/execute.py b/util/pyexec.py similarity index 97% rename from plugins/util/execute.py rename to util/pyexec.py index 9aecd7e..4a12589 100644 --- a/plugins/util/execute.py +++ b/util/pyexec.py @@ -1,4 +1,5 @@ -import http, web +import http +import web def eval_py(code, paste_multiline=True): diff --git a/plugins/util/text.py b/util/text.py old mode 100755 new mode 100644 similarity index 98% rename from plugins/util/text.py rename to util/text.py index 9bc40f4..e3604dc --- a/plugins/util/text.py +++ b/util/text.py @@ -144,7 +144,9 @@ def truncate_words(content, length=10, suffix='...'): # from <http://stackoverflow.com/questions/250357/smart-truncate-in-python> def truncate_str(content, length=100, suffix='...'): - """Truncates a string after a certain number of chars.""" + """Truncates a string after a certain number of chars. + @rtype : str + """ if len(content) <= length: return content else: @@ -184,7 +186,7 @@ def truncate_str(content, length=100, suffix='...'): # Expression to match some_token and some_token="with spaces" (and similarly # for single-quoted strings). -split_re = re.compile(r"""((?:[^\s'"]*(?:(?:"(?:[^"\\]|\\.)*" | '(?:[""" \ +split_re = re.compile(r"""((?:[^\s'"]*(?:(?:"(?:[^"\\]|\\.)*" | '(?:[""" r"""^'\\]|\\.)*')[^\s'"]*)+) | \S+)""", re.VERBOSE) diff --git a/plugins/util/textgen.py b/util/textgen.py similarity index 75% rename from plugins/util/textgen.py rename to util/textgen.py index 24aff86..9954f17 100644 --- a/plugins/util/textgen.py +++ b/util/textgen.py @@ -10,18 +10,19 @@ class TextGenerator(object): self.default_templates = default_templates self.parts = parts self.variables = variables - print self.variables def generate_string(self, template=None): """ Generates one string using the specified templates. If no templates are specified, use a random template from the default_templates list. """ + # this is bad if self.default_templates: text = self.templates[template or random.choice(self.default_templates)] else: text = random.choice(self.templates) + # replace static variables in the template with provided values if self.variables: for key, value in self.variables.items(): text = text.replace("{%s}" % key, value) @@ -30,7 +31,12 @@ class TextGenerator(object): required_parts = TEMPLATE_RE.findall(text) for required_part in required_parts: - part = random.choice(self.parts[required_part]) + ppart = self.parts[required_part] + # check if the part is a single string or a list + if not isinstance(ppart, basestring): + part = random.choice(self.parts[required_part]) + else: + part = self.parts[required_part] text = text.replace("{%s}" % required_part, part) return text @@ -42,4 +48,4 @@ class TextGenerator(object): return strings def get_template(self, template): - return self.templates[template] \ No newline at end of file + return self.templates[template] diff --git a/util/timeformat.py b/util/timeformat.py new file mode 100644 index 0000000..7ec6abf --- /dev/null +++ b/util/timeformat.py @@ -0,0 +1,66 @@ +from util import text + +def format_time(seconds, count=3, accuracy=6, simple=False): + """ + Takes a length of time in seconds and returns a string describing that length of time. + This function has a number of optional arguments that can be combined: + + SIMPLE: displays the time in a simple format + >>> format_time(SECONDS) + 1 hour, 2 minutes and 34 seconds + >>> format_time(SECONDS, simple=True) + 1h 2m 34s + + COUNT: how many periods should be shown (default 3) + >>> format_time(SECONDS) + 147 years, 9 months and 8 weeks + >>> format_time(SECONDS, count=6) + 147 years, 9 months, 7 weeks, 18 hours, 12 minutes and 34 seconds + """ + + if simple: + periods = [ + ('c', 60 * 60 * 24 * 365 * 100), + ('de', 60 * 60 * 24 * 365 * 10), + ('y', 60 * 60 * 24 * 365), + ('m', 60 * 60 * 24 * 30), + ('d', 60 * 60 * 24), + ('h', 60 * 60), + ('m', 60), + ('s', 1) + ] + else: + periods = [ + (('century', 'centuries'), 60 * 60 * 24 * 365 * 100), + (('decade', 'decades'), 60 * 60 * 24 * 365 * 10), + (('year', 'years'), 60 * 60 * 24 * 365), + (('month', 'months'), 60 * 60 * 24 * 30), + (('day', 'days'), 60 * 60 * 24), + (('hour', 'hours'), 60 * 60), + (('minute', 'minutes'), 60), + (('second', 'seconds'), 1) + ] + + periods = periods[-accuracy:] + + strings = [] + i = 0 + for period_name, period_seconds in periods: + if i < count: + if seconds > period_seconds: + period_value, seconds = divmod(seconds, period_seconds) + i += 1 + if simple: + strings.append("{}{}".format(period_value, period_name)) + else: + if period_value == 1: + strings.append("{} {}".format(period_value, period_name[0])) + else: + strings.append("{} {}".format(period_value, period_name[1])) + else: + break + + if simple: + return " ".join(strings) + else: + return text.get_text_list(strings, "and") \ No newline at end of file diff --git a/plugins/util/timesince.py b/util/timesince.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/util/timesince.py rename to util/timesince.py diff --git a/plugins/util/urlnorm.py b/util/urlnorm.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/util/urlnorm.py rename to util/urlnorm.py diff --git a/plugins/util/web.py b/util/web.py old mode 100755 new mode 100644 similarity index 100% rename from plugins/util/web.py rename to util/web.py