Ejemplo n.º 1
0
def temp_welcome_standalone(msg, seconds):
    from rcon.recorded_commands import RecordedRcon

    rcon = RecordedRcon(SERVER_INFO)
    prev = rcon.set_welcome_message(msg, save=False)
    queue = get_queue()
    queue.enqueue_in(timedelta(seconds), welcome, prev)
Ejemplo n.º 2
0
def get_prunable_vips(days_of_inactivity=30):
    rcon = RecordedRcon(SERVER_INFO)

    age_limit = datetime.datetime.now() - datetime.timedelta(days=days_of_inactivity)
    vips = rcon.get_vip_ids()
    vips_by_steam_id = {vip["steam_id_64"]: vip["name"] for vip in vips}
    profiles = get_profiles(list(vips_by_steam_id.keys()), 1)

    for player in profiles:
        try:
            last_session = player["sessions"][0]
        except IndexError:
            print(f"""
                VIP Name: {vips_by_steam_id[player["steam_id_64"]]}
                Last seen: N/A
                AKAs: {'  ||  '.join(n["name"] for n in player["names"])}
            """)
            continue
        last_session = last_session["start"] or last_session["end"] or last_session["created"]
        # TODO: Handle no sessions
        if  last_session < age_limit:
            print(f"""
                VIP Name: {vips_by_steam_id[player["steam_id_64"]]}
                Last seen: {last_session.isoformat()}
                AKAs: {'  ||  '.join(n["name"] for n in player["names"])}
            """)
Ejemplo n.º 3
0
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")
Ejemplo n.º 4
0
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")
Ejemplo n.º 5
0
    def get_current_map(self):
        map_ = RecordedRcon(SERVER_INFO).get_map()
        if map_.endswith("_RESTART"):
            map_ = map_.replace("_RESTART", "")

        if map_ not in ALL_MAPS:
            raise ValueError("Invalid current map %s map_")

        return map_
Ejemplo n.º 6
0
def _set_real_vips(rcon: RecordedRcon, struct_log):
    config = RealVipConfig()
    if not config.get_enabled():
        logger.debug("Real VIP is disabled")
        return

    desired_nb_vips = config.get_desired_total_number_vips()
    min_vip_slot = config.get_minimum_number_vip_slot()
    vip_count = rcon.get_vips_count()

    remaining_vip_slots = max(desired_nb_vips - vip_count,
                              max(min_vip_slot, 0))
    rcon.set_vip_slots_num(remaining_vip_slots)
    logger.info("Real VIP set slots to %s", remaining_vip_slots)
Ejemplo n.º 7
0
 def __init__(self):
     self.rcon = Rcon(SERVER_INFO)
     self.rcon_2 = RecordedRcon(SERVER_INFO)
     self.red = get_redis_client()
     self.duplicate_guard_key = "unique_logs"
     self.log_history = self.get_log_history_list()
     logger.info("Registered hooks: %s", HOOKS)
Ejemplo n.º 8
0
def ingame_mods():
    red = _red()
    mods = red.hgetall("moderators") or {}

    if not mods:
        return []

    rcon = RecordedRcon(SERVER_INFO)
    players = rcon.get_players()
    mods_ids = set(v.decode() for v in mods.values())
    ig_mods = []
    for player in players:
        if player['steam_id_64'] in mods_ids:
            ig_mods.append({
                "username": player["name"],
                'steam_id_64': player['steam_id_64']
            })

    return ig_mods
