+\r
+def parse_stats_submission(body):\r
+ """\r
+ Parses the POST request body for a stats submission\r
+ """\r
+ # storage vars for the request body\r
+ game_meta = {}\r
+ events = {}\r
+ players = []\r
+ teams = []\r
+\r
+ # we're not in either stanza to start\r
+ in_P = in_Q = False\r
+\r
+ for line in body.split('\n'):\r
+ try:\r
+ (key, value) = line.strip().split(' ', 1)\r
+\r
+ # Server (S) and Nick (n) fields can have international characters.\r
+ if key in 'S' 'n':\r
+ value = unicode(value, 'utf-8')\r
+\r
+ if key not in 'P' 'Q' 'n' 'e' 't' 'i':\r
+ game_meta[key] = value\r
+\r
+ if key == 'Q' or key == 'P':\r
+ #log.debug('Found a {0}'.format(key))\r
+ #log.debug('in_Q: {0}'.format(in_Q))\r
+ #log.debug('in_P: {0}'.format(in_P))\r
+ #log.debug('events: {0}'.format(events))\r
+\r
+ # check where we were before and append events accordingly\r
+ if in_Q and len(events) > 0:\r
+ #log.debug('creating a team (Q) entry')\r
+ teams.append(events)\r
+ events = {}\r
+ elif in_P and len(events) > 0:\r
+ #log.debug('creating a player (P) entry')\r
+ players.append(events)\r
+ events = {}\r
+\r
+ if key == 'P':\r
+ #log.debug('key == P')\r
+ in_P = True\r
+ in_Q = False\r
+ elif key == 'Q':\r
+ #log.debug('key == Q')\r
+ in_P = False\r
+ in_Q = True\r
+\r
+ events[key] = value\r
+\r
+ if key == 'e':\r
+ (subkey, subvalue) = value.split(' ', 1)\r
+ events[subkey] = subvalue\r
+ if key == 'n':\r
+ events[key] = value\r
+ if key == 't':\r
+ events[key] = value\r
+ except:\r
+ # no key/value pair - move on to the next line\r
+ pass\r
+\r
+ # add the last entity we were working on\r
+ if in_P and len(events) > 0:\r
+ players.append(events)\r
+ elif in_Q and len(events) > 0:\r
+ teams.append(events)\r
+\r
+ return (game_meta, players, teams)\r
+\r
+\r
+def is_blank_game(gametype, players):\r
+ """Determine if this is a blank game or not. A blank game is either:\r
+\r
+ 1) a match that ended in the warmup stage, where accuracy events are not\r
+ present (for non-CTS games)\r
+\r
+ 2) a match in which no player made a positive or negative score AND was\r
+ on the scoreboard\r
+\r
+ ... or for CTS, which doesn't record accuracy events\r
+\r
+ 1) a match in which no player made a fastest lap AND was\r
+ on the scoreboard\r
+ """\r
+ r = re.compile(r'acc-.*-cnt-fired')\r
+ flg_nonzero_score = False\r
+ flg_acc_events = False\r
+ flg_fastest_lap = False\r
+\r
+ for events in players:\r
+ if is_real_player(events) and played_in_game(events):\r
+ for (key,value) in events.items():\r
+ if key == 'scoreboard-score' and value != 0:\r
+ flg_nonzero_score = True\r
+ if r.search(key):\r
+ flg_acc_events = True\r
+ if key == 'scoreboard-fastest':\r
+ flg_fastest_lap = True\r
+\r
+ if gametype == 'cts':\r
+ return not flg_fastest_lap\r
+ else:\r
+ return not (flg_nonzero_score and flg_acc_events)\r
+\r
+\r
+def get_remote_addr(request):\r
+ """Get the Xonotic server's IP address"""\r
+ if 'X-Forwarded-For' in request.headers:\r
+ return request.headers['X-Forwarded-For']\r
+ else:\r
+ return request.remote_addr\r
+\r
+\r
+def is_supported_gametype(gametype, version):\r