diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .gitmodules | 0 | ||||
-rw-r--r-- | config.py | 29 | ||||
-rw-r--r-- | config.yaml.example | 12 | ||||
-rw-r--r-- | db/__init__.py | 15 | ||||
-rwxr-xr-x | db/importer.py | 42 | ||||
-rw-r--r-- | db/schema.sql | 59 | ||||
-rwxr-xr-x | importer.py | 66 | ||||
-rwxr-xr-x | web/server.py | 57 | ||||
-rw-r--r-- | web/static/css/base.less | 37 | ||||
-rw-r--r-- | web/templates/base.html | 18 | ||||
-rw-r--r-- | web/templates/home.html | 30 |
12 files changed, 367 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..078d2ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +config.yaml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/.gitmodules diff --git a/config.py b/config.py new file mode 100644 index 0000000..1f9be4d --- /dev/null +++ b/config.py @@ -0,0 +1,29 @@ +import yaml + +class Config: + def __init__(self, cdict): + attrs = set(self.attrs) # copy and "unfreeze" + for k, v in cdict.items(): + attrs.remove(k) # check if the key is allowed, mark it as present + setattr(self, k, v) + if len(attrs) != 0: + raise KeyError('missing required bot config keys: %s' % attrs) + +class WebConfig(Config): + attrs = frozenset([ + 'port', + 'host', + 'cookie_secret', + ]) + +class DBConfig(Config): + attrs = frozenset([ + 'host', + 'user', + 'password', + 'database', + ]) + +__doc = yaml.load(open('config.yaml', 'r')) +web = WebConfig(__doc['web']) +db = DBConfig(__doc['db']) diff --git a/config.yaml.example b/config.yaml.example new file mode 100644 index 0000000..37a6e17 --- /dev/null +++ b/config.yaml.example @@ -0,0 +1,12 @@ +web: + port: 8888 + host: 'localhost:8888' + cookie_secret: 'dis is super sekrit' + +db: + host: localhost + user: ykill + password: ykill + database: ykill + +# vim: set et ft=yaml: diff --git a/db/__init__.py b/db/__init__.py new file mode 100644 index 0000000..ead5232 --- /dev/null +++ b/db/__init__.py @@ -0,0 +1,15 @@ +import oursql + +from config import db as dbconfig + +conn = oursql.connect(host=dbconfig.host, db=dbconfig.database, user=dbconfig.user, passwd=dbconfig.password, autoreconnect=True) + +def ykill_cursor(): + return conn.cursor(oursql.DictCursor) + +def execute(cursor, sql, *values): + cursor.execute(sql, values) + +def query(cursor, sql, *values): + execute(cursor, sql, *values) + return cursor.fetchall() diff --git a/db/importer.py b/db/importer.py new file mode 100755 index 0000000..39f97ce --- /dev/null +++ b/db/importer.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +import json +from pprint import pprint + +import __init__ as db + +with db.ykill_cursor() as c: + db.execute(c, 'INSERT INTO kills (kill_id, solar_system_id, kill_time, moon_id) VALUES(?, ?, ?, ?)', + kill['killID'], kill['solarSystemID'], kill['killTime'], kill['moonID']) + + victim = kill['victim'] + parambatch = [( + kill['killID'], 1, victim['characterID'], victim['characterName'], victim['shipTypeID'], + victim['allianceID'], victim['allianceName'], victim['corporationID'], victim['corporationName'], victim['factionID'], victim['factionName'], + victim['damageTaken'], None, None, None, + )] + for attacker in kill['attackers']: + parambatch.append(( + kill['killID'], 0, attacker['characterID'], attacker['characterName'], attacker['shipTypeID'], + attacker['allianceID'], attacker['allianceName'], attacker['corporationID'], attacker['corporationName'], attacker['factionID'], attacker['factionName'], + attacker['damageDone'], attacker['finalBlow'], attacker['securityStatus'], attacker['weaponTypeID'], + )) + c.executemany(''' + INSERT INTO characters ( + kill_id, victim, character_id, character_name, ship_type_id, + alliance_id, alliance_name, corporation_id, corporation_name, faction_id, faction_name, + damage, final_blow, security_status, weapon_type_id + ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', parambatch + ) + + parambatch = [] + for item in kill['items']: + parambatch.append((kill['killID'], item['typeID'], item['flag'], + item['qtyDropped'], item['qtyDestroyed'], item['singleton'])) + c.executemany(''' + INSERT INTO items ( + kill_id, type_id, flag, dropped, destroyed, singleton + ) VALUES(?, ?, ?, ?, ?, ?) + ''', parambatch + ) diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..7c601ce --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,59 @@ +DROP TABLE IF EXISTS `characters`; +DROP TABLE IF EXISTS `items`; +DROP TABLE IF EXISTS `item_costs`; +DROP TABLE IF EXISTS `kill_costs`; +DROP TABLE IF EXISTS `kills`; + +CREATE TABLE `kills` ( + `kill_id` int unsigned NOT NULL, + `solar_system_id` int NOT NULL, + `kill_time` datetime NOT NULL, + `moon_id` int unsigned NOT NULL, + PRIMARY KEY (`kill_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `characters` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `kill_id` int unsigned NOT NULL, + `victim` tinyint(1) NOT NULL, + `character_id` int unsigned NOT NULL, + `character_name` varchar(64) NOT NULL, + `ship_type_id` int NOT NULL, + `alliance_id` int unsigned NOT NULL, + `alliance_name` varchar(64) NOT NULL, + `corporation_id` int unsigned NOT NULL, + `corporation_name` varchar(64) NOT NULL, + `faction_id` int NOT NULL, + `faction_name` varchar(64) NOT NULL, + `damage` int DEFAULT NULL, + `final_blow` tinyint(1) DEFAULT NULL, + `security_status` float DEFAULT NULL, + `weapon_type_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_char_km` FOREIGN KEY (`kill_id`) REFERENCES `kills` (`kill_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `items` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `kill_id` int unsigned NOT NULL, + `type_id` int NOT NULL, + `flag` tinyint(3) unsigned NOT NULL, + `dropped` int unsigned NOT NULL, + `destroyed` int unsigned NOT NULL, + `singleton` tinyint(4) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_item_km` FOREIGN KEY (`kill_id`) REFERENCES `kills` (`kill_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `item_costs` ( + `type_id` int NOT NULL UNIQUE, + `cost` bigint unsigned NOT NULL, + PRIMARY KEY (`type_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `kill_costs` ( + `kill_id` int unsigned NOT NULL UNIQUE, + `cost` bigint unsigned NOT NULL, + PRIMARY KEY (`kill_id`), + CONSTRAINT `fk_kill_cost_km` FOREIGN KEY (`kill_id`) REFERENCES `kills` (`kill_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/importer.py b/importer.py new file mode 100755 index 0000000..44c95dc --- /dev/null +++ b/importer.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +from http.client import HTTPSConnection +import json +import operator +from pprint import pprint + +import db + +def insert_kill(c, kill): + db.execute(c, 'INSERT INTO kills (kill_id, solar_system_id, kill_time, moon_id) VALUES(?, ?, ?, ?)', + kill['killID'], kill['solarSystemID'], kill['killTime'], kill['moonID']) + + victim = kill['victim'] + parambatch = [( + kill['killID'], 1, victim['characterID'], victim['characterName'], victim['shipTypeID'], + victim['allianceID'], victim['allianceName'], victim['corporationID'], victim['corporationName'], victim['factionID'], victim['factionName'], + victim['damageTaken'], None, None, None, + )] + for attacker in kill['attackers']: + parambatch.append(( + kill['killID'], 0, attacker['characterID'], attacker['characterName'], attacker['shipTypeID'], + attacker['allianceID'], attacker['allianceName'], attacker['corporationID'], attacker['corporationName'], attacker['factionID'], attacker['factionName'], + attacker['damageDone'], attacker['finalBlow'], attacker['securityStatus'], attacker['weaponTypeID'], + )) + c.executemany(''' + INSERT INTO characters ( + kill_id, victim, character_id, character_name, ship_type_id, + alliance_id, alliance_name, corporation_id, corporation_name, faction_id, faction_name, + damage, final_blow, security_status, weapon_type_id + ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', parambatch + ) + + parambatch = [] + for item in kill['items']: + parambatch.append((kill['killID'], item['typeID'], item['flag'], + item['qtyDropped'], item['qtyDestroyed'], item['singleton'])) + c.executemany(''' + INSERT INTO items ( + kill_id, type_id, flag, dropped, destroyed, singleton + ) VALUES(?, ?, ?, ?, ?, ?) + ''', parambatch + ) + + db.conn.commit() + +def main(): + conn = HTTPSConnection('zkillboard.com', timeout=10) + with db.ykill_cursor() as c: + groups = db.query(c, 'SELECT groupID FROM eve.invGroups WHERE categoryID = ?', 6) + groups = list(map(operator.itemgetter('groupID'), groups)) + for i in range(0, len(groups), 10): + query_groups = map(str, groups[i:i+10]) + conn.request('GET', '/api/groupID/{}/api-only'.format(','.join(query_groups))) + response = conn.getresponse() + if response.status != 200: + raise Exception('got {} {} from zkb'.format(response.status, response.reason)) + kills = json.loads(response.read().decode('utf-8')) + response.close() + print('inserting', len(kills), 'kills') + for kill in kills: + insert_kill(c, kill) + +if __name__ == '__main__': + main() diff --git a/web/server.py b/web/server.py new file mode 100755 index 0000000..01475c1 --- /dev/null +++ b/web/server.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import json +from lesscss import lessc +import tornado.gen +import tornado.httpclient +import tornado.ioloop +import tornado.web +import operator +import os + +import config + +class BaseHandler(tornado.web.RequestHandler): + def render(self, *args, **kwargs): + kwargs['host'] = config.web.host + return super(BaseHandler, self).render(*args, **kwargs) + + def render_string(self, *args, **kwargs): + s = super(BaseHandler, self).render_string(*args, **kwargs) + return s.replace(b'\n', b'') # this is like Django's {% spaceless %} + +class MainHandler(BaseHandler): + @tornado.web.asynchronous + @tornado.gen.coroutine + def get(self): + http_client = tornado.httpclient.AsyncHTTPClient() + kills_url = 'https://zkillboard.com/api/kills/corporationID/98182803/limit/1' + losses_url = 'https://zkillboard.com/api/losses/corporationID/98182803/limit/1' + kills_res, losses_res = yield [http_client.fetch(kills_url), http_client.fetch(losses_url)] + kills = json.loads(kills_res.body.decode('utf-8')) + losses = json.loads(losses_res.body.decode('utf-8')) + kills = sorted(kills + losses, key=operator.itemgetter('killTime'), reverse=True) + self.render('home.html', kills=kills) + + +class CSSHandler(tornado.web.RequestHandler): + def get(self, css_path): + css_path = os.path.join(os.path.dirname(__file__), 'static', css_path) + '.less' + with open(css_path, 'r') as f: + self.set_header('Content-Type', 'text/css') + css = lessc.compile(f.read()) + self.write(css) + +if __name__ == '__main__': + tornado.web.Application( + handlers=[ + (r'/', MainHandler), + (r"/(css/.+)\.css", CSSHandler), + ], + template_path=os.path.join(os.path.dirname(__file__), 'templates'), + static_path=os.path.join(os.path.dirname(__file__), 'static'), + cookie_secret=config.web.cookie_secret, + debug=True, + ).listen(config.web.port) + print('Listening on :%d' % config.web.port) + tornado.ioloop.IOLoop.instance().start() diff --git a/web/static/css/base.less b/web/static/css/base.less new file mode 100644 index 0000000..714d5ab --- /dev/null +++ b/web/static/css/base.less @@ -0,0 +1,37 @@ +* { + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + height: 100%; + background: #000; + color: #ddd; + font-family: sans-serif; +} + +a { + text-decoration: none; + color: #dde; +} + +form { + input { + background: #fff; + border: 1px solid #cdd; + padding: 5px 10px; + } +} + +.clear { + clear: both; +} + +#wrapper { + width: 900px; + margin: 50px auto; + background: #111; +} diff --git a/web/templates/base.html b/web/templates/base.html new file mode 100644 index 0000000..afd3c68 --- /dev/null +++ b/web/templates/base.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="/css/base.css" /> + <script src="//ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools.js"></script> +</head> +<body> + <div id="wrapper"> + <div id="topbar"> + <div id="title"></div> + <div id="nav"> + </div> + </div> + + {% block main %}{% end %} + </div> +</body> +</html> diff --git a/web/templates/home.html b/web/templates/home.html new file mode 100644 index 0000000..6d4f69c --- /dev/null +++ b/web/templates/home.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block main %} + +<div class="kill_list"> + {% for kill in kills %} + <a href="/kill/{{ kill['killID'] }}"> + {{ kill['killTime'] }} + {{ kill['solarSystemID'] }} + <img src="//image.eveonline.com/Type/{{ kill['victim']['shipTypeID'] }}_64.png"> + <img src="//image.eveonline.com/Character/{{ kill['victim']['characterID'] }}_64.jpg"> + <img src="//image.eveonline.com/Corporation/{{ kill['victim']['corporationID'] }}_64.png"> + <img src="//image.eveonline.com/Alliance/{{ kill['victim']['allianceID'] }}_64.png"> + {{ kill['victim']['characterName'] }} + {{ kill['victim']['corporationName'] }} + {{ kill['victim']['allianceName'] }} + {% for attacker in kill['attackers'] %} + {% if attacker['finalBlow'] %} + {% set killer = attacker %} + {% break %} + {% end %} + {% end %} + {{ killer['characterName'] }} + {{ killer['corporationName'] }} + {{ killer['allianceName'] }} + </a> + {% end %} +</div> + +{% end %} |