commit 02c067420a1d9a695a284023a718ddcd99e84556 Author: Andrew Williams Date: Tue Sep 6 14:33:27 2011 +0100 Initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..649c7b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +env +*.pyc +build diff --git a/apishiv/__init__.py b/apishiv/__init__.py new file mode 100644 index 0000000..fcc844f --- /dev/null +++ b/apishiv/__init__.py @@ -0,0 +1,135 @@ +from datetime import datetime + +from flask import Flask, render_template, request, redirect, flash, session, url_for +from jinja2 import evalcontextfilter, Markup +from eveapi import EVEAPIConnection, Error +from cache import DbCacheHandler +from utils import mask_check + +eveapi = EVEAPIConnection(cacheHandler=DbCacheHandler()) +app = Flask('apishiv') + +API_ACCESS_TYPE = { + 0: 'Account Balance', + 1: 'Asset List', + 2: 'Calendar Event Attendees', + 3: 'Character Sheet', + 4: 'Standings/Contacts (PCs/Corps/Alliances)', + 5: 'Contact Notifications', + 6: 'Faction Warfare Stats', + 7: 'Industry Jobs', + 8: 'Kill Log', + 9: 'Mail Bodies', + 10: 'Mailing Lists', + 11: 'Mail Messages', + 12: 'Market Orders', + 13: 'Medals', + 14: 'Notifications', + 15: 'Notification Texts', + 16: 'Research Jobs', + 17: 'Skill In Training', + 18: 'Skill Queue', + 19: 'Standings (NPC)', + 20: 'Calendar Events', + 21: 'Wallet Journal', + 22: 'Wallet Transactions', + 23: 'Character Information', + 24: 'Private Character Information', + 25: 'Account Status', + 26: 'Contracts', +} + +def auth_from_session(session): + return eveapi.auth(keyID=session['keyid'], vCode=session['vcode']) + +@app.template_filter() +@evalcontextfilter +def humanize(eval_ctx, n): + if type(n) in [int, long, float]: + s, ns = str(n).split('.')[0][::-1], '' + for x in xrange(1,len(s)+1): + ns = s[x-1]+ns + if not x % 3 and len(s) > x: ns = ","+ns + if eval_ctx.autoescape: ns = Markup(ns) + return ns + +@app.template_filter() +@evalcontextfilter +def unixdate(eval_ctx, n): + ns = str(datetime.fromtimestamp(int(n)).strftime('%Y-%m-%d %H:%M:%S')) + if eval_ctx.autoescape: ns = Markup(ns) + return ns + +@app.route('/', methods=['GET', 'POST']) +def index(): + if session.get('characters', None): + return redirect(url_for('character_list')) + if request.method == 'POST': + if not request.form['keyid'] or not request.form['vcode']: + flash('Please provide a Key and verification code') + else: + auth = eveapi.auth(keyID=request.form['keyid'], vCode=request.form['vcode']) + try: + res = auth.account.ApiKeyInfo() + except Error as e: + flash('Invalid KeyID/vCode, please try another') + else: + if res: + session['keyid'] = request.form['keyid'] + session['vcode'] = request.form['vcode'] + session['accessmask'] = res.key.accessMask + session['characters'] = {} + + for c in res.key.characters: + session['characters'][c.characterID] = c.characterName + + return redirect(url_for('character_list')) + + return render_template('index.html') + +@app.route('/characters') +def character_list(): + if not session.get('characters', None): + return redirect(url_for('index')) + + auth = auth_from_session(session) + access = [] + for id, name in API_ACCESS_TYPE.items(): + access.append((name, mask_check(session['accessmask'], id))) + + charinfo = {} + if mask_check(session['accessmask'], 3): + for id, name in session['characters'].items(): + res = auth.char.CharacterSheet(characterID=id) + charinfo[id] = {'corporation': res.corporationName, 'balance': res.balance, 'sp_total': 0} + if hasattr(res, 'allianceName') and type(res.allianceName) == unicode: + charinfo[id]['alliance'] = res.allianceName + for skill in res.skills: + charinfo[id]['sp_total'] += skill.skillpoints + + if mask_check(session['accessmask'], 23): + status = auth.account.AccountStatus() + else: + status = None + + return render_template('characters.html', characters=session['characters'], access=access, charinfo=charinfo, status=status) + +@app.route('/characters/') +def character(character_id): + if mask_check(session['accessmask'], 3): + auth = auth_from_session(session) + charinfo = auth.eve.CharacterInfo(characterID=character_id) + charsheet = auth.char.CharacterSheet(characterID=character_id) + corp = eveapi.corp.CorporationSheet(corporationID=charsheet.corporationID) + skilltree = eveapi.eve.SkillTree() + skilllist = {} + for skillgroup in skilltree.skillGroups: + for skill in skillgroup.skills: + skilllist[skill['typeID']] = skill['typeName'] + return render_template('character.html', character=charsheet, corp=corp, skilllist=skilllist, charinfo=charinfo) + return redirect(url_for('characters')) + +@app.route('/clear') +def clear(): + session.clear() + return redirect(url_for('index')) diff --git a/apishiv/cache.py b/apishiv/cache.py new file mode 100644 index 0000000..364778e --- /dev/null +++ b/apishiv/cache.py @@ -0,0 +1,77 @@ +from hashlib import sha1 +import logging +import sqlite3 + +try: + SQLITE_PATH = getattr(settings, SQLITE_DB_PATH, '/tmp/apishiv.sqlite3') +except: + SQLITE_PATH = '/tmp/apishiv.sqlite3' + +class DbCacheHandler: + """ + Database backed cache handler for Entity's eveapi module + """ + + def __init__(self, conn=SQLITE_PATH): + self._conn_url = conn + + @property + def log(self): + if not hasattr(self, '_log'): + self._log = logging.getLogger(self.__class__.__name__) + return self._log + + @property + def conn(self): + if not hasattr(self, '_conn') or not self._conn: + self._conn = sqlite3.connect(self._conn_url) + self.setup() + return self._conn + + @property + def cursor(self): + if not hasattr(self, '_cursor') or not self._cursor: + self._cursor = self.conn.cursor() + return self._cursor + + def setup(self): + if not hasattr(self, '_setupchecked'): + self.cursor.execute('CREATE TABLE IF NOT EXISTS api_cache(docid TEXT PRIMARY KEY, xml TEXT, cacheduntil TEXT)') + self._setupchecked = True + + def disconnect(self): + if hasattr(self, '_cursor'): + self._cursor.close() + self._cursor = None + if hasattr(self, '_conn'): + self._conn.close() + self._conn = None + + @staticmethod + def _gen_docid(host, path, params): + return sha1("%s%s?%s" % (host, path, params)).hexdigest() + + def retrieve(self, host, path, params): + docid = self._gen_docid(host, path, params) + self.log.debug("Retrieving document: %s" % docid) + try: + self.cursor.execute("SELECT xml FROM api_cache WHERE docid = ? and datetime(cacheduntil, 'utc') >= current_timestamp", (docid,)) + res = self.cursor.fetchone() + self.disconnect() + except sqlite3.Error as e: + self.log.error("Error retrieving document: %s", e.args[0]) + else: + if res: + self.log.debug("Found %s documents for ID %s" % (len(res), docid)) + return res[0] + return None + + def store(self, host, path, params, doc, obj): + docid = self._gen_docid(host, path, params) + self.log.debug("Storing document: %s (%s)" % (docid, path)) + try: + self.cursor.execute('REPLACE INTO api_cache (docid, xml, cacheduntil) VALUES (?, ?, ?)', (docid, doc, obj.cachedUntil)) + self.conn.commit() + self.disconnect() + except sqlite3.Error as e: + self.log.error("Error storing document: %s", e.args[0]) diff --git a/apishiv/static/icons/cross.png b/apishiv/static/icons/cross.png new file mode 100644 index 0000000..3fb863d Binary files /dev/null and b/apishiv/static/icons/cross.png differ diff --git a/apishiv/static/icons/tick.png b/apishiv/static/icons/tick.png new file mode 100644 index 0000000..418fa80 Binary files /dev/null and b/apishiv/static/icons/tick.png differ diff --git a/apishiv/static/knife.png b/apishiv/static/knife.png new file mode 100644 index 0000000..42018f0 Binary files /dev/null and b/apishiv/static/knife.png differ diff --git a/apishiv/static/skills/level0.gif b/apishiv/static/skills/level0.gif new file mode 100644 index 0000000..65eb477 Binary files /dev/null and b/apishiv/static/skills/level0.gif differ diff --git a/apishiv/static/skills/level0t.gif b/apishiv/static/skills/level0t.gif new file mode 100644 index 0000000..191c872 Binary files /dev/null and b/apishiv/static/skills/level0t.gif differ diff --git a/apishiv/static/skills/level1.gif b/apishiv/static/skills/level1.gif new file mode 100644 index 0000000..bae1da4 Binary files /dev/null and b/apishiv/static/skills/level1.gif differ diff --git a/apishiv/static/skills/level1t.gif b/apishiv/static/skills/level1t.gif new file mode 100644 index 0000000..a9f823f Binary files /dev/null and b/apishiv/static/skills/level1t.gif differ diff --git a/apishiv/static/skills/level2.gif b/apishiv/static/skills/level2.gif new file mode 100644 index 0000000..627681d Binary files /dev/null and b/apishiv/static/skills/level2.gif differ diff --git a/apishiv/static/skills/level2t.gif b/apishiv/static/skills/level2t.gif new file mode 100644 index 0000000..6220340 Binary files /dev/null and b/apishiv/static/skills/level2t.gif differ diff --git a/apishiv/static/skills/level3.gif b/apishiv/static/skills/level3.gif new file mode 100644 index 0000000..7b186c9 Binary files /dev/null and b/apishiv/static/skills/level3.gif differ diff --git a/apishiv/static/skills/level3t.gif b/apishiv/static/skills/level3t.gif new file mode 100644 index 0000000..156b465 Binary files /dev/null and b/apishiv/static/skills/level3t.gif differ diff --git a/apishiv/static/skills/level4.gif b/apishiv/static/skills/level4.gif new file mode 100644 index 0000000..0f7e9ac Binary files /dev/null and b/apishiv/static/skills/level4.gif differ diff --git a/apishiv/static/skills/level4t.gif b/apishiv/static/skills/level4t.gif new file mode 100644 index 0000000..9cdd7fd Binary files /dev/null and b/apishiv/static/skills/level4t.gif differ diff --git a/apishiv/static/skills/level5.gif b/apishiv/static/skills/level5.gif new file mode 100644 index 0000000..47e3491 Binary files /dev/null and b/apishiv/static/skills/level5.gif differ diff --git a/apishiv/static/style.css b/apishiv/static/style.css new file mode 100644 index 0000000..164a991 --- /dev/null +++ b/apishiv/static/style.css @@ -0,0 +1,112 @@ +body { font-family: sans-serif; font-size:12px; background: #eee url('/static/knife.png') no-repeat -150px -50px; } +a, h1, h2 { color: #377BA8; } +h1, h2 { font-family: 'Georgia', serif; margin: 0; } +h1 { border-bottom: 2px solid #eee; } +h2 { font-size: 1.2em; } + +.page { margin: 2em auto; width: 700px; border: 5px solid #ccc; + padding: 0.8em; background: white; } +.entries { list-style: none; margin: 0; padding: 0; } +.entries li { margin: 0.8em 1.2em; } +.entries li h2 { margin-left: -1em; } +.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } +.add-entry dl { font-weight: bold; } +.metanav { text-align: right; font-size: 0.8em; padding: 0.3em; + margin-bottom: 1em; background: #fafafa; } +.flash { background: #CEE5F5; padding: 0.5em; + border: 1px solid #AACBE2; } +.error { background: #F0D6D6; padding: 0.5em; } +.footer { clear: both; } +.character_info > div { margin-bottom: 2em; } +.character_info .character_portrait, .character_info .character_corporation, .character_info .character_alliance { float: right; } +.character_info .character_corporation { clear: right; margin-left: 2em; } +.character_info .character_corporation span, .character_info .character_alliance span { font-weight: bold; display: block; text-align: center; } +.character_info .character_attributes ul { margin: .25em 2em; padding: 0; } +.character_info .character_skills table { width: 100%; } +.character_info .character_skills .intraining { background-color: #cc6600; } +.character_info .character_stats table { width: 300px; } +.character_info .character_attributes .stat { font-weight: bold; } +.character_skills th { cursor:pointer;cursor:hand; } +.toggle_off th { background-color: #000; } +.skill_controls { margin-bottom: .5em; } +.skill_controls a { + background: none repeat scroll 0 0 #377BA8; + /*set up the layout just right*/ + display: inline-block; + padding: 5px 10px 6px; + position: relative; + /*style the cursor and text of the button*/ + color: #fff !important; + text-decoration: none; + font-weight: bold; + line-height: 1; + cursor: pointer; + /*add rounded corners in mozilla and webkit*/ + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + /*Add box shadows and a bottom border with rgba colors*/ + -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + text-shadow: 0 -1px 1px rgba(0,0,0,0.25); + border-bottom: 1px solid rgba(0,0,0,0.25); + + +} + +table +{ + text-align: center; + font-weight: normal; + font-size: 11px; + color: #fff; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 15px; +} + +table td +{ + color: #000; + padding: 4px; + text-align: left; +} + +table tr { + background-color: #ddd; +} + +table tr:nth-child(odd) { + background-color: #eee; +} + +table tr:hover { + background-color: #eed; +} + +table tr#alt-application { + background-color: #FFA545; +} + +table th +{background-color: #cccccc; +color: black; +padding: 4px; +text-align: left; +font-size: 12px; +font-weight: bold;} + +table th a +{color: #fff !important; +} + +table th a:visted +{ +color: #fff !important; +} + +form span.helptext +{ +color: #666 !important; +font-style:italic; +} diff --git a/apishiv/templates/character.html b/apishiv/templates/character.html new file mode 100644 index 0000000..610c588 --- /dev/null +++ b/apishiv/templates/character.html @@ -0,0 +1,94 @@ +{% extends "layout.html" %} +{% block body %} + +
+

{{ character.name }}

+ +
+ +
+ +
+ {{ corp.ticker }} + {{ corp.name }} +
+ + {% if corp.allianceID %} +
+ {{ corp.allianceName }} + {{ corp.allianceName }} +
+ {% endif %} + + +
+

Attributes

+ +
    +
  • Race: {{ character.race }}
  • +
  • Gender: {{ character.gender }}
  • +
  • Corporation: {{ corp.corporationName }}
  • +
  • Balance: {{ character.balance|humanize }} ISK
  • + {% if charinfo.lastKnownLocation %} +
  • Last Location: {{ charinfo.lastKnownLocation }}
  • + {% endif %} + {% if charinfo.shipTypeName %} +
  • Currently Flying: {{ charinfo.shipTypeName }}
  • + {% endif %} +
+
+ +
+

Stats

+ + + + + + + + +
StatValue
Intelligence {{ character.attributes.intelligence }}
Memory{{ character.attributes.memory }}
Charisma{{ character.attributes.charisma }}
Perception{{ character.attributes.perception }}
Willpower{{ character.attributes.willpower }}
+
+ +
+

Skills

+ + + + + {% for skill in character.skills %} + + {% endfor %} +
{{ skilllist[skill.typeID] }} Level {{ skill.level }}{{ skill.skillpoints|humanize }} SP
+
+
+ + + + +{% endblock %} diff --git a/apishiv/templates/characters.html b/apishiv/templates/characters.html new file mode 100644 index 0000000..ec76dd6 --- /dev/null +++ b/apishiv/templates/characters.html @@ -0,0 +1,29 @@ +{% extends "layout.html" %} +{% block body %} + +
+

Characters

+ +
    + {% for id, name in characters.items() %} +
  • {{ name }}{{ name }}
    {{ charinfo[id]['sp_total']|humanize }} SP
    {{ charinfo[id]['balance']|humanize }} ISK
    {{ charinfo[id]['corporation'] }}{% if charinfo[id]['alliance'] %}
    {{ charinfo[id]['alliance'] }}{% endif %}
  • + {% endfor %} +
+ {% if status %} +

Account Status

+

Paid Until {{ status.paidUntil|unixdate }}
+ Created Date {{ status.createDate|unixdate }}
+ {% endif %} +

+
+

Access Permissions

+
+ + {% for name, c in access %} + + {% endfor %} +
{{ name }}
+
+ +{% endblock %} + diff --git a/apishiv/templates/index.html b/apishiv/templates/index.html new file mode 100644 index 0000000..d75bd30 --- /dev/null +++ b/apishiv/templates/index.html @@ -0,0 +1,15 @@ +{% extends "layout.html" %} +{% block body %} +

Key Input

+
+
+
Key ID: +
+
Verification Code: +
+
+
+
+ +{% endblock %} + diff --git a/apishiv/templates/layout.html b/apishiv/templates/layout.html new file mode 100644 index 0000000..1183dc3 --- /dev/null +++ b/apishiv/templates/layout.html @@ -0,0 +1,22 @@ + +API Shiv + +
+

API Shiv

+ {% if session.keyid %} + + {% endif %} +{% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} +{% endwith %} + {% block body %}{% endblock %} + +
diff --git a/apishiv/utils.py b/apishiv/utils.py new file mode 100644 index 0000000..0b66050 --- /dev/null +++ b/apishiv/utils.py @@ -0,0 +1,5 @@ +def mask_check(accessmask, bit): + """ Returns a bool indicating if the bit is set in the accessmask """ + mask = 1 << bit + return (accessmask & mask) > 0 + diff --git a/app.py b/app.py new file mode 100644 index 0000000..9240b4d --- /dev/null +++ b/app.py @@ -0,0 +1,10 @@ +import os +import logging +from hashlib import sha1 +from apishiv import app + +logging.basicConfig(level=logging.DEBUG) +app.secret_key = 'b63a3eb385a757d41ff5217f96d1bef965a6f657' + +if __name__ == '__main__': + app.run('0.0.0.0', 3333, debug=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8a23728 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask +-e git+https://github.com/nikdoof/eveapi.git#egg=eveapi