Initial import

This commit is contained in:
2011-09-06 14:33:27 +01:00
commit 02c067420a
25 changed files with 504 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
env
*.pyc
build

135
apishiv/__init__.py Normal file
View File

@@ -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/<character_id>')
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'))

77
apishiv/cache.py Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
apishiv/static/knife.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

112
apishiv/static/style.css Normal file
View File

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

View File

@@ -0,0 +1,94 @@
{% extends "layout.html" %}
{% block body %}
<div class="character_info">
<h2>{{ character.name }}</h2>
<div class="character_portrait">
<img src="https://image.eveonline.com/Character/{{ character.characterID }}_256.jpg" />
</div>
<div class="character_corporation">
<span>{{ corp.ticker }}</span>
<img src="https://image.eveonline.com/Corporation/{{ character.corporationID }}_64.png" alt="{{ corp.name }}"/>
</div>
{% if corp.allianceID %}
<div class="character_alliance">
<span>{{ corp.allianceName }}</span>
<img src="https://image.eveonline.com/Alliance/{{ corp.allianceID }}_64.png" alt="{{ corp.allianceName }}"/>
</div>
{% endif %}
<div class="character_attributes">
<h2>Attributes</h2>
<ul>
<li><span class="stat">Race:</span> <span class="value">{{ character.race }}</span></li>
<li><span class="stat">Gender:</span> <span class="value">{{ character.gender }}</span></li>
<li><span class="stat">Corporation:</span> <span class="value">{{ corp.corporationName }}</span></li>
<li><span class="stat">Balance:</span> <span class="value">{{ character.balance|humanize }} ISK</span></li>
{% if charinfo.lastKnownLocation %}
<li><span class="stat">Last Location:</span> <span class="value"><a href="http://evemaps.dotlan.net/system/{{ charinfo.lastKnownLocation }}">{{ charinfo.lastKnownLocation }}</a></span></li>
{% endif %}
{% if charinfo.shipTypeName %}
<li><span class="stat">Currently Flying:</span> <span class="value">{{ charinfo.shipTypeName }}</span></li>
{% endif %}
</ul>
</div>
<div class="character_stats">
<h2>Stats</h2>
<table>
<tr><th>Stat</th><th>Value</th></tr>
<tr><td class="stat">Intelligence</td><td> {{ character.attributes.intelligence }}</td></tr>
<tr><td class="stat">Memory</td><td>{{ character.attributes.memory }}</td></tr>
<tr><td class="stat">Charisma</td><td>{{ character.attributes.charisma }}</td></tr>
<tr><td class="stat">Perception</td><td>{{ character.attributes.perception }}</td></tr>
<tr><td class="stat">Willpower</td><td>{{ character.attributes.willpower }}</td></tr>
</table>
</div>
<div class="character_skills">
<h2>Skills</h2>
<div class="skill_controls">
<a href=#" id="openAll">Open All</a>
<a href=#" id="collapseAll">Collapse All</a>
</div>
<table>
{% for skill in character.skills %}
<tr class="child"><td>{{ skilllist[skill.typeID] }} </td><td><img src="/static/skills/level{{ skill.level }}.gif" alt="Level {{ skill.level }}" /></td><td>{{ skill.skillpoints|humanize }} SP</td></tr>
{% endfor %}
</table>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function(){
$(".skill_heading")
.css("cursor", "pointer")
.attr("title", "Click to expand/collapse")
.click(function(){
$('tr[parent$=' + $(this).attr('id') + ']').toggle();
});
$("#openAll").bind("click", function(e){
e.preventDefault();
$(".child").show();
});
$("#collapseAll").bind("click", function(e){
e.preventDefault();
$(".child").hide();
});
$('tr[parent]').hide();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends "layout.html" %}
{% block body %}
<div style="float:left;">
<h2>Characters</h2>
<ul>
{% for id, name in characters.items() %}
<li style="list-style-type: none;"><table><tr><td><a href="{{ url_for('character', character_id=id) }}"><img border="2" src="http://image.eveonline.com/Character/{{ id }}_128.jpg" alt='{{ name }}'/></a></td><td valign="top" width="150px"><b>{{ name }}</b><br/>{{ charinfo[id]['sp_total']|humanize }} SP<br/>{{ charinfo[id]['balance']|humanize }} ISK<br/>{{ charinfo[id]['corporation'] }}{% if charinfo[id]['alliance'] %}<br/>{{ charinfo[id]['alliance'] }}{% endif %}</td></tr></table></li>
{% endfor %}
</ul>
{% if status %}
<h2>Account Status</h2>
<p><b>Paid Until</b> {{ status.paidUntil|unixdate }}<br/>
<b>Created Date</b> {{ status.createDate|unixdate }}<br/>
{% endif %}
</div>
<div style="float: right;">
<h2>Access Permissions</h2>
<br/>
<table>
{% for name, c in access %}
<tr><td>{{ name }}</td><td><img width="16" src="{% if c %}{{ url_for('static', filename='icons/cross.png') }}{% else %}{{ url_for('static', filename='icons/tick.png') }}{% endif %}"/></td>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends "layout.html" %}
{% block body %}
<h2>Key Input</h2>
<form action="{{ url_for('index') }}" method=post>
<dl>
<dt>Key ID:
<dd><input type='text' name='keyid'>
<dt>Verification Code:
<dd><input type='text' name='vcode'>
<dd><input type='submit' value='Scrape'>
</dl>
</form>
{% endblock %}

View File

@@ -0,0 +1,22 @@
<!doctype html>
<title>API Shiv</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
<h1>API Shiv</h1>
{% if session.keyid %}
<div class=metanav>
<a href="{{ url_for('character_list') }}">Character List</a>
<a href="{{ url_for('clear') }}">Restart Session</a>
</div>
{% endif %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class=flash>{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
<div class=footer>
</div>
</div>

5
apishiv/utils.py Normal file
View File

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

10
app.py Normal file
View File

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

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
flask
-e git+https://github.com/nikdoof/eveapi.git#egg=eveapi