Ejemplo n.º 9
0
def toggle_votekick(rcon: RecordedRcon):
    config = AutoVoteKickConfig()

    condition_type = config.get_condition_type().upper()
    min_online = config.get_min_online_mods()
    min_ingame = config.get_min_ingame_mods()
    condition = all if condition_type == 'AND' else any
    online_mods_ = online_mods()
    ingame_mods_ = ingame_mods(rcon)

    ok_online = len(online_mods_) >= min_online
    ok_ingame = len(ingame_mods_) >= min_ingame

    if condition([ok_ingame, ok_online]):
        logger.debug(
            f"Turning votekick off {condition_type=} {min_online=} {min_ingame=} {ok_online=} {ok_ingame=} {online_mods_=} {ingame_mods_=}"
        )
        rcon.set_votekick_enabled(False)
    else:
        logger.debug(
            f"Turning votekick on {condition_type=} {min_online=} {min_ingame=} {ok_online=} {ok_ingame=} {online_mods_=} {ingame_mods_=}"
        )
        rcon.set_votekick_enabled(True)
Ejemplo n.º 10
0
    def apply_with_retry(self, nb_retry=2):
        success = False

        for i in range(nb_retry):
            try:
                success = self.apply_results()
            except:
                logger.exception("Applying vote map result failed.")
            else:
                break

        if not success:
            logger.warning("Falling back to adding all maps to the rotation")
            RecordedRcon(SERVER_INFO).set_maprotation(ALL_MAPS)
Ejemplo n.º 11
0
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():
        rcon.set_broadcast(struct_log['message'])

    if config.is_welcome():
        rcon.set_welcome_message(struct_log['message'])
Ejemplo n.º 12
0
def run():
    max_fails = 5
    from rcon.settings import SERVER_INFO
    rcon = RecordedRcon(SERVER_INFO)
    recorder = MapsRecorder(rcon)

    while True:
        try:
            recorder.detect_map_change()
            toggle_votekick(rcon)
        except CommandFailedError:
            max_fails -= 1
            if max_fails <= 0:
                logger.exception("Routines 5 failures in a row. Stopping")
                raise
        time.sleep(30)
Ejemplo n.º 13
0
def bluk_vip(name_ids, mode="override"):
    errors = []
    ctl = RecordedRcon(SERVER_INFO)
    vips = ctl.get_vip_ids()
    for vip in vips:
        ctl.do_remove_vip(vip["steam_id_64"])

    for name, steam_id in name_ids:
        try:
            ctl.do_add_vip(name.strip(), steam_id)
        except CommandFailedError:
            error = "Failed to add {name} {steam_id}"
            logger.warning("Retrying once for: %s", error)
            time.sleep(1)
            try:
                ctl.do_add_vip(name.strip(), steam_id)
            except Exception:
                logger.exception(error)
                errors.append(error)

    if not errors:
        errors.append("ALL OK")
    return errors
