def flag_player(request): data = _get_data(request) res = None try: player, flag = add_flag_to_player( steam_id_64=data["steam_id_64"], flag=data["flag"], comment=data.get("comment"), ) res = flag send_to_discord_audit( "`flag`: steam_id_64: `{}` player: `{}` flag: `{}`comment:`{}`".format( data["steam_id_64"], " | ".join(n["name"] for n in player["names"]), flag["flag"], data.get("comment", ""), ), request.user.username, ) except KeyError: logger.warning("Missing parameters") # TODO return 400 except CommandFailedError: logger.exception("Failed to flag") return JsonResponse( {"result": res, "command": "flag_player", "arguments": data, "failed": not res} )
def auto_kick(_, log): try: config = get_config().get('NAME_KICKS') except KeyError: logger.error("Invalid configuration file, NAME_KICKS key is missing") return for r in config['regexps']: name = log["player"] info = recorded_rcon.get_player_info(name) try: profile = get_player_profile(info["steam_id_64"], 0) for f in config.get("whitelist_flags", []): if player_has_flag(profile, f): logger.debug( "Not checking nickname validity for whitelisted player %s (%s)", name, info["steam_id_64"]) return except: logger.exception("Unable to check player profile") if re.match(r, name): logger.info("%s matched player %s", r, name) recorded_rcon.do_kick(player=name, reason=config["reason"], by="NAME_KICK") try: send_to_discord_audit( f"`{name}` kicked from regexp `{r}`", by="NAME_KICK", webhookurl=config.get("discord_webhook_url")) except Exception: logger.error("Unable to send to audit_log") return
def ban_if_blacklisted(rcon: RecordedRcon, steam_id_64, name): with enter_session() as sess: player = get_player(sess, steam_id_64) if not player: logger.error("Can't check blacklist, player not found %s", steam_id_64) return if player.blacklist and player.blacklist.is_blacklisted: try: logger.info("Player %s was banned due blacklist, reason: %s", str(name), player.blacklist.reason) rcon.do_perma_ban(player=name, reason=player.blacklist.reason, by=f"BLACKLIST: {player.blacklist.by}") safe_save_player_action(rcon=rcon, player_name=name, action_type="PERMABAN", reason=player.blacklist.reason, by=f"BLACKLIST: {player.blacklist.by}", steam_id_64=steam_id_64) try: send_to_discord_audit( f"`BLACKLIST` -> {dict_to_discord(dict(player=name, reason=player.blacklist.reason))}", "BLACKLIST") except: logger.error("Unable to send blacklist to audit log") except: send_to_discord_audit( "Failed to apply ban on blacklisted players, please check the logs and report the error", "ERROR")
def set_real_vip_config(request): error = None data = _get_data(request) try: config = RealVipConfig() real_vip_config = { "enabled": (bool, config.set_enabled), "desired_total_number_vips": (int, config.set_desired_total_number_vips), "minimum_number_vip_slot": (int, config.set_minimum_number_vip_slot), } for k, v in data.items(): if k in real_vip_config: cast, setter = real_vip_config[k] send_to_discord_audit(f"RealVIP set {dict_to_discord({k: v})}", request.user.username) setter(cast(v)) except Exception as e: logger.exception("Failed to set realvip config") error = repr(e) return api_response( result=_get_real_vip_config(), failed=bool(error), error=error, command="get_real_vip_config", )
def detect_map_change(self): try: current_map = self.rcon.get_map() except CommandFailedError: logger.info("Faied to get current map. Skipping") return logger.debug("Checking for map change current: %s prev: %s", current_map, self.prev_map) if self.prev_map != current_map: if self.prev_map in ALL_MAPS: self.maps_history.save_map_end(self.prev_map) if current_map in ALL_MAPS: self.maps_history.save_new_map(current_map) logger.info( "Map change detected updating state. Prev map %s New Map %s", self.prev_map, current_map, ) send_to_discord_audit( f"map change detected {dict_to_discord(dict(previous=self.prev_map, new=current_map))}", by="MAP_RECORDER", silent=False) self.prev_map = current_map self.last_map_change_time = datetime.now() return True return False
def detect_map_change(self): try: current_map = self.rcon.get_map() except Exception: logger.info("Faied to get current map. Skipping") return logger.debug("Checking for map change current: %s prev: %s", current_map, self.prev_map) if self.prev_map != current_map: if (self.prev_map and self.prev_map.replace("_RESTART", "") in ALL_MAPS and current_map and current_map.replace("_RESTART", "") in ALL_MAPS): self.maps_history.save_map_end(self.prev_map) if current_map and current_map.replace("_RESTART", "") in ALL_MAPS: self.maps_history.save_new_map(current_map) logger.info( "Map change detected updating state. Prev map %s New Map %s", self.prev_map, current_map, ) if not os.getenv("SILENT_MAP_RECORDER", None): send_to_discord_audit( f"map change detected {dict_to_discord(dict(previous=self.prev_map, new=current_map))}", by="MAP_RECORDER", silent=False, ) on_map_change(self.prev_map, current_map) self.prev_map = current_map return True return False
def do_service(request): data = _get_data(request) client = get_supervisor_client() error = None res = None actions = { 'START': client.supervisor.startProcess, 'STOP': client.supervisor.stopProcess } action = data.get('action') service_name = data.get('service_name') if not action or action.upper() not in actions: return api_response(error="action must be START or STOP", status_code=400) if not service_name: return api_response(error="process_name must be set", status_code=400) try: res = actions[action.upper()](service_name) send_to_discord_audit(f"do_service {service_name} {action}", request.user.username) except Fault as e: error = repr(e) return api_response(result=res, failed=bool(error), error=error)
def ban_if_blacklisted(rcon, steam_id_64, name): with enter_session() as sess: player = get_player(sess, steam_id_64) if not player: logger.error("Can't check blacklist, player not found %s", steam_id_64) return if player.blacklist and player.blacklist.is_blacklisted: logger.info("Player %s was banned due blacklist, reason: %s", str(player), player.blacklist.reason) rcon.do_perma_ban(name, player.blacklist.reason) # TODO save author of blacklist safe_save_player_action(rcon=rcon, player_name=name, action_type="PERMABAN", reason=player.blacklist.reason, by='BLACKLIST', steam_id_64=steam_id_64) try: send_to_discord_audit( f"`BLACKLIST` -> {dict_to_discord(dict(player=name, reason=player.blacklist.reason))}", "BLACKLIST") except: logger.error("Unable to send blacklist to audit log")
def ban_if_has_vac_bans(rcon: RecordedRcon, steam_id_64, name): try: max_days_since_ban = int(MAX_DAYS_SINCE_BAN) max_game_bans = ( float("inf") if int(MAX_GAME_BAN_THRESHOLD) <= 0 else int(MAX_GAME_BAN_THRESHOLD) ) except ValueError: # No proper value is given logger.error( "Invalid value given for environment variable BAN_ON_VAC_HISTORY_DAYS or MAX_GAME_BAN_THRESHOLD" ) return if max_days_since_ban <= 0: return # Feature is disabled with enter_session() as sess: player = get_player(sess, steam_id_64) if not player: logger.error("Can't check VAC history, player not found %s", steam_id_64) return bans = get_player_bans(steam_id_64) if not bans or not isinstance(bans, dict): logger.warning( "Can't fetch Bans for player %s, received %s", steam_id_64, bans ) # Player couldn't be fetched properly (logged by get_player_bans) return if should_ban(bans, max_game_bans, max_days_since_ban): reason = AUTO_BAN_REASON.format( DAYS_SINCE_LAST_BAN=bans.get("DaysSinceLastBan"), MAX_DAYS_SINCE_BAN=str(max_days_since_ban), ) logger.info( "Player %s was banned due VAC history, last ban: %s days ago", str(player), bans.get("DaysSinceLastBan"), ) rcon.do_perma_ban(player=name, reason=reason, by="VAC BOT") try: audit_params = dict( player=name, steam_id_64=player.steam_id_64, reason=reason, days_since_last_ban=bans.get("DaysSinceLastBan"), vac_banned=bans.get("VACBanned"), number_of_game_bans=bans.get("NumberOfGameBans"), ) send_to_discord_audit( f"`VAC/GAME BAN` -> {dict_to_discord(audit_params)}", "AUTOBAN" ) except: logger.exception("Unable to send vac ban to audit log")
def async_upload_vips(request): errors = [] send_to_discord_audit("upload_vips", request.user.username) # Handle file upload vips = [] if request.method == "POST": for name, data in request.FILES.items(): for l in data: try: l = l.decode() if not l: continue try: steam_id, name = l.split(" ", 1) except ValueError: steam_id, name = l.split("\t", 1) if len(steam_id) != 17: errors.append( f"{l} has an invalid steam id, expecter length of 17" ) continue if not name: errors.append( f"{l} doesn't have a name attached to the steamid") continue vips.append((name, steam_id)) except UnicodeDecodeError: errors.append( "File encoding is not supported. Must use UTF8") break except Exception as e: errors.append(f"Error on line {l} {repr(2)}") else: return api_response(error="Bad method", status_code=400) if vips: worker_bulk_vip(vips, job_key=f"upload_vip_{os.getenv('SERVER_NUMBER')}", mode="override") else: errors.append("No vips submitted") # Render list page with the documents and the form return api_response( result="Job submitted, will take several minutes", failed=bool(errors), error="\n".join(errors), command="async_upload_vips", )
def audit(func_name, request, arguments): dont_audit = ["get_"] try: if any(func_name.startswith(s) for s in dont_audit): return args = dict(**arguments) try: del args["by"] except KeyError: pass arguments = " ".join([f"{k}: `{v}`" for k, v in args.items()]) send_to_discord_audit("`{}`: {}".format(func_name, arguments), request.user.username) except: logger.exception("Can't send audit log")
def upload_vips(request): message = "Upload a VIP file!" send_to_discord_audit("upload_vips", request.user.username) # Handle file upload if request.method == "POST": form = DocumentForm(request.POST, request.FILES) if form.is_valid(): message = "" vips = ctl.get_vip_ids() for vip in vips: ctl.do_remove_vip(vip["steam_id_64"]) message = f"{len(vips)} removed\n" count = 0 for name, data in request.FILES.items(): if name.endswith(".json"): message = "JSON is not handled yet" break else: for l in data: try: l = l.decode() steam_id, name = l.split(" ", 1) if len(steam_id) != 17: raise ValueError ctl.do_add_vip(name.strip(), steam_id) count += 1 except UnicodeDecodeError: message = "File encoding is not supported. Must use UTF8" break except ValueError: message += f"Line: '{l}' is invalid, skipped\n" except CommandFailedError: message = "The game serveur returned an error while adding a VIP. You need to upload again" break message += f"{count} added" else: message = "The form is not valid. Fix the following error:" else: form = DocumentForm() # An empty, unbound form # Render list page with the documents and the form context = {"form": form, "message": message} return render(request, "list.html", context)
def unflag_player(request): # Note is this really not restful data = _get_data(request) res = None try: player, flag = remove_flag(data["flag_id"]) res = flag send_to_discord_audit( "`unflag`: flag: `{}` player: `{}`".format( flag["flag"], " | ".join(n["name"] for n in player["names"]) ), request.user.username, ) except KeyError: logger.warning("Missing parameters") # TODO return 400 except CommandFailedError: logger.exception("Failed to remove flag") return JsonResponse( {"result": res, "command": "flag_player", "arguments": data, "failed": not res} )
def set_standard_messages(request): failed = False data = _get_data(request) try: msgs = StandardMessages() res = msgs.set_messages(data["message_type"], data["messages"]) send_to_discord_audit("set_standard_messages", request.user.username) except CommandFailedError as e: failed = True res = repr(e) except: logger.exception("Error setting standard messages config") failed = True res = "Error setting standard messages config" return JsonResponse({ "result": res, "command": "get_standard_messages", "arguments": data, "failed": failed, })
def notify_camera(rcon: RecordedRcon, struct_log): send_to_discord_audit(message=struct_log["message"], by=struct_log["player"]) try: if hooks := get_prepared_discord_hooks("camera"): embeded = DiscordEmbed( title=f'{struct_log["player"]} - {struct_log["steam_id_64_1"]}', description=struct_log["sub_content"], color=242424, ) for h in hooks: h.add_embed(embeded) h.execute() except Exception: logger.exception("Unable to forward to hooks") config = CameraConfig() if config.is_broadcast(): temporary_broadcast(rcon, struct_log["message"], 60) if config.is_welcome(): temporary_welcome(rcon, struct_log["message"], 60)
def auto_ban_if_tks_right_after_connection(rcon: RecordedRcon, log): config = get_config() config = config.get("BAN_TK_ON_CONNECT") if not config or not config.get("enabled"): return player_name = log["player"] player_steam_id = log["steam_id_64_1"] player_profile = None vips = {} try: player_profile = get_player_profile(player_steam_id, 0) except: logger.exception("Unable to get player profile") try: vips = set(v['steam_id_64'] for v in rcon.get_vip_ids()) except: logger.exception("Unable to get VIPS") last_logs = get_recent_logs(end=500, player_search=player_name, exact_player_match=True) logger.debug("Checking TK from %s", player_name) author = config.get("author_name", "Automation") reason = config.get("message", "No reasons provided") discord_msg = config.get("discord_webhook_message", "No message provided") webhook = config.get("discord_webhook_url") max_time_minute = config.get("max_time_after_connect_minutes", 5) excluded_weapons = [w.lower() for w in config.get("exclude_weapons", [])] ignore_after_kill = config.get("ignore_tk_after_n_kills", 1) ignore_after_death = config.get("ignore_tk_after_n_death", 1) whitelist_players = config.get("whitelist_players", {}) tk_tolerance_count = config.get("teamkill_tolerance_count", 1) if player_profile: if whitelist_players.get('is_vip') and player_steam_id in vips: logger.debug("Not checking player because he's VIP") return if whitelist_players.get('has_at_least_n_sessions') and player_profile[ 'sessions_count'] >= whitelist_players.get( 'has_at_least_n_sessions'): logger.debug("Not checking player because he has %s sessions", player_profile['sessions_count']) return flags = whitelist_players.get('has_flag', []) if not isinstance(flags, list): flags = [flags] for f in flags: if player_has_flag(player_profile, f): logger.debug("Not checking player because he has flag %s", f) return last_action_is_connect = False last_connect_time = None kill_counter = 0 death_counter = 0 tk_counter = 0 for log in reversed(last_logs["logs"]): logger.debug(log) if log["action"] == "CONNECTED": last_action_is_connect = log last_connect_time = log["timestamp_ms"] kill_counter = 0 death_counter = 0 continue if log["action"] == "TEAM KILL" and log[ 'player'] == player_name and last_action_is_connect: if excluded_weapons and log["weapon"].lower() in excluded_weapons: logger.debug( "Not counting TK as offense due to weapon exclusion") continue if log['timestamp_ms'] - last_connect_time > max_time_minute * 60 * 1000: logger.debug( "Not counting TK as offense due to elapsed time exclusion, last connection time %s, tk time %s", datetime.datetime.fromtimestamp(last_connect_time / 1000), datetime.datetime.fromtimestamp(log["timestamp_ms"])) continue logger.info("Banning player %s for TEAMKILL after connect %s", player_name, log) tk_counter += 1 if tk_counter > tk_tolerance_count: rcon.do_perma_ban( player=player_name, reason=reason, by=author, ) send_to_discord_audit(discord_msg.format(player=player_name), by=author, webhookurl=webhook) elif is_player_death(player_name, log): death_counter += 1 if death_counter >= ignore_after_death: last_action_is_connect = False elif is_player_kill(player_name, log): kill_counter += 1 if kill_counter >= ignore_after_kill: last_action_is_connect = False