diff options
author | Guillaume Seguin <ixce@ed3n-n.aulo.in> | 2009-07-24 03:55:36 +0200 |
---|---|---|
committer | Guillaume Seguin <ixce@ed3n-n.aulo.in> | 2009-07-24 03:55:36 +0200 |
commit | 2644e3d7246e74b8048f5b5d7024a6b4d9510f42 (patch) | |
tree | 85e62363d2e734d1e999ce36ae535e330de16dc2 | |
download | wowarmopy-2644e3d7246e74b8048f5b5d7024a6b4d9510f42.tar.gz wowarmopy-2644e3d7246e74b8048f5b5d7024a6b4d9510f42.tar.bz2 |
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | setup.py | 15 | ||||
-rw-r--r-- | wowarmopy/__init__.py | 5 | ||||
-rw-r--r-- | wowarmopy/armory.py | 100 | ||||
-rw-r--r-- | wowarmopy/auth.py | 131 | ||||
-rw-r--r-- | wowarmopy/calendar.py | 109 | ||||
-rw-r--r-- | wowarmopy/constants.py | 18 | ||||
-rw-r--r-- | wowarmopy/gamedata.py | 30 |
8 files changed, 412 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72715c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.swp +*.swo +*.pyc +build/ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bd33c43 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +#! /usr/bin/env python + +import sys, os, glob +from distutils.core import setup + +setup ( + name = "WoWArmoPy", + version = "0.0.1", + description = "Python interface to World of Warcraft Armory", + author = "Guillaume Seguin", + author_email = "guillaume@segu.in", + url = "http://guillaume.segu.in/git", + license = "GPL", + packages = ["wowarmopy"], + ) diff --git a/wowarmopy/__init__.py b/wowarmopy/__init__.py new file mode 100644 index 0000000..58f9a6d --- /dev/null +++ b/wowarmopy/__init__.py @@ -0,0 +1,5 @@ +__all__ = [ + 'Armory', +] + +from armory import Armory diff --git a/wowarmopy/armory.py b/wowarmopy/armory.py new file mode 100644 index 0000000..49c5c04 --- /dev/null +++ b/wowarmopy/armory.py @@ -0,0 +1,100 @@ +import re +import httplib, urllib, urlparse + +import simplejson + +import auth +import constants +import calendar + +class Armory (object): + + auth = None + realm = None + character_name = None + guild_name = None + locale = None + + def __init__ (self, realm, character_name = None, guild_name = None, locale = "us"): + """Initializes Armory object""" + self.realm = realm + self.character_name = character_name + self.guild_name = guild_name + self.locale = locale + + def login (self, username, password): + self.auth = auth.ArmoryAuth (self) + self.auth.login (username, password) + + def refresh_login (self): + assert self.auth != None + self.auth.refresh_login () + + def get_base_url (self, login = False, secure = False): + if secure: + url = "https://" + else: + url = "http://" + if self.locale == "us": + url += "www." + else: + url += self.locale + "." + if login: + url += constants.login_base_url + else: + url+= constants.armory_base_url + return url + + def get_calendar (self, **kwargs): + url = self.get_base_url (secure = True) + constants.calendar_user_url + data = self.get_json (url, **kwargs) + return calendar.Calendar (self, data) + + def get_calendar_event_details (self, event_id): + url = self.get_base_url (secure = True) + constants.calendar_detail_url + data = self.get_json (url, event = event_id) + return data + + def get_json (self, url, **kwargs): + raw = self.get_file (url, **kwargs) + match = re.search ("^\w+\((.+)\);$", raw); + if not match: + print "DEBUG (raw data) :", raw + raise RuntimeError, "Failed to fetch json data" + json = simplejson.loads (match.groups ()[0]) + return json + + def get_params (self, **kwargs): + keys = { + "character_name": 'n', + "realm": 'r', + "guild_name": 'gn', + "calendar_type": 'type', + "month": 'month', + "year": 'year', + "event": 'e', + } + params = {} + for key in keys: + if key in kwargs: + params[keys[key]] = kwargs[key] + elif hasattr (self, key): + params[keys[key]] = getattr (self, key) + return params + + def get_file (self, url, **kwargs): + parsed_url = urlparse.urlsplit (url) + scheme, domain, path, query, fragment = parsed_url + query = query + "&" if query != '' else "?" + query += urllib.urlencode (self.get_params (**kwargs)) + if scheme == "https": + h = httplib.HTTPSConnection (domain) + else: + h = httplib.HTTPConnection (domain) + headers = {"User-Agent": "Mozilla/5.0 Gecko/20070219 Firefox/2.0.0.2", # ensure returns XML + "Cookie": "cookieMenu=all; cookies=true; "} + if self.auth: + headers["Cookie"] += self.auth.get_cookie () + h.request ("GET", "%s?%s#%s" % (path, query, fragment), None, headers) + r = h.getresponse () + return r.read () diff --git a/wowarmopy/auth.py b/wowarmopy/auth.py new file mode 100644 index 0000000..97a6981 --- /dev/null +++ b/wowarmopy/auth.py @@ -0,0 +1,131 @@ +import re +import httplib, urllib, urlparse + +import constants + +def login_http (url, cookies = None, data = None, post = False): + parsed_url = urlparse.urlsplit (url) + scheme, domain, path, query, fragment = parsed_url + if scheme == "https": + h = httplib.HTTPSConnection (domain) + else: + h = httplib.HTTPConnection (domain) + headers = {"User-Agent": "Mozilla/5.0 Gecko/20070219 Firefox/2.0.0.2", # ensure returns XML + "Cookie": "cookieMenu=all; cookies=true; "} + if data: + headers["Content-type"] = "application/x-www-form-urlencoded" + if cookies: + headers["Cookie"] += " ".join (["%s=%s;" % cookie for cookie in cookies.items ()]) + if post: + params = urllib.urlencode (data) + method = "POST" + else: + params = None + method = "GET" + + h.request (method, "%s?%s#%s" % (path, query, fragment), params, headers) + return h.getresponse () + +def login_final_bounce (url): + # Let's bounce to our page that will give us our short term cookie, URL has Kerbrose style ticket. + finalstage = login_http (url) + + # Did we get a 200? + if finalstage.status == 200: + # Get the short term cookie at last + short_cookie = None + match = re.search ("%s=(.*?);" % constants.temporary_cookie, + finalstage.getheader ("set-cookie")) + if match: + short_cookie = match.groups ()[0] + return short_cookie + + # Finally we didn't get 200? + raise RuntimeError, "Login broken" + +class ArmoryAuth (object): + + short_cookie = None + long_cookie = None + + def __init__ (self, armory): + self.armory = armory + + def login (self, username, password): + # Create the base URL we will be POSTing to. + url = self.armory.get_base_url (login = True, secure = True) \ + + constants.login_url \ + + "?app=armory" + + # Ensure we add the correct bounce point. + if self.armory.locale == "us": + url += "&ref=http://www.wowarmory.com/index.xml" + else: + url += "&ref=http://%s.wowarmory.com/index.xml" % self.armory.locale + + # Ensure we have no final stage. + redirectstage = None + + # Post the first stage + stage1 = login_http (url, None, { 'accountName': username, 'password': password }, True) + + # Check what happened. + if stage1.status == 200: + raise RuntimeError, "Could not login, authenticators unsupported" + elif stage1.status == 302: + redirectstage = stage1 + + # We should have been redirected by now. + if not redirectstage: + raise RuntimeError, "Login broken" + + # Time to obtain our next URL and our long term cookie. + long_cookie = None + #print redirectstage.getheaders () + match = re.search ("%s=(.*?);" % constants.persistant_cookie, + redirectstage.getheader ("set-cookie")) + if match: + long_cookie = match.groups ()[0] + + # Let's bounce to our page that will give us our short term cookie, URL has Kerbrose style ticket. + short_cookie = login_final_bounce (redirectstage.getheader ("location")) + + self.short_cookie = short_cookie + self.long_cookie = long_cookie + + #print self.short_cookie, self.long_cookie + + def get_cookie (self): + if self.short_cookie: + return "%s=%s; " % (constants.temporary_cookie, self.short_cookie) + else: + return "" + + # FIXME : doesn't work, meh + def refresh_login (self): + # Create the base URL we will be POSTing to. + url = self.armory.get_base_url (login = True, secure = True) \ + + constants.login_url \ + + "?app=armory" + + # Ensure we add the correct bounce point. + if self.armory.locale == "us": + url += "&ref=http://www.wowarmory.com/index.xml" + else: + url += "&ref=http://%s.wowarmory.com/index.xml" % self.armory.locale + + # All we need to do is goto the armory login page passing our long life cookie, we should get 302 instantly. + stage1 = login_http (url, { constants.persistant_cookie: self.long_cookie }) + + # Let's see + if stage1.status == 200: + # It's no good, our cookie doesn't work anymore. + raise RuntimeError, "Invalid login details" + elif stage1.status == 302: + # Let's bounce to our page that will give us our short term cookie, URL has Kerbrose style ticket. + short_cookie = login_final_bounce (stage1.getheader ("location")) + self.short_cookie = short_cookie + + # Finally we didn't get 302 or 200? + raise RuntimeError, "Login broken" + diff --git a/wowarmopy/calendar.py b/wowarmopy/calendar.py new file mode 100644 index 0000000..ae0348c --- /dev/null +++ b/wowarmopy/calendar.py @@ -0,0 +1,109 @@ +import datetime, time +import gamedata +import constants + +class EventInvitee (object): + + id = None + name = None + classId = None + raceId = None + genderId = None + raw_status = None + moderator = None + + def __init__ (self, json): + self.load_from_json (json) + + def load_from_json (self, json): + self.id = json['id'] + self.name = json['invitee'] + self.classId = json['classId'] + self.raceId = json['raceId'] + self.genderId = json['genderId'] + self.raw_status = json['status'] + self.moderator = json['moderator'] + + def get_class_name (self): + return gamedata.classes[self.classId] + class_name = property (get_class_name) + + def get_race_name (self): + return gamedata.racees[self.raceId] + race_name = property (get_race_name) + + def get_portrait_image_url (self): + return constants.images_base + constants.images_portrait_url_pattern \ + % (self.genderId, self.raceId, self.classId) + portrait_image = property (get_portrait_image_url) + + def get_race_image_url (self): + return constants.images_base + constants.images_race_url_pattern \ + % (self.raceId, self.genderId) + race_image = property (get_race_image_url) + + def get_class_image_url (self): + return constants.images_base + constants.images_class_url_pattern \ + % self.classId + class_image = property (get_class_image_url) + + + def __repr__ (self): + return "[%s %s %s %s : %s]" % \ + (gamedata.genders[self.genderId], gamedata.races[self.raceId], + gamedata.classes[self.classId], self.name.encode ("utf-8"), + self.raw_status.encode ("utf-8")) + +class Event (object): + + id = None + armory = None + date = None + summary = None + description = None + invitees = None + location = None + icon = None + + def __init__ (self, armory, json): + self.armory = armory + time.sleep (3) + self.load_from_json (json) + + def load_from_json (self, json): + self.id = json['id'] + details = self.armory.get_calendar_event_details (self.id) + self.date = datetime.datetime.fromtimestamp (details['start'] / 1000) + self.summary = details['summary'] + self.description = details['description'] + if "location" in details: + self.location = details['location'] + self.icon = details['icon'] + self.invitees = [EventInvitee (invitee) for invitee in details['invites']] + + def get_icon_url (self): + return constants.images_base + constants.images_eventicon_url_pattern % self.icon + + def __repr__ (self): + return "[%s : %s]" % (self.date.strftime ("%d/%m/%Y %H:%M"), self.summary.encode ("utf-8")) + +class Calendar (object): + + armory = None + updated = None + month = None + year = None + events = None + + def __init__ (self, armory, json): + self.armory = armory + self.load_from_json (json) + + def load_from_json (self, json): + self.year = json['year'] + self.month = json['month'] + self.updated = datetime.datetime.fromtimestamp (json['now'] / 1000) + self.events = [Event (self.armory, event) for event in json['events']] + + def __repr__ (self): + return "[Calendar for %d/%d]" % (self.month, self.year) diff --git a/wowarmopy/constants.py b/wowarmopy/constants.py new file mode 100644 index 0000000..aca3167 --- /dev/null +++ b/wowarmopy/constants.py @@ -0,0 +1,18 @@ +armory_base_url = 'wowarmory.com/' +login_base_url = 'battle.net/' +login_url = 'login/login.xml' + +persistant_cookie = 'BAC-tassadar' +temporary_cookie = 'JSESSIONID' + +search_url = 'search.xml' + +calendar_user_url = 'vault/calendar/month-user.json' +calendar_world_url = 'vault/calendar/month-world.json' +calendar_detail_url = 'vault/calendar/detail.json' + +images_base = "http://armory.worldofwarcraft.com/" +images_portrait_url_pattern = "images/portraits/wow-80/%d-%d-%d.gid" +images_race_url_pattern = "images/icons/race/%d-%d.gif" +images_class_url_pattern = "images/icons/class/%d.gif" +images_eventicon_url_pattern = "wow-icons/_images/calendar/75/%s.png" diff --git a/wowarmopy/gamedata.py b/wowarmopy/gamedata.py new file mode 100644 index 0000000..40ac215 --- /dev/null +++ b/wowarmopy/gamedata.py @@ -0,0 +1,30 @@ +genders = { + 0: "Male", + 1: "Female" + } + +races = { + 1: "Human", + 2: "Orc", + 3: "Dwarf", + 4: "Nightelf", + 5: "Undead", + 6: "Tauren", + 7: "Gnome", + 8: "Troll", + 10: "Bloodelf", + 11: "Draenei", + } + +classes = { + 1: "Warrior", + 2: "Paladin", + 3: "Hunter", + 4: "Rogue", + 5: "Priest", + 6: "Death-Knight", + 7: "Shaman", + 8: "Mage", + 9: "Warlock", + 11: "Druid", + } |