Ejemplo n.º 14
0
class LiveStats:
    def __init__(self):
        self.rcon = RecordedRcon(SERVER_INFO)
        self.voted_yes_regex = re.compile(".*PV_Favour.*")
        self.voted_no_regex = re.compile(".*PV_Against.*")
        self.red = get_redis_client()

    def _get_player_session_time(self, player):
        if not player or not player.get("profile"):
            #logger.warning("Can't use player profile")
            return -1

        player_time_sec = player.get("profile",
                                     {}).get("current_playtime_seconds", 0)
        if player["name"] == "Dr.WeeD":
            logger.debug("Player %s - playtime sec: %s", player["name"],
                         player_time_sec)
        return player_time_sec

    def _is_log_from_current_session(self, now, player, log):
        if player["name"] == "Dr.WeeD":
            logger.debug(
                "%s %s %s %s", log["timestamp_ms"],
                (now.timestamp() - self._get_player_session_time(player)) *
                1000, log["timestamp_ms"] >=
                (now.timestamp() - self._get_player_session_time(player)) *
                1000, log['raw'])
        return (
            log["timestamp_ms"] >=
            (now.timestamp() - self._get_player_session_time(player)) * 1000)

    def _get_indexed_logs_by_player_for_session(self, now, indexed_players,
                                                logs):
        logs_indexed = {}
        for l in logs:
            player = indexed_players.get(l["player"])
            player2 = indexed_players.get(l["player2"])

            try:
                # Only consider stats for a player from his last connection (so a disconnect reconnect should reset stats) otherwise multiple sessions could be blended into one, even if they are far apart
                if player and self._is_log_from_current_session(
                        now, player, l):
                    logs_indexed.setdefault(l["player"], []).append(l)
                if player2 and self._is_log_from_current_session(
                        now, player2, l):
                    logs_indexed.setdefault(l["player2"], []).append(l)
            except KeyError:
                logger.exception("Invalid log line %s", l)

        return logs_indexed

    def _is_player_death(self, player, log):
        return player["name"] == log["player2"]

    def _is_player_kill(self, player, log):
        return player["name"] == log["player"]

    def _add_kd(self, attacker_key, victim_key, stats, player, log):
        if self._is_player_kill(player, log):
            stats[attacker_key] += 1
        elif self._is_player_death(player, log):
            stats[victim_key] += 1
        else:
            logger.warning(
                "Log line does not belong to player '%s' line: '%s'",
                player["name"],
                log["raw"],
            )

    def _add_kill(self, stats, player, log):
        self._add_kd("kills", "deaths", stats, player, log)

    def _add_tk(self, stats, player, log):
        self._add_kd("teamkills", "deaths_by_tk", stats, player, log)

    def _add_vote(self, stats, player, log):
        if self.voted_no_regex.match(log["raw"]):
            stats["nb_voted_no"] += 1
        elif self.voted_yes_regex.match(log["raw"]):
            stats["nb_voted_yes"] += 1
        else:
            logger.warning(
                "VOTE log line does not match either vote yes or no regex: %s",
                log["raw"],
            )

    def _add_vote_started(self, stats, player, log):
        stats["nb_vote_started"] += 1

    def _streaks_accumulator(self, player, log, stats, streaks):
        action = log["action"]

        if action == "KILL":
            if self._is_player_kill(player, log):
                streaks.kill += 1
                streaks.death = 0
                streaks.teamkills = 0
            elif self._is_player_death(player, log):
                streaks.kill = 0
                streaks.deaths_by_tk = 0
                streaks.death += 1
        if action == "TEAM KILL":
            if self._is_player_kill(player, log):
                streaks.teamkills += 1
            if self._is_player_death(player, log):
                streaks.deaths_by_tk += 1
        stats["kills_streak"] = max(streaks.kill, stats["kills_streak"])
        stats["deaths_without_kill_streak"] = max(
            streaks.death, stats["deaths_without_kill_streak"])
        stats["teamkills_streak"] = max(streaks.teamkills,
                                        stats["teamkills_streak"])
        stats["deaths_by_tk_streak"] = max(streaks.deaths_by_tk,
                                           stats["deaths_by_tk_streak"])

    def get_current_players_stats(self):
        players = self.rcon.get_players()
        if not players:
            logger.debug("No players")
            return {}

        profiles_by_id = {
            profile["steam_id_64"]: profile
            for profile in get_profiles([p["steam_id_64"] for p in players],
                                        nb_sessions=0)
        }
        logger.info("%s players, %s profiles loaded", len(players),
                    len(profiles_by_id))
        oldest_session_seconds = self._get_player_session_time(
            max(players, key=self._get_player_session_time))
        logger.debug("Oldest session: %s", oldest_session_seconds)
        now = datetime.datetime.now()
        min_timestamp = (
            now -
            datetime.timedelta(seconds=oldest_session_seconds)).timestamp()
        logger.debug("Min timestamp: %s", min_timestamp)
        logs = get_recent_logs(min_timestamp=min_timestamp)
        logger.info("%s log lines to process", len(logs["logs"]))

        indexed_players = {p["name"]: p for p in players}
        indexed_logs = self._get_indexed_logs_by_player_for_session(
            now, indexed_players, logs["logs"])
        stats_by_player = {}

        actions_processors = {
            "KILL": self._add_kill,
            "TEAM KILL": self._add_tk,
            "VOTE STARTED": self._add_vote_started,
            "VOTE": self._add_vote,
        }
        for p in players:
            logger.debug("Crunching stats for %s", p)
            player_logs = indexed_logs.get(p["name"], [])
            stats = {
                "player":
                p["name"],
                "steam_id_64":
                p.get("steam_id_64"),
                "steaminfo":
                profiles_by_id.get(p.get("steam_id_64"), {}).get("steaminfo"),
                "kills":
                0,
                "kills_streak":
                0,
                "deaths":
                0,
                "deaths_without_kill_streak":
                0,
                "teamkills":
                0,
                "teamkills_streak":
                0,
                "deaths_by_tk":
                0,
                "deaths_by_tk_streak":
                0,
                "nb_vote_started":
                0,
                "nb_voted_yes":
                0,
                "nb_voted_no":
                0,
                "time_seconds":
                self._get_player_session_time(p),
            }

            streaks = Streaks()

            for l in player_logs:
                action = l["action"]
                processor = actions_processors.get(action,
                                                   lambda **kargs: None)
                processor(stats=stats, player=p, log=l)
                self._streaks_accumulator(p, l, stats, streaks)

            # stats = self._compute_stats(stats)
            stats_by_player[p["name"]] = self._compute_stats(stats)

        return stats_by_player

    def set_live_stats(self):
        snapshot_ts = datetime.datetime.now().timestamp()
        stats = self.get_current_players_stats()
        self.red.set(
            "LIVE_STATS",
            pickle.dumps(dict(snapshot_timestamp=snapshot_ts, stats=stats)),
        )

    def get_cached_stats(self):
        stats = self.red.get("LIVE_STATS")
        if stats:
            stats = pickle.loads(stats)
        return stats

    def _compute_stats(self, stats):
        new_stats = dict(**stats)
        new_stats["kills_per_minute"] = round(
            stats["kills"] / max(stats["time_seconds"] / 60, 1), 2)
        new_stats["deaths_per_minute"] = round(
            stats["deaths"] / max(stats["time_seconds"] / 60, 1), 2)
        new_stats["kill_death_ratio"] = round(
            stats["kills"] / max(stats["deaths"], 1), 2)
        return new_stats
