From 1585395e7e530665c565b88fea15cbea68d3a88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gl=C3=BCpker?= Date: Sun, 1 Oct 2017 14:50:19 +0200 Subject: Initial public release --- .gitignore | 4 + Caching.py | 41 +++++++++ Database.py | 34 +++++++ QueryServer.py | 176 +++++++++++++++++++++++++++++++++++ SteamAPI.py | 209 ++++++++++++++++++++++++++++++++++++++++++ config/lobby.json.example | 30 ++++++ config/secrets.json.example | 4 + config/secrets.md | 7 ++ main.py | 173 ++++++++++++++++++++++++++++++++++ static/hide.js | 17 ++++ static/style.css | 198 +++++++++++++++++++++++++++++++++++++++ static/vivagraph.min.js | 2 + templates/error.jinja | 16 ++++ templates/lobby_html.jinja | 100 ++++++++++++++++++++ templates/premades_html.jinja | 102 +++++++++++++++++++++ 15 files changed, 1113 insertions(+) create mode 100644 .gitignore create mode 100644 Caching.py create mode 100755 Database.py create mode 100644 QueryServer.py create mode 100644 SteamAPI.py create mode 100644 config/lobby.json.example create mode 100644 config/secrets.json.example create mode 100644 config/secrets.md create mode 100755 main.py create mode 100644 static/hide.js create mode 100644 static/style.css create mode 100644 static/vivagraph.min.js create mode 100644 templates/error.jinja create mode 100644 templates/lobby_html.jinja create mode 100644 templates/premades_html.jinja diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6265d09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +cache +docs +__pycache__ +config/*.json diff --git a/Caching.py b/Caching.py new file mode 100644 index 0000000..a048af2 --- /dev/null +++ b/Caching.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import os +import time +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +CACHE_DIR = os.path.join(THIS_DIR, 'cache') + +class Caching(): + + def readCache(profile, steamid, timetolife): + filepath = os.path.join(CACHE_DIR, profile) + filename = str(steamid)+'.tmp' + complete = os.path.join(filepath, filename) + try: + fileinfo = os.stat(complete) + except FileNotFoundError: + return False + if fileinfo.st_mtime < (time.time() - timetolife): + return False + + with open(complete, 'rt') as cachefile: + content = cachefile.read() + + return content + + def writeCache(profile, steamid, content): + filepath = os.path.join(CACHE_DIR, profile) + filename = str(steamid)+'.tmp' + complete = os.path.join(filepath, filename) + try: + os.makedirs(filepath, exist_ok=True) + with open(complete, 'wt') as cachefile: + cachefile.write(content) + except: + print('Could not create cache file/dir.') + return False + return True + +if __name__ == "__main__": + # TODO(andre): Maybe run tests here? + print('This is a module.') diff --git a/Database.py b/Database.py new file mode 100755 index 0000000..001f2b2 --- /dev/null +++ b/Database.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +# import os +# import time +import sqlite3 +# database = sqlite3.connect('data.db') + +class Database(): + def __init__(self): + self._connection = sqlite3.connect('data.db') + self._cursor = self._connection.cursor() + self._cursor. + # print(self._database) + print(self._cursor) + + def initDB(self): + print('If db not exists...generate schema here') + + def login(self, username, password): + print('try to login') + return 'userobject' or false + # Use flask session management? + # What crypt/hash to use? + + def getMyTracked(self, userid): + return 'list of tracked users' + + def msg(self, msg): + print(msg) + +if __name__ == "__main__": + database = Database() + database.initDB() + database.msg('2') diff --git a/QueryServer.py b/QueryServer.py new file mode 100644 index 0000000..669e7a3 --- /dev/null +++ b/QueryServer.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 + +# Time to live for caching. 0 to disable caching +CACHE_TTL = 36000 #15 + +sourcequery = [ + 50, # HL Opposing Force + 240, # CS:S + 320, # HL2 DM + 440, # TF2 + 550, # L4D2 + 630, # AlienSwarm + 730, # CS:GO + 4000, # Garrys Mod + 107410, # ARMA III + # 221100, # DayZ + 252490, # Rust + 282440, # Quake Live + 328070, # Reflex + 346110 # ARK: Survival Evolved + ] +sourcequeryinc = [ + 107410, # ARMA III + 346110 # ARK: Survival Evolved + ] + +import json +import socket +from SteamAPI import SteamAPI +if CACHE_TTL > 0: + from Caching import Caching + +class QueryServer(): + + def QueryServer(ip, port, gameid): + """Query a supported server. + + Args: + ip: IP of the gameserver + port: Port of the gameserver + gameid: Steam gameid of running game + Returns: + dict() with parsed server values""" + + port = int(port) + gameid = int(gameid) + + if CACHE_TTL > 0: + cache = Caching.readCache('server', '%s-%s-%s' % (str(gameid), ip, str(port)), CACHE_TTL) + + if cache: + print('From cache') + return json.loads(cache) + + socket.setdefaulttimeout(5) + if int(gameid) in sourcequery: + print('Querying server...') + serverdata = QueryServer._querySourceServer(ip, port, gameid) + + if CACHE_TTL > 0: + serialized = json.dumps(serverdata) + Caching.writeCache('server', '%s-%s-%s' % (str(gameid), ip, str(port)), serialized) + + return serverdata + + return None + + def _querySourceServer(ip, port, gameid): + """Query servers using the source query protocol. + Some servers listen for these queries on an higher port.""" + if gameid in sourcequeryinc: + port += 1 + + print(ip, port, gameid) + conn = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + try: + conn.connect( (ip, port) ) + conn.sendall( b'\xFF\xFF\xFF\xFF\x54' + b'Source Engine Query' + b'\x00' ) + message = conn.recv(4096) + except: + print('Exception in SourceQuery connection') + return None + finally: + conn.close() + + data = dict() + + # [0]-[3] -> 4*\xFF + data['header'] = chr(message[4]) + data['protocol'] = chr(message[5]) + message = message[6:] + + # Servername + nullterm = message.index(b'\0') + data['name'] = message[:nullterm] + message = message[nullterm+1:] + + # Mapname + nullterm = message.index(b'\0') + data['map'] = message[:nullterm] + message = message[nullterm+1:] + + # Folder + nullterm = message.index(b'\0') + data['folder'] = message[:nullterm] + message = message[nullterm+1:] + + # Game + nullterm = message.index(b'\0') + data['game'] = message[:nullterm] + message = message[nullterm+1:] + + data['gameid'] = (message[1] << 8) + message[0] + data['players'] = message[2] + data['playersmax'] = message[3] + data['bots'] = message[4] + data['servertype'] = chr(message[5]) # dedicated/local/proxy(tv) + data['enviroment'] = chr(message[6]) # windows/linux/mac + data['visibility'] = message[7] # 0public 1private + data['vac'] = message[8] + message = message[9:] + + # Game + nullterm = message.index(b'\0') + data['gameversion'] = message[:nullterm] + message = message[nullterm+1:] + + extradataflag = message[0] + message = message[1:] + + if extradataflag & 0x80: + data['port'] = (message[1] << 8) + message[0] + message = message[2:] + + if extradataflag & 0x10: + data['steamid'] = (message[7] << 56) + (message[6] << 48) + \ + (message[5] << 40) + (message[4] << 32) + \ + (message[3] << 24) + (message[2] << 16) + \ + (message[1] << 8) + (message[0]) + message = message[8:] + + if extradataflag & 0x40: + data['sourcetvport'] = (message[1] << 8) + message[0] + message = message[2:] + nullterm = message.index(b'\0') + data['sourcetvname'] = message[:nullterm] + message = message[nullterm+1:] + + if extradataflag & 0x20: + nullterm = message.index(b'\0') + data['keywords'] = message[:nullterm] + message = message[nullterm+1:] + + if extradataflag & 0x01: + data['sgameid'] = (message[7] << 56) + (message[6] << 48) + \ + (message[5] << 40) + (message[4] << 32) + \ + (message[3] << 24) + (message[2] << 16) + \ + (message[1] << 8) + (message[0]) + message = message[8:] + + # Everything is type bytes, so convert things to utf8? + for key, value in data.items(): + if isinstance(value, bytes): + try: + value = value.decode('utf-8', errors='replace') + data[key] = value + except UnicodeDecodeError: + data[key] = "UTF8 Error" + pass + return data + +if __name__ == "__main__": + # TODO(andre): Maybe run our tests here? + print('This is a module.') + diff --git a/SteamAPI.py b/SteamAPI.py new file mode 100644 index 0000000..580b375 --- /dev/null +++ b/SteamAPI.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +from concurrent.futures import ThreadPoolExecutor +from urllib.error import HTTPError +from urllib.error import URLError +from urllib.request import urlopen +import json +from Caching import Caching + +################################################## +## Settings +################################################## +# Directory for a local cache & time to live for cache items in seconds +CACHE = True + +################################################## + +class SteamAPI(): + def __init__(self, token): + self.token = token + + def getProfiles(self, steamids): + """Get steam profiles. + + Args: + steamids: Steamids to fetch profiles for + Returns: + dict() with str(steamid) as keys + """ + profile = dict() + steamids_copy = [str(steamid) for steamid in steamids] + + if CACHE: + cachedids = [] + for steamid in steamids_copy: + cache = Caching.readCache('profile', steamid, 15) + if cache: + cachedids.append(steamid) + profile[steamid] = json.loads(cache) + for steamid in cachedids: + steamids_copy.remove(steamid) + + if len(steamids_copy): + steamidlist = ','.join([str(x) for x in steamids_copy]) + url = 'https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?format=json&key=%s&steamids=%s' % (self.token, steamidlist) + # print(url) + response = urlopen(url) + if response.status != 200: + print('Isn\'t this an HTTPError?') + return [] + data = response.read().decode('utf-8') + jsondata = json.loads(data) + + for player in jsondata['response']['players']: + currentid = player['steamid'] + profile[currentid] = player + + if CACHE: + # save newly loaded profiles + for steamid in steamids_copy: + Caching.writeCache('profile', steamid, json.dumps(profile[steamid])) + + return profile + + def getMultipleFriends(self, steamids): + executor = ThreadPoolExecutor(max_workers=10) + # Ask steam about friends for each + results = dict() + for steamid in steamids: + results[steamid] = executor.submit(self.getFriends, steamid) + + profiles = executor.submit(self.getProfiles, steamids).result() + + for steamid in steamids: + profiles[steamid]['friends'] = results[steamid].result() + + return profiles + + def getFriends(self, steamid): + """Fetch steam friends for given steamid. + + Args: + steamid: Steamid, whose friendslist to fetch + Returns: + List of steamids. TODO(andre): We lose additional information here. + """ + if CACHE: + cache = Caching.readCache('friends', steamid, 15*60) + if cache: + return json.loads(cache) + + url = 'https://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=%s&steamid=%s&relationship=friend' % (self.token, str(steamid)) + # print(url) + try: + response = urlopen(url) + data = response.read().decode('utf-8') + jsondata = json.loads(data) + except HTTPError: + # f.e. profile is private + jsondata = None + + friends = [] + if jsondata and 'friendslist' in jsondata and 'friends' in jsondata['friendslist']: + friends = [friend['steamid'] for friend in jsondata['friendslist']['friends']] + if CACHE: + Caching.writeCache('friends', steamid, json.dumps(friends)) + + return friends + + def getGameSchema(self, gameid): + """Fetch info about a game. + + Args: + gameid: Appid of the game + Returns: + gameName, gameVersion, availableGameStats (Achievements/Stats) + """ + if CACHE: + cache = Caching.readCache('gameschema', gameid, 7*24*60*60) + if cache: + jsondata = json.loads(cache) + return jsondata['game'] + + url = 'http://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/?key=%s&appid=%s&format=json' % (self.token, str(gameid)) + print(url) + try: + response = urlopen(url) + data = response.read().decode('utf-8') + jsondata = json.loads(data) + except HTTPError as e: + # f.e. profile is private + print('Fetch failed.', e.code, e.reason) + jsondata = None + + if 'game' in jsondata: + if CACHE: + Caching.writeCache('gameschema', gameid, json.dumps(jsondata)) + return jsondata['game'] + else: + print('No game in json:', jsondata) + return None + + def getGames(self, steamid): + """Fetch a list of games a person possesses. + + Args: + steamid: Steamid, whose gamelist to fetch + Returns: + Tuple with (number of games, gameinfo [appid, name, playtime_2weeks, playtime_forever, icons]) + """ + if CACHE: + cache = Caching.readCache('games', steamid, 60*60) + if cache: + jsondata = json.loads(cache) + return ( jsondata['response']['game_count'], jsondata['response']['games'] ) + + url = 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=%s&steamid=%s&include_appinfo=1&format=json' % (self.token, str(steamid)) + print(url) + try: + response = urlopen(url) + data = response.read().decode('utf-8') + jsondata = json.loads(data) + except HTTPError: + # f.e. profile is private + jsondata = None + + if 'response' in jsondata and 'games' in jsondata['response']: + if CACHE: + Caching.writeCache('games', steamid, json.dumps(jsondata)) + return ( jsondata['response']['game_count'], jsondata['response']['games'] ) + return None + + def getPlayerGameStats(self, steamid, gameid): + """Fetch the list of achievements a person achieved in a game + + Args: + steamid: Steamid of the particular user + gameid: Appid of the game we want to check + Returns: + Tuple with (number of games, gameinfo [appid, name, playtime_2weeks, playtime_forever, icons]) + """ + if CACHE: + cache = Caching.readCache('playergamestats', '%s-%s' % (str(steamid), str(gameid)), 24*60*60) + if cache: + jsondata = json.loads(cache) + return jsondata + else: + print('nocache') + + url = 'http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?key=%s&steamid=%s&appid=%s' % (self.token, str(steamid), str(gameid)) + print(url) + try: + response = urlopen(url) + data = response.read().decode('utf-8') + jsondata = json.loads(data) + except HTTPError: + # f.e. profile is private + jsondata = None + + if 'playerstats' in jsondata: + if CACHE: + cache = Caching.writeCache('playergamestats', '%s-%s' % (str(steamid), str(gameid)), json.dumps(jsondata)) + return jsondata + return None + +if __name__ == "__main__": + # TODO(andre): Maybe run tests here? + print('This is a module.') + diff --git a/config/lobby.json.example b/config/lobby.json.example new file mode 100644 index 0000000..35891ce --- /dev/null +++ b/config/lobby.json.example @@ -0,0 +1,30 @@ +{ + "76561198002556232": { "main": "Cicco" }, + "76561198196226474": { "main": "Cicco" }, + "76561197980760765": { "main": "CoNo" }, + "76561197961395108": { "main": "Crackhead" }, + "76561198173319946": { "main": "Crackhead" }, + "76561198134086527": { "main": "Crosby" }, + "76561198236978570": { "main": "Crosby" }, + "76561198057870737": { "main": "Dark Nightfall" }, + "76561197960356051": { "main": "Der n00b" }, + "76561197985005183": { "main": "MaD-.^" }, + "76561198155724084": { "main": "MaD-.^" }, + "76561198195069474": { "main": "MaD-.^" }, + "76561198274742338": { "main": "MaD-.^" }, + "76561198075447635": { "main": "Monte" }, + "76561197963063991": { "main": "Penguin" }, + "76561198155270977": { "main": "Penguin" }, + "76561198157995991": { "main": "Penguin" }, + "76561198091264830": { "main": "Pitchblack" }, + "76561198118197952": { "main": "Quinto" }, + "76561198213556884": { "main": "Quinto" }, + "76561198041000035": { "main": "Seas" }, + "76561197967103924": { "main": "Smoky" }, + "76561197975243134": { "main": "Smoky" }, + "76561198128110622": { "main": "Steak" }, + "76561198036851146": { "main": "Stranger" }, + "76561198161286427": { "main": "Stranger" }, + "76561198213233440": { "main": "Stranger" }, + "76561198215648187": { "main": "Stranger" } +} diff --git a/config/secrets.json.example b/config/secrets.json.example new file mode 100644 index 0000000..726b04e --- /dev/null +++ b/config/secrets.json.example @@ -0,0 +1,4 @@ +{ + "flask_secret": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "steam_token": "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" +} diff --git a/config/secrets.md b/config/secrets.md new file mode 100644 index 0000000..51f548d --- /dev/null +++ b/config/secrets.md @@ -0,0 +1,7 @@ +# Flask Secret +Used throughout Flask for everything encryption related. +Just put any random string here, this is the key for the encryptions. + +# Steam Token +You need an API token, to query the steam API. +Get your API token here: http://steamcommunity.com/dev/apikey diff --git a/main.py b/main.py new file mode 100755 index 0000000..d6c655a --- /dev/null +++ b/main.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +from flask import Flask, flash, redirect, render_template, request, session, url_for + +# from wsgiref.util import setup_testing_defaults +from collections import OrderedDict + +import json +import os +import re +import sys +import time, datetime +# import traceback + +from SteamAPI import SteamAPI +from QueryServer import QueryServer + +# Set some directories +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +TEMPLATES = os.path.join(THIS_DIR, 'templates') +ASSETS = os.path.join(THIS_DIR, 'assets') +CONFIG = os.path.join(THIS_DIR, 'config') + +app = Flask(__name__) +steam = None +with open(os.path.join(CONFIG, 'secrets.json'), 'rt') as secretjson: + secrets = json.load(secretjson) + app.secret_key = secrets["flask_secret"] + steam = SteamAPI(secrets["steam_token"]) + +################################################## +## Helper functions +################################################## + +intervals = ( + ('w', 604800), # 60 * 60 * 24 * 7 + ('d', 86400), # 60 * 60 * 24 + ('h', 3600), # 60 * 60 + ('m', 60), + ('s', 1), + ) + +def display_time(seconds, granularity=2): + result = [] + for name, count in intervals: + value = seconds // count + if value: + seconds -= value * count + if value == 1: + name = name.rstrip('s') + result.append("{}{}".format(int(value), name)) + return ' '.join(result[:granularity]) + +def grepSteamids(text): + steamids = [] + SteamIDfromText = re.findall(r'STEAM_\d:(\d):(\d+)', text) + for steamid in SteamIDfromText: + steam64id = 76561197960265728 + int(steamid[0]) + (int(steamid[1]) * 2) + steamids.append(steam64id) + return steamids + +################################################## +## Functions for different pages +################################################## + +@app.route("/") +def main(): + return redirect(url_for('lobby')) + +@app.errorhandler(404) +def not_found(e): + return render_template('error.jinja', error = 'Die angeforderte Seite konnte nicht gefunden werden.') + +@app.route("/lobby") +def lobby(): + steamids = dict() + friends = request.args.get('friends') + + if friends: + for steamid in friends.split(','): + for friend in steam.getFriends(steamid): + if friend not in steamids: + steamids[friend] = {} + else: + # Load config (steamids, names) + with open(os.path.join(CONFIG, 'lobby.json'), 'rt') as config: + steamids = json.load(config) + + profiledata = steam.getProfiles(steamids.keys()) + + # Merge new data in loaded config + for steamid in profiledata: + if steamid not in steamids: + continue + for key, value in profiledata[steamid].items(): + steamids[steamid][key] = value + + serverinfo = dict() + for steamid, playerdata in steamids.items(): + if 'gameid' not in playerdata \ + or 'gameserverip' not in playerdata: + continue + if ':' not in playerdata['gameserverip']: + continue + gameserver = playerdata['gameserverip'] + if gameserver not in serverinfo: + ip, port = gameserver.split(':') + port = int(port) + gameid = playerdata['gameid'] + print('Query Server:', ip, port, gameid) + server = QueryServer.QueryServer(ip, port, gameid) + print('Response:', server) + if server: + serverinfo[gameserver] = server + + # Sort steamids to be more appealing + steamids = OrderedDict(sorted(steamids.items(), key = lambda player: player[1]['personaname'].lower())) + steamids = OrderedDict(sorted(steamids.items(), reverse=True, key = lambda player: int(player[1]['lastlogoff']))) + steamids = OrderedDict(sorted(steamids.items(), key = lambda player: (player[1]['personastate'] > 0) and player[1]['personastate'] or 10)) + steamids = OrderedDict(sorted(steamids.items(), key = lambda player: ('gameextrainfo' in player[1] and player[1]['gameextrainfo'].lower() or "zzz"))) + + return render_template('lobby_html.jinja', + steamids = steamids, + serverinfo = serverinfo, + states = ['Offline', 'Online', 'Busy', 'Away', 'Snooze', 'Looking to trade', 'Looking to play'], + display_time = display_time, + current_time = time.time()) + +@app.route("/premadefinder", methods=['GET', 'POST']) +def premades(): + steamids = [] + profiles = dict() + connections = set() + + if request.method == 'POST': + postdata = request.form['statustext'] + + steamids = grepSteamids(postdata) + steamids = [str(x) for x in steamids] + if len(steamids) > 50: + return render_template( + 'error.jinja', + error='Es sind maximal 50 Steamids erlaubt.' + ) + + # Ask steam about profiles + profiles = steam.getMultipleFriends(steamids) + + # Add connection between friends. + # Friends are always bidirectional, so we use set and min/max to avoid duplicates + for steamid in steamids: + for friend in profiles[steamid]['friends']: + if friend in steamids: + friend_a = min(steamid, friend) + friend_b = max(steamid, friend) + connections.add((friend_a, friend_b)) + + return render_template('premades_html.jinja', + steamids=steamids, + profiles=profiles, + connections=connections + ) + +if __name__ == '__main__': + # print(steam.getFriends("76561197963063991")) + # print(steam.getFriends("76561197963882989")) + app.run(threaded=True) + +# Changelog +########### +# Internals changed to use Flask framework +# Some design changes +# ?friends=steamid,steamid,... diff --git a/static/hide.js b/static/hide.js new file mode 100644 index 0000000..67cb5c9 --- /dev/null +++ b/static/hide.js @@ -0,0 +1,17 @@ +var hidden = false; + +function hideOffline() { + hidden = !hidden; + var playerboxes = document.querySelectorAll('div.Offline'); + for(index in playerboxes) /* Show/Hide player row */ + { + if(typeof playerboxes[index] != 'object') continue; + playerboxes[index].style.display = (hidden) ? 'none' : 'inline-block'; + } + /* And update the text on our button. */ + document.querySelector('#offlinetoggle').innerHTML = (hidden) ? 'Show Offline' : 'Hide Offline'; +} +/* Execute function on page load. */ +setTimeout(hideOffline, 1); + +// vim: commentstring=/*\ %s\ */ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..d206836 --- /dev/null +++ b/static/style.css @@ -0,0 +1,198 @@ +body { + color: white; + background-color: #1d1d1d; + font-family: sans-serif; +} +a { + color: white; + text-decoration: none; +} + +ul#flash { + padding:10px; + margin:10px 0; + background-color: #FFBABA; + color: #D8000C; +} + +ul#flash > li:before { + content: "\26A0"; +} + +ul#flash > li { + font-style:normal; + font-weight:400; + speak:none; + display:inline-block; + text-decoration:inherit; + margin-right:.2em; + text-align:center; + font-variant:normal; + text-transform:none; + line-height:1em; + margin-left:.2em; +} + +ul#menu { + list-style-type: none; + margin-bottom: 20px; + padding: 0px; + overflow: hidden; + /* background-color: #ff00ff; */ +} + +li.menu { + float: left; + padding: 0px 5px; +} + +li a { + display: block; + width: 150px; + text-align: center; + text-decoration: none; +} + +li.app { + float: right; +} + +li a:hover { + background-color: #ff0000; +} + +.joinbutton { + color: white; + background-origin: padding-box; + background-image: linear-gradient(to bottom, rgb(164, 208, 7) 5%, rgb(83, 105, 4) 95%); + font-size: 12px; + line-height: 20px; + padding: 4px 6px 4px 6px; + border-radius: 4px; +} + +.joinbutton:hover { + background-image: linear-gradient(to bottom, rgb(164, 208, 7) 25%, rgb(83, 105, 4) 75%); +} + +/* Playerbox with Playerstats / Gamestats inside */ +div.player { + display: inline-block; + width: 330px; + min-height: 150px; + text-align: center; + margin: 2px 5px 2px 5px; + padding: 0px; + /* border: 1px solid black; */ +} +/* Intern box of playerbox */ +div.interna { + display: inline-block; + vertical-align: top; + width: 120px; + height: 165px; + margin: 0px; + padding-bottom: 5px; + overflow: hidden; + /* background-color: pink; */ +} +/* Additional settings for box with serverstats */ +div.stats { + font-size: small; + width: 200px; + /* background-color: purple; */ +} +div.form { + border: 1px dashed gray; + text-align: center; +} +div#premades { + border: 1px dashed rgb(150, 150, 0); + height: 500px; +} +div#info { + text-align: center; +} + +div.error { + border: 1px dashed red; + padding: 50px; + font-size: 150%; + text-align: center; +} + +svg { + width: 100%; + height: 100%; +} +xmp { + margin: 5px; +} + +/* Some special colors for 3rdpartygames, serverinfo, warnings etc */ +.error { color: rgb(200, 0, 0); font-weight: bold; } +.info { color: rgb(200, 200, 0); font-size: smaller; font-weight: bold; } +#disclaimer { + color: rgb(150, 150, 0); + font-size: smaller; + padding-left: 50px; + margin-top: 20px; + margin-bottom: 10px; +} +div.buttons { margin-top: 10px; } +div.serverinfo { margin-top: 10px; margin-bottom: 10px; font-size: x-small; } +img.avatar { width: 100px; height: 100px; margin: 1px 5px 1px 5px; } +span.gamename { font-size: small; color: rgb(87, 203, 222); } +span.ingameother { color: rgb(52, 132, 0); font-style: italic; } +span.maininfo { font-size: x-small; } +span.steamname { font-size: medium; /* font-family: serif; */ font-weight: bold; width: 110px; word-wrap: break-word; } +img.gameimage { width: 95%; } + +/***************************************/ +/* Colors for different online states: */ +/* offline online busy away snooze */ +/* lookingtotrade lookingtoplay */ +/***************************************/ +/* Whole player info box */ +div.online { border: 1px solid rgb(87, 203, 222); } +div.ingame { border: 1px solid rgb(144, 186, 60); } +div.away { border: 1px solid rgb(255, 235, 0); + opacity: 0.6; + filter: alpha(opacity=60); +} +div.busy { border: 1px solid rgb(48, 134, 149); + opacity: 0.5; + filter: alpha(opacity=50); +} +div.snooze { border: 1px solid rgb(135, 134, 135); + opacity: 0.5; + filter: alpha(opacity=50); +} +div.lookingtotrade { border: 1px solid rgb(255, 255, 0); } +div.lookingtoplay { border: 1px solid rgb(0, 255, 0); } +div.offline { + border: 1px dashed rgb(137, 137, 137); + opacity: 0.4; + filter: alpha(opacity=40); +} + +/* avatar */ +img.online { border: 1px solid rgb(87, 203, 222); } +img.ingame { border: 1px solid rgb(144, 186, 60); } +img.away { border: 1px solid rgb(255, 235, 0); } +img.busy { border: 1px solid rgb(48, 134, 149); } +img.snooze { border: 1px solid rgb(135, 134, 135); } +img.lookingtotrade { border: 1px solid rgb(255, 255, 0); } +img.lookingtoplay { border: 1px solid rgb(0, 255, 0); } +img.offline { border: 1px dashed rgb(137, 137, 137); filter: grayscale(1); -webkit-filter: grayscale(1); } + +/* color for username / gameinfo */ +span.ingame,a.ingame { color: rgb(144, 186, 60); } +span.online,a.online { color: rgb(87, 203, 222); } +span.away,a.away { color: rgb(255, 235, 0); } +span.busy { color: rgb(48, 134, 149); } +span.snooze { color: rgb(135, 134, 135); } +span.lookingtotrade { color: rgb(255, 255, 0); } +span.lookingtoplay { color: rgb(0, 255, 0); } +span.offline,a.offline { color: rgb(137, 137, 137); } + diff --git a/static/vivagraph.min.js b/static/vivagraph.min.js new file mode 100644 index 0000000..c84b0b7 --- /dev/null +++ b/static/vivagraph.min.js @@ -0,0 +1,2 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;"undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),n.Viva=e()}}(function(){return function e(n,t,r){function o(a,u){if(!t[a]){if(!n[a]){var s="function"==typeof require&&require;if(!u&&s)return s(a,!0);if(i)return i(a,!0);var f=new Error("Cannot find module '"+a+"'");throw f.code="MODULE_NOT_FOUND",f}var c=t[a]={exports:{}};n[a][0].call(c.exports,function(e){var t=n[a][1][e];return o(t?t:e)},c,c.exports,e,n,t,r)}return t[a].exports}for(var i="function"==typeof require&&require,a=0;a=0==m>=4?null:(f=u-i,d=o-a,p=a*i-o*u,v=f*e+d*n+p,g=f*t+d*r+p,0!==v&&0!==g&&v>=0==g>=0?null:(y=s*d-f*c,0===y?null:(x=y<0?-y/2:y/2,x=0,w=c*p-d*l,b.x=(w<0?w-x:w+x)/y,w=f*l-s*p,b.y=(w<0?w-x:w+x)/y,b)))}n.exports=r},{}],4:[function(e,n,t){n.exports.degree=e("./src/degree.js"),n.exports.betweenness=e("./src/betweenness.js")},{"./src/betweenness.js":5,"./src/degree.js":6}],5:[function(e,n,t){function r(e,n){function t(e){g[e]/=2}function r(e){g[e.id]=0}function o(e){s=e.id,u(s),i()}function i(){for(e.forEachNode(a);c.length;){for(var n=c.pop(),t=(1+v[n])/p[n],r=d[n],o=0;o1&&(o=Array.prototype.splice.call(arguments,1));for(var i=0;i0&&o.addLink(i,t-1+r*e),r>0&&o.addLink(i,t+(r-1)*e)}return o}function f(e,n,t){if(e<1||n<1||t<1)throw new Error("Invalid number of nodes in grid3 graph");var r,o,i,a=p();if(1===e&&1===n&&1===t)return a.addNode(0),a;for(i=0;i0&&a.addLink(s,r-1+o*e+u),o>0&&a.addLink(s,r+(o-1)*e+u),i>0&&a.addLink(s,r+o*e+(i-1)*e*n)}return a}function c(e){if(e<0)throw new Error("Invalid number of nodes in balanced tree");var n,t=p(),r=Math.pow(2,e);for(0===e&&t.addNode(1),n=1;n= 0");var n,t=p();for(n=0;n=n)throw new Error("Choose smaller `k`. It cannot be larger than number of nodes `n`");var i,a,u=e("ngraph.random").random(o||42),s=p();for(i=0;i=0&&t.links.splice(n,1)),r&&(n=o(e,r.links),n>=0&&r.links.splice(n,1)),U(e,"remove"),F(),!0}function h(e,n){var t,r=f(e);if(!r)return null;for(t=0;t0&&(O.fire("changed",M),M.length=0)}function P(){return Object.keys?k:j}function k(e){if("function"==typeof e)for(var n=Object.keys(A),t=0;t=0?i:-1,o);return v.push(a),a},getTotalMovement:function(){return x},removeSpring:function(e){if(e){var n=v.indexOf(e);return n>-1?(v.splice(n,1),!0):void 0}},getBestNewBodyPosition:function(e){return h.getBestNewPosition(e)},getBBox:function(){return h.box},gravity:function(e){return void 0!==e?(n.gravity=e,g.options({gravity:e}),this):n.gravity},theta:function(e){return void 0!==e?(n.theta=e,g.options({theta:e}),this):n.theta}};return o(n,w),a(w),w}n.exports=r},{"./lib/bounds":16,"./lib/createBody":17,"./lib/dragForce":18,"./lib/eulerIntegrator":19,"./lib/spring":20,"./lib/springForce":21,"ngraph.events":7,"ngraph.expose":8,"ngraph.merge":13,"ngraph.quadtreebh":22}],16:[function(e,n,t){n.exports=function(n,t){function r(){var e=n.length;if(0!==e){for(var t=Number.MAX_VALUE,r=Number.MAX_VALUE,o=Number.MIN_VALUE,a=Number.MIN_VALUE;e--;){var u=n[e];u.isPinned?(u.pos.x=u.prevPos.x,u.pos.y=u.prevPos.y):(u.prevPos.x=u.pos.x,u.prevPos.y=u.pos.y),u.pos.xo&&(o=u.pos.x),u.pos.ya&&(a=u.pos.y)}i.x1=t,i.x2=o,i.y1=r,i.y2=a}}var o=e("ngraph.random").random(42),i={x1:0,y1:0,x2:0,y2:0};return{box:i,update:r,reset:function(){i.x1=i.y1=0,i.x2=i.y2=0},getBestNewPosition:function(e){var n=i,r=0,a=0;if(e.length){for(var u=0;u1&&(s.velocity.x=c/l,s.velocity.y=d/l),r=n*s.velocity.x,i=n*s.velocity.y,s.pos.x+=r,s.pos.y+=i,o+=Math.abs(r),a+=Math.abs(i)}return(o*o+a*a)/u}n.exports=r},{}],20:[function(e,n,t){function r(e,n,t,r,o){this.from=e,this.to=n,this.length=t,this.coeff=r,this.weight="number"==typeof o?o:1}n.exports=r},{}],21:[function(e,n,t){n.exports=function(n){var t=e("ngraph.merge"),r=e("ngraph.random").random(42),o=e("ngraph.expose");n=t(n,{springCoeff:2e-4,springLength:80});var i={update:function(e){var t=e.from,o=e.to,i=e.length<0?n.springLength:e.length,a=o.pos.x-t.pos.x,u=o.pos.y-t.pos.y,s=Math.sqrt(a*a+u*u);0===s&&(a=(r.nextDouble()-.5)/50,u=(r.nextDouble()-.5)/50,s=Math.sqrt(a*a+u*u));var f=s-i,c=(!e.coeff||e.coeff<0?n.springCoeff:e.coeff)*f/s*e.weight;t.force.x+=c*a,t.force.y+=c*u,o.force.x-=c*a,o.force.y-=c*u}};return o(n,i,["springCoeff","springLength"]),i}},{"ngraph.expose":8,"ngraph.merge":13,"ngraph.random":26}],22:[function(e,n,t){function r(e,n){return 0===n?e.quad0:1===n?e.quad1:2===n?e.quad2:3===n?e.quad3:null}function o(e,n,t){0===n?e.quad0=t:1===n?e.quad1=t:2===n?e.quad2=t:3===n&&(e.quad3=t)}n.exports=function(n){function t(){var e=h[m];return e?(e.quad0=null,e.quad1=null,e.quad2=null,e.quad3=null,e.body=null,e.mass=e.massX=e.massY=0,e.left=e.right=e.top=e.bottom=0):(e=new f,h[m]=e),++m,e}function i(e){var n,t,r,o,i=p,a=0,u=0,f=1,c=0,d=1;for(i[0]=y;f;){var v=i[c],h=v.body;f-=1,c+=1;var m=h!==e;h&&m?(t=h.pos.x-e.pos.x,r=h.pos.y-e.pos.y,o=Math.sqrt(t*t+r*r),0===o&&(t=(s.nextDouble()-.5)/50,r=(s.nextDouble()-.5)/50,o=Math.sqrt(t*t+r*r)),n=l*h.mass*e.mass/(o*o*o),a+=n*t,u+=n*r):m&&(t=v.massX/v.mass-e.pos.x,r=v.massY/v.mass-e.pos.y,o=Math.sqrt(t*t+r*r),0===o&&(t=(s.nextDouble()-.5)/50,r=(s.nextDouble()-.5)/50,o=Math.sqrt(t*t+r*r)),(v.right-v.left)/oi&&(i=f),ca&&(a=c)}var d=i-r,l=a-o;for(d>l?a=o+d:i=r+l,m=0,y=t(),y.left=r,y.right=i,y.top=o,y.bottom=a,n=s-1,n>=0&&(y.body=e[n]);n--;)u(e[n],y)}function u(e){for(v.reset(),v.push(y,e);!v.isEmpty();){var n=v.pop(),i=n.node,a=n.body;if(i.body){var u=i.body;if(i.body=null,d(u.pos,a.pos)){var f=3;do{var c=s.nextDouble(),l=(i.right-i.left)*c,p=(i.bottom-i.top)*c;u.pos.x=i.left+l,u.pos.y=i.top+p,f-=1}while(f>0&&d(u.pos,a.pos));if(0===f&&d(u.pos,a.pos))return}v.push(i,u),v.push(i,a)}else{var g=a.pos.x,h=a.pos.y;i.mass=i.mass+a.mass,i.massX=i.massX+a.mass*g,i.massY=i.massY+a.mass*h;var m=0,x=i.left,w=(i.right+x)/2,b=i.top,E=(i.bottom+b)/2;g>w&&(m+=1,x=w,w=i.right),h>E&&(m+=2,b=E,E=i.bottom);var L=r(i,m);L?v.push(L,a):(L=t(),L.left=x,L.top=b,L.right=w,L.bottom=E,L.body=a,o(i,m,L))}}}n=n||{},n.gravity="number"==typeof n.gravity?n.gravity:-1,n.theta="number"==typeof n.theta?n.theta:.8;var s=e("ngraph.random").random(1984),f=e("./node"),c=e("./insertStack"),d=e("./isSamePosition"),l=n.gravity,p=[],v=new c,g=n.theta,h=[],m=0,y=t();return{insertBodies:a,getRoot:function(){return y},updateBodyForce:i,options:function(e){return e?("number"==typeof e.gravity&&(l=e.gravity),"number"==typeof e.theta&&(g=e.theta),this):{gravity:l,theta:g}}}}},{"./insertStack":23,"./isSamePosition":24,"./node":25,"ngraph.random":26}],23:[function(e,n,t){function r(){this.stack=[],this.popIdx=0}function o(e,n){this.node=e,this.body=n}n.exports=r,r.prototype={isEmpty:function(){return 0===this.popIdx},push:function(e,n){var t=this.stack[this.popIdx];t?(t.node=e,t.body=n):this.stack[this.popIdx]=new o(e,n),++this.popIdx},pop:function(){if(this.popIdx>0)return this.stack[--this.popIdx]},reset:function(){this.popIdx=0}}},{}],24:[function(e,n,t){n.exports=function(e,n){var t=Math.abs(e.x-n.x),r=Math.abs(e.y-n.y);return t<1e-8&&r<1e-8}},{}],25:[function(e,n,t){n.exports=function(){this.body=null,this.quad0=null,this.quad1=null,this.quad2=null,this.quad3=null,this.mass=0,this.massX=0,this.massY=0,this.left=0,this.top=0,this.bottom=0,this.right=0}},{}],26:[function(e,n,t){function r(e){var n="number"==typeof e?e:+new Date,t=function(){return n=n+2127912214+(n<<12)&4294967295,n=4294967295&(3345072700^n^n>>>19),n=n+374761393+(n<<5)&4294967295,n=4294967295&(n+3550635116^n<<9),n=n+4251993797+(n<<3)&4294967295,n=4294967295&(3042594569^n^n>>>16),(268435455&n)/268435456};return{next:function(e){return Math.floor(t()*e)},nextDouble:function(){return t()}}}function o(e,n){var t=n||r();if("function"!=typeof t.next)throw new Error("customRandom does not match expected API: next() function is missing");return{forEach:function(n){var r,o,i;for(r=e.length-1;r>0;--r)o=t.next(r+1),i=e[o],e[o]=e[r],e[r]=i,n(i);e.length&&n(e[0])},shuffle:function(){var n,r,o;for(n=e.length-1;n>0;--n)r=t.next(n+1),o=e[r],e[r]=e[n],e[n]=o;return e}}}n.exports={random:r,randomIterator:o}},{}],27:[function(e,n,t){function r(e,n,t){function r(e){u.nodes.push(s(e))}function o(e){u.links.push(f(e))}function i(e){var n={id:e.id};return void 0!==e.data&&(n.data=e.data),n}function a(e){var n={fromId:e.fromId,toId:e.toId};return void 0!==e.data&&(n.data=e.data),n}var u={nodes:[],links:[]},s=n||i,f=t||a;return e.forEachNode(r),e.forEachLink(o),JSON.stringify(u)}n.exports=r},{}],28:[function(e,n,t){function r(e,n){var t=o(e);if(void 0===n)return t;for(var r=Object.keys(n),i=0;iv&&(r=1),u(e,r,{x:e.touches[0].clientX,y:e.touches[0].clientY}),v=t,m(e),y(e)}},j=function(e){p=!1,o.off("touchmove",k),o.off("touchend",j),o.off("touchcancel",j),c=null,r&&r(e)},A=function(e,t){m(e),y(e),d=t.clientX,l=t.clientY,c=e.target||e.srcElement,n&&n(e,{x:d,y:l}),p||(p=!0,o.on("touchmove",k),o.on("touchend",j),o.on("touchcancel",j))},_=function(e){return 1===e.touches.length?A(e,e.touches[0]):void(2===e.touches.length&&(m(e), +y(e),v=P(e.touches[0],e.touches[1])))};return e.addEventListener("mousedown",b),e.addEventListener("touchstart",_),{onStart:function(e){return n=e,this},onDrag:function(e){return t=e,this},onStop:function(e){return r=e,this},onScroll:function(e){return N(e),this},release:function(){e.removeEventListener("mousedown",b),e.removeEventListener("touchstart",_),o.off("mousemove",w),o.off("mouseup",E),o.off("touchmove",k),o.off("touchend",j),o.off("touchcancel",j),N(null)}}}n.exports=r;var o=e("../Utils/documentEvents.js"),i=e("../Utils/browserInfo.js"),a=e("../Utils/findElementPosition.js")},{"../Utils/browserInfo.js":39,"../Utils/documentEvents.js":40,"../Utils/findElementPosition.js":41}],36:[function(e,n,t){function r(e,n){var t=o(n),r=null,i={},a={x:0,y:0};return t.mouseDown(function(e,n){r=e,a.x=n.clientX,a.y=n.clientY,t.mouseCapture(r);var o=i[e.id];return o&&o.onStart&&o.onStart(n,a),!0}).mouseUp(function(e){t.releaseMouseCapture(r),r=null;var n=i[e.id];return n&&n.onStop&&n.onStop(),!0}).mouseMove(function(e,n){if(r){var t=i[r.id];return t&&t.onDrag&&t.onDrag(n,{x:n.clientX-a.x,y:n.clientY-a.y}),a.x=n.clientX,a.y=n.clientY,!0}}),{bindDragNDrop:function(e,n){i[e.id]=n,n||delete i[e.id]}}}n.exports=r;var o=e("../WebGL/webglInputEvents.js")},{"../WebGL/webglInputEvents.js":57}],37:[function(e,n,t){function r(e,n){function t(e){return d[e]}n=o(n,{maxX:1024,maxY:1024,seed:"Deterministic randomness made me do this"});var r=i(n.seed),u=new a(Number.MAX_VALUE,Number.MAX_VALUE,Number.MIN_VALUE,Number.MIN_VALUE),s={},f=function(e){return{x:r.next(n.maxX),y:r.next(n.maxY)}},c=function(e,n){e.xn.x2&&(n.x2=e.x),e.yn.y2&&(n.y2=e.y)},d="function"==typeof Object.create?Object.create(null):{},l=function(e){d[e.id]=f(e),c(d[e.id],u)},p=function(){0!==e.getNodesCount()&&(u.x1=Number.MAX_VALUE,u.y1=Number.MAX_VALUE,u.x2=Number.MIN_VALUE,u.y2=Number.MIN_VALUE,e.forEachNode(l))},v=function(e){s[e.id]=e},g=function(e){for(var n=0;n=0:"boolean"!=typeof z||z}function r(){G=G||window.document.body,F=F||i(e,{springLength:80,springCoeff:2e-4}),O=O||a(e,{container:G}),n.hasOwnProperty("renderLinks")||(n.renderLinks=!0),n.prerender=n.prerender||0,U=(O.inputManager||s)(e,O)}function l(){O.beginRender(),n.renderLinks&&O.renderLinks(),O.renderNodes(),O.endRender()}function p(){return W=F.step()&&!H,l(),!W}function v(e){return R?void(V+=e):void(e?(V+=e,R=f(function(){return p()},M)):(X=0,V=0,R=f(p,M)))}function g(){J||(W=!1,R.restart())}function h(){if("number"==typeof n.prerender&&n.prerender>0)for(var e=0;e0?n.insertBefore(r,n.firstChild):n.appendChild(r),r},releaseLink:function(e){var t=d[e.id];t&&(n.removeChild(t),delete d[e.id])},addNode:function(e,t){var r=l(e);if(r)return r.position=t,r.node=e,c[e.id]=r,n.appendChild(r),r},releaseNode:function(e){var t=c[e.id];t&&(n.removeChild(t),delete c[e.id])},renderNodes:function(){for(var e in c)if(c.hasOwnProperty(e)){var n=c[e];m.x=n.position.x,m.y=n.position.y,p(n,m,n.node)}},renderLinks:function(){for(var e in d)if(d.hasOwnProperty(e)){var n=d[e];y.x=n.position.from.x,y.y=n.position.from.y,x.x=n.position.to.x,x.y=n.position.to.y,g(n,y,x,n.link)}},getGraphicsRoot:function(e){return"function"==typeof e&&(t?e(t):r=e),t},getSvgRoot:function(){return t}};return i(b),b}n.exports=r;var o=e("simplesvg"),i=e("ngraph.events"),a=e("../Input/domInputManager.js")},{"../Input/domInputManager.js":34,"ngraph.events":7,simplesvg:28}],50:[function(e,n,t){function r(e){e=c(e,{enableBlending:!0,preserveDrawingBuffer:!1,clearColor:!1,clearColorValue:{r:1,g:1,b:1,a:1}});var n,t,r,d,l,p,v,g,h=0,m=0,y=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],x=[],w=[],b={},E={},L=i(),N=a(),P=function(e){return u()},k=function(e){return s(3014898687)},j=function(){L.updateTransform(y),N.updateTransform(y)},A=function(){y=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},_=function(){n&&t&&(d=t.width=Math.max(n.offsetWidth,1),l=t.height=Math.max(n.offsetHeight,1),r&&r.viewport(0,0,d,l),L&&L.updateSize(d/2,l/2),N&&N.updateSize(d/2,l/2))},I=function(e){e.fire("rescaled")};t=window.document.createElement("canvas");var T={getLinkUI:function(e){return E[e]},getNodeUI:function(e){return b[e]},node:function(e){if("function"==typeof e)return P=e,this},link:function(e){if("function"==typeof e)return k=e,this},placeNode:function(e){return p=e,this},placeLink:function(e){return v=e,this},inputManager:o,beginRender:function(){},endRender:function(){m>0&&L.render(),h>0&&N.render()},bringLinkToFront:function(e){var n,t,r=L.getFrontLinkId();L.bringToFront(e),r>e.id&&(n=e.id,t=w[r],w[r]=w[n],w[r].id=r,w[n]=t,w[n].id=n)},graphCenterChanged:function(e,n){y[12]=2*e/d-1,y[13]=1-2*n/l,j()},addLink:function(e,n){var t=m++,r=k(e);return r.id=t,r.pos=n,L.createLink(r),w[t]=r,E[e.id]=r,r},addNode:function(e,n){var t=h++,r=P(e);return r.id=t,r.position=n,r.node=e,N.createNode(r),x[t]=r,b[e.id]=r,r},translateRel:function(e,n){y[12]+=2*y[0]*e/d/y[0],y[13]-=2*y[5]*n/l/y[5],j()},scale:function(e,n){var t=2*n.x/d-1,r=1-2*n.y/l;return t-=y[12],r-=y[13],y[12]+=t*(1-e),y[13]+=r*(1-e),y[0]*=e,y[5]*=e,j(),I(this),y[0]},resetScale:function(){return A(),r&&(_(),j()),this},init:function(o){var i={};if(e.preserveDrawingBuffer&&(i.preserveDrawingBuffer=!0),n=o,_(),A(),n.appendChild(t),r=t.getContext("experimental-webgl",i),!r){var a="Could not initialize WebGL. Seems like the browser doesn't support it.";throw window.alert(a),a}if(e.enableBlending&&(r.blendFunc(r.SRC_ALPHA,r.ONE_MINUS_SRC_ALPHA),r.enable(r.BLEND)),e.clearColor){var u=e.clearColorValue;r.clearColor(u.r,u.g,u.b,u.a),this.beginRender=function(){r.clear(r.COLOR_BUFFER_BIT)}}L.load(r),L.updateSize(d/2,l/2),N.load(r),N.updateSize(d/2,l/2),j(),"function"==typeof g&&g(t)},release:function(e){t&&e&&e.removeChild(t)},isSupported:function(){var e=window.document.createElement("canvas"),n=e&&e.getContext&&e.getContext("experimental-webgl");return n},releaseLink:function(e){m>0&&(m-=1);var n=E[e.id];delete E[e.id],L.removeLink(n);var t=n.id;if(t0&&(h-=1);var n=b[e.id];delete b[e.id],N.removeNode(n);var t=n.id;if(te.length){var r=new Float32Array(e.length*t*2);return r.set(e),r}return e}function a(n,t){for(var r={},o=0;o=w.length&&s();var i=w[r.textureNumber];i.ctx.drawImage(n,r.col*h,r.row*h,h,h),b[e]=n.src,y[n.src]=o,i.isDirty=!0,t(o)}function c(n){var t=n/e<<0,r=n%e,o=r/g<<0,i=r%g;return{textureNumber:t,row:o,col:i}}function d(){E.isDirty=!0,x=0,v=null}function l(){v&&(window.clearTimeout(v),x+=1,v=null),x>10?d():v=window.setTimeout(d,400)}function p(e,n){var t=w[e.textureNumber].canvas,r=w[n.textureNumber].ctx,o=n.col*h,i=n.row*h;r.drawImage(t,e.col*h,e.row*h,h,h,o,i,h,h),w[e.textureNumber].isDirty=!0,w[n.textureNumber].isDirty=!0}var v,g=Math.sqrt(e||1024)<<0,h=g,m=1,y={},x=0,w=[],b=[];if(!o(e))throw"Tiles per texture should be power of two.";var E={isDirty:!1,clearDirty:n,remove:t,getTextures:r,getCoordinates:a,load:u};return E}function o(e){return 0===(e&e-1)}var i=e("./texture.js");n.exports=r},{"./texture.js":52}],55:[function(e,n,t){function r(e,n){return{_texture:0,_offset:0,size:"number"==typeof e?e:32,src:n}}n.exports=r},{}],56:[function(e,n,t){function r(){function e(e,n){e.nativeObject&&h.deleteTexture(e.nativeObject);var t=h.createTexture();h.activeTexture(h["TEXTURE"+n]),h.bindTexture(h.TEXTURE_2D,t),h.texImage2D(h.TEXTURE_2D,0,h.RGBA,h.RGBA,h.UNSIGNED_BYTE,e.canvas),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_MAG_FILTER,h.LINEAR),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_MIN_FILTER,h.LINEAR_MIPMAP_NEAREST),h.generateMipmap(h.TEXTURE_2D),h.uniform1i(x["sampler"+n],n),e.nativeObject=t}function n(){if(v.isDirty){var n,t=v.getTextures();for(n=0;n0&&(A-=1),e.id0&&(e.src&&v.remove(e.src),y.copyArrayPart(_,e.id*N,A*N,N))}function c(e,n){n._offset=e._offset}function d(e){L=!0,E=e}function l(e,n){w=e,b=n,L=!0}function p(){h.useProgram(g),h.bindBuffer(h.ARRAY_BUFFER,m),h.bufferData(h.ARRAY_BUFFER,_,h.DYNAMIC_DRAW),L&&(L=!1,h.uniformMatrix4fv(x.transform,!1,E),h.uniform2f(x.screenSize,w,b)),h.vertexAttribPointer(x.vertexPos,2,h.FLOAT,!1,3*Float32Array.BYTES_PER_ELEMENT,0),h.vertexAttribPointer(x.customAttributes,1,h.FLOAT,!1,3*Float32Array.BYTES_PER_ELEMENT,8),n(),h.drawArrays(h.TRIANGLES,0,6*A)}var v,g,h,m,y,x,w,b,E,L,N=18,P=o(),k=i(),j=1024,A=0,_=new Float32Array(64);return{load:t,position:r,createNode:s,removeNode:f,replaceProperties:c,updateTransform:d,updateSize:l,render:p}}function o(){return["precision mediump float;","varying vec4 color;","varying vec3 vTextureCoord;","uniform sampler2D u_sampler0;","uniform sampler2D u_sampler1;","uniform sampler2D u_sampler2;","uniform sampler2D u_sampler3;","void main(void) {"," if (vTextureCoord.z == 0.) {"," gl_FragColor = texture2D(u_sampler0, vTextureCoord.xy);"," } else if (vTextureCoord.z == 1.) {"," gl_FragColor = texture2D(u_sampler1, vTextureCoord.xy);"," } else if (vTextureCoord.z == 2.) {"," gl_FragColor = texture2D(u_sampler2, vTextureCoord.xy);"," } else if (vTextureCoord.z == 3.) {"," gl_FragColor = texture2D(u_sampler3, vTextureCoord.xy);"," } else { gl_FragColor = vec4(0, 1, 0, 1); }","}"].join("\n")}function i(){return["attribute vec2 a_vertexPos;","attribute float a_customAttributes;","uniform vec2 u_screenSize;","uniform mat4 u_transform;","uniform float u_tilesPerTexture;","varying vec3 vTextureCoord;","void main(void) {"," gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);","float corner = mod(a_customAttributes, 4.);","float tileIndex = mod(floor(a_customAttributes / 4.), u_tilesPerTexture);","float tilesPerRow = sqrt(u_tilesPerTexture);","float tileSize = 1./tilesPerRow;","float tileColumn = mod(tileIndex, tilesPerRow);","float tileRow = floor(tileIndex/tilesPerRow);","if(corner == 0.0) {"," vTextureCoord.xy = vec2(0, 1);","} else if(corner == 1.0) {"," vTextureCoord.xy = vec2(1, 1);","} else if(corner == 2.0) {"," vTextureCoord.xy = vec2(0, 0);","} else {"," vTextureCoord.xy = vec2(1, 0);","}","vTextureCoord *= tileSize;","vTextureCoord.x += tileColumn * tileSize;","vTextureCoord.y += tileRow * tileSize;","vTextureCoord.z = floor(floor(a_customAttributes / 4.)/u_tilesPerTexture);","}"].join("\n")}var a=e("./webglAtlas.js"),u=e("./webgl.js");n.exports=r},{"./webgl.js":53,"./webglAtlas.js":54}],57:[function(e,n,t){function r(e){function n(){x=null}function t(e){x=e}function r(e){return"function"==typeof e&&k.push(e),A}function i(e){return"function"==typeof e&&P.push(e),A}function a(e){return"function"==typeof e&&N.push(e),A}function u(e){return"function"==typeof e&&L.push(e),A}function s(e){return"function"==typeof e&&E.push(e),A}function f(e){return"function"==typeof e&&b.push(e),A}function c(e){return"function"==typeof e&&w.push(e),A}function d(e,n,t){if(e&&e.size){var r=e.position,o=e.size;return r.x-oh.byteLength){var e=new ArrayBuffer(2*h.byteLength),n=new Float32Array(e),t=new Uint32Array(e);t.set(y),m=n,y=t,h=e}};return{load:function(a){n=a,r=o(a),e=r.createProgram(v,p),n.useProgram(e),i=r.getLocations(e,["a_vertexPos","a_color","u_screenSize","u_transform"]),n.enableVertexAttribArray(i.vertexPos),n.enableVertexAttribArray(i.color),t=n.createBuffer()},position:function(e,n,t){var r=e.id,o=r*d;m[o]=n.x,m[o+1]=n.y,y[o+2]=e.color,m[o+3]=t.x,m[o+4]=t.y,y[o+5]=e.color},createLink:function(e){x(),g+=1,a=e.id},removeLink:function(e){g>0&&(g-=1),e.id0&&r.copyArrayPart(y,e.id*d,g*d,d)},updateTransform:function(e){c=!0,f=e},updateSize:function(e,n){u=e,s=n,c=!0},render:function(){n.useProgram(e),n.bindBuffer(n.ARRAY_BUFFER,t),n.bufferData(n.ARRAY_BUFFER,h,n.DYNAMIC_DRAW),c&&(c=!1,n.uniformMatrix4fv(i.transform,!1,f),n.uniform2f(i.screenSize,u,s)),n.vertexAttribPointer(i.vertexPos,2,n.FLOAT,!1,3*Float32Array.BYTES_PER_ELEMENT,0),n.vertexAttribPointer(i.color,4,n.UNSIGNED_BYTE,!0,3*Float32Array.BYTES_PER_ELEMENT,8),n.drawArrays(n.LINES,0,2*g),a=g-1},bringToFront:function(e){a>e.id&&r.swapArrayPart(m,e.id*d,a*d,d),a>0&&(a-=1)},getFrontLinkId:function(){return a}}}var o=e("./webgl.js");n.exports=r},{"./webgl.js":53}],60:[function(e,n,t){function r(){function e(){if((k+1)*w>=L.byteLength){var e=new ArrayBuffer(2*L.byteLength),n=new Float32Array(e),t=new Uint32Array(e);t.set(P),N=n,P=t,L=e}}function n(e){d=e,v=o(e),c=v.createProgram(E,b),d.useProgram(c),p=v.getLocations(c,["a_vertexPos","a_color","u_screenSize","u_transform"]),d.enableVertexAttribArray(p.vertexPos),d.enableVertexAttribArray(p.color),l=d.createBuffer()}function t(e,n){var t=e.id;N[t*x]=n.x,N[t*x+1]=-n.y,N[t*x+2]=e.size,P[t*x+3]=e.color}function r(e){y=!0,m=e}function i(e,n){g=e,h=n,y=!0}function a(e){k>0&&(k-=1),e.id0&&v.copyArrayPart(P,e.id*x,k*x,x)}function u(){e(),k+=1}function s(){}function f(){d.useProgram(c),d.bindBuffer(d.ARRAY_BUFFER,l),d.bufferData(d.ARRAY_BUFFER,L,d.DYNAMIC_DRAW),y&&(y=!1,d.uniformMatrix4fv(p.transform,!1,m),d.uniform2f(p.screenSize,g,h)),d.vertexAttribPointer(p.vertexPos,3,d.FLOAT,!1,x*Float32Array.BYTES_PER_ELEMENT,0),d.vertexAttribPointer(p.color,4,d.UNSIGNED_BYTE,!0,x*Float32Array.BYTES_PER_ELEMENT,12),d.drawArrays(d.POINTS,0,k)}var c,d,l,p,v,g,h,m,y,x=4,w=3*Float32Array.BYTES_PER_ELEMENT+Uint32Array.BYTES_PER_ELEMENT,b=["precision mediump float;","varying vec4 color;","void main(void) {"," gl_FragColor = color;","}"].join("\n"),E=["attribute vec3 a_vertexPos;","attribute vec4 a_color;","uniform vec2 u_screenSize;","uniform mat4 u_transform;","varying vec4 color;","void main(void) {"," gl_Position = u_transform * vec4(a_vertexPos.xy/u_screenSize, 0, 1);"," gl_PointSize = a_vertexPos.z * u_transform[0][0];"," color = a_color.abgr;","}"].join("\n"),L=new ArrayBuffer(16*w),N=new Float32Array(L),P=new Uint32Array(L),k=0;return{load:n,position:t,updateTransform:r,updateSize:i,removeNode:a,createNode:u,replaceProperties:s,render:f}}var o=e("./webgl.js");n.exports=r},{"./webgl.js":53}],61:[function(e,n,t){function r(e,n){return{size:"number"==typeof e?e:10,color:o(n)}}var o=e("./parseColor.js");n.exports=r},{"./parseColor.js":51}],62:[function(e,n,t){n.exports="0.8.1"},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/templates/error.jinja b/templates/error.jinja new file mode 100644 index 0000000..60c185f --- /dev/null +++ b/templates/error.jinja @@ -0,0 +1,16 @@ + + + CS:GO Lobbylinkfinder + + + + +
+

