tf_attr_list.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/python3
  2. import base64
  3. import chevron
  4. import configparser
  5. import hashlib
  6. import html.parser
  7. import io
  8. import json
  9. import urllib
  10. import urllib.request
  11. import urllib.parse
  12. template_string = r'''
  13. <!DOCTYPE html>
  14. <html>
  15. <head>
  16. <meta charset="UTF-8">
  17. <title>TF2 Attribute List</title>
  18. <style>
  19. html { scroll-behavior: smooth; }
  20. body {
  21. font-family: Verdana,Arial,sans-serif;
  22. font-size: 0.75em;
  23. background-color: #111;
  24. color: #FFF;
  25. }
  26. td { padding: 0.25em; }
  27. a { color: #1eb; }
  28. .attribute {
  29. vertical-align: top;
  30. }
  31. .header {
  32. font-weight: bold;
  33. position: sticky;
  34. top: 0;
  35. background-color: #111;
  36. }
  37. .value_type {
  38. overflow: hidden;
  39. }
  40. .footer {
  41. margin-top: 2.5em;
  42. text-align: center;
  43. }
  44. .positive { color: #28F; }
  45. .negative { color: #F00; }
  46. tr:nth-child(even) { background: #222; }
  47. tr:hover { background: #333; }
  48. tr:target {
  49. box-shadow: 0 0 1em .25em #abb;
  50. position: relative;
  51. }
  52. .id a {
  53. color: inherit;
  54. text-decoration: none;
  55. display: block;
  56. }
  57. </style>
  58. </head>
  59. <body>
  60. <h1>List of Team Fortress 2 attributes:</h1>
  61. <p>Page generated for schema dated {{update_time}}. <a href="" download>Click here to download this page for offline viewing.</a></p>
  62. <table class="attribute-list">
  63. <thead>
  64. <tr class="attribute header">
  65. <th class="id">ID</th>
  66. <th class="name">Name</th>
  67. <th class="description">Description</th>
  68. <th class="is_hidden">Hidden</th>
  69. <th class="value_type">Value Type</th>
  70. <th class="class_name">Class</th>
  71. </tr>
  72. </thead>
  73. <tbody>
  74. {{#result.attributes}}
  75. <tr class="attribute entry" id="{{defindex}}">
  76. <td class="id"><a href="#{{defindex}}">{{defindex}}</a></td>
  77. <td class="name {{effect_type}}">{{name}}</td>
  78. <td class="description">{{description_string}}</td>
  79. <td class="is_hidden">{{#hidden}}true{{/hidden}}{{^hidden}}false{{/hidden}}</td>
  80. <td class="value_type">{{description_format}}</td>
  81. <td class="class_name">{{attribute_class}}</td>
  82. </tr>
  83. {{/result.attributes}}
  84. </tbody>
  85. </table>
  86. <div class="footer">
  87. <div>Generated with <a href="https://git.csrd.science/nosoop/py-tf2-schema-renderer">this script</a>.</div>
  88. <div>Table sorting provided by <a href="https://github.com/tristen/tablesort">tablesort</a>.</div>
  89. </div>
  90. <!--
  91. - table sorting functionality
  92. - the page degrades gracefully without JS, but sorting is pretty nice to have
  93. -->
  94. {{#inline_scripts}}
  95. <script src="https://unpkg.com/tablesort@5.0.2/dist/tablesort.min.js"
  96. integrity="sha384-MAoWp5qWRu0089eW0evm9gRng72sxH0daE2mkSXN8RoQ99hG/x+Wi8GxUbrgbYLp"
  97. crossorigin="anonymous"></script>
  98. <script src="https://unpkg.com/tablesort@5.0.2/dist/sorts/tablesort.number.min.js"
  99. integrity="sha384-YPqBWGhq2JCwnOo6VsMYgE5XvUqQrzskaIGxc7CByfV6o9viRVq8uAhCs/0iPQBw"
  100. crossorigin="anonymous"></script>
  101. {{/inline_scripts}}
  102. <script>
  103. new Tablesort(document.querySelector('.attribute-list'));
  104. window.onload = window.onhashchange = () => {
  105. if (location.hash) {
  106. document.getElementById(location.hash.slice(1)).scrollIntoView({
  107. behavior: "auto", block: "center", inline: "center"
  108. });
  109. }
  110. };
  111. </script>
  112. </body>
  113. </html>
  114. '''
  115. def get_schema_overview(params):
  116. opts = dict(params.items())
  117. url = "https://api.steampowered.com/IEconItems_440/GetSchemaOverview/v0001/"
  118. with urllib.request.urlopen(f"{url}?{urllib.parse.urlencode(opts)}") as r:
  119. return { **json.load(r), 'update_time': r.info().get('last-modified') }
  120. def inline_scripts(text, render):
  121. """
  122. Helper function to parse script elements, download the scripts and inline them.
  123. """
  124. class ScriptElementParser(html.parser.HTMLParser):
  125. def __init__(self):
  126. super(ScriptElementParser, self).__init__()
  127. self.scripts = []
  128. self.current = []
  129. def handle_starttag(self, tag, attrs):
  130. if tag == 'script':
  131. self.current.append(dict(attrs))
  132. def handle_endtag(self, tag):
  133. self.scripts.append(self.current.pop())
  134. parser = ScriptElementParser()
  135. parser.feed(text)
  136. f = io.StringIO()
  137. for script in parser.scripts:
  138. with urllib.request.urlopen(script['src']) as r:
  139. data = r.read()
  140. if 'integrity' in script:
  141. ct, hash = script['integrity'].split('-', 1)
  142. h = hashlib.new(ct)
  143. h.update(data)
  144. if base64.b64encode(h.digest()).decode('ascii') != hash:
  145. raise ValueError(f"{script['src']} failed subresource integrity check {script['integrity']} (got {base64.b64encode(h.digest())})")
  146. f.write(f"<script>\n/* {script['src']} */\n{data.decode('utf8')}\n</script>\n")
  147. return f.getvalue()
  148. config = configparser.ConfigParser()
  149. config.read('config.ini', encoding = "utf8")
  150. # tf_schema_overview.json is the WebAPI call to IEconItems_440/GetSchemaOverview
  151. with open('attributes.html', 'wt', encoding = 'utf8') as f:
  152. f.write(chevron.render(template_string, {
  153. **get_schema_overview(config['DEFAULT']),
  154. 'inline_scripts': inline_scripts,
  155. }))