Ejemplo n.º 15
0
    AutoVoteKickConfig,
    CameraConfig,
    InvalidConfigurationError,
    StandardMessages,
)
from rcon.user_config import DiscordHookConfig
from rcon.utils import LONG_HUMAN_MAP_NAMES, map_name
from rcon.utils import MapsHistory
from rcon.watchlist import PlayerWatch
from rcon.workers import temporary_broadcast, temporary_welcome
from .auth import login_required, api_response
from .multi_servers import forward_request, forward_command
from .utils import _get_data

logger = logging.getLogger("rconweb")
ctl = RecordedRcon(SERVER_INFO)


def set_temp_msg(request, func, name):
    data = _get_data(request)
    failed = False
    error = None
    try:
        func(ctl, data["msg"], data["seconds"])
    except Exception as e:
        failed = True
        error = repr(e)

    return api_response(failed=failed, error=error, result=None, command=name)

Ejemplo n.º 16
0
import logging
import re

from rcon.player_history import get_player_profile, player_has_flag
from rcon.recorded_commands import RecordedRcon
from rcon.discord import send_to_discord_audit
from rcon.config import get_config
from rcon.settings import SERVER_INFO
from rcon.game_logs import on_connected

logger = logging.getLogger(__name__)

recorded_rcon = RecordedRcon(SERVER_INFO)


