async def reopen(self, ctx): """ Reabre una carrera asíncrona. Comienza a aceptar de nuevo resultados para la carrera asíncrona. Solo funciona en el canal "submit" asociado a la carrera, y solamente si lo usa el creador original de la carrera o un moderador. """ db_conn, db_cur = open_db(ctx.guild.id) race = get_async_by_submit(db_cur, ctx.channel.id) if not race: close_db(db_conn) return if not check_race_permissions(ctx, race[2], race[11]): close_db(db_conn) raise commands.errors.CommandInvokeError( "Esta operación solo puede realizarla el creador original de la carrera o un moderador." ) if race[5] == 1: author = ctx.author async with write_lock: insert_player_if_not_exists(db_cur, author.id, author.name, author.discriminator, author.mention) update_async_status(db_cur, race[0], 0) commit_db(db_conn) close_db(db_conn) await ctx.reply("Esta carrera ha sido reabierta.", mention_author=False) else: close_db(db_conn) raise commands.errors.CommandInvokeError( "Esta carrera no está cerrada.")
async def match(self, ctx, *params): """ Abre un canal de texto para una carrera privada. Opcionalmente, puede asignársele un nombre. Si no se indica, se pondrá un nombre por defecto. Tras el nombre, debe mencionarse a los jugadores o roles que participarán en la carrera. Si no se menciona a ninguno, únicamente el creador de la carrera tendrá acceso al canal. Este comando solo puede ser ejecutado por un moderador. """ db_conn, db_cur = open_db(ctx.guild.id) creator = ctx.author if not params: close_db(db_conn) raise commands.errors.CommandInvokeError("Faltan argumentos para ejecutar el comando.") # Comprobación de límite: máximo de 10 carreras privadas en el servidor races = get_active_private_races(db_cur) if races and len(races) >= 10: close_db(db_conn) raise commands.errors.CommandInvokeError("Demasiadas carreras activas en el servidor. Contacta a un moderador para purgar alguna.") # Comprobación de nombre name = params[0] players = params[1:] if re.match(r'<@[!&]?\d+>', name): name = "carrera-privada" players = params if len(name) > 20: name = name[:20] # Obtener participantes de la carrera participants = [ctx.author] roles = [] for p in players: mention = re.match(r'<@!?(\d+)>', p) if mention: discord_id = int(mention.group(1)) member = ctx.guild.get_member(discord_id) if member: participants.append(member) continue mention = re.match(r'<@&(\d+)>', p) if mention: role_id = int(mention.group(1)) role = ctx.guild.get_role(role_id) if role: roles.append(role) # Crear canal para la carrera channel_overwrites = { ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), ctx.guild.me: discord.PermissionOverwrite(read_messages=True) } for m in participants: channel_overwrites[m] = discord.PermissionOverwrite(read_messages=True) for r in roles: channel_overwrites[r] = discord.PermissionOverwrite(read_messages=True) race_channel = await ctx.guild.create_text_channel(name, overwrites=channel_overwrites) async with write_lock: for p in participants: insert_player_if_not_exists(db_cur, p.id, p.name, p.discriminator, p.mention) insert_private_race(db_cur, name, creator.id, race_channel.id) commit_db(db_conn) close_db(db_conn) text_ans = 'Abierta carrera privada con nombre: {}\nCanal: {}'.format(name, race_channel.mention) await ctx.reply(text_ans, mention_author=False)
async def asyncstart(self, ctx, name: str, *preset): """ Inicia una carrera asíncrona. Debe asignársele obligatoriamente un nombre. Tras el nombre se puede indicar un preset de ALTTPR, en cuyo caso se generará automáticamente una seed. También puede añadirse una descripción cualquiera. Este comando crea aleatoriamente los canales de Discord necesarios para alojar la carrera asíncrona. """ db_conn, db_cur = open_db(ctx.guild.id) # Comprobación de límite: máximo de 10 asíncronas en el servidor asyncs = get_active_async_races(db_cur) if asyncs and len(asyncs) >= 10: close_db(db_conn) raise commands.errors.CommandInvokeError( "Demasiadas asíncronas activas en el servidor. Contacta a un moderador para purgar alguna." ) # Comprobación de nombre válido if re.match(r'https://alttpr\.com/([a-z]{2}/)?h/\w{10}$', name) or is_preset(name): close_db(db_conn) raise commands.errors.CommandInvokeError( "El nombre de la carrera no puede ser un preset o una URL de seed." ) if len(name) > 20: name = name[:20] # Crear o procesar seed seed = None seed_hash = None seed_code = None seed_url = None desc = " ".join(preset) spoiler_file = None async with ctx.typing(): if ctx.message.attachments: attachment = ctx.message.attachments[0] try: seed = await generate_from_attachment(attachment) except: close_db(db_conn) raise commands.errors.CommandInvokeError( "Error al generar la seed. Asegúrate de que el YAML introducido sea válido." ) elif preset: if re.match(r'https://alttpr\.com/([a-z]{2}/)?h/\w{10}$', preset[0]): seed = await generate_from_hash((preset[0]).split('/')[-1]) if seed: desc = " ".join(preset[1:]) else: seed = await generate_from_preset(preset) if seed: seed_url = seed.url if not hasattr(seed, "randomizer"): # VARIA seed_hash = seed.data["seedKey"] elif seed.randomizer in ["sm", "smz3"]: seed_code = " | ".join(seed.code.split()) seed_hash = seed.slug_id else: seed_code = " | ".join(seed.code) seed_hash = seed.hash spoiler_file = get_spoiler(seed) # Crear canales y rol para la async server = ctx.guild async_role = None res_overwrites = None spoiler_overwrites = None if ctx.invoked_with == "blindasync": res_overwrites = { server.default_role: discord.PermissionOverwrite(read_messages=False, send_messages=False), server.me: discord.PermissionOverwrite(read_messages=True, send_messages=True) } spoiler_overwrites = { server.default_role: discord.PermissionOverwrite(read_messages=False), server.me: discord.PermissionOverwrite(read_messages=True) } else: async_role = await server.create_role(name=name) res_overwrites = { server.default_role: discord.PermissionOverwrite(read_messages=False, send_messages=False), server.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), async_role: discord.PermissionOverwrite(read_messages=True) } spoiler_overwrites = { server.default_role: discord.PermissionOverwrite(read_messages=False), server.me: discord.PermissionOverwrite(read_messages=True), async_role: discord.PermissionOverwrite(read_messages=True) } async_category = await server.create_category_channel(name) submit_channel = await server.create_text_channel( "{}-submit".format(name), category=async_category) results_channel = await server.create_text_channel( "{}-results".format(name), category=async_category, overwrites=res_overwrites) spoilers_channel = await server.create_text_channel( "{}-spoilers".format(name), category=async_category, overwrites=spoiler_overwrites) results_text = get_results_text(db_cur, submit_channel.id) results_msg = await results_channel.send(results_text) creator = ctx.author async with write_lock: insert_player_if_not_exists(db_cur, creator.id, creator.name, creator.discriminator, creator.mention) if ctx.invoked_with == "blindasync": insert_async(db_cur, name, creator.id, desc, seed_hash, seed_code, seed_url, None, submit_channel.id, results_channel.id, results_msg.id, spoilers_channel.id) else: insert_async(db_cur, name, creator.id, desc, seed_hash, seed_code, seed_url, async_role.id, submit_channel.id, results_channel.id, results_msg.id, spoilers_channel.id) commit_db(db_conn) async_data = get_async_data(db_cur, submit_channel.id) close_db(db_conn) data_msg = await submit_channel.send(async_data, file=spoiler_file) await data_msg.pin() await submit_channel.send( "Enviad resultados usando el comando: `!done hh:mm:ss CR`\n" "Por ejemplo: `!done 1:40:35 144`, `!done ff` (este último registra un forfeit)\n" "Usad preferiblemente tiempo real, no in-game time.\n" "Por favor, mantened este canal lo más limpio posible y SIN SPOILERS." ) text_ans = 'Abierta carrera asíncrona con nombre: {}\nEnvía resultados en {}'.format( name, submit_channel.mention) await ctx.reply(text_ans, mention_author=False)
async def done(self, ctx, time: str = "", collection: int = 0): """ Envía un resultado de la carrera asíncrona. Requiere indicar un tiempo en formato hh:mm:ss. Opcionalmente, se puede especificar la tasa de colección de ítems para ALTTPR. Para registrar un forfeit, introducir FF en lugar del tiempo. Solo funciona en el canal "submit" asociado a la carrera. Un segundo comando "done" del mismo jugador reemplazará el resultado anterior. """ message = ctx.message await message.delete() db_conn, db_cur = open_db(ctx.guild.id) race = get_async_by_submit(db_cur, ctx.channel.id) if not race: close_db(db_conn) return if race[5] == 0: if ctx.invoked_with == "forfeit" or ctx.invoked_with == "ff" or time.lower( ) == "ff": time = "99:59:59" collection = 0 if re.match(r'\d?\d:[0-5]\d:[0-5]\d$', time) and collection >= 0: time_arr = [int(x) for x in time.split(':')] time_s = 3600 * time_arr[0] + 60 * time_arr[1] + time_arr[2] author = ctx.author async with write_lock: insert_player_if_not_exists(db_cur, author.id, author.name, author.discriminator, author.mention) save_async_result(db_cur, race[0], author.id, time_s, collection) commit_db(db_conn) results_text = get_results_text(db_cur, race[11]) results_channel = ctx.guild.get_channel(race[12]) results_msg = await results_channel.fetch_message(race[13]) await results_msg.edit(content=results_text) if race[10]: async_role = ctx.guild.get_role(race[10]) await author.add_roles(async_role) await ctx.send("GG {}, tu resultado se ha registrado.".format( author.mention)) else: close_db(db_conn) raise commands.errors.CommandInvokeError( "Parámetros inválidos.") else: close_db(db_conn) raise commands.errors.CommandInvokeError( "Esta carrera asíncrona no está abierta.") close_db(db_conn)
async def purge(self, ctx): """ Purga una carrera asíncrona. Elimina todos los roles y canales asociados a la carrera. Los resultados, si hay alguno, se archivarán en "async-historico" Solo funciona en el canal "submit" asociado a la carrera, y solamente si lo usa el creador original de la carrera o un moderador. También sirve para eliminar el canal asociado a una carrera privada. """ db_conn, db_cur = open_db(ctx.guild.id) race = get_async_by_submit(db_cur, ctx.channel.id) if not race: race = get_private_race_by_channel(db_cur, ctx.channel.id) if race: if check_race_permissions(ctx, race[2], race[5]): author = ctx.author async with write_lock: insert_player_if_not_exists(db_cur, author.id, author.name, author.discriminator, author.mention) update_private_status(db_cur, race[0], 2) commit_db(db_conn) race_channel = ctx.guild.get_channel(race[5]) await race_channel.delete() else: close_db(db_conn) raise commands.errors.CommandInvokeError( "Esta operación solo puede realizarla el creador original de la carrera o un moderador." ) close_db(db_conn) return if not check_race_permissions(ctx, race[2], race[11]): close_db(db_conn) raise commands.errors.CommandInvokeError( "Esta operación solo puede realizarla el creador original de la carrera o un moderador." ) if race[5] == 1: author = ctx.author async with write_lock: insert_player_if_not_exists(db_cur, author.id, author.name, author.discriminator, author.mention) update_async_status(db_cur, race[0], 2) commit_db(db_conn) # Copia de resultados al historial, si los hay submit_channel = ctx.guild.get_channel(race[11]) results = get_results_for_race(db_cur, submit_channel.id) if results: history_channel = get_async_history_channel(db_cur) my_hist_channel = None if not history_channel[0] or not ctx.guild.get_channel( history_channel[0]): history_overwrites = { ctx.guild.default_role: discord.PermissionOverwrite(send_messages=False), ctx.guild.me: discord.PermissionOverwrite(send_messages=True) } my_hist_channel = await ctx.guild.create_text_channel( "async-historico", overwrites=history_overwrites) async with write_lock: set_async_history_channel(db_cur, my_hist_channel.id) commit_db(db_conn) else: my_hist_channel = ctx.guild.get_channel(history_channel[0]) await my_hist_channel.send( get_async_data(db_cur, submit_channel.id)) await my_hist_channel.send( get_results_text(db_cur, submit_channel.id)) # Eliminación de roles y canales if race[10]: async_role = ctx.guild.get_role(race[10]) await async_role.delete() category = submit_channel.category await submit_channel.delete() results_channel = ctx.guild.get_channel(race[12]) await results_channel.delete() spoilers_channel = ctx.guild.get_channel(race[14]) await spoilers_channel.delete() await category.delete() close_db(db_conn) else: close_db(db_conn) raise commands.errors.CommandInvokeError( "La carrera debe cerrarse antes de ser purgada.")