|
@@ -0,0 +1,205 @@
|
|
|
|
+#!/usr/bin/python3
|
|
|
|
+
|
|
|
|
+import base64
|
|
|
|
+import chevron
|
|
|
|
+import configparser
|
|
|
|
+import datetime
|
|
|
|
+import hashlib
|
|
|
|
+import html.parser
|
|
|
|
+import io
|
|
|
|
+import json
|
|
|
|
+import urllib
|
|
|
|
+import urllib.request
|
|
|
|
+import urllib.parse
|
|
|
|
+
|
|
|
|
+template_string = r'''
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
+<html>
|
|
|
|
+<head>
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
+ <title>TF2 Item List</title>
|
|
|
|
+
|
|
|
|
+ <style>
|
|
|
|
+ html { scroll-behavior: smooth; }
|
|
|
|
+ body {
|
|
|
|
+ font-family: Verdana,Arial,sans-serif;
|
|
|
|
+ font-size: 0.75em;
|
|
|
|
+
|
|
|
|
+ background-color: #111;
|
|
|
|
+ color: #FFF;
|
|
|
|
+ }
|
|
|
|
+ td {
|
|
|
|
+ padding: 0.25em;
|
|
|
|
+ }
|
|
|
|
+ a {
|
|
|
|
+ color: #1eb;
|
|
|
|
+ }
|
|
|
|
+ .item {
|
|
|
|
+ vertical-align: top;
|
|
|
|
+ }
|
|
|
|
+ .header {
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ position: sticky;
|
|
|
|
+ top: 0;
|
|
|
|
+ background-color: #111;
|
|
|
|
+ }
|
|
|
|
+ .footer {
|
|
|
|
+ margin-top: 2.5em;
|
|
|
|
+ text-align: center;
|
|
|
|
+ }
|
|
|
|
+ tr:nth-child(even) { background: #222; }
|
|
|
|
+ tr:hover { background: #333; }
|
|
|
|
+
|
|
|
|
+ .item-list { width: 100%; }
|
|
|
|
+
|
|
|
|
+ tr:target {
|
|
|
|
+ box-shadow: 0 0 1em .25em #abb;
|
|
|
|
+ position: relative;
|
|
|
|
+ }
|
|
|
|
+ .id a {
|
|
|
|
+ color: inherit;
|
|
|
|
+ text-decoration: none;
|
|
|
|
+ display: block;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .q_0 { color: #B2B2B2; }
|
|
|
|
+ .q_3 { color: #476291; }
|
|
|
|
+ .q_5 { color: #8650AC; }
|
|
|
|
+ .q_6 { color: #FFD700; }
|
|
|
|
+ .q_11 { color: #CF6A32; }
|
|
|
|
+ .q_15 { color: #FAFAFA; }
|
|
|
|
+ </style>
|
|
|
|
+</head>
|
|
|
|
+<body>
|
|
|
|
+ <h1>List of Team Fortress 2 items:</h1>
|
|
|
|
+
|
|
|
|
+ <p>Page generated for schema last modified {{update_time}}. <a href="" download>Click here to download this page for offline viewing.</a></p>
|
|
|
|
+
|
|
|
|
+ <table class="item-list">
|
|
|
|
+ <thead>
|
|
|
|
+ <tr class="item header">
|
|
|
|
+ <th class="id">ID</th>
|
|
|
|
+ <th class="name">Internal Name</th>
|
|
|
|
+ <th class="en_name">Localized Name</th>
|
|
|
|
+ <th class="slot">Item Slot</th>
|
|
|
|
+ <th class="cname">Entity</th>
|
|
|
|
+ </tr>
|
|
|
|
+ </thead>
|
|
|
|
+ <tbody>
|
|
|
|
+ {{#result.items}}
|
|
|
|
+ <tr class="item entry" id="{{defindex}}">
|
|
|
|
+ <td class="id"><a href="#{{defindex}}">{{defindex}}</a></td>
|
|
|
|
+ <td class="name q_{{item_quality}}">{{name}}</td>
|
|
|
|
+ <td>{{item_name}}</td>
|
|
|
|
+ <td>{{item_slot}}</td>
|
|
|
|
+ <td>{{item_class}}</td>
|
|
|
|
+ </tr>
|
|
|
|
+ {{/result.items}}
|
|
|
|
+ </tbody>
|
|
|
|
+ </table>
|
|
|
|
+ <div class="footer">
|
|
|
|
+ <div>
|
|
|
|
+ Generated with <a href="https://git.csrd.science/nosoop/py-tf2-schema-renderer">this script</a>.
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ Table sorting provided by <a href="https://github.com/tristen/tablesort">tablesort</a>.
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!--
|
|
|
|
+ - table sorting functionality
|
|
|
|
+ - the page degrades gracefully without JS, but sorting is pretty nice to have
|
|
|
|
+ -->
|
|
|
|
+ {{#inline_scripts}}
|
|
|
|
+ <script src="https://unpkg.com/tablesort@5.0.2/dist/tablesort.min.js"
|
|
|
|
+ integrity="sha384-MAoWp5qWRu0089eW0evm9gRng72sxH0daE2mkSXN8RoQ99hG/x+Wi8GxUbrgbYLp"
|
|
|
|
+ crossorigin="anonymous"></script>
|
|
|
|
+ <script src="https://unpkg.com/tablesort@5.0.2/dist/sorts/tablesort.number.min.js"
|
|
|
|
+ integrity="sha384-YPqBWGhq2JCwnOo6VsMYgE5XvUqQrzskaIGxc7CByfV6o9viRVq8uAhCs/0iPQBw"
|
|
|
|
+ crossorigin="anonymous"></script>
|
|
|
|
+ {{/inline_scripts}}
|
|
|
|
+ <script>
|
|
|
|
+ new Tablesort(document.querySelector('.item-list'));
|
|
|
|
+ window.onload = window.onhashchange = () => {
|
|
|
|
+ if (location.hash) {
|
|
|
|
+ document.getElementById(location.hash.slice(1)).scrollIntoView({
|
|
|
|
+ behavior: "auto", block: "center", inline: "center"
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ </script>
|
|
|
|
+</body>
|
|
|
|
+</html>
|
|
|
|
+'''
|
|
|
|
+
|
|
|
|
+def process_item_class(item):
|
|
|
|
+ used_by = item.get("used_by_classes", [])
|
|
|
|
+ if used_by:
|
|
|
|
+ item['used_by_classes'] = [ { "class": c.lower() } for c in used_by ]
|
|
|
|
+ return item
|
|
|
|
+
|
|
|
|
+def get_schema_items(params):
|
|
|
|
+ opts = dict(params.items())
|
|
|
|
+ opts['start'] = 0
|
|
|
|
+
|
|
|
|
+ url = "https://api.steampowered.com/IEconItems_440/GetSchemaItems/v0001/"
|
|
|
|
+ while True:
|
|
|
|
+ with urllib.request.urlopen(f"{url}?{urllib.parse.urlencode(opts)}") as r:
|
|
|
|
+ data = json.load(r)['result']
|
|
|
|
+
|
|
|
|
+ yield from data.get('items', [])
|
|
|
|
+
|
|
|
|
+ if 'next' not in data:
|
|
|
|
+ break
|
|
|
|
+ opts['start'] = data['next']
|
|
|
|
+
|
|
|
|
+def inline_scripts(text, render):
|
|
|
|
+ """
|
|
|
|
+ Helper function to parse script elements, download the scripts and inline them.
|
|
|
|
+ """
|
|
|
|
+ class ScriptElementParser(html.parser.HTMLParser):
|
|
|
|
+ def __init__(self):
|
|
|
|
+ super(ScriptElementParser, self).__init__()
|
|
|
|
+ self.scripts = []
|
|
|
|
+ self.current = []
|
|
|
|
+
|
|
|
|
+ def handle_starttag(self, tag, attrs):
|
|
|
|
+ if tag == 'script':
|
|
|
|
+ self.current.append(dict(attrs))
|
|
|
|
+
|
|
|
|
+ def handle_endtag(self, tag):
|
|
|
|
+ self.scripts.append(self.current.pop())
|
|
|
|
+
|
|
|
|
+ parser = ScriptElementParser()
|
|
|
|
+ parser.feed(text)
|
|
|
|
+
|
|
|
|
+ f = io.StringIO()
|
|
|
|
+ for script in parser.scripts:
|
|
|
|
+ with urllib.request.urlopen(script['src']) as r:
|
|
|
|
+ data = r.read()
|
|
|
|
+ if 'integrity' in script:
|
|
|
|
+ ct, hash = script['integrity'].split('-', 1)
|
|
|
|
+ h = hashlib.new(ct)
|
|
|
|
+ h.update(data)
|
|
|
|
+ if base64.b64encode(h.digest()).decode('ascii') != hash:
|
|
|
|
+ raise ValueError(f"{script['src']} failed subresource integrity check {script['integrity']} (got {base64.b64encode(h.digest())})")
|
|
|
|
+ f.write(f"<script>\n/* {script['src']} */\n{data.decode('utf8')}\n</script>\n")
|
|
|
|
+
|
|
|
|
+ return f.getvalue()
|
|
|
|
+
|
|
|
|
+def schema_mtime(params):
|
|
|
|
+ url = "https://api.steampowered.com/IEconItems_440/GetSchemaOverview/v0001/"
|
|
|
|
+ with urllib.request.urlopen(f"{url}?{urllib.parse.urlencode(dict(params.items()))}") as r:
|
|
|
|
+ return r.info().get('last-modified')
|
|
|
|
+
|
|
|
|
+config = configparser.ConfigParser()
|
|
|
|
+config.read('config.ini', encoding = "utf8")
|
|
|
|
+
|
|
|
|
+with open('items.html', 'wt', encoding = 'utf8') as f:
|
|
|
|
+ f.write(chevron.render(template_string, {
|
|
|
|
+ 'result': {
|
|
|
|
+ 'items': list(map(process_item_class, get_schema_items(config['DEFAULT'])))
|
|
|
|
+ },
|
|
|
|
+ 'update_time': schema_mtime(config['DEFAULT']),
|
|
|
|
+ 'inline_scripts': inline_scripts
|
|
|
|
+ }))
|