@on_connected
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(
Ejemplo n.º 17
0
def broadcast(msg):
    from rcon.recorded_commands import RecordedRcon

    rcon = RecordedRcon(SERVER_INFO)
    rcon.set_broadcast(msg)
Ejemplo n.º 18
0
def welcome(msg):
    from rcon.recorded_commands import RecordedRcon

    rcon = RecordedRcon(SERVER_INFO)
    rcon.set_welcome_message(msg)
Ejemplo n.º 19
0
            failure = True
            res = None

        audit(func.__name__, request, arguments)
        #logger.debug("%s %s -> %s", func.__name__, arguments, res)
        return JsonResponse({
            "result": res,
            "command": func.__name__,
            "arguments": data,
            "failed": failure
        })

    return wrapper


ctl = RecordedRcon(SERVER_INFO)


def make_table(scoreboard):
    return '\n'.join(["Rank  Name                  Ratio Kills Death"] + [
        f"{('#'+ str(idx+1)).ljust(6)}{obj['player'].ljust(22)}{obj['ratio'].ljust(6)}{str(obj['(real) kills']).ljust(6)}{str(obj['(real) death']).ljust(5)}"
        for idx, obj in enumerate(scoreboard)
    ])


@csrf_exempt
def text_scoreboard(request):
    try:
        minutes = abs(int(request.GET.get('minutes')))
    except (ValueError, KeyError, TypeError):
        minutes = 180
Ejemplo n.º 20
0
                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)


if __name__ == "__main__":
    from rcon.settings import SERVER_INFO
    log = {
        'version': 1,
        'timestamp_ms': 1627734269000,
        'relative_time_ms': 221.212,
        'raw': '[543 ms (1627734269)] CONNECTED Dr.WeeD',
        'line_without_time': 'CONNECTED Dr.WeeD',
        'action': 'CONNECTED',
        'player': 'Dr.WeeD',
        'steam_id_64_1': None,
        'player2': None,
        'steam_id_64_2': None,
        'weapon': None,
        'message': 'Dr.WeeD',
        'sub_content': None
    }
    real_vips(RecordedRcon(SERVER_INFO), struct_log=log)
