diff --git a/README.md b/README.md index cff76d3..cfb69ac 100644 --- 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!"). diff --git a/core/irc.py b/core/irc.py index bd0d4d7..09febb6 100644 --- a/core/irc.py +++ b/core/irc.py @@ -213,6 +213,7 @@ class IRC(object): self.logger = logger self.nick = nick self.vars = {} + self.history = {} self.parsed_queue = Queue.Queue() # responses from the server are placed here # format: [rawline, prefix, command, params, diff --git a/plugins/admin.py b/plugins/admin.py index 858d191..4e665b9 100644 --- a/plugins/admin.py +++ b/plugins/admin.py @@ -193,11 +193,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) @@ -213,11 +213,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/correction.py b/plugins/correction.py index 112ecf8..9e66371 100644 --- a/plugins/correction.py +++ b/plugins/correction.py @@ -1,6 +1,8 @@ from util import hook +import re +<<<<<<< HEAD @hook.regex(r'^(s|S)/.*/.*/\S*$') def correction(inp, message=None, input=None, notice=None, db=None): splitinput = input.msg.split("/") @@ -24,8 +26,38 @@ def correction(inp, message=None, input=None, notice=None, db=None): message(u"Correction, <{}> {}".format(nick, msg.replace(find, "\x02" + replace + "\x02"))) else: notice(u"{} can't be found in your last message".format(find)) +======= +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() +>>>>>>> develop else: - if nick == input.nick: - notice(u"I haven't seen you say anything here yet") + 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: - notice(u"I haven't seen {} say anything here yet".format(nick)) + continue + + return u"Did not find {} in any recent messages.".format(find) + diff --git a/plugins/cryptocoins.py b/plugins/cryptocoins.py index fa51db7..42d5945 100644 --- a/plugins/cryptocoins.py +++ b/plugins/cryptocoins.py @@ -8,15 +8,6 @@ exchanges = { "func": lambda data: u"Blockchain // Buy: \x0307${:,.2f}\x0f -" u" Sell: \x0307${:,.2f}\x0f".format(data["USD"]["buy"], data["USD"]["sell"]) }, - "mtgox": { - "api_url": "https://mtgox.com/api/1/BTCUSD/ticker", - "func": lambda data: u"MtGox // Current: \x0307{}\x0f - High: \x0307{}\x0f - Low: \x0307{}\x0f" - u" - Best Ask: \x0307{}\x0f - Volume: {}".format(data['return']['last']['display'], - data['return']['high']['display'], - data['return']['low']['display'], - data['return']['buy']['display'], - data['return']['vol']['display']) - }, "coinbase": { "api_url": "https://coinbase.com/api/v1/prices/spot_rate", "func": lambda data: u"Coinbase // Current: \x0307${:,.2f}\x0f".format(float(data['amount'])) diff --git a/plugins/seen.py b/plugins/history.py similarity index 61% rename from plugins/seen.py rename to plugins/history.py index d8004f4..06283ae 100644 --- a/plugins/seen.py +++ b/plugins/history.py @@ -1,30 +1,25 @@ -"""seen.py: written by sklnd in about two beers July 2009""" - +from collections import deque +from util import hook, timesince import time import re -from util import hook, timesince +db_ready = [] -db_ready = False - - -def db_init(db): - """check to see that our db has the the seen table and return a connection.""" +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 not 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 = True + db_ready.append(conn_name) -@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 +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(:name,:time,:quote,:chan,:host)", {'name': input.nick.lower(), @@ -35,8 +30,38 @@ def seen_sieve(paraml, input=None, db=None): 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." + + @hook.command -def seen(inp, nick='', chan='', db=None, input=None): +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(): @@ -48,8 +73,7 @@ def seen(inp, nick='', chan='', db=None, input=None): 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) + db_init(db, conn.name) last_seen = db.execute("select name, time, quote from seen_user where name" " like :name and chan = :chan", {'name': inp, 'chan': chan}).fetchone() diff --git a/plugins/minecraft_ping.py b/plugins/minecraft_ping.py index ea254dc..978ca19 100644 --- a/plugins/minecraft_ping.py +++ b/plugins/minecraft_ping.py @@ -1,31 +1,46 @@ -# TODO: Rewrite this whole mess import socket import struct import json +import traceback from util import hook try: import DNS - # Please remember to install the dependency 'pydns' - pydns_installed = True + has_dns = True except ImportError: - pydns_installed = False + has_dns = False -mccolors = [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"] +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')] -def mc_color_format(motd): - for colorcombo in mccolors: - colorarray = colorcombo.split(",") - motd = motd.replace(colorarray[1], colorarray[0]) - motd = motd.replace(u"\xa7k", "") - return motd +## 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): @@ -38,81 +53,116 @@ def unpack_varint(s): if not b & 0x80: return d +pack_data = lambda d: struct.pack('>b', len(d)) + d +pack_port = lambda i: struct.pack('>H', i) -def pack_data(d): - return struct.pack('>b', len(d)) + d - - -def pack_port(i): - return struct.pack('>H', i) +## DATA FUNCTIONS def mcping_modern(host, port): - """ pings a server using the modern (1.7+) protocol and returns formatted output """ - # connect to the server - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((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) - # send handshake + status request - s.send(pack_data("\x00\x00" + pack_data(host.encode('utf8')) + pack_port(port) + "\x01")) - s.send(pack_data("\x00")) + try: + s.connect((host, port)) + except socket.gaierror: + raise PingError("Invalid hostname") + except socket.timeout: + raise PingError("Request timed out") - # read response - unpack_varint(s) # Packet length - unpack_varint(s) # Packet ID - l = unpack_varint(s) # String length + # send handshake + status request + s.send(pack_data("\x00\x00" + pack_data(host.encode('utf8')) + pack_port(port) + "\x01")) + s.send(pack_data("\x00")) - if not l > 1: - raise Exception + # read response + unpack_varint(s) # Packet length + unpack_varint(s) # Packet ID + l = unpack_varint(s) # String length - d = "" - while len(d) < l: - d += s.recv(1024) + if not l > 1: + raise PingError("Invalid response") - # Close our socket - s.close() + 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"] - if data["description"].get("text", None): + try: desc = u" ".join(data["description"]["text"].split()) - else: + except TypeError: desc = u" ".join(data["description"].split()) max_players = data["players"]["max"] online = data["players"]["online"] except Exception as e: - return "Invalid data: {}; error: {}".format(data, e) - return mc_color_format(u"{}\x0f - {}\x0f - {}/{} players.".format(desc, version, online, - max_players)).replace("\n", u"\x0f - ") + # 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 formatted output """ + """ pings a server using the legacy (1.6 and older) protocol and returns data """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((host, port)) - sock.send('\xfe\x01') - response = sock.recv(1) - print response + + 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': - return "Server gave invalid response: " + repr(response) + 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') - message = u"{} - {}/{} players".format(mc_color_format(data[0]), data[1], data[2]) + 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 - message = u"{} \x0f- {} - {}/{} players".format(mc_color_format(data[3]), - mc_color_format(data[2]), data[4], data[5]) + 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 message + return output -def get_srv_data(domain): +## FORMATTING/PARSING FUNCTIONS + +def check_srv(domain): """ takes a domain and finds minecraft SRV records """ DNS.DiscoverNameServers() srv_req = DNS.Request(qtype='srv') @@ -128,61 +178,55 @@ 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) - except: - raise Exception("The port '{}' is invalid.".format(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 pydns_installed: - srv_data = get_srv_data(inp) + 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 -@hook.command -@hook.command("mcp6") -def mcping6(inp): - """mcping6 [:port] - Ping a Minecraft server version 1.6 or smaller to check status.""" - #try: - host, port = parse_input(inp) - #except Exception as ex: - # return ex.args[0] - try: - return mcping_legacy(host, port) - except: - return "The 1.6 server {}:{} looks offline from here.".format(host, port) +def format_colors(motd): + for original, replacement in mc_colors: + motd = motd.replace(original, replacement) + motd = motd.replace(u"\xa7k", "") + return motd -@hook.command -@hook.command("mcp7") -def mcping7(inp): - """mcping [:port] - Ping a Minecraft server version 1.7 or greater to check status.""" - try: - host, port = parse_input(inp) - except Exception as ex: - return ex.args[0] - try: - return mcping_modern(host, port) - except: - return "The 1.7 server {}:{} looks offline from here.".format(host, port) +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 [:port] - Ping a Minecraft server to check status.""" - # try: - host, port = parse_input(inp) - #except Exception as e: - # return e.args[0] -# + try: + host, port = parse_input(inp) + except ParseError as e: + return "Could not parse input ({})".format(e) try: - return mcping_modern(host, port) - except: + data = mcping_modern(host, port) + except PingError: try: - return mcping_legacy(host, port) - except: - return "The server {} ({}:{}) looks offline from here.".format(inp, host, port) + 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/plugins/minecraft_status.py index 8bb7e07..4ca67d3 100644 --- a/plugins/minecraft_status.py +++ b/plugins/minecraft_status.py @@ -18,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 -- Checks if 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/plugins/minecraft_user.py b/plugins/minecraft_user.py new file mode 100644 index 0000000..4026994 --- /dev/null +++ b/plugins/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 -- Gets information about the Minecraft user .""" + 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/plugins/osrc.py b/plugins/osrc.py index 7df2085..99cba1c 100644 --- a/plugins/osrc.py +++ b/plugins/osrc.py @@ -3,7 +3,6 @@ from bs4 import BeautifulSoup from util import hook, http, web -api_url = "http://osrc.dfm.io/{}/stats" user_url = "http://osrc.dfm.io/{}" @@ -12,18 +11,19 @@ def osrc(inp): """osrc -- Gets an Open Source Report Card for """ user_nick = inp.strip() - url = api_url.format(user_nick) + url = user_url.format(user_nick) try: - response = http.get_json(url) + soup = http.get_soup(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] + report = soup.find("div", {"id": "description"}).find("p").get_text() - response["short_url"] = web.try_isgd(user_url.format(user_nick)) + # Split and join to remove all the excess whitespace, slice the + # string to remove the trailing full stop. + report = " ".join(report.split())[:-1] - return "{nick} is a {lang_user}. {nick} is a {hacker_type} " \ - "who seems to {work_time} - {short_url}".format(**response) + short_url = web.try_isgd(url) + + return "{} - {}".format(report, short_url) diff --git a/plugins/steam_calc.py b/plugins/steam_calc.py index 30b656e..6684eba 100644 --- a/plugins/steam_calc.py +++ b/plugins/steam_calc.py @@ -38,12 +38,14 @@ def steamcalc(inp, reply=None): """steamcalc [currency] - Gets value of steam account and total hours played. Uses steamcommunity.com/id/. """ - 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) @@ -51,6 +53,18 @@ 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 reader = unicode_dictreader(csv_data) diff --git a/plugins/suggest.py b/plugins/suggest.py new file mode 100644 index 0000000..0e02210 --- /dev/null +++ b/plugins/suggest.py @@ -0,0 +1,27 @@ +import json + +from util import hook, http, text +from bs4 import BeautifulSoup + + +@hook.command +def suggest(inp): + """suggest -- Gets suggested phrases for a google search""" + + 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] + suggestions = [suggestion[0] for suggestion in suggestions] + + if not suggestions: + return 'no suggestions found' + + out = u", ".join(suggestions) + + # defuckify text + soup = BeautifulSoup(out) + out = soup.get_text() + + return text.truncate_str(out, 200) diff --git a/plugins/tell.py b/plugins/tell.py index 38e80ed..2310cbd 100644 --- a/plugins/tell.py +++ b/plugins/tell.py @@ -6,19 +6,18 @@ import re from util import hook, timesince -db_ready = False +db_ready = [] -def db_init(db): - """check to see that our db has the tell table and return a dbection.""" + +def db_init(db, conn): + """Check that our db has the tell table, create it if not.""" global db_ready - if not 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 = True - - return db + db_ready.append(conn.name) def get_tells(db, user_to): @@ -28,11 +27,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) @@ -52,10 +51,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): +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) @@ -74,7 +73,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 -- Relay to when is around.""" query = inp.split(' ', 1) @@ -99,10 +98,10 @@ def tell(inp, nick='', chan='', db=None, input=None, notice=None): 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/tvdb.py b/plugins/tvdb.py index 55914ca..b5fa12f 100644 --- a/plugins/tvdb.py +++ b/plugins/tvdb.py @@ -1,28 +1,10 @@ -""" -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 base_url = "http://thetvdb.com/api/" - - -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)) +api_key = "469B73127CA0C411" def get_episodes_for_series(series_name, api_key): @@ -30,7 +12,7 @@ def get_episodes_for_series(series_name, api_key): # http://thetvdb.com/wiki/index.php/API:GetSeries try: query = http.get_xml(base_url + 'GetSeries.php', seriesname=series_name) - except URLError: + except http.URLError: res["error"] = "error contacting thetvdb.com" return res @@ -43,9 +25,8 @@ def get_episodes_for_series(series_name, 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 diff --git a/plugins/update.py b/plugins/update.py new file mode 100644 index 0000000..67e55f2 --- /dev/null +++ b/plugins/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/util/http.py b/util/http.py index 4409211..7f8bd66 100644 --- a/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/util/text.py b/util/text.py index 3fd679a..e3604dc 100644 --- a/util/text.py +++ b/util/text.py @@ -144,7 +144,9 @@ def truncate_words(content, length=10, suffix='...'): # from 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: