Merge branch 'develop' into refresh

This commit is contained in:
Luke Rogers 2013-11-30 16:47:18 +13:00
commit a78a86602d
7 changed files with 312 additions and 107 deletions

View file

@ -1,24 +0,0 @@
from util import http, hook
@hook.command(autohelp=False)
def bitcoin(inp, message=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'),
}
message("Current: \x0307{!s}\x0f - High: \x0307{!s}\x0f"
" - Low: \x0307{!s}\x0f - Volume: {!s}".format(ticker['buy'], ticker['high'], ticker['low'], ticker['vol']))
@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${!s}\x0f - High: \x0307${!s}\x0f"
" - Low: \x0307${!s}\x0f - Volume: {!s}LTC".format(ticker['buy'],ticker['high'],ticker['low'],ticker['vol_cur']))

57
plugins/coins.py Normal file
View file

@ -0,0 +1,57 @@
from util import http, hook
@hook.command("butt", autohelp=False)
@hook.command("btc", autohelp=False)
@hook.command(autohelp=False)
def bitcoin(inp):
"bitcoin <exchange> -- Gets current exchange rate for bitcoins from several exchanges, default is Blockchain. Supports MtGox, Bitpay, Coinbase and BitStamp."
exchanges = {
"blockchain": {
"api_url": "https://blockchain.info/ticker",
"func": lambda data: u"Blockchain // Buy: \x0307${:,.2f}\x0f - 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 - 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']))
},
"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 - Low: \x0307${:,.2f}\x0f - Volume: {:,.2f} BTC".format(float(data['last']), float(data['high']), float(data['low']), \
float(data['volume']))
}
}
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(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']))

View file

@ -1,6 +1,7 @@
from util import hook
import socket
import struct
import json
try:
import DNS
@ -10,50 +11,101 @@ 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)
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"]
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
def mcping_connect(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
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
def pack_data(d):
return struct.pack('>b', len(d)) + d
def pack_port(i):
return struct.pack('>H', i)
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))
# 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 Exception
d = ""
while len(d) < l:
d += s.recv(1024)
# Close our socket
s.close()
# Load json and return
data = json.loads(d.decode('utf8'))
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))
version = data["version"]["name"]
desc = data["description"]
max = 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)).replace("\n", u"\x0f - ")
def srvData(domain):
def mcping_legacy(host, port):
""" pings a server using the legacy (1.6 and older) protocol and returns formatted output """
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
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(mc_color_format(data[0]), data[1], 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])
sock.close()
return message
def get_srv_data(domain):
""" takes a domain and finds minecraft SRV records """
DNS.ParseResolvConf()
srv_req = DNS.Request(qtype='srv')
srv_result = srv_req.req('_minecraft._tcp.{}'.format(domain))
@ -64,34 +116,64 @@ def srvData(domain):
return data
@hook.command
def mcping(inp):
"""mcping <server>[:port] - Ping a Minecraft server to check status."""
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:
host, port = inp.split(":", 1)
try:
port = int(port)
except:
return "error: invalid port!"
return format_motd(mcping_connect(host, port))
raise Exception("The port '{}' is invalid.".format(port))
return host, port
if pydns_installed:
srv_data = get_srv_data(inp)
if srv_data:
return str(srv_data[1]), int(srv_data[0])
return inp, 25565
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
@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)
@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)
@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:
return mcping_modern(host, port)
except:
try:
return mcping_legacy(host, port)
except:
return "The server {} ({}:{}) looks offline from here.".format(inp, host, port)

92
plugins/newegg.py Normal file
View file

@ -0,0 +1,92 @@
from util import hook, http, text, web
import json
import re
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)
def format_item(item):
""" 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("\x02SHELL SHOCKER®\x02")
# join all the tags together in a comma seperated string ("tag1, tag2, tag3")
tag_text = u", ".join(tags)
# 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)
@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)
@hook.command
def newegg(inp):
"""newegg <item name> -- Searches newegg.com for <item name>"""
# form the search request
request = {
"PageNumber": 1,
"BrandId": -1,
"NValue": "",
"StoreDepaId": -1,
"NodeId": -1,
"Keyword": inp,
"IsSubCategorySearch": False,
"SubCategoryId": -1,
"Sort": "FEATURED",
"CategoryId": -1,
"IsUPCCodeSearch": False
}
# 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
item = r["ProductListItems"][0]
return format_item(item)

View file

@ -25,7 +25,7 @@ def stock(inp):
quote['PercentChange'] = 100 * change / (price - change)
ret = "%(Name)s - %(LastTradePriceOnly)s " \
ret = "\x02%(Name)s\x02 (\x02%(symbol)s\x02) - %(LastTradePriceOnly)s " \
"\x03%(color)s%(Change)s (%(PercentChange).2f%%)\x03 " \
"Day Range: %(DaysRange)s " \
"MCAP: %(MarketCapitalization)s" % quote

View file

@ -35,7 +35,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 +59,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
@ -88,7 +88,7 @@ def twitter(inp, bot=None):
text = " ".join(tweet.text.split())
if user.verified:
prefix = "+"
prefix = u"\u2713"
else:
prefix = ""
@ -126,17 +126,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 = ""

View file

@ -13,6 +13,10 @@ 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 "%d %s%s" % (num, text, "s"[num==1:])
def get_video_description(video_id):
request = http.get_json(api_url.format(video_id))
@ -34,9 +38,13 @@ def get_video_description(video_id):
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 '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 += ' - {}, {} (\x02{:.1f}\x02%)'.format(likes,
dislikes, percent)
if 'viewCount' in data:
out += ' - \x02%s\x02 views' % format(data['viewCount'], ",d")
@ -56,16 +64,6 @@ def get_video_description(video_id):
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))