Ejemplo n.º 21
0
class VoteMap:
    def __init__(self) -> None:
        self.red = get_redis_client()

    def is_vote(self, message):
        match = re.match("(\d)", message)
        if not match:
            match = re.match("!votemap\s*(.*)", message)
        if not match:
            return False
        if not match.groups():
            raise InvalidVoteError("You must specify the number of the map")

        return match.groups()[0]

    def register_vote(self, player_name, vote_timestamp, vote_content):
        try:
            current_map = MapsHistory()[0]
            min_time = current_map["start"]
        except IndexError:
            logger.error("Map history is empty - Can't register vote")
            raise
        except KeyError:
            logger.error("Map history is corrupted - Can't register vote")
            raise

        if vote_timestamp < min_time:
            logger.warning(
                f"Vote is too old {player_name=}, {vote_timestamp=}, {vote_content=}, {current_map=}"
            )

        selection = self.get_selection()

        try:
            vote_idx = int(vote_content)
            selected_map = selection[vote_idx]
            self.red.hset("VOTES", player_name, selected_map)
        except (TypeError, ValueError, IndexError):
            raise InvalidVoteError(
                f"Vote must be a number between 0 and {len(selection) - 1}")

        logger.info(
            f"Registered vote from {player_name=} for {selected_map=} - {vote_content=}"
        )
        return selected_map

    def get_vote_overview(self):
        try:
            votes = self.get_votes()
            maps = Counter(votes.values()).most_common()
            return {"total_votes": len(votes), "winning_maps": maps}
        except Exception:
            logger.exception("Can't produce vote overview")

    def clear_votes(self):
        self.red.delete("VOTES")

    def get_votes(self):
        votes = self.red.hgetall("VOTES") or {}
        return {k.decode(): v.decode() for k, v in votes.items()}

    def get_current_map(self):
        map_ = RecordedRcon(SERVER_INFO).get_map()
        if map_.endswith("_RESTART"):
            map_ = map_.replace("_RESTART", "")

        if map_ not in ALL_MAPS:
            raise ValueError("Invalid current map %s map_")

        return map_

    def gen_selection(self):
        config = VoteMapConfig()

        logger.debug(
            f"""Generating new map selection for vote map with the following criteria:
            {ALL_MAPS}
            {config.get_votemap_number_of_options()=} 
            {config.get_votemap_ratio_of_offensives_to_offer()=} 
            {config.get_votemap_number_of_last_played_map_to_exclude()=} 
            {config.get_votemap_consider_offensive_as_same_map()=} 
            {config.get_votemap_allow_consecutive_offensives()=} 
            {config.get_votemap_allow_consecutive_offensives_of_opposite_side()=} 
            {config.get_votemap_default_method()=}
        """)
        selection = suggest_next_maps(
            MapsHistory(),
            ALL_MAPS,
            selection_size=config.get_votemap_number_of_options(),
            exclude_last_n=config.
            get_votemap_number_of_last_played_map_to_exclude(),
            offsensive_ratio=config.get_votemap_ratio_of_offensives_to_offer(),
            consider_offensive_as_same_map=config.
            get_votemap_consider_offensive_as_same_map(),
            allow_consecutive_offensive=config.
            get_votemap_allow_consecutive_offensives(),
            allow_consecutive_offensives_of_opposite_side=config.
            get_votemap_allow_consecutive_offensives_of_opposite_side(),
            current_map=self.get_current_map(),
        )
        self.red.delete("MAP_SELECTION")
        self.red.lpush("MAP_SELECTION", *selection)
        logger.info("Saved new selection: %s", selection)

    def get_selection(self):
        return [v.decode() for v in self.red.lrange("MAP_SELECTION", 0, -1)]

    def pick_least_played_map(self, maps):
        maps_history = MapsHistory()

        if not maps:
            raise ValueError("Can't pick a default. No maps to pick from")

        history = [obj["name"] for obj in maps_history]
        index = 0
        for name in maps:
            try:
                idx = history.index(name)
            except ValueError:
                return name
            index = max(idx, index)

        return history[index]

    def pick_default_next_map(self):
        selection = self.get_selection()
        maps_history = MapsHistory()
        config = VoteMapConfig()
        all_maps = ALL_MAPS

        if not config.get_votemap_allow_default_to_offsensive():
            logger.debug(
                "Not allowing default to offensive, removing all offensive maps"
            )
            selection = [m for m in selection if not "offensive" in m]
            all_maps = [m for m in ALL_MAPS if not "offensive" in m]

        if not maps_history:
            raise ValueError("Map history is empty")

        return {
            DefaultMethods.least_played_suggestions:
            partial(self.pick_least_played_map, selection),
            DefaultMethods.least_played_all_maps:
            partial(self.pick_least_played_map, all_maps),
            DefaultMethods.random_all_maps:
            lambda: random.choice(
                list(set(all_maps) - set([maps_history[0]["name"]]))),
            DefaultMethods.random_suggestions:
            lambda: random.choice(
                list(set(selection) - set([maps_history[0]["name"]]))),
        }[config.get_votemap_default_method()]()

    def apply_results(self):
        config = VoteMapConfig()
        votes = self.get_votes()
        first = Counter(votes.values()).most_common(1)
        if not first:
            next_map = self.pick_default_next_map()
            logger.warning(
                "No votes recorded, defaulting with %s using default winning map %s",
                config.get_votemap_default_method(),
                next_map,
            )
        else:
            logger.info(f"{votes=}")
            next_map = first[0][0]
            if next_map not in ALL_MAPS:
                raise ValueError(
                    f"{next_map=} is not part of the all map list {ALL_MAPS=}")
            if next_map not in (selection := self.get_selection()):
                raise ValueError(
                    f"{next_map=} is not part of vote select {selection=}")
            logger.info(f"Winning map {next_map=}")

        # Sanity checks below
        rcon = RecordedRcon(SERVER_INFO)
        current_map = rcon.get_map()
        if current_map.replace("_RESTART", "") not in ALL_MAPS:
            raise ValueError(
                f"{current_map=} is not part of the all map list {ALL_MAPS=}")

        try:
            history_current_map = MapsHistory()[0]["name"]
        except (IndexError, KeyError):
            raise ValueError("History is empty")

        if current_map != history_current_map:
            raise ValueError(
                f"{current_map=} does not match history map {history_current_map=}"
            )

        # Apply rotation safely
        current_rotation = rcon.get_map_rotation()
        try:
            current_map_idx = current_rotation.index(
                current_map.replace("_RESTART", ""))
        except ValueError:
            logger.warning(
                f"{current_map=} is not in {current_rotation=} will try to add"
            )
            rcon.do_add_map_to_rotation(current_map.replace("_RESTART", ""))

        current_rotation = rcon.get_map_rotation()
        try:
            current_map_idx = current_rotation.index(
                current_map.replace("_RESTART", ""))
        except ValueError as e:
            raise ValueError(
                f"{current_map=} is not in {current_rotation=} addint it failed"
            ) from e

        try:
            next_map_idx = current_rotation.index(next_map)
        except ValueError:
            logger.info(
                f"{next_map=} no in rotation, adding it {current_rotation=}")
            rcon.do_add_map_to_rotation(next_map)
        else:
            if next_map_idx < current_map_idx:
                logger.info(
                    f"{next_map=} is before {current_map=} removing and re-adding"
                )
                rcon.do_remove_map_from_rotation(next_map)
                rcon.do_add_map_to_rotation(next_map)

        current_rotation = rcon.get_map_rotation()

        try:
            next_map_idx = current_rotation.index(next_map)
            if next_map_idx < current_map_idx:
                raise ValueError(f"{next_map=} is still before {current_map=}")
        except ValueError as e:
            raise ValueError(
                f"{next_map=} failed to be added to {current_rotation=}")

        maps_to_remove = current_rotation[current_map_idx + 1:next_map_idx]
        logger.debug(f"{current_map=} {current_rotation=} - {maps_to_remove=}")

        for map in maps_to_remove:
            # Remove the maps that are in between the current map and the desired next map
            rcon.do_remove_map_from_rotation(map)

        for map in maps_to_remove:
            rcon.do_add_map_to_rotation(map)

        # Check that it worked
        current_rotation = rcon.get_map_rotation()
        if (not current_rotation[current_map_idx] == current_map.replace(
                '_RESTART', '')
                and current_rotation[current_map_idx + 1] == next_map):
            raise ValueError(
                f"Applying the winning map {next_map=} after the {current_map=} failed: {current_rotation=}"
            )

        logger.info(
            f"Successfully applied winning mapp {next_map=}, new rotation {current_rotation=}"
        )
        return True
