def put_lobby(self, game: str, version: int, userid: UserID, data: Dict[str, Any]) -> None: """ Given a game, version and a user ID, save lobby information for that user. Parameters: game - String identifying a game series. version - Integer identifying the version of the game in the series. userid - Integer identifying a user. data - A dictionary of lobby information to store. """ data = copy.deepcopy(data) if 'id' in data: del data['id'] # Add json to lobby sql = ("INSERT INTO lobby (game, version, userid, time, data) " + "VALUES (:game, :version, :userid, :time, :data) " + "ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)") self.execute( sql, { 'game': game, 'version': version, 'userid': userid, 'time': Time.now(), 'data': self.serialize(data), }, )
def get_all_play_session_infos( self, game: str, version: int) -> List[Tuple[UserID, ValidatedDict]]: """ Given a game and version, look up all play session information. Parameters: game - String identifying a game series. version - Integer identifying the version of the game in the series. Returns: A list of Tuples, consisting of a UserID and the dictionary that would be returned for that user if get_play_session_info() was called for that user. """ sql = ("SELECT id, time, userid, data FROM playsession " "WHERE game = :game AND version = :version " "AND time > :time") cursor = self.execute( sql, { 'game': game, 'version': version, 'time': Time.now() - Time.SECONDS_IN_HOUR, }, ) ret = [] for result in cursor.fetchall(): data = ValidatedDict(self.deserialize(result['data'])) data['id'] = result['id'] data['time'] = result['time'] ret.append((UserID(result['userid']), data)) return ret
def get_time_sensitive_settings(self, game: str, version: int, name: str) -> Optional[ValidatedDict]: """ Given a game/version/name, look up the current time-sensitive settings for this game. Parameters: game - String identifier of the game we want settings for. version - Integer identifying the game version we want settings for. name - The name of the setting we are concerned with. Returns: A ValidatedDict of stored settings if the current setting is found, or None otherwise. If settings were found, they are guaranteed to include the attributes 'start_time' and 'end_time' which will both be seconds since the unix epoch (UTC). """ sql = ( "SELECT data, start_time, end_time FROM time_sensitive_settings WHERE " "game = :game AND version = :version AND name = :name AND start_time <= :time AND end_time > :time" ) cursor = self.execute(sql, { 'game': game, 'version': version, 'name': name, 'time': Time.now() }) if cursor.rowcount != 1: # setting doesn't exist return None result = cursor.fetchone() retval = ValidatedDict(self.deserialize(result['data'])) retval['start_time'] = result['start_time'] retval['end_time'] = result['end_time'] return retval
def destroy_play_session_info(self, game: str, version: int, userid: UserID) -> None: """ Given a game, version and a user ID, throw away session info for that play session. Parameters: game - String identifying a game series. version - Integer identifying the version of the game in the series. userid - Integer identifying a user, as possibly looked up by UserData. """ # Kill this play session sql = ( "DELETE FROM playsession WHERE game = :game AND version = :version AND userid = :userid" ) self.execute( sql, { 'game': game, 'version': version, 'userid': userid, }, ) # Prune any orphaned lobbies too sql = "DELETE FROM playsession WHERE time <= :time" self.execute(sql, {'time': Time.now() - Time.SECONDS_IN_HOUR})
def get_all_lobbies(self, game: str, version: int) -> List[Tuple[UserID, ValidatedDict]]: """ Given a game and version, look up all active lobbies. Parameters: game - String identifying a game series. version - Integer identifying the version of the game in the series. Returns: A list of dictionaries representing lobby info stored by a game class. """ sql = ("SELECT userid, id, data FROM lobby " "WHERE game = :game AND version = :version AND time > :time") cursor = self.execute( sql, { 'game': game, 'version': version, 'time': Time.now() - Time.SECONDS_IN_HOUR, }, ) ret = [] for result in cursor.fetchall(): data = ValidatedDict(self.deserialize(result['data'])) data['id'] = result['id'] ret.append((UserID(result['userid']), data)) return ret
def verify_event_w_update_status(self, loc: str, extid: int) -> None: call = self.call_node() event_w = Node.void('event_w') call.add_child(event_w) event_w.set_attribute('method', 'update_status') event_w.add_child(Node.s32('uid', extid)) event_w.add_child(Node.string('p_name', self.NAME)) event_w.add_child(Node.s32('exp', 0)) event_w.add_child(Node.s32('customize', 0)) event_w.add_child(Node.s32('tid', 0)) event_w.add_child(Node.string('t_name', '')) event_w.add_child(Node.string('lid', loc)) event_w.add_child(Node.string('s_name', '')) event_w.add_child(Node.s8('pref', 51)) event_w.add_child(Node.s32('time', Time.now())) event_w.add_child(Node.s8('status', 1)) event_w.add_child(Node.s8('stage', 0)) event_w.add_child(Node.s32('mid', -1)) event_w.add_child(Node.s8('ng', -1)) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/event_w/@status")
def put_time_based_achievement( self, game: str, version: int, userid: UserID, achievementid: int, achievementtype: str, data: Dict[str, Any], ) -> None: """ Given a game/version/userid and achievement id/type, save a time-based achievement. Assumes that time-based achievements are immutable once saved. Parameters: game - String identifier of the game looking up the user. version - Integer version of the game looking up the user. userid - Integer user ID, as looked up by one of the above functions. achievementid - Integer ID, as provided by a game. achievementtype - The type of achievement. data - A dictionary of data that the game wishes to retrieve later. """ refid = self.get_refid(game, version, userid) # Add achievement JSON to achievements sql = ( "INSERT INTO time_based_achievement (refid, id, type, timestamp, data) " + "VALUES (:refid, :id, :type, :ts, :data)") self.execute( sql, { 'refid': refid, 'id': achievementid, 'type': achievementtype, 'ts': Time.now(), 'data': self.serialize(data) })
def verify_gameend_regist( self, ref_id: str, jid: int, scores: List[Dict[str, Any]], ) -> None: call = self.call_node() # Construct node gameend = Node.void('gameend') call.add_child(gameend) gameend.set_attribute('method', 'regist') gameend.add_child(Node.s32('retry', 0)) pcbinfo = Node.void('pcbinfo') gameend.add_child(pcbinfo) pcbinfo.set_attribute('client_data_version', '0') data = Node.void('data') gameend.add_child(data) player = Node.void('player') data.add_child(player) player.add_child(Node.string('refid', ref_id)) player.add_child(Node.s32('jid', jid)) player.add_child(Node.string('name', self.NAME)) result = Node.void('result') data.add_child(result) result.set_attribute('count', str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 if score['clear']: bits |= 0x2 if score['fc']: bits |= 0x4 if score['ex']: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 tune = Node.void('tune') result.add_child(tune) tune.set_attribute('id', str(scoreid)) tune.add_child(Node.s32('music', score['id'])) tune.add_child(Node.s64('timestamp', Time.now() * 1000)) player_1 = Node.void('player') tune.add_child(player_1) player_1.set_attribute('rank', '1') scorenode = Node.s32('score', score['score']) player_1.add_child(scorenode) scorenode.set_attribute('seq', str(score['chart'])) scorenode.set_attribute('clear', str(bits)) scorenode.set_attribute('combo', '69') player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) # Swap with server resp = self.exchange('', call) self.assert_path(resp, "response/gameend/data/player/session_id")
def put_attempt( self, game: str, version: int, userid: Optional[UserID], songid: int, songchart: int, location: int, points: int, data: Dict[str, Any], new_record: bool, timestamp: Optional[int] = None, ) -> None: """ Given a game/version/song/chart and user ID, save a single score attempt. Note that this is different than put_score above, because a user may have only one score per song/chart in a given game, but they can have as many history entries as times played. Parameters: game - String representing a game series. version - Integer representing which version of the game. userid - Integer representing a user. Usually looked up with UserData. songid - ID of the song according to the game. songchart - Chart number according to the game. location - Machine ID where this score was earned. points - Points obtained on this song. data - Optional data that the game wishes to record along with the score. new_record - Whether this score was a new record or not. timestamp - Optional integer specifying when the attempt happened. """ # First look up the song/chart from the music DB musicid = self.__get_musicid(game, version, songid, songchart) ts = timestamp if timestamp is not None else Time.now() # Add to score history sql = ( "INSERT INTO `score_history` (userid, musicid, timestamp, lid, new_record, points, data) " + "VALUES (:userid, :musicid, :timestamp, :location, :new_record, :points, :data)" ) try: self.execute( sql, { 'userid': userid if userid is not None else 0, 'musicid': musicid, 'timestamp': ts, 'location': location, 'new_record': 1 if new_record else 0, 'points': points, 'data': self.serialize(data), }, ) except IntegrityError: raise ScoreSaveException( f'There is already an attempt by {userid if userid is not None else 0} for music id {musicid} at {ts}' )
def handle_info_rb5_info_read_shop_ranking_request(self, request: Node) -> Node: start_music_id = request.child_value('min') end_music_id = request.child_value('max') root = Node.void('info') shop_score = Node.void('shop_score') root.add_child(shop_score) shop_score.add_child(Node.s32('time', Time.now())) profiles: Dict[UserID, ValidatedDict] = {} for songid in range(start_music_id, end_music_id + 1): allscores = self.data.local.music.get_all_scores( self.game, self.music_version, songid=songid, ) for ng in [ self.CHART_TYPE_BASIC, self.CHART_TYPE_MEDIUM, self.CHART_TYPE_HARD, self.CHART_TYPE_SPECIAL, ]: scores = sorted( [score for score in allscores if score[1].chart == ng], key=lambda score: score[1].points, reverse=True, ) for i in range(len(scores)): userid, score = scores[i] if userid not in profiles: profiles[userid] = self.get_any_profile(userid) profile = profiles[userid] data = Node.void('data') shop_score.add_child(data) data.add_child(Node.s32('rank', i + 1)) data.add_child(Node.s16('music_id', songid)) data.add_child(Node.s8('note_grade', score.chart)) data.add_child( Node.s8( 'clear_type', self._db_to_game_clear_type( score.data.get_int('clear_type')))) data.add_child( Node.s32('user_id', profile.get_int('extid'))) data.add_child( Node.s16( 'icon_id', profile.get_dict('config').get_int('icon_id'))) data.add_child(Node.s32('score', score.points)) data.add_child(Node.s32('time', score.timestamp)) data.add_child(Node.string('name', profile.get_str('name'))) return root
def get_schedule_duration(self, schedule: str) -> Tuple[int, int]: """ Given a schedule type, returns the timestamp for the start and end of the current schedule of this type. """ if schedule not in ['daily', 'weekly']: raise Exception( 'Logic error, specify either \'daily\' or \'weekly\' for schedule type!' ) if schedule == 'daily': return (Time.beginning_of_today(), Time.end_of_today()) if schedule == 'weekly': return (Time.beginning_of_this_week(), Time.end_of_this_week()) # Should never happen return (0, 0)
def verify_game_save(self, location: str, refid: str, packet: int, block: int, exp: int) -> None: call = self.call_node() game = Node.void('game') call.add_child(game) game.set_attribute('method', 'save') game.set_attribute('refid', refid) game.set_attribute('locid', location) game.set_attribute('ver', '0') game.add_child(Node.u8('headphone', 0)) game.add_child(Node.u8('hispeed', 16)) game.add_child(Node.u16('appeal_id', 19)) game.add_child(Node.u16('frame0', 0)) game.add_child(Node.u16('frame1', 0)) game.add_child(Node.u16('frame2', 0)) game.add_child(Node.u16('frame3', 0)) game.add_child(Node.u16('frame4', 0)) last = Node.void('last') game.add_child(last) last.set_attribute('music_type', '1') last.set_attribute('music_id', '29') last.set_attribute('sort_type', '4') game.add_child(Node.u32('earned_gamecoin_packet', packet)) game.add_child(Node.u32('earned_gamecoin_block', block)) game.add_child(Node.u32('gain_exp', exp)) game.add_child(Node.u32('m_user_cnt', 0)) game.add_child(Node.bool_array('have_item', [False] * 512)) game.add_child(Node.bool_array('have_note', [False] * 512)) tracking = Node.void('tracking') game.add_child(tracking) m0 = Node.void('m0') tracking.add_child(m0) m0.add_child(Node.u8('type', 2)) m0.add_child(Node.u32('id', 5)) m0.add_child(Node.u32('score', 774566)) tracking.add_child(Node.time('p_start', Time.now() - 300)) tracking.add_child(Node.time('p_end', Time.now())) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/game")
def should_schedule(self, game: str, version: int, name: str, schedule: str) -> bool: """ Given a game/version/name pair and a schedule value, return whether this scheduled work is overdue or not. """ if schedule not in ['daily', 'weekly']: raise Exception( 'Logic error, specify either \'daily\' or \'weekly\' for schedule type!' ) sql = ("SELECT year, day FROM scheduled_work " "WHERE game = :game AND version = :version AND " "name = :name AND schedule = :schedule") cursor = self.execute(sql, { 'game': game, 'version': version, 'name': name, 'schedule': schedule }) if cursor.rowcount != 1: # No scheduled work was registered, so time to get going! return True result = cursor.fetchone() if schedule == 'daily': # Just look at the day and year, make sure it matches year, day = Time.days_into_year() if year != result['year']: # Wrong year, so we certainly need to run! return True if day != result['day']: # Wrong day and we're daily, so need to run! return True if schedule == 'weekly': # Find the beginning of the week (Monday), as days since epoch. if Time.week_in_days_since_epoch() != result['day']: # Wrong week, so we should run! return True # We have already run this work for this schedule return False
def handle_player_write_request(self, request: Node) -> Node: refid = request.child_value('rid') profile = self.put_profile_by_refid(refid, request) root = Node.void('player') if profile is None: root.add_child(Node.s32('uid', 0)) else: root.add_child(Node.s32('uid', profile.get_int('extid'))) root.add_child(Node.s32('time', Time.now())) return root
def mark_scheduled(self, game: str, version: int, name: str, schedule: str) -> None: if schedule not in ['daily', 'weekly']: raise Exception( 'Logic error, specify either \'daily\' or \'weekly\' for schedule type!' ) if schedule == 'daily': year, day = Time.days_into_year() sql = ( "INSERT INTO scheduled_work (game, version, name, schedule, year, day) " + "VALUES (:game, :version, :name, :schedule, :year, :day) " + "ON DUPLICATE KEY UPDATE year=VALUES(year), day=VALUES(day)") self.execute( sql, { 'game': game, 'version': version, 'name': name, 'schedule': schedule, 'year': year, 'day': day, }, ) if schedule == 'weekly': days = Time.week_in_days_since_epoch() sql = ( "INSERT INTO scheduled_work (game, version, name, schedule, day) " + "VALUES (:game, :version, :name, :schedule, :day) " + "ON DUPLICATE KEY UPDATE day=VALUES(day)") self.execute( sql, { 'game': game, 'version': version, 'name': name, 'schedule': schedule, 'day': days, }, )
def handle_ranking_read_request(self, request: Node) -> Node: root = Node.void('ranking') licenses = Node.void('lic_10') root.add_child(licenses) originals = Node.void('org_10') root.add_child(originals) licenses.add_child(Node.time('time', Time.now())) originals.add_child(Node.time('time', Time.now())) hitchart = self.data.local.music.get_hit_chart(self.game, self.version, 10) rank = 1 for (mid, _plays) in hitchart: record = Node.void('record') originals.add_child(record) record.add_child(Node.s16('id', mid)) record.add_child(Node.s16('rank', rank)) rank = rank + 1 return root
def destroy_lobby(self, lobbyid: int) -> None: """ Given a lobby ID, destroy the lobby. The lobby ID can be obtained by reading the 'id' field of the get_lobby response. Parameters: lobbyid: Integer identifying a lobby. """ # Delete this lobby sql = "DELETE FROM lobby WHERE id = :id" self.execute(sql, {'id': lobbyid}) # Prune any orphaned lobbies too sql = "DELETE FROM lobby WHERE time <= :time" self.execute(sql, {'time': Time.now() - Time.SECONDS_IN_HOUR})
def verify_playerdata_usergamedata_advanced_rivalload( self, refid: str, loadflag: int) -> None: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'rivalload')) data.add_child(Node.u64('targettime', Time.now() * 1000)) data.add_child(Node.string('shoparea', '.')) data.add_child(Node.bool('isdouble', False)) data.add_child(Node.s32('loadflag', loadflag)) data.add_child(Node.s32('ddrcode', 0)) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/data/recordtype") if loadflag != 2: # As implemented, its possible for a machine not in an arcade to have scores. # So, if the test PCBID we're using isn't in an arcade, we won't fetch scores # for area records (flag 2), so don't check for these in that case. self.assert_path(resp, "response/playerdata/data/record/mcode") self.assert_path(resp, "response/playerdata/data/record/notetype") self.assert_path(resp, "response/playerdata/data/record/rank") self.assert_path(resp, "response/playerdata/data/record/clearkind") self.assert_path(resp, "response/playerdata/data/record/flagdata") self.assert_path(resp, "response/playerdata/data/record/name") self.assert_path(resp, "response/playerdata/data/record/area") self.assert_path(resp, "response/playerdata/data/record/code") self.assert_path(resp, "response/playerdata/data/record/score") self.assert_path(resp, "response/playerdata/data/record/ghostid") if resp.child_value('playerdata/data/recordtype') != loadflag: raise Exception('Invalid record type returned!')
def run_scheduled_work(config: Dict[str, Any]) -> None: data = Data(config) # Only run scheduled work for enabled components enabled_factories: List[Any] = [] enabled_caches: List[Any] = [] if config.get('support', {}).get(GameConstants.IIDX, False): enabled_factories.append(IIDXFactory) enabled_caches.append(IIDXCache) if config.get('support', {}).get(GameConstants.POPN_MUSIC, False): enabled_factories.append(PopnMusicFactory) enabled_caches.append(PopnMusicCache) if config.get('support', {}).get(GameConstants.JUBEAT, False): enabled_factories.append(JubeatFactory) enabled_caches.append(JubeatCache) if config.get('support', {}).get(GameConstants.BISHI_BASHI, False): enabled_factories.append(BishiBashiFactory) enabled_caches.append(BishiBashiCache) if config.get('support', {}).get(GameConstants.DDR, False): enabled_factories.append(DDRFactory) enabled_caches.append(DDRCache) if config.get('support', {}).get(GameConstants.SDVX, False): enabled_factories.append(SoundVoltexFactory) enabled_caches.append(SoundVoltexCache) if config.get('support', {}).get(GameConstants.REFLEC_BEAT, False): enabled_factories.append(ReflecBeatFactory) enabled_caches.append(ReflecBeatCache) if config.get('support', {}).get(GameConstants.MUSECA, False): enabled_factories.append(MusecaFactory) enabled_caches.append(MusecaCache) # First, run any backend scheduled work for factory in enabled_factories: factory.run_scheduled_work(data, config) # type: ignore # Now, warm the caches for the frontend for cache in enabled_caches: cache.preload(data, config) # type: ignore # Now, possibly delete old log entries keep_duration = config.get('event_log_duration', 0) if keep_duration > 0: # Calculate timestamp of events we should delete oldest_event = Time.now() - keep_duration data.local.network.delete_events(oldest_event)
def handle_info_rb5_info_read_hit_chart_request(self, request: Node) -> Node: version = request.child_value('ver') root = Node.void('info') root.add_child(Node.s32('ver', version)) ranking = Node.void('ranking') root.add_child(ranking) def add_hitchart(name: str, start: int, end: int, hitchart: List[Tuple[int, int]]) -> None: base = Node.void(name) ranking.add_child(base) base.add_child(Node.s32('bt', start)) base.add_child(Node.s32('et', end)) new = Node.void('new') base.add_child(new) for (mid, plays) in hitchart: d = Node.void('d') new.add_child(d) d.add_child(Node.s16('mid', mid)) d.add_child(Node.s32('cnt', plays)) # Weekly hit chart add_hitchart( 'weekly', Time.now() - Time.SECONDS_IN_WEEK, Time.now(), self.data.local.music.get_hit_chart(self.game, self.music_version, 1024, 7), ) # Monthly hit chart add_hitchart( 'monthly', Time.now() - Time.SECONDS_IN_DAY * 30, Time.now(), self.data.local.music.get_hit_chart(self.game, self.music_version, 1024, 30), ) # All time hit chart add_hitchart( 'total', Time.now() - Time.SECONDS_IN_DAY * 365, Time.now(), self.data.local.music.get_hit_chart(self.game, self.music_version, 1024, 365), ) return root
def _destroy_session(self, session: str, sesstype: str) -> None: """ Destroy a previously-created session. Parameters: session - A session string as returned from create_session. """ # Remove the session token sql = "DELETE FROM session WHERE session = :session AND type = :sesstype" self.execute(sql, { 'session': session, 'sesstype': sesstype }, safe_write_operation=True) # Also weed out any other defunct sessions sql = "DELETE FROM session WHERE expiration < :timestamp" self.execute(sql, {'timestamp': Time.now()}, safe_write_operation=True)
def create_news(self, title: str, body: str) -> int: """ Given a title and body, create a new news entry. Parameters: title - String title of the entry. body - String body of the entry, may contain HTML. Returns: The ID of the newly created entry. """ sql = "INSERT INTO news (timestamp, title, body) VALUES (:timestamp, :title, :body)" cursor = self.execute(sql, { 'timestamp': int(Time.now()), 'title': title, 'body': body }) return cursor.lastrowid
def get_hit_chart( self, game: str, version: int, count: int, days: Optional[int] = None, ) -> List[Tuple[int, int]]: """ Look up a game's most played songs. Parameters: game - String representing a game series. version - Integer representing which version of the game. count - Number of scores to look up. Returns: A list of tuples, containing the songid and the number of plays across all charts for that song. """ sql = ( "SELECT music.songid AS songid, COUNT(score_history.timestamp) AS plays FROM score_history, music " + "WHERE score_history.musicid = music.id AND music.game = :game AND music.version = :version " ) if days is not None: # Only select the last X days of hit chart sql = sql + "AND score_history.timestamp > :timestamp " timestamp = Time.now() - (Time.SECONDS_IN_DAY * days) else: timestamp = None sql = sql + "GROUP BY songid ORDER BY plays DESC LIMIT :count" cursor = self.execute( sql, { 'game': game, 'version': version, 'count': count, 'timestamp': timestamp }) most_played = [] for result in cursor.fetchall(): most_played.append((result['songid'], result['plays'])) return most_played
def put_event( self, event: str, data: Dict[str, Any], timestamp: Optional[int] = None, userid: Optional[UserID] = None, arcadeid: Optional[ArcadeID] = None, ) -> None: if timestamp is None: timestamp = Time.now() sql = "INSERT INTO audit (timestamp, userid, arcadeid, type, data) VALUES (:ts, :uid, :aid, :type, :data)" self.execute( sql, { 'ts': timestamp, 'type': event, 'data': self.serialize(data), 'uid': userid, 'aid': arcadeid })
def create_client(self, name: str) -> int: """ Given a name, create a new client and generate an authorization token. Parameters: name - String name of the client. Returns: The ID of the newly created client. """ sql = "INSERT INTO client (timestamp, name, token) VALUES (:timestamp, :name, :token)" cursor = self.execute( sql, { 'timestamp': int(Time.now()), 'name': name, 'token': str(uuid.uuid4()), }, ) return cursor.lastrowid
def create_server(self, uri: str, token: str) -> int: """ Given a uri and a token, create a new server. Parameters: uri - String name of the server. token - Authorization token we will use when talking to the server. Returns: The ID of the newly created server. """ sql = "INSERT INTO server (timestamp, uri, token, config) VALUES (:timestamp, :uri, :token, 0)" cursor = self.execute( sql, { 'timestamp': int(Time.now()), 'uri': uri, 'token': token, }, ) return cursor.lastrowid
def handle_game_load_daily_request(self, request: Node) -> Node: extid = intish(request.attribute('code')) refid = request.attribute('refid') game = Node.void('game') profiledict = None if extid is not None: # Rival daily loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) else: # Self daily loading userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: profiledict = self.get_profile(userid) if profiledict is not None: play_stats = self.get_play_statistics(userid) # Day play counts last_play_date = play_stats.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if ( last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2] ): today_count = play_stats.get_int('today_plays', 0) else: today_count = 0 daycount = Node.void('daycount') game.add_child(daycount) daycount.set_attribute('playcount', str(today_count)) # Daily combo stuff, unclear how this works dailycombo = Node.void('dailycombo') game.add_child(dailycombo) dailycombo.set_attribute('daily_combo', str(0)) dailycombo.set_attribute('daily_combo_lv', str(0)) return game
def _create_session(self, opid: int, optype: str, expiration: int = (30 * 86400)) -> str: """ Given an ID, create a session string. Parameters: opid - ID we wish to start a session for. expiration - Number of seconds before this session is invalid. Returns: A string that can be used as a session ID. """ # Create a new session that is unique while True: session = ''.join( random.choice('0123456789ABCDEF') for _ in range(BaseData.SESSION_LENGTH)) sql = "SELECT session FROM session WHERE session = :session" cursor = self.execute(sql, {'session': session}) if cursor.rowcount == 0: # Make sure sessions expire in a reasonable amount of time expiration = Time.now() + expiration # Use that session sql = ("INSERT INTO session (id, session, type, expiration) " + "VALUES (:id, :session, :optype, :expiration)") cursor = self.execute( sql, { 'id': opid, 'session': session, 'optype': optype, 'expiration': expiration }, safe_write_operation=True, ) if cursor.rowcount == 1: return session
def get_play_session_info(self, game: str, version: int, userid: UserID) -> Optional[ValidatedDict]: """ Given a game, version and a user ID, look up play session information for that user. Parameters: game - String identifying a game series. version - Integer identifying the version of the game in the series. userid - Integer identifying a user, as possibly looked up by UserData. Returns: A dictionary representing play session info stored by a game class, or None if there is no active session for this game/version/user. The dictionary will always contain an 'id' field which is the play session ID, and a 'time' field which represents the timestamp when the play session began. """ sql = ( "SELECT id, time, data FROM playsession " "WHERE game = :game AND version = :version AND userid = :userid " "AND time > :time") cursor = self.execute( sql, { 'game': game, 'version': version, 'userid': userid, 'time': Time.now() - Time.SECONDS_IN_HOUR, }, ) if cursor.rowcount != 1: # Settings doesn't exist return None result = cursor.fetchone() data = ValidatedDict(self.deserialize(result['data'])) data['id'] = result['id'] data['time'] = result['time'] return data
def login() -> Response: username = request.form['username'] password = request.form['password'] userid = g.data.local.user.from_username(username) if userid is None: error('Unrecognized username or password!') return Response(render_template('account/login.html', **{'title': 'Log In', 'show_navigation': False, 'username': username})) if g.data.local.user.validate_password(userid, password): aes = AESCipher(g.config['secret_key']) sessionID = g.data.local.user.create_session(userid, expiration=90 * 86400) response = make_response(redirect(url_for('home_pages.viewhome'))) response.set_cookie( 'SessionID', aes.encrypt(sessionID), expires=Time.now() + (90 * Time.SECONDS_IN_DAY), ) return response else: error('Unrecognized username or password!') return Response(render_template('account/login.html', **{'title': 'Log In', 'show_navigation': False, 'username': username}))