def audit_log(): body = request.json type = body.get('type') data = body.get('data') uuid = body.get('uuid') if not type: return jsonify({ 'err': 1, 'message': 'Missing type' }), 400 player_id = None if uuid: player = Player.query.filter_by(uuid=uuid).first() if player: player_id = player.id AuditLog.create( type, data=data, server_id=g.server.id, player_id=player_id, commit=True ) return jsonify({ 'err': 0 })
def login(): form = LoginForm() if form.validate_on_submit(): username = request.form['username'] password = request.form['password'] next_path = request.form.get('next') username = username.strip() player = Player.query.filter_by(username=username).first() if player: user = player.user else: # TODO: check renames user = User.query.filter( or_(User.username == username, User.email == username) ).first() if user and user.check_password(password): if not user.session_key: user.generate_session_key(commit=False) session['user_session_key'] = user.session_key session.permanent = True if not user.last_login: session['first_login'] = True user.last_login = datetime.utcnow() user.save(commit=True) stats.incr('login.success') if user.mfa_login: session['mfa_stage'] = 'password-verified' return redirect(url_for('verify_mfa', next=next_path)) flash('Successfully logged in', 'success') return redirect(next_path or url_for('index')) else: flash('Invalid username/password combination', 'error') stats.incr('login.invalid') AuditLog.create( AuditLog.INVALID_LOGIN, username=username, matched_user_id=user.id if user else None, ip=request.remote_addr, commit=True ) return render_template('login.html', form=form), 401 return render_template('login.html', form=form)
def register(): uuid = request.form.get('uuid') email = request.form.get('email') username = request.form.get('username') player = Player.query.filter_by(uuid=uuid).first() if not player: return jsonify({ 'err': 1, 'message': 'Please try again later' }) user = User.query.filter_by(player=player).first() if user and user.email: return jsonify({ 'err': 1, 'message': 'You are already registered!' }) if not h.is_valid_email(email): return jsonify({ 'err': 1, 'message': 'Not a valid email address' }) other_user = User.query.filter_by(email=email).first() if other_user: return jsonify({ 'err': 1, 'message': 'Email already in use' }) email_tokens = EmailToken.query.filter_by(uuid=uuid) for email_token in email_tokens: db.session.delete(email_token) # old-style user without an email, just let them verify an email if user: send_verify_email(email, user) else: send_creation_email(email, uuid, username) total_time = libplayer.get_total_player_time(player.id) if total_time < app.config['MINIMUM_REGISTER_PLAYER_TIME']: rollbar.report_message('Player creating user account super quickly', level='error', request=request) AuditLog.create( AuditLog.QUICK_USER_CREATE, player_id=player.id, username=player.username, commit=True ) return jsonify({ 'err': 0, 'message': 'Email sent! Check your inbox for further instructions' })
def login(): form = LoginForm() if form.validate_on_submit(): username = request.form['username'] password = request.form['password'] next_path = request.form.get('next') username = username.strip() player = Player.query.filter_by(username=username).first() if player: user = player.user else: # TODO: check renames user = User.query.filter( or_(User.username == username, User.email == username)).first() if user and user.check_password(password): if not user.session_key: user.generate_session_key(commit=False) session['user_session_key'] = user.session_key session.permanent = True if not user.last_login: session['first_login'] = True user.last_login = datetime.utcnow() user.save(commit=True) stats.incr('login.success') if user.mfa_login: session['mfa_stage'] = 'password-verified' return redirect(url_for('verify_mfa', next=next_path)) flash('Successfully logged in', 'success') return redirect(next_path or url_for('index')) else: flash('Invalid username/password combination', 'error') stats.incr('login.invalid') AuditLog.create(AuditLog.INVALID_LOGIN, username=username, matched_user_id=user.id if user else None, ip=request.remote_addr, commit=True) return render_template('login.html', form=form), 401 return render_template('login.html', form=form)
def subscribe_to_topic(user, topic, commit=True): subscription = ForumTopicSubscription( user=user, topic=topic ) subscription.save(commit=False) AuditLog.create('topic_subscribe', user_id=user.id, data={ 'topic_id': topic.id }, commit=commit)
def edit_post(post_id): post = ForumPost.query.options( joinedload(ForumPost.topic) .joinedload(ForumTopic.forum) .joinedload(Forum.moderators) ).options( joinedload(ForumPost.user) .joinedload(User.forum_profile) ).get(post_id) if not post or post.deleted: abort(404) user = g.user if user.forum_ban: abort(403) if user != post.user and not user.admin and user not in post.topic.forum.moderators: abort(403) form = PostForm(obj=post) if form.validate_on_submit(): body = request.form['body'] AuditLog.create( 'forum_post_edit', user_id=user.id, post_id=post_id, old_text=post.body, new_text=body, commit=False ) post.body = body post.updated = datetime.utcnow() post.updated_by = user post.save(commit=True) return redirect(post.url) retval = { 'post': post, 'form': form } return render_template('forums/edit_post.html', **retval)
def unsubscribe(encoded_email, type, signature): if not verify_unsubscribe_request(encoded_email, type, signature): rollbar.report_message('Unsubscribe signature failure', level='error', extra_data={ 'current_user_id': g.user.id if g.user else None, 'path': request.path, }) abort(403) email = urllib.unquote(encoded_email) user = User.query.filter_by(email=email).first() if not user: flash('Invalid link', 'error') return redirect(url_for('index')) if g.user and g.user != user: rollbar.report_message('Unsubscribing email of a different user', level='warning', extra_data={ 'current_user_id': g.user.id, 'current_user_email': g.user.email, 'target_user_id': user.id, 'target_user_email': email }) AuditLog.create('unsubscribe', user_id=g.user.id if g.user else user.id, data={ 'email': email, 'type': type }, commit=False) preference = user.get_notification_preference(type, can_commit=False) preference.email = False preference.save(commit=True) email_preferences_url = url_for('notifications_settings') flash( Markup( 'Successfully unsubscribed! Manage more email preferences <a href="%s">here</a>.' % email_preferences_url), 'success') return redirect(url_for('index'))
def forum_ban(): by_user = g.user user_id = request.args['user_id'] AuditLog.create(AuditLog.USER_FORUM_BAN, user_id=user_id, by_user_id=by_user.id, commit=False) ban = ForumBan(user_id=user_id, by_user_id=by_user.id) ban.save(commit=True) flash('User banned from forums', 'success') return redirect(request.referrer)
def register(): uuid = request.form.get('uuid') email = request.form.get('email') username = request.form.get('username') player = Player.query.filter_by(uuid=uuid).first() if not player: return jsonify({'err': 1, 'message': 'Please try again later'}) user = User.query.filter_by(player=player).first() if user and user.email: return jsonify({'err': 1, 'message': 'You are already registered!'}) if not h.is_valid_email(email): return jsonify({'err': 1, 'message': 'Not a valid email address'}) other_user = User.query.filter_by(email=email).first() if other_user: return jsonify({'err': 1, 'message': 'Email already in use'}) email_tokens = EmailToken.query.filter_by(uuid=uuid) for email_token in email_tokens: db.session.delete(email_token) # old-style user without an email, just let them verify an email if user: send_verify_email(email, user) else: send_creation_email(email, uuid, username) total_time = libplayer.get_total_player_time(player.id) if total_time < app.config['MINIMUM_REGISTER_PLAYER_TIME']: rollbar.report_message('Player creating user account super quickly', level='error', request=request) AuditLog.create(AuditLog.QUICK_USER_CREATE, player_id=player.id, username=player.username, commit=True) return jsonify({ 'err': 0, 'message': 'Email sent! Check your inbox for further instructions' })
def forum_ban(): by_user = g.user user_id = request.args['user_id'] AuditLog.create( AuditLog.USER_FORUM_BAN, user_id=user_id, by_user_id=by_user.id, commit=False ) ban = ForumBan(user_id=user_id, by_user_id=by_user.id) ban.save(commit=True) flash('User banned from forums', 'success') return redirect(request.referrer)
def ban_player(player, reason=None, with_ip=False, by_user_id=None, source=None, commit=True, **kwargs): reason = reason or 'The Ban Hammer has spoken!' api.ban_player(player, reason, with_ip) AuditLog.create( AuditLog.PLAYER_BAN, player_id=player.id, username=player.username, by_user_id=by_user_id, reason=reason, source=source, commit=False, **kwargs ) player.banned = True player.save(commit=commit)
def notifications_settings(): user = g.user preferences = user.get_notification_preferences() form = generate_notification_settings_form(preferences) if form.validate_on_submit(): for preference in preferences: preference.email = getattr(form, '%s_email' % preference.name).data preference.ingame = getattr(form, '%s_ingame' % preference.name).data preference.save(commit=False) db.session.commit() AuditLog.create('notification_settings_saved', user_id=user.id, data={ x.name: { 'email': x.email, 'ingame': x.ingame } for x in preferences }, commit=True) flash('Notification settings saved!', 'success') grouped_preferences = {} for preference in preferences: definition = preference.definition grouped_preferences.setdefault(definition.setting_category, []).append(preference) template_vars = { 'form': form, 'grouped_preferences': grouped_preferences, 'active_option': 'notifications' } return render_template('settings/notifications.html', **template_vars)
def edit_post(post_id): post = ForumPost.query.options( joinedload(ForumPost.topic).joinedload(ForumTopic.forum)).options( joinedload(ForumPost.user).joinedload( User.forum_profile)).get(post_id) if not post or post.deleted: abort(404) user = g.user if user.forum_ban: abort(403) if user != post.user and not user.admin_or_moderator: abort(403) form = PostForm(obj=post) if form.validate_on_submit(): body = request.form['body'] AuditLog.create('forum_post_edit', user_id=user.id, post_id=post_id, old_text=post.body, new_text=body, commit=False) post.body = body post.updated = datetime.utcnow() post.updated_by = user post.save(commit=True) return redirect(post.url) retval = {'post': post, 'form': form} return render_template('forums/edit_post.html', **retval)
def unsubscribe(encoded_email, type, signature): if not verify_unsubscribe_request(encoded_email, type, signature): rollbar.report_message('Unsubscribe signature failure', level='error', extra_data={ 'current_user_id': g.user.id if g.user else None, 'path': request.path, }) abort(403) email = urllib.unquote(encoded_email) user = User.query.filter_by(email=email).first() if not user: flash('Invalid link', 'error') return redirect(url_for('index')) if g.user and g.user != user: rollbar.report_message('Unsubscribing email of a different user', level='warning', extra_data={ 'current_user_id': g.user.id, 'current_user_email': g.user.email, 'target_user_id': user.id, 'target_user_email': email }) AuditLog.create('unsubscribe', user_id=g.user.id if g.user else user.id, data={ 'email': email, 'type': type }, commit=False) preference = user.get_notification_preference(type, can_commit=False) preference.email = False preference.save(commit=True) email_preferences_url = url_for('notifications_settings') flash( Markup('Successfully unsubscribed! Manage more email preferences <a href="%s">here</a>.' % email_preferences_url), 'success' ) return redirect(url_for('index'))
def ban_player(player, reason=None, with_ip=False, by_user_id=None, source=None, commit=True, **kwargs): reason = reason or 'The Ban Hammer has spoken!' api.ban_player(player, reason, with_ip) AuditLog.create(AuditLog.PLAYER_BAN, player_id=player.id, username=player.username, by_user_id=by_user_id, reason=reason, source=source, commit=False, **kwargs) player.banned = True player.save(commit=commit)
def forum_unban(): by_user = g.user user_id = request.args['user_id'] ban = ForumBan.query.filter_by(user_id=user_id).first() if not ban: flash('User not forum banned', 'error') return redirect(request.referrer) AuditLog.create(AuditLog.USER_FORUM_UNBAN, user_id=user_id, by_user_id=by_user.id, commit=False) db.session.delete(ban) db.session.commit() flash('User unbanned from forums', 'success') return redirect(request.referrer)
def forum_topic_unsubscribe(topic_id): user = g.user topic = ForumTopic.query.get(topic_id) if not topic or topic.deleted: abort(404) subscription = ForumTopicSubscription.query.filter_by(user=user, topic=topic).first() if subscription: db.session.delete(subscription) flash('Unsubscribed from the topic successfully!', 'success') AuditLog.create('topic_unsubscribe', user_id=user.id, data={'topic_id': topic.id}) else: flash('You weren\'t subscribed to the topic', 'warning') return redirect(topic.url)
def forum_topic_unsubscribe(topic_id): user = g.user topic = ForumTopic.query.get(topic_id) if not topic or topic.deleted: abort(404) subscription = ForumTopicSubscription.query.filter_by( user=user, topic=topic ).first() if subscription: db.session.delete(subscription) flash('Unsubscribed from the topic successfully!', 'success') AuditLog.create('topic_unsubscribe', user_id=user.id, data={ 'topic_id': topic.id }) else: flash('You weren\'t subscribed to the topic', 'warning') return redirect(topic.url)
def forum_unban(): by_user = g.user user_id = request.args['user_id'] ban = ForumBan.query.filter_by(user_id=user_id).first() if not ban: flash('User not forum banned', 'error') return redirect(request.referrer) AuditLog.create( AuditLog.USER_FORUM_UNBAN, user_id=user_id, by_user_id=by_user.id, commit=False ) db.session.delete(ban) db.session.commit() flash('User unbanned from forums', 'success') return redirect(request.referrer)
def audit_log(): body = request.json type = body.get('type') data = body.get('data') uuid = body.get('uuid') if not type: return jsonify({'err': 1, 'message': 'Missing type'}), 400 player_id = None if uuid: player = Player.query.filter_by(uuid=uuid).first() if player: player_id = player.id AuditLog.create(type, data=data, server_id=g.server.id, player_id=player_id, commit=True) return jsonify({'err': 0})
def _query_server(server, mojang_status): server_status = api.get_server_status(server) or {} player_stats = [] players_to_sync_ban = Player.query.filter( Player.uuid.in_(server_status.get('banned_uuids', [])), Player.banned == False).all() if players_to_sync_ban: player_ids_to_sync_ban = [x.id for x in players_to_sync_ban] Player.query.filter(Player.id.in_(player_ids_to_sync_ban)).update( { 'banned': True, }, synchronize_session=False) for player in players_to_sync_ban: AuditLog.create(AuditLog.PLAYER_BAN, player_id=player.id, username=player.username, source='server_sync', commit=False) online_player_ids = [] for player_info in server_status.get('players', []): username = player_info['username'] uuid = player_info['uuid'] player = Player.query.options(joinedload( Player.titles)).filter_by(uuid=uuid).first() if player: if player.username != username: h.avoid_duplicate_username(username) player.set_username(username) player.save(commit=False) else: h.avoid_duplicate_username(username) player = Player(username=username, uuid=uuid) player.save(commit=False) statsd.incr('player.created') online_player_ids.append(player.id) last_activity = PlayerActivity.query.filter_by( server=server, player=player).order_by(PlayerActivity.timestamp.desc()).first() # if the last activity for this player is an 'exit' activity (or there isn't an activity), # create a new 'enter' activity since they just joined this minute if not last_activity or last_activity.activity_type == PLAYER_ACTIVITY_TYPES[ 'exit']: enter = PlayerActivity( server=server, player=player, activity_type=PLAYER_ACTIVITY_TYPES['enter']) enter.save(commit=False) if server.id == app.config['MAIN_SERVER_ID']: if player.banned: player.banned = False AuditLog.create(AuditLog.PLAYER_UNBAN, player_id=player.id, username=player.username, source='server_sync', commit=False) nickname_ansi = player_info.get('nickname_ansi') nickname = player_info.get('nickname') player.nickname_ansi = nickname_ansi player.nickname = nickname player.save(commit=False) ip = player_info.get('address') if ip: if not IPTracking.query.filter_by(ip=ip, player=player).first(): existing_player_ip = IPTracking(ip=ip, player=player) existing_player_ip.save(commit=False) if geoip.is_nok(ip): libplayer.ban_player(player, with_ip=True, source='query', ip=ip, commit=False) stats = PlayerStats.query.filter_by(server=server, player=player).first() if not stats: stats = PlayerStats(server=server, player=player) stats.last_seen = datetime.utcnow() stats.pvp_logs = player_info.get('pvp_logs') stats.time_spent = (stats.time_spent or 0) + 1 stats.save(commit=False) titles = [{ 'name': x.name, 'broadcast': x.broadcast } for x in player.titles] player_stats.append({ 'username': player.username, 'uuid': player.uuid, 'minutes': stats.time_spent, 'rank': stats.rank, 'titles': titles }) five_minutes_ago = datetime.utcnow() - timedelta(minutes=10) result = PlayerStats.query.filter(PlayerStats.server == server, PlayerStats.last_seen > five_minutes_ago) recent_player_ids = [x.player_id for x in result] # find all players that have recently left and insert an 'exit' activity for them # if their last activity was an 'enter' for player_id in set(recent_player_ids) - set(online_player_ids): latest_activity = PlayerActivity.query.filter_by(server=server, player_id=player_id)\ .order_by(PlayerActivity.timestamp.desc()).first() if latest_activity and latest_activity.activity_type == PLAYER_ACTIVITY_TYPES[ 'enter']: ex = PlayerActivity(server=server, player_id=player_id, activity_type=PLAYER_ACTIVITY_TYPES['exit']) ex.save(commit=False) player_count = server_status.get('numplayers', 0) or 0 cpu_load = server_status.get('load', 0) or 0 tps = server_status.get('tps', 0) or 0 status = ServerStatus(server=server, player_count=player_count, cpu_load=cpu_load, tps=tps) status.save(commit=True) api.send_stats( server, { 'player_stats': player_stats, 'session': mojang_status.session, 'account': mojang_status.account, 'auth': mojang_status.auth }) _handle_groups(server, server_status.get('groups', []))
def _query_server(server, mojang_status): server_status = api.get_server_status(server) or {} player_stats = [] online_player_ids = [] for player_info in server_status.get('players', []): username = player_info['username'] uuid = player_info['uuid'] player = Player.query.options( joinedload(Player.titles) ).filter_by(uuid=uuid).first() if player: if player.username != username: AuditLog.create( AuditLog.PLAYER_RENAME, player_id=player.id, old_name=player.username, new_name=username, commit=False ) player.username = username player.save(commit=False) else: player = Player(username=username, uuid=uuid) player.save(commit=False) online_player_ids.append(player.id) last_activity = PlayerActivity.query.filter_by(server=server, player=player)\ .order_by(PlayerActivity.timestamp.desc()).first() # if the last activity for this player is an 'exit' activity (or there isn't an activity), # create a new 'enter' activity since they just joined this minute if not last_activity or last_activity.activity_type == PLAYER_ACTIVITY_TYPES['exit']: enter = PlayerActivity(server=server, player=player, activity_type=PLAYER_ACTIVITY_TYPES['enter']) enter.save(commit=False) # respect nicknames from the main server if server.id == app.config['MAIN_SERVER_ID']: nickname_ansi = player_info.get('nickname_ansi') nickname = player_info.get('nickname') player.nickname_ansi = nickname_ansi player.nickname = nickname player.save(commit=False) ip = player_info.get('address') if ip: if not IPTracking.query.filter_by(ip=ip, player=player).first(): existing_player_ip = IPTracking(ip=ip, player=player) existing_player_ip.save(commit=False) stats = PlayerStats.query.filter_by(server=server, player=player).first() if not stats: stats = PlayerStats(server=server, player=player) stats.last_seen = datetime.utcnow() stats.pvp_logs = player_info.get('pvp_logs') stats.time_spent = (stats.time_spent or 0) + 1 stats.save(commit=False) titles = [{'name': x.name, 'broadcast': x.broadcast} for x in player.titles] player_stats.append({ 'username': player.username, 'uuid': player.uuid, 'minutes': stats.time_spent, 'rank': stats.rank, 'titles': titles }) five_minutes_ago = datetime.utcnow() - timedelta(minutes=10) result = PlayerStats.query.filter(PlayerStats.server == server, PlayerStats.last_seen > five_minutes_ago) recent_player_ids = [x.player_id for x in result] # find all players that have recently left and insert an 'exit' activity for them # if their last activity was an 'enter' for player_id in set(recent_player_ids) - set(online_player_ids): latest_activity = PlayerActivity.query.filter_by(server=server, player_id=player_id)\ .order_by(PlayerActivity.timestamp.desc()).first() if latest_activity and latest_activity.activity_type == PLAYER_ACTIVITY_TYPES['enter']: ex = PlayerActivity(server=server, player_id=player_id, activity_type=PLAYER_ACTIVITY_TYPES['exit']) ex.save(commit=False) player_count = server_status.get('numplayers', 0) or 0 cpu_load = server_status.get('load', 0) or 0 tps = server_status.get('tps', 0) or 0 status = ServerStatus(server=server, player_count=player_count, cpu_load=cpu_load, tps=tps) status.save(commit=True) api.send_stats(server, { 'player_stats': player_stats, 'session': mojang_status.session, 'account': mojang_status.account, 'auth': mojang_status.auth }) _handle_groups(server, server_status.get('groups', []))
def _query_server(server, mojang_status): server_status = api.get_server_status(server) or {} player_stats = [] players_to_sync_ban = Player.query.filter( Player.uuid.in_(server_status.get('banned_uuids', [])), Player.banned == False ).all() if players_to_sync_ban: player_ids_to_sync_ban = [x.id for x in players_to_sync_ban] Player.query.filter( Player.id.in_(player_ids_to_sync_ban) ).update({ 'banned': True, }, synchronize_session=False) for player in players_to_sync_ban: AuditLog.create( AuditLog.PLAYER_BAN, player_id=player.id, username=player.username, source='server_sync', commit=False ) players = server_status.get('players', []) online_player_ids = [] players_to_nok_ban = [] for player_info in players: username = player_info['username'] uuid = player_info['uuid'] player = Player.query.options( joinedload(Player.titles) ).filter_by(uuid=uuid).first() if player: if player.username != username: h.avoid_duplicate_username(username) player.set_username(username) player.save(commit=False) else: h.avoid_duplicate_username(username) player = Player(username=username, uuid=uuid) player.save(commit=False) statsd.incr('player.created') online_player_ids.append(player.id) last_activity = PlayerActivity.query.filter_by( server=server, player=player ).order_by( PlayerActivity.timestamp.desc() ).first() # if the last activity for this player is an 'exit' activity (or there isn't an activity), # create a new 'enter' activity since they just joined this minute if not last_activity or last_activity.activity_type == PLAYER_ACTIVITY_TYPES['exit']: enter = PlayerActivity(server=server, player=player, activity_type=PLAYER_ACTIVITY_TYPES['enter']) enter.save(commit=False) if server.id == app.config['MAIN_SERVER_ID']: if player.banned: player.banned = False AuditLog.create( AuditLog.PLAYER_UNBAN, player_id=player.id, username=player.username, source='server_sync', commit=False ) nickname_ansi = player_info.get('nickname_ansi') nickname = player_info.get('nickname') player.nickname_ansi = nickname_ansi player.nickname = nickname player.save(commit=False) ip = player_info.get('address') if ip: if not IPTracking.query.filter_by(ip=ip, player=player).first(): existing_player_ip = IPTracking(ip=ip, player=player) existing_player_ip.save(commit=False) if geoip.is_nok(ip): players_to_nok_ban.append((player, ip)) stats = PlayerStats.query.filter_by(server=server, player=player).first() if not stats: stats = PlayerStats(server=server, player=player) stats.last_seen = datetime.utcnow() stats.pvp_logs = player_info.get('pvp_logs') stats.time_spent = (stats.time_spent or 0) + 1 stats.save(commit=False) titles = [{'name': x.name, 'broadcast': x.broadcast} for x in player.titles] player_stats.append({ 'username': player.username, 'uuid': player.uuid, 'minutes': stats.time_spent, 'rank': stats.rank, 'titles': titles }) if len(players) and (float(len(players_to_nok_ban)) / float(len(players))) < 0.5: for player, ip in players_to_nok_ban: libplayer.ban_player(player, with_ip=True, source='query', ip=ip, commit=False) five_minutes_ago = datetime.utcnow() - timedelta(minutes=10) result = PlayerStats.query.filter(PlayerStats.server == server, PlayerStats.last_seen > five_minutes_ago) recent_player_ids = [x.player_id for x in result] # find all players that have recently left and insert an 'exit' activity for them # if their last activity was an 'enter' for player_id in set(recent_player_ids) - set(online_player_ids): latest_activity = PlayerActivity.query.filter_by(server=server, player_id=player_id)\ .order_by(PlayerActivity.timestamp.desc()).first() if latest_activity and latest_activity.activity_type == PLAYER_ACTIVITY_TYPES['enter']: ex = PlayerActivity(server=server, player_id=player_id, activity_type=PLAYER_ACTIVITY_TYPES['exit']) ex.save(commit=False) player_count = server_status.get('numplayers', 0) or 0 cpu_load = server_status.get('load', 0) or 0 tps = server_status.get('tps', 0) or 0 status = ServerStatus(server=server, player_count=player_count, cpu_load=cpu_load, tps=tps) status.save(commit=True) api.send_stats(server, { 'player_stats': player_stats, 'session': mojang_status.session, 'account': mojang_status.account, 'auth': mojang_status.auth }) _handle_groups(server, server_status.get('groups', []))