Ejemplo n.º 22
0
    rcon = RecordedRcon(SERVER_INFO)

    age_limit = datetime.datetime.now() - datetime.timedelta(days=days_of_inactivity)
    vips = rcon.get_vip_ids()
    vips_by_steam_id = {vip["steam_id_64"]: vip["name"] for vip in vips}
    profiles = get_profiles(list(vips_by_steam_id.keys()), 1)

    for player in profiles:
        try:
            last_session = player["sessions"][0]
        except IndexError:
            print(f"""
                VIP Name: {vips_by_steam_id[player["steam_id_64"]]}
                Last seen: N/A
                AKAs: {'  ||  '.join(n["name"] for n in player["names"])}
            """)
            continue
        last_session = last_session["start"] or last_session["end"] or last_session["created"]
        # TODO: Handle no sessions
        if  last_session < age_limit:
            print(f"""
                VIP Name: {vips_by_steam_id[player["steam_id_64"]]}
                Last seen: {last_session.isoformat()}
                AKAs: {'  ||  '.join(n["name"] for n in player["names"])}
            """)


if __name__ == '__main__':
    #print(get_prunable_vips())
    light_get_vips_count(RecordedRcon(SERVER_INFO))
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
 def __init__(self):
     self.rcon = RecordedRcon(SERVER_INFO)
     self.voted_yes_regex = re.compile(".*PV_Favour.*")
     self.voted_no_regex = re.compile(".*PV_Against.*")
     self.red = get_redis_client()