Es ist ein Fehler aufgetreten!

+
{{ error }}
+ Zurück + Zur Startseite +
+ + + diff --git a/templates/lobby_html.jinja b/templates/lobby_html.jinja new file mode 100644 index 0000000..fcf440b --- /dev/null +++ b/templates/lobby_html.jinja @@ -0,0 +1,100 @@ + + CS:GO Lobbylinkfinder + + + + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + + {# avatar: 32x32, avatarmedium: 64x64, avatarfull: 184x184 #} + + {% for steamid,profile in steamids.items() %} + {% if profile['gameextrainfo'] %}{% set state = "Ingame" %}{% else %}{% set state = states[profile['personastate']].lower().replace(' ', '') %}{% endif %} + +
+ +
+ {# Ingame (Steam), Ingame (Nonsteam), Offline/Other #} + {% if profile['gameid'] %} + {{ profile['gameextrainfo'] }} + {% if profile['gameserverip'] not in serverinfo %} + {# No serverinfo -> Display game logo #} + + {% endif %} +
+ {% if profile['lobbysteamid'] %} + + {% elif profile['gameserverip'] %} + {# playing on a server, got info? #} + {% if serverinfo[profile['gameserverip']] %} +
+ {% set game = serverinfo[profile['gameserverip']]['game'] %} + {% set servername = serverinfo[profile['gameserverip']]['name'] %} + {% set mapname = serverinfo[profile['gameserverip']]['map'] %} + {% set players = serverinfo[profile['gameserverip']]['players'] %} + {% set playersmax = serverinfo[profile['gameserverip']]['playersmax'] %} + {% if game != profile['gameextrainfo'] %} + {{ game }}
+ {% endif %} + {{ servername }}
+ {{ mapname }} ({{ players }}/{{ playersmax }}) +
+ {% endif %} + + {% endif %} + {% elif profile['gameextrainfo'] %} + {{ profile['gameextrainfo'] }}
+ (non-steam) + {% else %} + {% if profile['personastate'] == 0 %} + Offline since {{ display_time(current_time - profile['lastlogoff']) }} + {% else %} + {{ states[profile['personastate']] }} + {% endif %} + {% if profile['communityvisibilitystate'] == 1 %} +
Profile private + {% endif %} + {% endif %} +
+
+ {% endfor %} + + {% if steamids['76561197963063991'] %} +

+ Falls dir hier Accounts fehlen oder du Vorschläge hast oder hier nicht genannt werden willst, + dann wende dich an Penguin! +

+ {% endif %} + + + + +