Initial import
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
env
|
||||
*.pyc
|
||||
build
|
||||
135
apishiv/__init__.py
Normal 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
@@ -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])
|
||||
BIN
apishiv/static/icons/cross.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
apishiv/static/icons/tick.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
apishiv/static/knife.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
apishiv/static/skills/level0.gif
Normal file
|
After Width: | Height: | Size: 73 B |
BIN
apishiv/static/skills/level0t.gif
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
apishiv/static/skills/level1.gif
Normal file
|
After Width: | Height: | Size: 76 B |
BIN
apishiv/static/skills/level1t.gif
Normal file
|
After Width: | Height: | Size: 435 B |
BIN
apishiv/static/skills/level2.gif
Normal file
|
After Width: | Height: | Size: 78 B |
BIN
apishiv/static/skills/level2t.gif
Normal file
|
After Width: | Height: | Size: 440 B |
BIN
apishiv/static/skills/level3.gif
Normal file
|
After Width: | Height: | Size: 80 B |
BIN
apishiv/static/skills/level3t.gif
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
apishiv/static/skills/level4.gif
Normal file
|
After Width: | Height: | Size: 84 B |
BIN
apishiv/static/skills/level4t.gif
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
apishiv/static/skills/level5.gif
Normal file
|
After Width: | Height: | Size: 84 B |
112
apishiv/static/style.css
Normal 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;
|
||||
}
|
||||
94
apishiv/templates/character.html
Normal 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 %}
|
||||
29
apishiv/templates/characters.html
Normal 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 %}
|
||||
|
||||
15
apishiv/templates/index.html
Normal 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 %}
|
||||
|
||||
22
apishiv/templates/layout.html
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,2 @@
|
||||
flask
|
||||
-e git+https://github.com/nikdoof/eveapi.git#egg=eveapi
|
||||