async def send_account(channel: discord.TextChannel, a_player: classes.ActivePlayer): """ Actually send its account to the player. :param channel: Current match channel. :param a_player: Player to send the account to. """ msg = None # Try 3 times to send a DM: ctx = a_player.account.get_new_context(ContextWrapper.user(a_player.id)) for j in range(3): try: msg = await disp.ACC_UPDATE.send(ctx, account=a_player.account) break except discord.errors.Forbidden: pass if not msg: # Else validate the account and send it to staff channel instead await disp.ACC_CLOSED.send(channel, a_player.mention) await a_player.account.validate() msg = await disp.ACC_STAFF.send(ContextWrapper.channel( cfg.channels["staff"]), f'<@&{cfg.roles["admin"]}>', a_player.mention, account=a_player.account) # Set the account message, log the account: a_player.account.message = msg await disp.ACC_LOG.send(ContextWrapper.channel(cfg.channels["spam"]), a_player.name, a_player.id, a_player.account.id)
async def on_rule_accept(player, interaction_id, interaction, interaction_values): user = interaction.user if modules.loader.is_all_locked(): raise modules.interactions.InteractionNotAllowed # reaction to the rule message? p = Player.get(user.id) if not p: # if new player # create a new profile p = Player(user.id, user.name) await modules.roles.role_update(p) await modules.database.async_db_call(modules.database.set_element, "users", p.id, p.get_data()) await disp.REG_RULES.send( ContextWrapper.channel(cfg.channels["register"]), user.mention) elif p.is_away: p.is_away = False await modules.roles.role_update(p) await p.db_update("away") await disp.AWAY_BACK.send( ContextWrapper.channel(cfg.channels["register"]), p.mention) else: i_ctx = InteractionContext(interaction) await disp.REG_RULES_ALREADY.send(i_ctx) await modules.roles.role_update(p)
async def on_ready(): # Initialise matches channels Match.init_channels(client, cfg.channels["matches"]) modules.roles.init(client) # Init signal handler modules.signal.init() # fetch rule message, remove all reaction but the bot's channel = client.get_channel(cfg.channels["rules"]) msg = await channel.fetch_message(channel.last_message_id) if msg.author.id == client.user.id: ctx = _interactions_handler.get_new_context(msg) await disp.RULES.edit(ctx) else: ctx = _interactions_handler.get_new_context(channel) await disp.RULES.send(ctx) # Update all players roles for p in Player.get_all_players_list(): await modules.roles.role_update(p) _add_main_handlers(client) if not modules.lobby.get_all_names_in_lobby(): try: last_lobby = modules.database.get_field( "restart_data", 0, "last_lobby") except KeyError: pass else: for p_id in last_lobby: try: player = Player.get(int(p_id)) if player and not modules.lobby.is_lobby_stuck( ) and player.is_registered: modules.lobby.add_to_lobby(player) except ValueError: pass modules.database.set_field("restart_data", 0, {"last_lobby": list()}) names = modules.lobby.get_all_names_in_lobby() if names: await disp.LB_QUEUE.send( ContextWrapper.channel(cfg.channels["lobby"]), names_in_lobby=modules.lobby.get_all_names_in_lobby()) modules.loader.unlock_all(client) log.info('Client is ready!') await disp.RDY.send(ContextWrapper.channel(cfg.channels["spam"]), cfg.VERSION)
async def init(self): for p in self.p_list: self.players[p.id] = p await p.on_match_selected(self.match.proxy) # print(f"{p.name}, matches: {p.stats.nb_matches_played}") # ctx = ContextWrapper.user(p.id) # try: # await disp.MATCH_DM_PING.send(ctx, match.id, match.channel.name) # except discord.errors.Forbidden: # log.warning(f"Player id:[{p.id}], name:[{p.name}] is refusing DMs") # Open match channel await roles.modify_match_channel(self.match.channel, view=True) await disp.LB_MATCH_STARTING.send( ContextWrapper.channel(cfg.channels["lobby"]), self.match.channel.id) players_ping = " ".join(p.mention for p in self.players.values()) await disp.MATCH_INIT.send(self.match.channel, players_ping) # Initialize teams self.match.teams[0] = Team(0, f"Team 1", self.match.proxy) self.match.teams[1] = Team(1, f"Team 2", self.match.proxy) await self.info() self.auto_captain.start() await disp.CAP_AUTO_ANNOUNCE.send(self.match.channel)
def get_new_context(self, ctx): self.__locked = True if self.__msg: self.clean() ctx = ContextWrapper.wrap(ctx) ctx.interaction_payload = self.__payload return ctx
async def _lobby_loop(): for p in _lobby_list: now = tools.timestamp_now() if p.lobby_stamp < (now - 7800): remove_from_lobby(p) await disp.LB_TOO_LONG.send( ContextWrapper.channel(cfg.channels["lobby"]), p.mention, names_in_lobby=get_all_names_in_lobby()) elif p.lobby_stamp < (now - 7200): if p not in _warned_players: ih = interactions.InteractionHandler(p, views.reset_button) _warned_players[p] = ih _add_ih_callback(ih, p) ctx = ih.get_new_context( ContextWrapper.channel(cfg.channels["lobby"])) await disp.LB_WARNING.send(ctx, p.mention)
async def decline(captain, interaction_id, interaction, values): ctx = ContextWrapper.wrap(self.match.channel, author=interaction.user) self.clean() if captain is self.expected: await disp.CONFIRM_DECLINE.send(ctx) else: await disp.CONFIRM_CANCELED.send(ctx)
async def timeout(self, ctx, *args): if len(args) == 1 and args[0] == "help": await disp.RM_TIMEOUT_HELP.send(ctx) return if len(ctx.message.mentions) != 1: await disp.RM_MENTION_ONE.send(ctx) return player = Player.get(ctx.message.mentions[0].id) if not player: # player isn't even registered in the system... player = Player(ctx.message.mentions[0].id, ctx.message.mentions[0].name) await db.async_db_call(db.set_element, "users", player.id, player.get_data()) if player.is_lobbied: lobby.remove_from_lobby(player) await disp.RM_LOBBY.send( ContextWrapper.channel(cfg.channels["lobby"]), player.mention, names_in_lobby=lobby.get_all_names_in_lobby()) if player.match: await disp.RM_IN_MATCH.send(ctx) return if len(args) == 0: if player.is_timeout: await disp.RM_TIMEOUT_INFO.send( ctx, dt.utcfromtimestamp( player.timeout).strftime("%Y-%m-%d %H:%M UTC")) return await roles.role_update(player) await roles.perms_muted(False, player.id) await disp.RM_TIMEOUT_NO.send(ctx) return # =timeout @player remove if len(args) == 1 and args[0] == 'remove': player.timeout = 0 await player.db_update("timeout") await disp.RM_TIMEOUT_FREE.send(ctx, player.mention) await roles.role_update(player) await roles.perms_muted(False, player.id) return # Check if command is correct (=timeout @player 12 d) time = tools.time_calculator(" ".join(args)) if time == 0: await disp.RM_TIMEOUT_INVALID.send(ctx) return end_time = tools.timestamp_now() + time player.timeout = end_time await roles.role_update(player) await player.db_update("timeout") await roles.perms_muted(True, player.id) await disp.RM_TIMEOUT.send( ctx, player.mention, dt.utcfromtimestamp(end_time).strftime("%Y-%m-%d %H:%M UTC"))
async def on_match_over(self): player_pings = [" ".join(tm.all_pings) for tm in self.match.teams] self.auto_info_loop.cancel() self.ih.clean() self.match.plugin_manager.on_round_over() round_no = self.match.round_no self.match.ready_next_process() await disp.MATCH_ROUND_OVER.send(self.match.channel, *player_pings, round_no) try: await census.process_score(self.match.data, self.match.last_start_stamp, self.match.channel) try: await i_maker.publish_match_image(self.match) except Exception as e: # Should not happen log.error(f"Error in publish_match_image : {e}") await disp.PUBLISH_ERROR.send(ContextWrapper.channel(cfg.channels["results"]), self.match.id, round_no) except ApiNotReachable as e: log.error(f"ApiNotReachable caught when processing scores : {e.url}") await disp.API_SCORE_ERROR.send(ContextWrapper.channel(cfg.channels["results"]), self.match.id, round_no) self.match.start_next_process()
async def base_select(captain, interaction_id, interaction, values): author = interaction.user ctx = ContextWrapper.wrap(self.__match.channel, author=interaction.user) try: value = int(values[0]) except (ValueError, IndexError): raise InteractionInvalid("invalid value!") base = self.find_by_id(value) if not base: raise InteractionInvalid("unknown base!") await self.__select_base(ctx, captain, base)
async def on_user_react(p, interaction_id, interaction, interaction_values): user = interaction.user if user.id == player.id: ctx = ContextWrapper.channel(cfg.channels["lobby"]) ctx.author = user reset_timeout(player) await disp.LB_REFRESHED.send(ctx) else: i_ctx = InteractionContext(interaction) await disp.LB_REFRESH_NO.send(i_ctx) raise interactions.InteractionNotAllowed
async def accept(captain, interaction_id, interaction, values): if captain is not self.expected: i_ctx = InteractionContext(interaction) await disp.CONFIRM_NOT_CAPTAIN.send(i_ctx, self.expected.mention) raise InteractionNotAllowed elif self.confirm_func: ctx = ContextWrapper.wrap(self.match.channel, author=interaction.user) kwargs = self.kwargs self.clean() await self.confirm_func(ctx, **kwargs) else: raise InteractionInvalid("no confirm function!")
async def on_answer(self, player, is_accept): if player is self.captains[0] and not player.active: i = 0 elif player is self.captains[1] and not player.active: i = 1 else: return False if is_accept: await self.add_captain(i, player) else: self.captains[i] = None ctx = ContextWrapper.wrap(self.match.channel, author=player) await disp.CAP_DENY_OK.send(ctx) self.auto_captain.restart() await self.get_new_auto(i) return True
async def terminate_account(a_player: classes.ActivePlayer): """ Terminate the account: ask the user to log off and remove the reaction. :param a_player: Player whose account should be terminated. """ # Get account and terminate it acc = a_player.account acc.terminate() # Remove the reaction handler and update the account message await disp.ACC_UPDATE.edit(acc.message, account=acc) # If account was validated, ask the player to log off: if acc.is_validated and acc.message.channel.id != cfg.channels["staff"]: await disp.ACC_OVER.send(ContextWrapper.user(acc.a_player.id)) # If account was validated, update the db with usage if acc.is_validated: # Prepare data p_usage = { "id": acc.id, "time_start": acc.last_usage["time_start"], "time_stop": acc.last_usage["time_stop"], "match_id": a_player.match.id } # Update the account element await db.async_db_call(db.push_element, "accounts_usage", acc.id, {"usages": acc.last_usage}) try: # Update the player element await db.async_db_call(db.push_element, "accounts_usage", a_player.id, {"usages": p_usage}) except db.DatabaseError: # If the player element doesn't exist, create it data = dict() data["_id"] = a_player.id data["unique_usages"] = a_player.unique_usages data["usages"] = [p_usage] await db.async_db_call(db.set_element, "accounts_usage", a_player.id, data) # Reset the account state acc.clean() del _busy_accounts[acc.id] _available_accounts[acc.id] = acc
async def on_dm(message): # Check if too many requests from this user: if await spam_checker.is_spam(message.author, message.channel): return if message.content[:1] == "=": message.content = message.content[1:] if message.content.lower().startswith(("stat", "stats", "s")): await on_stats(message.author) elif message.content.lower().startswith(("modmail ", "dm ", "staff ")): i = message.content.index(' ') message.content = message.content[i + 1:] player = Player.get(message.author.id) await disp.BOT_DM.send(ContextWrapper.channel(cfg.channels["staff"]), player=player, msg=message) await disp.BOT_DM_RECEIVED.send(message.author) elif message.content.lower().startswith(("help", "h")): await disp.HELP.send(message.author, is_dm=True) spam_checker.unlock(message.author.id)
async def is_spam(author, channel, ctx=None): a_id = author.id if a_id in __spam_list and __spam_list[a_id] > 0: if a_id in __last_requests and __last_requests[ a_id] < tools.timestamp_now() - 30: log.info( f"Automatically unlocked id[{a_id}], name[{author.name}] from spam filter" ) unlock(a_id) __last_requests[a_id] = tools.timestamp_now() if a_id not in __spam_list: __spam_list[a_id] = 1 return False __spam_list[a_id] += 1 if __spam_list[a_id] == 1: return False if __spam_list[a_id] % __SPAM_MSG_FREQUENCY == 0: if not ctx: ctx = ContextWrapper.wrap(channel, author=author) await disp.STOP_SPAM.send(ctx) return True
async def unregister(self, ctx): if not await _check_channels(ctx, cfg.channels["register"]): return player = await get_check_player(ctx) if not player: return if player.is_lobbied: lobby.remove_from_lobby(player) await disp.RM_LOBBY.send( ContextWrapper.channel(cfg.channels["lobby"]), player.mention, names_in_lobby=lobby.get_all_names_in_lobby()) if not player.match: try: await db.async_db_call(db.remove_element, "users", player.id) except db.DatabaseError: pass # ignored if not yet in db await roles.remove_roles(player.id) player.remove() await disp.RM_OK.send(ctx) return await disp.RM_IN_MATCH.send(ctx)
async def remove(self, ctx): if ctx.channel.id == cfg.channels["lobby"]: player = await get_check_player(ctx) if not player: return if player.is_lobbied: lobby.remove_from_lobby(player) await disp.RM_LOBBY.send( ContextWrapper.channel(cfg.channels["lobby"]), player.mention, names_in_lobby=lobby.get_all_names_in_lobby()) return await disp.RM_NOT_LOBBIED.send(ctx) return # if ctx.channel.id == cfg.channels["register"]: # player = await get_check_player(ctx) # if not player: # return # #TODO: remove ig names else: await disp.WRONG_CHANNEL_2.send(ctx, ctx.command.name, f"<#{ctx.channel.id}>")
async def launch(ctx, id_list, tier): print("TIER 1") players = list() for p_id in id_list: player = Player.get(p_id) if not player: print(f"user {p_id}") user = await bot.fetch_user(p_id) player = Player(user.id, user.name) await db.async_db_call(db.set_element, "users", player.id, player.get_data()) await player.register(None) players.append(player) for p in players: lobby.add_to_lobby(p) if tier == 1: return print("TIER 2") await asyncio.sleep(1) match = players[0].match while match.status is not MatchStatus.IS_CAPTAIN: await asyncio.sleep(1) cap_1_ctx = ContextWrapper.wrap(ctx.channel) cap_1_ctx.message = ctx.message cap_1_ctx.author = ctx.guild.get_member(players[0].id) await match.on_volunteer(players[0]) cap_2_ctx = ContextWrapper.wrap(ctx.channel) cap_2_ctx.message = ctx.message cap_2_ctx.author = ctx.guild.get_member(players[1].id) await match.on_volunteer(players[1]) if tier == 2: return print("TIER 3") while match.status is not MatchStatus.IS_PICKING: await asyncio.sleep(1) picked = ContextWrapper.user(players[2].id) cap_1_ctx.message.mentions.clear() cap_1_ctx.message.mentions.append(picked.author) await match.command.pick(cap_1_ctx, [""]) if tier == 3: return print("TIER 4") while match.status is not MatchStatus.IS_FACTION: await asyncio.sleep(1) cap_2_ctx.message.mentions.clear() cap_1_ctx.message.mentions.clear() await match.command.pick(cap_2_ctx, ["VS"]) await match.command.pick(cap_1_ctx, ["TR"]) if tier == 4: return print("TIER 5") while match.status is not MatchStatus.IS_BASING: await asyncio.sleep(1) # We assume tester is an admin await match.command.base(ctx, ["ceres"]) if tier == 5: return print("TIER 6") while match.status is not MatchStatus.IS_WAITING: await asyncio.sleep(1) match.change_check("online") match.change_check("account") await match.command.ready(cap_1_ctx) await match.command.ready(cap_2_ctx)
async def _send_stuck_msg(): await disp.LB_STUCK.send(ContextWrapper.channel(cfg.channels["lobby"]))
async def _auto_ping(): if _MatchClass.find_empty() is None: return await disp.LB_NOTIFY.send(ContextWrapper.channel(cfg.channels["lobby"]), f'<@&{cfg.roles["notify"]}>', get_lobby_len(), cfg.general["lobby_size"])
async def _remove_msg(self, msg, view): try: ctx = ContextWrapper.wrap(msg) await ctx.edit(view=view) except NotFound: log.warning("NotFound exception when trying to remove message!")
async def on_ready(captain, interaction_id, interaction, interaction_values): ctx = ContextWrapper.wrap(self.match.channel, author=interaction.user) ctx.cmd_name = "ready" await self.ready(ctx, captain)
async def process_score(match: 'match.classes.MatchData', start_time: int, match_channel: 'TextChannel' = None): """ Calculate the result score for the MatchData object provided. :param match: MatchData object to fill with scores. :param start_time: Round start timestamp: will process score starting form this time. :param match_channel: Match channel for illegal weapons display (optional). :raise ApiNotReachable: If an API call fail. """ # Temp data structures ig_dict = dict() current_ill_weapons = dict() # Start and end timestamps start = start_time end = start + (match.round_length * 60) # Fill player dictionary (in-game id -> player object) for tm in match.teams: for player in tm.players: if not player.is_disabled: ig_dict[int(player.ig_id)] = player else: print(f"{player.name} is disabled!") # Request url: url = f'http://census.daybreakgames.com/s:{cfg.general["api_key"]}/get/ps2:v2/characters_event/?character_id=' \ f'{",".join(str(p.ig_id) for p in ig_dict.values())}&type=KILL&after={start}&before={end}&c:limit=500' j_data = await http_request(url, retries=5) if j_data["returned"] == 0: raise ApiNotReachable(f"Empty answer on score calculation (url={url})") event_list = j_data["characters_event_list"] ill_weapons = dict() # Loop through all events retrieved: for event in event_list: # Get opponent player oppo = ig_dict.get(int(event["character_id"])) if not oppo: # interaction with outside player, skip it continue opo_loadout = oppo.get_loadout(int(event["character_loadout_id"])) player = ig_dict.get(int(event["attacker_character_id"])) if not player: # interaction with outside player, skip it continue player_loadout = player.get_loadout(int(event["attacker_loadout_id"])) # Get weapon weap_id = int(event["attacker_weapon_id"]) is_hs = int(event["is_headshot"]) == 1 weapon = Weapon.get(weap_id) if not weapon: log.error(f'Weapon not found in database: id={weap_id}') weapon = Weapon.get(0) # Parse event into loadout objects if oppo is player: # Player killed themselves player_loadout.add_one_suicide() elif oppo.team is player.team: # Team-kill player_loadout.add_one_tk() opo_loadout.add_one_death(0) else: # Regular kill if not weapon.is_banned: # If weapon is allowed pts = weapon.points player_loadout.add_one_kill(pts, is_hs) opo_loadout.add_one_death(pts) else: # If weapon is banned, add it to illegal weapons list player_loadout.add_illegal_weapon(weapon.id) if player not in ill_weapons: ill_weapons[player] = AutoDict() ill_weapons[player].auto_add(weapon.id, 1) # Display all banned-weapons uses for this player: for player in ill_weapons.keys(): for weap_id in ill_weapons[player]: weapon = Weapon.get(weap_id) if match_channel: await display.SC_ILLEGAL_WE.send(match_channel, player.mention, weapon.name, match.id, ill_weapons[player][weap_id]) await display.SC_ILLEGAL_WE.send( ContextWrapper.channel(cfg.channels["staff"]), player.mention, weapon.name, match.id, ill_weapons[player][weap_id]) # Also get base captures await get_captures(match, start, end)
def main(launch_str=""): _define_log(launch_str) # Init order MATTERS log.info("Starting init...") # Get data from the config file cfg.get_config(launch_str) # Set up intents intents = Intents.none() intents.guilds = True intents.members = True intents.bans = False intents.emojis = False intents.integrations = False intents.webhooks = False intents.invites = False intents.voice_states = False intents.presences = True intents.messages = True # intents.guild_messages Activated by the previous one # intents.dm_messages Activated by the previous one intents.reactions = False # intents.guild_reactions # intents.dm_reactions intents.typing = False intents.guild_typing = False intents.dm_typing = False client = commands.Bot(command_prefix=cfg.general["command_prefix"], intents=intents) # Remove default help client.remove_command('help') # Initialise db and get all the registered users and all bases from it modules.database.init(cfg.database) modules.database.get_all_elements(Player.new_from_data, "users") modules.database.get_all_elements(Base, "static_bases") modules.database.get_all_elements(Weapon, "static_weapons") # Get Account sheet from drive modules.accounts_handler.init(cfg.GAPI_JSON) # Establish connection with Jaeger Calendar modules.jaeger_calendar.init(cfg.GAPI_JSON) # Initialise display module ContextWrapper.init(client) # Init lobby modules.lobby.init(Match, client) # Init stat processor modules.stat_processor.init() # Add init handlers _add_init_handlers(client) if launch_str == "_test": _test(client) # Add all cogs modules.loader.init(client) # Run server client.run(cfg.general["token"])
async def on_message(client, message): # if bot, do nothing if message.author == client.user: return # if dm, send in staff if isinstance(message.channel, DMChannel): await on_dm(message) return # If message not in the bot's area of action if message.channel.id not in cfg.channels_list: return if len(message.content) == 0: return if message.content == cfg.emojis['info']: message.content = "=info" if message.content[0] != cfg.general["command_prefix"]: return # If bot is locked if is_all_locked(): if not is_admin(message.author): return # Admins can still use bot when locked # Save actual author actual_author = message.author # Check if too many requests from this user: if await spam_checker.is_spam(message.author, message.channel): return try: # Make the message lower-case: if not message.content.lower().startswith("=rename"): message.content = message.content.lower() message.content = message.content.replace(",", " ").replace( "/", " ").replace(";", " ") # Split on whitespaces args = message.content.split() new_args = list() for arg in args: if '@' in arg: continue try: arg_int = int(arg) except ValueError: pass else: if arg_int >= 21154535154122752: # minimum number for discord id member = message.channel.guild.get_member(arg_int) if member: message.mentions.append(member) continue try: member = await message.channel.guild.fetch_member( arg_int) except NotFound: message.mentions.append(FakeMember(arg_int)) continue if member: message.mentions.append(member) continue new_args.append(arg) message.content = " ".join(new_args) # Check for =as command if is_admin(message.author) and message.content[0:3] == "=as": try: message.author = message.mentions[0] del message.mentions[0] i = message.content[1:].index('=') message.content = message.content[i + 1:] except (ValueError, IndexError): ctx = ContextWrapper.wrap(message.channel, author=actual_author) await disp.WRONG_USAGE.send(ctx, "as") spam_checker.unlock(actual_author.id) return await client.process_commands(message) # if not spam, processes # Call finished, we can release user await sleep(0.5) finally: spam_checker.unlock(actual_author.id)