Merge branch 'develop' into refresh

Conflicts:
	core/irc.py
	plugins/correction.py
	plugins/history.py
	requirements.txt
This commit is contained in:
Luke Rogers 2014-03-06 10:56:05 +13:00
commit 9f029c8ceb
17 changed files with 463 additions and 210 deletions

View File

@ -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!").

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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']))

View File

@ -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 <nick> <channel> -- 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()

View File

@ -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 <server>[: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 <server>[: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 <server>[: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)

View File

@ -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 <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)

101
plugins/minecraft_user.py Normal file
View File

@ -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."

View File

@ -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 <github user> -- Gets an Open Source Report Card for <github user>"""
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)

View File

@ -38,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)
@ -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)

27
plugins/suggest.py Normal file
View File

@ -0,0 +1,27 @@
import json
from util import hook, http, text
from bs4 import BeautifulSoup
@hook.command
def suggest(inp):
"""suggest <phrase> -- 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)

View File

@ -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 <nick> <message> -- Relay <message> to <nick> when <nick> 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:

View File

@ -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

43
plugins/update.py Normal file
View File

@ -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")

View File

@ -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:

View File

@ -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: