summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules0
-rw-r--r--config.py29
-rw-r--r--config.yaml.example12
-rw-r--r--db/__init__.py15
-rwxr-xr-xdb/importer.py42
-rw-r--r--db/schema.sql59
-rwxr-xr-ximporter.py66
-rwxr-xr-xweb/server.py57
-rw-r--r--web/static/css/base.less37
-rw-r--r--web/templates/base.html18
-rw-r--r--web/templates/home.html30
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 %}