summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Seguin <ixce@ed3n-n.aulo.in>2009-07-24 03:55:36 +0200
committerGuillaume Seguin <ixce@ed3n-n.aulo.in>2009-07-24 03:55:36 +0200
commit2644e3d7246e74b8048f5b5d7024a6b4d9510f42 (patch)
tree85e62363d2e734d1e999ce36ae535e330de16dc2
downloadwowarmopy-2644e3d7246e74b8048f5b5d7024a6b4d9510f42.tar.gz
wowarmopy-2644e3d7246e74b8048f5b5d7024a6b4d9510f42.tar.bz2
Initial import, only provides basic armory calendar support for nowHEADmaster
-rw-r--r--.gitignore4
-rw-r--r--setup.py15
-rw-r--r--wowarmopy/__init__.py5
-rw-r--r--wowarmopy/armory.py100
-rw-r--r--wowarmopy/auth.py131
-rw-r--r--wowarmopy/calendar.py109
-rw-r--r--wowarmopy/constants.py18
-rw-r--r--wowarmopy/gamedata.py30
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",
+ }