diff --git a/bot.py b/bot.py index 0a6f7b2..ec7e376 100644 --- a/bot.py +++ b/bot.py @@ -290,7 +290,7 @@ async def init_bot(loop) -> naoTimesBot: bot.redisdb = redis_conn bot.logger.info("Success Loading Discord.py") bot.logger.info("Binding interactions...") - SlashCommand(bot, sync_commands=True, override_type=True) + SlashCommand(bot, sync_commands=False, override_type=True) except Exception as exc: # skipcq: PYL-W0703 bot.logger.error("Failed to load Discord.py") announce_error(exc) @@ -339,21 +339,20 @@ async def on_ready(): if not args_parsed.showtimes_fetch: bot.logger.info("Fetching nao_showtimes from server db to local json") js_data = await bot.ntdb.fetch_all_as_json() - showtimes_folder = os.path.join(bot.fcwd, "showtimes_folder") - if not os.path.isdir(showtimes_folder): - os.makedirs(showtimes_folder) - for fn, fdata in js_data.items(): - svfn = f"showtimes_{fn}" - bot.logger.info(f"showtimes: saving to file {fn}") - if fn == "supermod": - svfn = "showtimesadmin" - await bot.redisdb.set(svfn, fdata) + for admins in js_data["supermod"]: + bot.logger.info(f"showtimes: saving admin {admins['id']} data to redis") + await bot.redisdb.set(f"showadmin_{admins['id']}", admins) + for server in js_data["servers"]: + bot.logger.info(f"showtimes: saving server {server['id']} data to redis") + svfn = f"showtimes_{server['id']}" + await bot.redisdb.set(svfn, server) bot.logger.info("File fetched and saved to local json") bot.logger.info( "---------------------------------------------------------------" ) # noqa: E501 - except Exception: # skipcq: PYL-W0703 + except Exception as exc: # skipcq: PYL-W0703 bot.logger.error("Failed to validate if database is up and running.") + bot.echo_error(exc) bot.logger.error("IP:Port: {}:{}".format(mongos["ip_hostname"], mongos["port"])) bot.ntdb = None bot.logger.info("---------------------------------------------------------------") # noqa: E501 diff --git a/cogs/helpcmd.py b/cogs/helpcmd.py index 3118750..749cb5e 100644 --- a/cogs/helpcmd.py +++ b/cogs/helpcmd.py @@ -184,6 +184,15 @@ def is_msg_empty(msg: str, thr: int = 3) -> bool: return True return False + @staticmethod + def _owner_only_command(command: commands.Command): + if command.checks: + for check in command.checks: + fn_primitive_name = check.__str__() + if "is_owner" in fn_primitive_name: + return True + return False + async def help_command_fallback(self, ctx: commands.Context, messages: str): split_msg = messages.split(" ", 1) if len(split_msg) < 2: @@ -191,8 +200,14 @@ async def help_command_fallback(self, ctx: commands.Context, messages: str): cmd_data: Union[commands.Command, None] = self.bot.get_command(split_msg[1]) if cmd_data is None: return None + is_owner = await self.bot.is_owner(ctx.author) + if self._owner_only_command(cmd_data) and not is_owner: + return None cmd_opts = [] for key, val in cmd_data.clean_params.items(): + anotasi = val.annotation if val.annotation is not val.empty else None + if anotasi is not None: + anotasi = anotasi.__name__ cmd_sample = {"name": key} if val.default is val.empty: cmd_sample["type"] = "r" @@ -200,6 +215,8 @@ async def help_command_fallback(self, ctx: commands.Context, messages: str): else: cmd_sample["desc"] = f"Parameter `{key}` opsional dan bisa diabaikan!" cmd_sample["type"] = "o" + if anotasi is not None and "desc" in cmd_sample: + cmd_sample["desc"] += f"\n`{key}` akan dikonversi ke format `{anotasi}` nanti." cmd_opts.append(cmd_sample) extra_kwargs = {"cmd_name": cmd_data.qualified_name} if cmd_data.description: diff --git a/cogs/matematika.py b/cogs/matematika.py index d799a25..4c22884 100644 --- a/cogs/matematika.py +++ b/cogs/matematika.py @@ -1,10 +1,12 @@ import logging + import discord from discord.ext import commands, tasks from nthelper.bot import naoTimesBot -from nthelper.kalkuajaib import KalkulatorAjaib, GagalKalkulasi -from nthelper.wolfram import WolframAPI +from nthelper.kalkuajaib import GagalKalkulasi, KalkulatorAjaib +from nthelper.utils import DiscordPaginator, quote, rgb_to_color +from nthelper.wolfram import WolframAPI, WolframPod class Matematika(commands.Cog): @@ -50,6 +52,42 @@ async def kalkulasi_cmd(self, ctx: commands.Context, *, teks: str): ) await ctx.send(embed=embed) + @commands.command(name="wolfram", aliases=["wolframalpha", "wa"]) + async def wolfram_cmd(self, ctx: commands.Context, *, query: str): + if self.wolfram is None: # Ignore if no WolframAlpha thing + return + results = await self.wolfram.query(query) + if isinstance(results, str): + return await ctx.send(results) + SEARCH_URL = "https://www.wolframalpha.com/input/?i={}" + QUERY_STRINGIFY = query.replace(" ", "+") + + def _create_embed(pod: WolframPod): + embed = discord.Embed(title=pod.title, color=rgb_to_color(202, 103, 89)) + embed.set_author( + name="WolframAlpha", + url=SEARCH_URL.format(QUERY_STRINGIFY), + icon_url="https://p.n4o.xyz/i/wa_icon.png", + ) + embed.description = f"**Kueri**: `{query}`" + first_image = None + for n, subpod in enumerate(pod.pods, 1): + embed.add_field(name=pod.scanner + f" ({n})", value=quote(subpod.plaintext, True)) + if subpod.image and first_image is None: + first_image = subpod.image + if first_image is not None: + embed.set_image(url=first_image) + embed.set_footer( + text="Diprakasai dengan WolframAlpha™", icon_url="https://p.n4o.xyz/i/wa_icon.png" + ) + return embed + + paginator = DiscordPaginator(self.bot, ctx) + paginator.checker() + paginator.breaker() + paginator.set_generator(_create_embed) + await paginator.start(results.pods, 30.0) + def setup(bot: naoTimesBot): bot.add_cog(Matematika(bot)) diff --git a/cogs/mod.py b/cogs/mod.py index ce9f19a..a920b94 100644 --- a/cogs/mod.py +++ b/cogs/mod.py @@ -698,6 +698,9 @@ async def log_server_message_edit(self, before: discord.Message, after: discord. return user_data: discord.Member = before.author channel_data: discord.TextChannel = before.channel + # Possibly just Embed edit + if before.content == after.content: + return dict_data = { "kanal": channel_data.name, "author": { diff --git a/cogs/naotimesui.py b/cogs/naotimesui.py new file mode 100644 index 0000000..2269631 --- /dev/null +++ b/cogs/naotimesui.py @@ -0,0 +1,367 @@ +# A bridge between Showtimes and ShowtimesUI + +import asyncio +import logging +import platform +import socket +from base64 import b64encode +from inspect import iscoroutinefunction +from typing import Union + +import discord +import ujson +from discord.ext import commands, tasks + +from nthelper.bot import naoTimesBot +from nthelper.utils import get_current_time + + +def maybe_int(data, fallback=None): + if isinstance(data, int): + return data + try: + return int(data) + except ValueError: + return fallback if isinstance(fallback, int) else data + + +class naoTimesUIBridge(commands.Cog): + def __init__(self, bot: naoTimesBot) -> None: + self.bot = bot + self.logger = logging.getLogger("cogs.naotimesui.Socket") + + self._authenticated_sid = getattr(self.bot, "naotimesui", []) + naotimesui = bot.botconf.get("naotimesui", {}).get("socket", {}) + self._auth_passwd = naotimesui.get("password") + self._socket_port = maybe_int(naotimesui.get("port", 25670)) + if not isinstance(self._socket_port, int): + self._socket_port = 25670 + + self.server = None + self.server_lock = False + self.run_server.start() + + self._event_map = { + "authenticate": self.authenticate_user, + "pull data": self.on_pull_data, + "get server": self.get_server_info, + "get user": self.get_user_info, + "get channel": self.on_channel_info_request, + "get user perms": self.get_user_server_permission, + "create role": self.show_create_role, + "announce drop": self.on_announce_request, + "ping": self.on_ping, + } + + def cog_unload(self): + self.run_server.cancel() + setattr(self.bot, "naotimesui", self._authenticated_sid) + + @staticmethod + def hash_ip(addr): + if addr is None: + return "unknowniphashed" + if isinstance(addr, (tuple, list)): + addr = addr[0] + if not isinstance(addr, str): + if isinstance(addr, int): + addr = str(addr) + else: + addr = ujson.dumps(addr, ensure_ascii=False) + return b64encode(addr.encode("utf-8")).decode("utf-8") + + @staticmethod + async def maybe_asyncute(cb, *args, **kwargs): + real_func = cb + if hasattr(real_func, "func"): + real_func = cb.func + if iscoroutinefunction(real_func): + return await cb(*args, **kwargs) + return cb(*args, **kwargs) + + @tasks.loop(seconds=1, count=1) + async def run_server(self): + if self.server_lock: + return + self.logger.info("starting ws server...") + server = await asyncio.start_server(self.handle_message, "0.0.0.0", self._socket_port) + if platform.system() == "Linux": + server.sockets[0].setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + server.sockets[0].setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1) + server.sockets[0].setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3) + server.sockets[0].setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) + elif platform.system() == "Darwin": + TCP_KEEPALIVE = 0x10 + server.sockets[0].setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + server.sockets[0].setsockopt(socket.IPPROTO_TCP, TCP_KEEPALIVE, 3) + elif platform.system() == "Windows": + server.sockets[0].ioctl(socket.SIO_KEEPALIVE_VALS, (1, 10000, 3000)) + addr = server.sockets[0].getsockname() + self.server_lock = True + self.logger.info(f"serving on: {addr[0]}:{addr[1]}") + self.server = server + try: + async with server: + await server.serve_forever() + except asyncio.CancelledError: + self.logger.warning("close request received, shutting down...") + self.server.close() + await server.wait_closed() + self.logger.info("server closed.") + + async def on_pull_data(self, sid, data): + self.logger.info(f"{sid}: requested pull data for {data}") + remote_data = await self.bot.ntdb.get_server(data) + if not remote_data: + self.logger.error(f"{sid}:{data}: unknown server, ignoring!") + return + await self.bot.redisdb.set(f"showtimes_{remote_data['id']}", remote_data) + return "ok" + + def on_ping(self, sid, data): + self.logger.info(f"{sid}: requested ping") + return "pong" + + def _check_auth(self, sid): + if self._auth_passwd is None: + return True + if sid not in self._authenticated_sid: + return False + return True + + def authenticate_user(self, sid, data): + self.logger.info(f"trying to authenticating {sid}, comparing s::{data} and t::{self._auth_passwd}") + if self._auth_passwd is None: + self._authenticated_sid.append(sid) + return "ok" + if data == self._auth_passwd: + self.logger.info(f"authenticated {sid}") + self._authenticated_sid.append(sid) + return "ok" + return {"message": "not ok", "success": 0} + + def get_user_info(self, sid, data): + self.logger.info(f"{sid}: requested user info for {data}") + try: + user_id = int(data) + except (KeyError, ValueError, IndexError): + return {"message": "not a number", "success": 0} + user_data: Union[discord.User, None] = self.bot.get_user(user_id) + if user_data is None: + return {"message": "cannot find user", "success": 0} + parsed_user = {} + parsed_user["id"] = str(user_data.id) + parsed_user["name"] = user_data.name + parsed_user["avatar_url"] = str(user_data.avatar_url) + return parsed_user + + async def on_announce_request(self, sid, data): + self.logger.info(f"{sid}: requested anime drop announcement for {data}") + try: + server_id = data["id"] + channel_id = data["channel_id"] + anime_data = data["anime"] + except (KeyError, ValueError, IndexError): + return {"message": "Kurang data untuk melakukan announce", "success": 0} + try: + anime_title = anime_data["title"] + except (KeyError, ValueError, IndexError): + return {"message": "Tidak dapat menemukan judul anime di data `anime`", "success": 0} + try: + guild_id = int(server_id) + except (KeyError, ValueError, IndexError): + return {"message": "Server ID bukanlah angka", "success": 0} + try: + kanal_id = int(channel_id) + except (KeyError, ValueError, IndexError): + return {"message": "Channel ID bukanlah angka", "success": 0} + guild_info: Union[discord.Guild, None] = self.bot.get_guild(guild_id) + if guild_id is None: + return {"message": "Tidak dapat menemukan server", "success": 0} + channel: Union[discord.TextChannel, None] = guild_info.get_channel(kanal_id) + if not isinstance(channel, discord.TextChannel): + return {"message": "Kanal bukanlah kanal teks", "success": 0} + embed = discord.Embed(title=anime_title, color=0xB51E1E) + embed.add_field( + name="Dropped...", value=f"{anime_title} telah di drop dari Fansub ini :(", inline=False, + ) + embed.set_footer(text=f"Pada: {get_current_time()}") + try: + await channel.send(embed=embed) + except (discord.Forbidden, discord.HTTPException): + pass + return "ok" + + def on_channel_info_request(self, sid, data): + self.logger.info(f"{sid}: requested channel info for {data}") + try: + channel_id = int(data["id"]) + server_id = int(data["server"]) + except (KeyError, ValueError, IndexError): + return {"message": "ID bukanlah angka", "success": 0} + guild_info: Union[discord.Guild, None] = self.bot.get_guild(server_id) + if guild_info is None: + return {"message": "Tidak dapat menemukan server", "success": 0} + channel_info: Union[discord.abc.GuildChannel, None] = guild_info.get_channel(channel_id) + if channel_info is None: + return {"message": "Tidak dapat menemukan channel", "success": 0} + if not isinstance(channel_info, discord.TextChannel): + return {"message": "Channel bukan TextChannel", "success": 0} + return {"id": str(channel_info.id), "name": channel_info.name} + + async def show_create_role(self, sid, data): + self.logger.info(f"{sid}: requested role creation for {data}") + try: + server_id = data["id"] + role_name = data["name"] + except (KeyError, ValueError, IndexError): + return {"message": "Gagal unpack data dari server", "success": 0} + try: + guild_id = int(server_id) + except (KeyError, ValueError, IndexError): + return {"message": "ID bukanlah angka", "success": 0} + guild_info: Union[discord.Guild, None] = self.bot.get_guild(guild_id) + if guild_info is None: + return {"message": "Tidak dapat menemukan server", "success": 0} + try: + new_guild_id = await guild_info.create_role( + name=role_name, mentionable=True, colour=discord.Colour.random() + ) + except discord.Forbidden: + return { + "message": "Bot naoTimes tidak memiliki akses untuk membuat role di server anda.", + "success": 0, + } + except discord.HTTPException: + return { + "message": "Bot naoTimes tidak dapat membuat role tersebut, mohon coba sesaat lagi.", + "success": 0, + } + return {"id": str(new_guild_id.id), "name": new_guild_id.name} + + def get_user_server_permission(self, sid, data): + self.logger.info(f"{sid}: requested member perms info for {data}") + try: + server_id = data["id"] + admin_id = data["admin"] + except (KeyError, ValueError, IndexError): + return {"message": "Gagal unpack data dari server", "success": 0} + try: + guild_id = int(server_id) + user_id = int(admin_id) + except (KeyError, ValueError, IndexError): + return {"message": "ID bukanlah angka", "success": 0} + guild_info: Union[discord.Guild, None] = self.bot.get_guild(guild_id) + if guild_info is None: + return {"message": "cannot find server", "success": 0} + member_info: Union[discord.Member, None] = guild_info.get_member(user_id) + if member_info is None: + return {"message": "cannot find member", "success": 0} + perms_sets = member_info.guild_permissions + user_perms = [] + for perm_name, perm_val in perms_sets: + if perm_val: + user_perms.append(perm_name) + if isinstance(guild_info.owner, discord.Member): + if str(guild_info.owner.id) == str(member_info.id): + user_perms.append("owner") + return user_perms + + def get_server_info(self, sid, data): + self.logger.info(f"{sid}: requested server info for {data}") + try: + guild_id = int(data) + except (KeyError, ValueError, IndexError): + return {"message": "not a number", "success": 0} + guild_info: Union[discord.Guild, None] = self.bot.get_guild(guild_id) + if guild_info is None: + return {"message": "cannot find server", "success": 0} + guild_parsed = {} + guild_parsed["name"] = guild_info.name + guild_parsed["icon_url"] = str(guild_info.icon_url) + guild_parsed["id"] = str(guild_info.id) + owner_info = {} + if guild_info.owner: + owner_data: discord.Member = guild_info.owner + owner_info["id"] = str(owner_data.id) + owner_info["name"] = owner_data.name + owner_info["avatar_url"] = str(owner_data.avatar_url) + guild_parsed["owner"] = owner_info + return guild_parsed + + @staticmethod + def parse_json(recv_bytes: bytes): + if b"\x04" == recv_bytes[-len(b"\x04") :]: + recv_bytes = recv_bytes[: -len(b"\x04")] + decoded = recv_bytes.decode("utf-8").strip() + return ujson.loads(decoded) + + @staticmethod + def encode_message(any_data) -> bytes: + if isinstance(any_data, tuple): + any_data = list(any_data) + if isinstance(any_data, (list, dict)): + any_data = ujson.dumps(any_data) + elif isinstance(any_data, (int, float)): + any_data = str(any_data) + elif isinstance(any_data, bytes): + if b"\x04" != any_data[-len(b"\x04") :]: + any_data = any_data + b"\x04" + return any_data + return any_data.encode("utf-8") + b"\x04" + + async def on_message_emitter(self, sid: str, recv_data: bytes): + parsed = self.parse_json(recv_data) + if not isinstance(parsed, dict): + return {"message": "unknown message received", "success": 0, "event": None} + if "event" not in parsed: + return {"message": "unknown event", "success": 0, "event": None} + event = parsed["event"] + if event == "ping": + parsed["data"] = None + is_auth = self._check_auth(sid) + if not is_auth and event != "authenticate": + return {"message": "not authenticated", "success": -1, "event": event} + if "data" not in parsed: + return {"message": "no data received", "success": 0, "event": event} + data = parsed["data"] + callback = self._event_map.get(event) + if not callable(callback): + return {"message": "unknown event, ignored", "success": 1, "event": event} + try: + res = await self.maybe_asyncute(callback, sid, data) + except Exception as e: + return { + "message": f"An error occured while trying to execute callback, {str(e)}", + "success": 0, + "event": event, + } + if isinstance(res, object): + if "success" in res and "message" in res: + return {**res, "event": event} + elif "success" in res: + return { + "message": "success" if res["success"] == 1 else "failed", + "success": res["success"], + "event": event, + } + elif "message" in res: + return {"message": res["message"], "success": 1, "event": "event"} + return {"message": res, "success": 1, "event": event} + + async def handle_message(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + self.logger.info("request received, reading data...") + try: + data = await reader.readuntil(b"\x04") + addr = writer.get_extra_info("peername") + answer = await self.on_message_emitter(self.hash_ip(addr), data) + except asyncio.IncompleteReadError: + self.logger.error("incomplete data acquired") + answer = {"message": "incomplete data received", "status": 0} + writer.write(self.encode_message(answer)) + await writer.drain() + writer.close() + + +def setup(bot: naoTimesBot): + bot.add_cog(naoTimesUIBridge(bot)) diff --git a/cogs/peninjau.py b/cogs/peninjau.py index e9d4883..d079602 100644 --- a/cogs/peninjau.py +++ b/cogs/peninjau.py @@ -6,7 +6,7 @@ import os import re from datetime import datetime -from typing import List, Tuple, Union +from typing import Tuple from urllib.parse import urlencode import aiohttp @@ -19,6 +19,7 @@ from nthelper.bot import naoTimesBot from nthelper.jisho import JishoWord +from nthelper.kateglo import KategloError, KategloTipe, kateglo_relasi from nthelper.utils import DiscordPaginator logger = logging.getLogger("cogs.peninjau") @@ -494,29 +495,6 @@ async def chunked_translate(sub_data, number, target_lang, untranslated, mode=". return sub_data, untranslated -async def persamaankata(cari: str, mode: str = "sinonim") -> Union[List[str], str]: - """Mencari antonim/sinonim dari persamaankata.com""" - async with aiohttp.ClientSession() as sesi: - async with sesi.get("http://m.persamaankata.com/search.php?q={}".format(cari)) as resp: - response = await resp.text() - if resp.status > 299: - return "Tidak dapat terhubung dengan API." - - soup_data = BS4(response, "html.parser") - tesaurus = soup_data.find_all("div", attrs={"class": "thesaurus_group"}) - - if not tesaurus: - return "Tidak ada hasil." - - if mode == "antonim" and len(tesaurus) < 2: - return "Tidak ada hasil." - if mode == "antonim" and len(tesaurus) > 1: - result = tesaurus[1].text.strip().splitlines()[1:] - return list(filter(None, result)) - result = tesaurus[0].text.strip().splitlines()[1:] - return list(filter(None, result)) - - async def yahoo_finance(from_, to_): data_ = { "data": {"base": from_.upper(), "period": "day", "term": to_.upper()}, @@ -733,24 +711,24 @@ async def _process_kurs(self, dari, ke, jumlah=None): mode = "crypto" crypto_from = str(self.crypto_data[dari_cur]["id"]) crypto_to = "2781" + if ke_cur not in self.currency_data: + return f"Tidak dapat menemukan kode negara mata utang **{ke_cur}** di database" curr_sym_from = self.crypto_data[dari_cur]["symbol"] curr_sym_to = self.currency_data[ke_cur]["symbols"][0] curr_name_from = self.crypto_data[dari_cur]["name"] curr_name_to = self.currency_data[ke_cur]["name"] dari_cur = "USD" - if ke_cur not in self.currency_data: - return f"Tidak dapat menemukan kode negara mata utang **{ke_cur}** di database" elif ke_cur in self.crypto_data: mode = "crypto" crypto_from = "2781" + if dari_cur not in self.currency_data: + return f"Tidak dapat menemukan kode negara mata utang **{dari_cur}** di database" crypto_to = str(self.crypto_data[ke_cur]["id"]) curr_sym_from = self.currency_data[dari_cur]["symbols"][0] curr_sym_to = self.crypto_data[ke_cur]["symbol"] curr_name_from = self.currency_data[dari_cur]["name"] curr_name_to = self.crypto_data[ke_cur]["name"] ke_cur = "USD" - if dari_cur not in self.currency_data: - return f"Tidak dapat menemukan kode negara mata utang **{dari_cur}** di database" if mode == "normal": if dari_cur not in self.currency_data: @@ -843,40 +821,64 @@ async def kurs(self, ctx, from_, to_, total=None): await ctx.send(embed=embed) - @commands.command(aliases=["persamaankata", "persamaan"]) - async def sinonim(self, ctx, *, q_): - self.logger.info(f"searching {q_}") + @commands.command(name="sinonim", aliases=["persamaankata", "persamaan"]) + async def _sinonim_cmd(self, ctx: commands.Context, *, kata: str): + self.logger.info(f"searching {kata}") - result = await persamaankata(q_, "Sinonim") - if not isinstance(result, list): - self.logger.warning("no results.") - return await ctx.send(result) - result = "\n".join(result) - embed = discord.Embed(title="Sinonim: {}".format(q_), color=0x81E28D) - embed.set_footer(text="Diprakasai dengan: persamaankata.com") + try: + results = await kateglo_relasi(kata) + except KategloError as ke: + return await ctx.send(str(ke)) + + match_sinonim = filter(lambda x: x.tipe == KategloTipe.Sinonim, results) + + result_txt = [] + for sinonim in match_sinonim: + result_txt.append(sinonim.kata) + + if len(result_txt) < 1: + return await ctx.send(f"Tidak ada sinonim untuk kata `{kata}`") + + URL_KATEGLO = "https://kateglo.com/?mod=dictionary&action=view&phrase={}#panelRelated" + + result = ", ".join(result_txt) + embed = discord.Embed(title=f"Sinonim: {kata}", color=0x81E28D, url=URL_KATEGLO.format(kata)) + embed.set_footer(text="Diprakasai dengan: Kateglo.com") if not result: - embed.add_field(name=q_, value="Tidak ada hasil", inline=False) + embed.add_field(name=kata, value="Tidak ada hasil", inline=False) return await ctx.send(embed=embed) - embed.add_field(name=q_, value=result, inline=False) + embed.add_field(name=kata, value=result, inline=False) await ctx.send(embed=embed) - @commands.command(aliases=["lawankata"]) - async def antonim(self, ctx, *, q_): - self.logger.info(f"searching {q_}") + @commands.command(name="antonim", aliases=["lawankata"]) + async def _antonim_cmd(self, ctx: commands.Context, *, kata: str): + self.logger.info(f"searching {kata}") - result = await persamaankata(q_, "antonim") - if not isinstance(result, list): - self.logger.warning("no results.") - return await ctx.send(result) - result = "\n".join(result) - embed = discord.Embed(title="Antonim: {}".format(q_), color=0x81E28D) - embed.set_footer(text="Diprakasai dengan: persamaankata.com") + try: + results = await kateglo_relasi(kata) + except KategloError as ke: + return await ctx.send(str(ke)) + + match_antonim = filter(lambda x: x.tipe == KategloTipe.Antonim, results) + + result_txt = [] + for antonim in match_antonim: + result_txt.append(antonim.kata) + + if len(result_txt) < 1: + return await ctx.send(f"Tidak ada antonim untuk kata `{kata}`") + + URL_KATEGLO = "https://kateglo.com/?mod=dictionary&action=view&phrase={}#panelRelated" + + result = ", ".join(result_txt) + embed = discord.Embed(title=f"Antonim: {kata}", color=0x81E28D, url=URL_KATEGLO.format(kata)) + embed.set_footer(text="Diprakasai dengan: Kateglo.com") if not result: - embed.add_field(name=q_, value="Tidak ada hasil", inline=False) + embed.add_field(name=kata, value="Tidak ada hasil", inline=False) return await ctx.send(embed=embed) - embed.add_field(name=q_, value=result, inline=False) + embed.add_field(name=kata, value=result, inline=False) await ctx.send(embed=embed) @staticmethod diff --git a/cogs/presensi.py b/cogs/presensi.py index cc245ad..603ff27 100644 --- a/cogs/presensi.py +++ b/cogs/presensi.py @@ -74,6 +74,10 @@ def presensi(self, prefix: str = "!"): PresensiData("Muse ID", 2), PresensiData("with pain"), PresensiData("Towa-sama", 2), + PresensiData(":AyamePhone:"), + PresensiData("with YOUR MOM! HAH"), + PresensiData("Akhir Dunia", 2), + PresensiData("Keributan", 1), ] diff --git a/cogs/showtimes_module/base.py b/cogs/showtimes_module/base.py index b59d945..6b34548 100644 --- a/cogs/showtimes_module/base.py +++ b/cogs/showtimes_module/base.py @@ -6,6 +6,7 @@ import aiohttp import discord +import timeago from nthelper.redis import RedisBridge @@ -259,6 +260,9 @@ async def fetch_anilist( raise ValueError("airing_start is empty, need more data.") return compiled_data + if compiled_data["airing_start"] is None: + raise ValueError("airing_start is empty, need more data") + if isinstance(current_ep, str): current_ep = int(current_ep) if total_episode is not None and isinstance(total_episode, str): @@ -316,30 +320,10 @@ def get_last_updated(oldtime: int) -> str: Get last updated time from naoTimes database and convert it to "passed time" """ - current_time = datetime.now() - old_dt = datetime.utcfromtimestamp(oldtime) - delta_time = current_time - old_dt - - days_passed_by = delta_time.days - seconds_passed = delta_time.total_seconds() - if seconds_passed < 60: - text = "Beberapa detik yang lalu" - elif seconds_passed < 180: - text = "Beberapa menit yang lalu" - elif seconds_passed < 3600: - text = "{} menit yang lalu".format(round(seconds_passed / 60)) - elif seconds_passed < 86400: - text = "{} jam yang lalu".format(round(seconds_passed / 3600)) - elif days_passed_by < 31: - text = "{} hari yang lalu".format(days_passed_by) - elif days_passed_by < 365: - text = "{} bulan yang lalu".format(round(days_passed_by / 30)) - else: - calculate_year = round(days_passed_by / 365) - calculate_year = max(calculate_year, 1) - text = "{} bulan yang lalu".format(calculate_year) - - return text + current_time = datetime.now(tz=timezone.utc) + parsed_time = datetime.fromtimestamp(oldtime, tz=timezone.utc) + + return timeago.format(parsed_time, current_time, "in_ID") class ShowtimesBase: @@ -352,6 +336,10 @@ def __init__(self): self._async_lock = False self.logger = logging.getLogger("cogs.showtimes_module.base.ShowtimesBase") + @staticmethod + def get_unix(): + return int(round(datetime.now(tz=timezone.utc).timestamp())) + async def __acquire_lock(self): while True: if not self._async_lock: @@ -370,11 +358,9 @@ async def fetch_servers(self, redisdb: RedisBridge) -> list: async def fetch_super_admins(self, redisdb: RedisBridge): self.logger.info("dumping data...") - await self.__acquire_lock() - json_data = await redisdb.get("showtimesadmin") + json_data = await redisdb.getall("showadmin_*") if json_data is None: return [] - await self.__release_lock() return json_data async def fetch_showtimes(self, server_id: str, redisdb: RedisBridge) -> Union[dict, None]: @@ -435,7 +421,8 @@ async def choose_anime(self, bot, ctx, matches: list): format_value = [] for n, i in enumerate(matches): - format_value.append("{} **{}**".format(reactmoji[n], i)) + ani_title = i["name"] if i["type"] == "real" else i["real_name"] + format_value.append("{} **{}**".format(reactmoji[n], ani_title)) format_value.append("❌ **Batalkan**") embed.description = "\n".join(format_value) @@ -480,14 +467,29 @@ def check_react(reaction, user): await msg.delete() if res_matches: self.logger.info(f"picked: {res_matches[0]}") + if res_matches[0]["type"] == "alias": + res_matches[0]["name"] = res_matches[0]["real_name"] return res_matches - async def split_search_id(self, dataset: list, needed_id: str, matching_id: int): + @staticmethod + def _search_data_index(anilist_datasets: list, need_id: str, match_id: str) -> int: + idx = None + for n, data in enumerate(anilist_datasets): + if str(data[need_id]) == str(match_id): + idx = n + break + return idx + + @staticmethod + async def split_search_id(dataset: list, needed_id: str, matching_id: int, sort=False): def to_int(x): if isinstance(x, str): x = int(x) return x + if sort: + dataset.sort(key=lambda x: x[sort]) + mid_num = len(dataset) // 2 mid_data = dataset[mid_num] match_data = to_int(mid_data[needed_id]) @@ -513,7 +515,7 @@ def parse_status(status: dict) -> str: """ status_list = [] for work, c_stat in status.items(): - if c_stat == "y": + if c_stat: status_list.append("~~{}~~".format(work)) else: status_list.append("**{}**".format(work)) @@ -526,9 +528,9 @@ def get_current_ep(status_list: dict) -> Union[str, None]: Find episode `not_released` status in showtimes database If not exist return None """ - for ep in status_list: - if status_list[ep]["status"] == "not_released": - return ep + for status in status_list: + if not status["is_done"]: + return status return None @staticmethod @@ -539,7 +541,7 @@ def get_not_released_ep(status_list: dict) -> list: """ ep_list = [] for ep in status_list: - if status_list[ep]["status"] == "not_released": + if not ep["is_done"]: ep_list.append(ep) return ep_list @@ -548,9 +550,19 @@ def get_close_matches(target: str, lists: list) -> list: """ Find close matches from input target Sort everything if there's more than 2 results + + lists must be in this format: + [{ + "index": 0, + "name": "Anime title" + }] """ target_compiler = re.compile("({})".format(target), re.IGNORECASE) - return sorted(list(filter(target_compiler.search, lists))) + + def _match_re(data): + return target_compiler.search(data["name"]) + + return sorted(list(filter(_match_re, lists)), key=lambda x: x["name"]) @staticmethod def check_role(needed_role, user_roles: list) -> bool: @@ -583,14 +595,12 @@ def make_numbered_alias(alias_list: list) -> str: return "\n".join(t) @staticmethod - def any_progress(status: dict) -> bool: - """ - Check if there's any progress to the project - """ - for _, v in status.items(): - if v == "y": - return False - return True + def is_progressing(progress: dict) -> bool: + """Check if episode is progressing or not""" + for _, status in progress.items(): + if status: + return True + return False @staticmethod def get_role_name(role_id, roles) -> str: @@ -602,7 +612,8 @@ def get_role_name(role_id, roles) -> str: return r.name return "Unknown" - async def get_roles(self, posisi): + @staticmethod + async def get_roles(posisi): posisi_kw = { "tl": "tl", "translation": "tl", @@ -650,55 +661,80 @@ def split_until_less_than(dataset: list) -> list: max 2000 characters limit """ - def split_list(alist, wanted_parts=1): - length = len(alist) - return [ - alist[i * length // wanted_parts : (i + 1) * length // wanted_parts] - for i in range(wanted_parts) - ] + text_format = "**Mungkin**: " + concat_set = [] + finalized_sets = [] + first_run = True + for data in dataset: + if first_run: + concat_set.append(data) + check = text_format + ", ".join(concat_set) + if len(check) >= 1995: + last_occured = concat_set.pop() + finalized_sets.append(concat_set) + concat_set = [last_occured] + first_run = False + else: + concat_set.append(data) + if len(", ".join(concat_set)) >= 1995: + last_occured = concat_set.pop() + finalized_sets.append(concat_set) + concat_set = [last_occured] - text_format = "**Mungkin**: {}" - start_num = 2 - new_set = None + new_sets = [] while True: - internal_meme = False - new_set = split_list(dataset, start_num) - for set_ in new_set: - if len(text_format.format(", ".join(set_))) > 1995: - internal_meme = True - - if not internal_meme: + if len(", ".join(concat_set)) >= 1995: + new_sets.append(concat_set.pop()) + else: break - start_num += 1 - - return new_set - - async def collect_anime_with_alias(self, anime_list, alias_list): - srv_anilist = [] - srv_anilist_alias = [] - for ani, _ in anime_list.items(): - srv_anilist.append(ani) - for alias, _ in alias_list.items(): - srv_anilist_alias.append(alias) - return srv_anilist, srv_anilist_alias - - async def find_any_matches(self, judul, anilist: list, aliases: list, alias_map: dict) -> list: - matches = self.get_close_matches(judul, anilist) - if aliases: - temp_anilias = self.get_close_matches(judul, aliases) - for match in temp_anilias: - res = self.find_alias_anime(match, alias_map) - if res is None: - continue - if res not in matches: # To not duplicate result - matches.append(res) - return matches + if concat_set: + finalized_sets.append(concat_set) + if new_sets: + finalized_sets.append(new_sets) + + return finalized_sets + + @staticmethod + def propagate_anime_with_aliases(anime_lists): + propagated_anime = [] + for index, anime_data in enumerate(anime_lists): + propagated_anime.append( + {"id": anime_data["id"], "index": index, "name": anime_data["title"], "type": "real"} + ) + if "aliases" in anime_data and anime_data["aliases"]: + for alias in anime_data["aliases"]: + propagated_anime.append( + { + "id": anime_data["id"], + "index": index, + "name": alias, + "type": "alias", + "real_name": anime_data["title"], + } + ) + return propagated_anime + + def find_any_matches(self, match_title: str, propagated_lists: list) -> list: + matches = self.get_close_matches(match_title, propagated_lists) + # Deduplicates + deduplicated = [] + dedup_index = [] + for match in matches: + if str(match["index"]) not in dedup_index: + if match["type"] == "alias": + match["name"] = match["real_name"] + deduplicated.append(match) + dedup_index.append(str(match["index"])) + return deduplicated async def send_all_projects(self, ctx, dataset: list, srv_: str): if len(dataset) < 1: self.logger.warning(f"{srv_}: no registered data on database.") return await ctx.send("**Tidak ada anime yang terdaftar di database**") - sorted_data = sorted(dataset) + anime_title_set = [] + for anime in dataset: + anime_title_set.append(anime["title"]) + sorted_data = sorted(anime_title_set) self.logger.info(f"{srv_}: sending all registered anime.") sorted_data = self.split_until_less_than(sorted_data) first_time = True @@ -709,7 +745,8 @@ async def send_all_projects(self, ctx, dataset: list, srv_: str): else: await ctx.send("{}".format(", ".join(data))) - async def confirmation_dialog(self, bot, ctx, message: str) -> bool: + @staticmethod + async def confirmation_dialog(bot, ctx, message: str) -> bool: dis_msg = await ctx.send(message) to_react = ["✅", "❌"] for react in to_react: diff --git a/cogs/showtimes_module/data.py b/cogs/showtimes_module/data.py index 573cc55..064accc 100644 --- a/cogs/showtimes_module/data.py +++ b/cogs/showtimes_module/data.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import logging -import time from copy import deepcopy from functools import partial @@ -10,7 +9,7 @@ from nthelper.bot import naoTimesBot from nthelper.showtimes_helper import ShowtimesQueueData -from nthelper.utils import get_current_time, send_timed_msg +from nthelper.utils import confirmation_dialog, get_current_time, send_timed_msg from .base import ShowtimesBase, fetch_anilist @@ -54,14 +53,12 @@ async def ubahdata(self, ctx, *, judul): self.logger.warning(f"{server_message}: not the server admin") return await ctx.send("Hanya admin yang bisa mengubah data garapan.") - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -71,8 +68,12 @@ async def ubahdata(self, ctx, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] koleb_list = [] if "kolaborasi" in program_info: @@ -90,7 +91,7 @@ async def get_user_name(user_id): try: user_data = self.bot.get_user(int(user_id)) return "{}#{}".format(user_data.name, user_data.discriminator) - except AttributeError: + except (AttributeError, ValueError, TypeError): return "[Rahasia]" async def internal_change_staff(role, staff_list, emb_msg): @@ -103,7 +104,7 @@ async def internal_change_staff(role, staff_list, emb_msg): "TS": "Typesetter", "QC": "Quality Checker", } - self.logger.info(f"{matches[0]}: changing {role}") + self.logger.info(f"{ani_title}: changing {role}") embed = discord.Embed(title="Mengubah Staff", color=0xEB79B9) embed.add_field( name="{} ID".format(better_names[role]), @@ -120,10 +121,14 @@ async def internal_change_staff(role, staff_list, emb_msg): mentions = await_msg.mentions if not mentions: if await_msg.content.isdigit(): - staff_list[role]["id"] = await_msg.content + staff_list[role]["id"] = str(await_msg.content) usr_ = await get_user_name(await_msg.content) if usr_ == "[Rahasia]": usr_ = None + else: + usr_split = usr_.split("#") + # Remove denominator + usr_ = "#".join(usr_split[:-1]) staff_list[role]["name"] = usr_ await await_msg.delete() break @@ -139,20 +144,26 @@ async def internal_change_staff(role, staff_list, emb_msg): async def ubah_staff(emb_msg): first_run = True - self.logger.info(f"{matches[0]}: processing staff.") + self.logger.info(f"{ani_title}: processing staff.") while True: if first_run: - staff_list = deepcopy(srv_data["anime"][matches[0]]["staff_assignment"]) + staff_list = deepcopy(program_info["assignments"]) staff_list_key = list(staff_list.keys()) first_run = False staff_list_name = {} for k, v in staff_list.items(): usr_ = await get_user_name(v["id"]) + if usr_ == "[Rahasia]": + usr_ = None + else: + split_name = usr_.split("#") + if len(split_name) > 2: + usr_ = "#".join(split_name[:-1]) staff_list_name[k] = usr_ embed = discord.Embed( - title="Mengubah Staff", description="Anime: {}".format(matches[0]), color=0xEBA279, + title="Mengubah Staff", description="Anime: {}".format(ani_title), color=0xEBA279, ) embed.add_field(name="1⃣ TLor", value=staff_list_name["TL"], inline=False) embed.add_field(name="2⃣ TLCer", value=staff_list_name["TLC"], inline=False) @@ -198,20 +209,21 @@ def check_react(reaction, user): staff_list_key[reaction_pos], staff_list, emb_msg ) - self.logger.info(f"{matches[0]}: setting new staff.") - srv_data["anime"][matches[0]]["staff_assignment"] = staff_list + self.logger.info(f"{ani_title}: setting new staff.") + program_info["assignments"] = staff_list if koleb_list: for other_srv in koleb_list: osrv_data = await self.showqueue.fetch_database(other_srv) if osrv_data is None: continue - osrv_data["anime"][matches[0]]["staff_assignment"] = staff_list + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + osrv_data["anime"][indx_other]["assignments"] = staff_list await self.showqueue.add_job(ShowtimesQueueData(osrv_data, other_srv)) return emb_msg async def ubah_role(emb_msg): - self.logger.info(f"{matches[0]}: processing role.") + self.logger.info(f"{ani_title}: processing role.") embed = discord.Embed(title="Mengubah Role", color=0xEBA279) embed.add_field( name="Role ID", @@ -229,32 +241,32 @@ async def ubah_role(emb_msg): if not mentions: if await_msg.content.isdigit(): - srv_data["anime"][matches[0]]["role_id"] = await_msg.content + program_info["role_id"] = str(await_msg.content) await await_msg.delete() break if await_msg.content.startswith("auto"): c_role = await ctx.message.guild.create_role( - name=matches[0], colour=discord.Colour(0xDF2705), mentionable=True, + name=ani_title, colour=discord.Colour.random(), mentionable=True, ) - srv_data["anime"][matches[0]]["role_id"] = str(c_role.id) + program_info["role_id"] = str(c_role.id) await await_msg.delete() break else: - srv_data["anime"][matches[0]]["role_id"] = str(mentions[0].id) + program_info["role_id"] = str(mentions[0].id) await await_msg.delete() break - self.logger.info(f"{matches[0]}: setting role...") - role_ids = srv_data["anime"][matches[0]]["role_id"] + self.logger.info(f"{ani_title}: setting role...") + role_ids = program_info["role_id"] await send_timed_msg(ctx, f"Berhasil menambah role ID ke {role_ids}", 2) return emb_msg async def tambah_episode(emb_msg): - self.logger.info(f"{matches[0]}: adding new episode...") + self.logger.info(f"{ani_title}: adding new episode...") status_list = program_info["status"] - max_episode = list(status_list.keys())[-1] - anilist_data = await fetch_anilist(program_info["anilist_id"], 1, max_episode, True) + max_episode = status_list[-1]["episode"] + anilist_data = await fetch_anilist(program_info["id"], 1, max_episode, True) time_data = anilist_data["time_data"] embed = discord.Embed( @@ -285,10 +297,10 @@ async def tambah_episode(emb_msg): osrv_data = await self.showqueue.fetch_database(osrv) if osrv_data is None: continue - osrv_dumped[osrv] = osrv_data + osrv_dumped[osrv] = deepcopy(osrv_data) if ( - program_info["status"][max_episode]["status"] == "released" + program_info["status"][-1]["is_done"] and "fsdb_data" in program_info and self.fsdb_conn is not None ): @@ -296,47 +308,52 @@ async def tambah_episode(emb_msg): if "id" in program_info["fsdb_data"]: await self.fsdb_conn.update_project(program_info["fsdb_data"]["id"], "status", "Jalan") - self.logger.info(f"{matches[0]}: adding a total of {jumlah_tambahan}...") + self.logger.info(f"{ani_title}: adding a total of {jumlah_tambahan}...") for x in range( int(max_episode) + 1, int(max_episode) + jumlah_tambahan + 1 ): # range(int(c), int(c)+int(x)) st_data = {} staff_status = {} - staff_status["TL"] = "x" - staff_status["TLC"] = "x" - staff_status["ENC"] = "x" - staff_status["ED"] = "x" - staff_status["TM"] = "x" - staff_status["TS"] = "x" - staff_status["QC"] = "x" + staff_status["TL"] = False + staff_status["TLC"] = False + staff_status["ENC"] = False + staff_status["ED"] = False + staff_status["TM"] = False + staff_status["TS"] = False + staff_status["QC"] = False - st_data["status"] = "not_released" + st_data["is_done"] = False try: - st_data["airing_time"] = time_data[x - 1] + st_data["airtime"] = time_data[x - 1] except IndexError: pass - st_data["staff_status"] = staff_status + st_data["progress"] = staff_status + st_data["episode"] = x if osrv_dumped: for osrv, osrv_data in osrv_dumped.items(): - osrv_data["anime"][matches[0]]["status"][str(x)] = st_data - osrv_dumped[osrv] = osrv_data - srv_data["anime"][matches[0]]["status"][str(x)] = st_data + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + try: + osrv_data["anime"][indx_other]["status"].append(st_data) + osrv_dumped[osrv] = {"idx": indx_other, "data": osrv_data} + except (KeyError, IndexError): + continue + program_info["status"].append(st_data) if osrv_dumped: for osrv, osrv_data in osrv_dumped.items(): - osrv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) - await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + osrv_data["data"]["anime"][osrv_data["idx"]]["last_update"] = self.get_unix() + await self.showqueue.add_job(ShowtimesQueueData(osrv_data["data"], osrv)) + program_info["last_update"] = self.get_unix() await send_timed_msg(ctx, f"Berhasil menambah {jumlah_tambahan} episode baru", 2) return emb_msg async def hapus_episode(emb_msg): - self.logger.info(f"{matches[0]}: removing an episodes...") + self.logger.info(f"{ani_title}: removing an episodes...") status_list = program_info["status"] - max_episode = list(status_list.keys())[-1] + max_episode = status_list[-1]["episode"] embed = discord.Embed( title="Menghapus Episode", @@ -412,30 +429,51 @@ def check_react(reaction, user): current = int(total_episode[0]) total = int(total_episode[1]) + if current > max_episode: + await send_timed_msg( + ctx, f"Angka tidak bisa lebih dari episode maksimum ({current} > {max_episode})", 2 + ) + return emb_msg + if total > max_episode: + await send_timed_msg( + ctx, f"Angka akhir tidak bisa lebih dari episode maksimum ({current} > {max_episode})", 2 + ) + return emb_msg + + if current > total: + await send_timed_msg( + ctx, f"Angka awal tidak bisa lebih dari angka akhir ({current} > {total})", 2 + ) + return emb_msg + + self.logger.info(f"{ani_title}: removing a total of {total} episodes...") + to_remove = [] + for x in range(current, total + 1): + to_remove.append(str(x)) + + new_statues_sets = [] + for status in status_list: + if str(status["episode"]) not in to_remove: + new_statues_sets.append(status) + if koleb_list: for osrv in koleb_list: osrv_data = await self.showqueue.fetch_database(osrv) if osrv_data is None: continue - for x in range(current, total + 1): # range(int(c), int(c)+int(x)) - del osrv_data["anime"][matches[0]]["status"][str(x)] - osrv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + osrv_data["anime"][indx_other]["status"] = new_statues_sets + osrv_data["anime"][indx_other]["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) - self.logger.info(f"{matches[0]}: removing a total of {total} episodes...") - for x in range(current, total + 1): # range(int(c), int(c)+int(x)) - del srv_data["anime"][matches[0]]["status"][str(x)] + program_info["status"] = new_statues_sets - new_max_ep = list(srv_data["anime"][matches[0]]["status"].keys())[-1] - if ( - program_info["status"][new_max_ep]["status"] == "released" - and "fsdb_data" in program_info - and self.fsdb_conn is not None - ): + new_max_ep = new_statues_sets[-1] + if new_max_ep["is_done"] and "fsdb_data" in program_info and self.fsdb_conn is not None: self.logger.info("Updating FSDB Project to finished.") if "id" in program_info["fsdb_data"]: await self.fsdb_conn.update_project(program_info["fsdb_data"]["id"], "status", "Tamat") - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + program_info["last_update"] = self.get_unix() await send_timed_msg(ctx, f"Berhasil menghapus episode {current} ke {total}", 2) @@ -443,10 +481,10 @@ def check_react(reaction, user): async def hapus_utang_tanya(emb_msg): delete_ = False - self.logger.info(f"{matches[0]}: preparing to nuke project...") + self.logger.info(f"{ani_title}: preparing to nuke project...") while True: embed = discord.Embed( - title="Menghapus Utang", description="Anime: {}".format(matches[0]), color=0xCC1C20, + title="Menghapus Utang", description="Anime: {}".format(ani_title), color=0xCC1C20, ) embed.add_field( name="Peringatan!", @@ -491,10 +529,10 @@ def check_react(reaction, user): hapus_utang = False while True: guild_roles = ctx.message.guild.roles - total_episodes = len(srv_data["anime"][matches[0]]["status"]) - role_id = srv_data["anime"][matches[0]]["role_id"] + total_episodes = len(program_info["status"]) + role_id = program_info["role_id"] embed = discord.Embed( - title="Mengubah Data", description="Anime: {}".format(matches[0]), color=0xE7E363, + title="Mengubah Data", description="Anime: {}".format(ani_title), color=0xE7E363, ) embed.add_field( name="1⃣ Ubah Staff", value="Ubah staff yang mengerjakan anime ini.", inline=False, @@ -574,17 +612,17 @@ def check_react(reaction, user): break if exit_command: - self.logger.warning(f"{matches[0]}: cancelling...") + self.logger.warning(f"{ani_title}: cancelling...") return await ctx.send("**Dibatalkan!**") if hapus_utang: - self.logger.warning(f"{matches[0]}: nuking project...") + self.logger.warning(f"{ani_title}: nuking project...") if "fsdb_data" in program_info and self.fsdb_conn is not None: self.logger.info("Updating FSDB Project to dropped.") if "id" in program_info["fsdb_data"]: await self.fsdb_conn.update_project(program_info["fsdb_data"]["id"], "status", "Drop") current = self.get_current_ep(program_info["status"]) try: - if program_info["status"]["1"]["status"] == "not_released": + if not program_info["status"][0]["status"]: announce_it = False elif not current: announce_it = False @@ -593,31 +631,26 @@ def check_react(reaction, user): except KeyError: announce_it = True - del srv_data["anime"][matches[0]] + srv_data["anime"].pop(indx) for osrv in koleb_list: osrv_data = await self.showqueue.fetch_database(osrv) if osrv_data is None: continue - if ( - "kolaborasi" in osrv_data["anime"][matches[0]] - and server_message in osrv_data["anime"][matches[0]]["kolaborasi"] - ): - klosrv = deepcopy(osrv_data["anime"][matches[0]]["kolaborasi"]) - klosrv.remove(server_message) - - remove_all = False - if len(klosrv) == 1 and klosrv[0] == osrv: - remove_all = True - - if remove_all: - del osrv_data["anime"][matches[0]]["kolaborasi"] - else: - osrv_data["anime"][matches[0]]["kolaborasi"] = klosrv + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + try: + progoinfo = osrv_data["anime"][indx_other] + except IndexError: + continue + if "kolaborasi" in progoinfo and server_message in progoinfo["kolaborasi"]: + try: + osrv_data["anime"][indx_other]["kolaborasi"].remove(server_message) + except ValueError: + pass await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) - self.logger.info(f"{matches[0]}: storing final data...") - await ctx.send("Berhasil menghapus **{}** dari daftar utang".format(matches[0])) + self.logger.info(f"{ani_title}: storing final data...") + await ctx.send("Berhasil menghapus **{}** dari daftar utang".format(ani_title)) self.logger.info(f"{server_message}: updating database...") success, msg = await self.ntdb.update_data_server(server_message, srv_data) @@ -641,11 +674,15 @@ def check_react(reaction, user): if "announce_channel" in srv_data and srv_data["announce_channel"]: announce_chan = srv_data["announce_channel"] - target_chan = self.bot.get_channel(int(announce_chan)) - embed = discord.Embed(title="{}".format(matches[0]), color=0xB51E1E) + try: + target_chan = self.bot.get_channel(int(announce_chan)) + except (AttributeError, ValueError, TypeError): + self.logger.warning(f"{ani_title}: failed to fetch announce channel, ignoring...") + return + embed = discord.Embed(title=ani_title, color=0xB51E1E) embed.add_field( name="Dropped...", - value="{} telah di drop dari fansub ini :(".format(matches[0]), + value="{} telah di drop dari fansub ini :(".format(ani_title), inline=False, ) embed.set_footer(text=f"Pada: {get_current_time()}") @@ -655,7 +692,7 @@ def check_react(reaction, user): await target_chan.send(embed=embed) return - self.logger.info(f"{matches[0]}: saving new data...") + self.logger.info(f"{ani_title}: saving new data...") await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) self.logger.info(f"{server_message}: updating database...") @@ -678,7 +715,7 @@ def check_react(reaction, user): if server_message not in self.bot.showtimes_resync: self.bot.showtimes_resync.append(server_message) - await ctx.send("Berhasil menyimpan data baru untuk garapan **{}**".format(matches[0])) + await ctx.send("Berhasil menyimpan data baru untuk garapan **{}**".format(ani_title)) @commands.command(aliases=["addnew"]) @commands.guild_only() @@ -704,7 +741,13 @@ async def tambahutang(self, ctx): self.logger.warning(f"{server_message}: not the server admin") return await ctx.send("Hanya admin yang bisa menambah utang") - srv_anilist, _ = await self.collect_anime_with_alias(srv_data["anime"], srv_data["alias"]) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) + srv_anilist = [] + srv_anilist_ids = [] + for anime in propagated_anilist: + if anime["type"] == "real": + srv_anilist.append(anime["name"]) + srv_anilist_ids.append(anime["id"]) self.logger.info(f"{server_message}: creating initial data...") embed = discord.Embed(title="Menambah Utang", color=0x56ACF3) @@ -713,7 +756,6 @@ async def tambahutang(self, ctx): text="Dibawakan oleh naoTimes™®", icon_url="https://p.n4o.xyz/i/nao250px.png", ) emb_msg = await ctx.send(embed=embed) - current_time = int(round(time.time())) msg_author = ctx.message.author json_tables = { "ani_title": "", @@ -753,16 +795,24 @@ async def process_episode(table, emb_msg): ) await emb_msg.edit(embed=embed) + episode_content = None while True: await_msg = await self.bot.wait_for("message", check=check_if_author) if await_msg.content.isdigit(): - await await_msg.delete() + episode_content = await_msg.content + try: + await await_msg.delete() + except discord.NotFound: + pass break - await await_msg.delete() + try: + await await_msg.delete() + except discord.NotFound: + pass - anilist_data = await fetch_anilist(table["anilist_id"], 1, int(await_msg.content), True) + anilist_data = await fetch_anilist(table["anilist_id"], 1, int(episode_content), True) table["episodes"] = anilist_data["total_episodes"] table["time_data"] = anilist_data["time_data"] @@ -791,12 +841,22 @@ async def process_anilist(table, emb_msg): return False, "Dibatalkan oleh user." if await_msg.content.isdigit(): - await await_msg.delete() - break - - await await_msg.delete() + if await_msg.content in srv_anilist_ids: + await send_timed_msg(ctx, "ID Anime tersebut sudah terdaftar!", 2) + else: + break + else: + await send_timed_msg(ctx, "Mohon masukan angka!", 2) - anilist_data = await fetch_anilist(await_msg.content, 1, 1, True) + try: + anilist_data = await fetch_anilist(await_msg.content, 1, 1, True) + except Exception: # skipcq: PYL-W0703 + self.logger.warning(f"{server_message}: failed to fetch air start, please try again later.") + return ( + False, + "Gagal mendapatkan waktu mulai tayang, silakan coba lagi ketika sudah " + "ada kepastian kapan animenya mulai.", + ) poster_data, title = anilist_data["poster_data"], anilist_data["title"] time_data, episodes_total = anilist_data["time_data"], anilist_data["total_episodes"] poster_image, poster_color = poster_data["image"], poster_data["color"] @@ -837,7 +897,7 @@ def check_react(reaction, user): ) return ( False, - "Gagal mendapatkan start_date, silakan coba lagi ketika sudah " + "Gagal mendapatkan waktu mulai tayang, silakan coba lagi ketika sudah " "ada kepastian kapan animenya mulai.", ) table["ani_title"] = title @@ -846,7 +906,7 @@ def check_react(reaction, user): "color": poster_color, } table["anilist_id"] = str(await_msg.content) - table["mal_id"] = anilist_data["idMal"] + table["mal_id"] = str(anilist_data["idMal"]) table["start_time"] = start_time await emb_msg.clear_reactions() elif "❌" in str(res.emoji): @@ -891,7 +951,7 @@ async def process_role(table, emb_msg): self.logger.info(f"{server_message}: auto-generating role...") try: c_role = await ctx.message.guild.create_role( - name=table["ani_title"], colour=discord.Colour(0xDF2705), mentionable=True, + name=table["ani_title"], colour=discord.Colour.random(), mentionable=True, ) table["role_generated"] = c_role table["role_id"] = str(c_role.id) @@ -1052,7 +1112,7 @@ async def fetch_username_from_id(_id): try: user_data = self.bot.get_user(int(_id)) return "{}#{}".format(user_data.name, user_data.discriminator), True - except AttributeError: + except (AttributeError, ValueError, TypeError): return "[Rahasia]", False self.logger.info(f"{server_message}: checkpoint before commiting") @@ -1245,14 +1305,15 @@ def check_react(reaction, user): new_anime_data = {} staff_data = {} - status = {} + status = [] - new_anime_data["anilist_id"] = json_tables["anilist_id"] - new_anime_data["mal_id"] = json_tables["mal_id"] - new_anime_data["last_update"] = str(current_time) - new_anime_data["role_id"] = json_tables["role_id"] + new_anime_data["id"] = str(json_tables["anilist_id"]) + new_anime_data["mal_id"] = str(json_tables["mal_id"]) + new_anime_data["title"] = json_tables["ani_title"] + new_anime_data["last_update"] = self.get_unix() + new_anime_data["role_id"] = str(json_tables["role_id"]) new_anime_data["poster_data"] = json_tables["poster_data"] - new_anime_data["start_time"] = json_tables["start_time"] + new_anime_data["start_time"] = int(json_tables["start_time"]) def get_username_of_user(user_id): try: @@ -1289,27 +1350,30 @@ def get_username_of_user(user_id): "id": str(json_tables["qcer_id"]), "name": get_username_of_user(json_tables["qcer_id"]), } - new_anime_data["staff_assignment"] = staff_data + new_anime_data["assignments"] = staff_data self.logger.info(f"{server_message}: generating episode...") for x in range(int(json_tables["episodes"])): st_data = {} staff_status = {} - staff_status["TL"] = "x" - staff_status["TLC"] = "x" - staff_status["ENC"] = "x" - staff_status["ED"] = "x" - staff_status["TM"] = "x" - staff_status["TS"] = "x" - staff_status["QC"] = "x" + staff_status["TL"] = False + staff_status["TLC"] = False + staff_status["ENC"] = False + staff_status["ED"] = False + staff_status["TM"] = False + staff_status["TS"] = False + staff_status["QC"] = False - st_data["status"] = "not_released" - st_data["airing_time"] = json_tables["time_data"][x] - st_data["staff_status"] = staff_status - status[str(x + 1)] = st_data + st_data["is_done"] = False + st_data["airtime"] = json_tables["time_data"][x] + st_data["progress"] = staff_status + st_data["episode"] = x + 1 + status.append(st_data) new_anime_data["status"] = status + new_anime_data["aliases"] = [] + new_anime_data["kolaborasi"] = [] if "fsdb_id" in srv_data and self.fsdb_conn is not None: embed = discord.Embed(title="Menambah Utang", color=0x56ACF3) @@ -1324,7 +1388,7 @@ def get_username_of_user(user_id): existing_projects = {str(data["anime"]["mal_id"]): data["id"] for data in fansubs_projects} mal_id = new_anime_data["mal_id"] - fsani_data = await self.split_search_id(collect_anime_dataset, "mal_id", mal_id) + fsani_data = await self.split_search_id(collect_anime_dataset, "mal_id", int(mal_id)) if fsani_data is None: _, fsani_id = await self.fsdb_conn.import_mal(int(mal_id)) _, fsproject_id = await self.fsdb_conn.add_new_project(fsani_id, srv_data["fsdb_id"]) @@ -1340,7 +1404,7 @@ def get_username_of_user(user_id): _, fsproject_id = await self.fsdb_conn.add_new_project(fsani_id, srv_data["fsdb_id"]) new_anime_data["fsdb_data"] = {"id": fsproject_id, "ani_id": fsani_id} - srv_data["anime"][json_tables["ani_title"]] = new_anime_data + srv_data["anime"].append(new_anime_data) embed = discord.Embed(title="Menambah Utang", color=0x56ACF3) embed.add_field(name="Memproses!", value="Mengirim data...", inline=True) @@ -1382,3 +1446,45 @@ def get_username_of_user(user_id): if gen_all_success: await ctx.send("Bot telah otomatis menambah role ke member garapan, mohon cek!") + + @commands.command(name="showui") + async def _show_ui_cmd(self, ctx: commands.Context, guild_id: str = None): + if self.ntdb is None: + self.logger.info("owner hasn't enabled naoTimesDB yet.") + return + + server_message = guild_id + using_guild_id = True + if server_message is None: + try: + server_message = str(ctx.message.guild.id) + using_guild_id = False + except AttributeError: + return await ctx.send( + "Mohon jalankan di server, atau berikan ID server!\n" + f"Contoh: `{self.bot.prefixes(ctx)}showui xxxxxxxxxxx`" + ) + + self.logger.info(f"requesting {server_message}") + srv_data = await self.showqueue.fetch_database(server_message) + if srv_data is None: + self.logger.info("cannot find the server in database") + if using_guild_id: + await ctx.send(f"Tidak dapat menemukan ID `{server_message}` di database naoTimes!") + return + + author = ctx.message.author.id + srv_owner = srv_data["serverowner"] + if str(author) not in srv_owner: + self.logger.warning(f"User {author} are unauthorized to create a new database") + return await ctx.send("Tidak berhak untuk melihat password, hanya Admin yang terdaftar yang bisa") + + self.logger.info("Making new login info!") + do_continue = await confirmation_dialog( + self.bot, ctx, "Perintah ini akan memperlihatkan kode rahasia untuk login di WebUI, lanjutkan?" + ) + if not do_continue: + return + + _, return_msg = await self.ntdb.generate_login_info(server_message) + await ctx.send(return_msg) diff --git a/cogs/showtimes_module/fsdb.py b/cogs/showtimes_module/fsdb.py index 0e79cf1..64fcbc6 100644 --- a/cogs/showtimes_module/fsdb.py +++ b/cogs/showtimes_module/fsdb.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import asyncio import logging from functools import partial @@ -9,7 +8,7 @@ from nthelper.bot import naoTimesBot from nthelper.showtimes_helper import ShowtimesQueueData -from nthelper.utils import HelpGenerator +from nthelper.utils import DiscordPaginator, HelpGenerator from .base import ShowtimesBase @@ -78,8 +77,6 @@ async def fsdb_integrasi(self, ctx): self.logger.warning(f"{server_message}: already integrated with fsdb.") return await ctx.send("Fansub sudah terintegrasi dengan FansubDB.") - srv_anilist, _ = await self.collect_anime_with_alias(srv_data["anime"], srv_data["alias"]) - self.logger.info(f"{server_message}: creating initial data...") embed = discord.Embed(title="Integrasi FansubDB", color=0x56ACF3) embed.add_field(name="Memulai Proses!", value="Mempersiapkan...", inline=False) @@ -210,72 +207,82 @@ def check_react(reaction, user): osrv_dumped = {} if collect_anime_dataset: collect_anime_dataset.sort(key=lambda x: x["mal_id"]) - for ani in srv_anilist: - mal_id = srv_data["anime"][ani]["mal_id"] + for n, ani in enumerate(srv_data["anime"]): + mal_id = ani["mal_id"] fsdata = await self.split_search_id(collect_anime_dataset, "mal_id", mal_id) if fsdata is None: res, fs_id = await self.fsdb_conn.import_mal(int(mal_id)) else: fs_id = fsdata["id"] - srv_data["anime"][ani]["fsdb_data"] = {"ani_id": fs_id} + srv_data["anime"][n]["fsdb_data"] = {"ani_id": fs_id} fansubs_projects, _ = await self.fsdb_conn.fetch_fansub_projects(json_tables["fs_id"]) existing_projects = {str(data["anime"]["id"]): data["id"] for data in fansubs_projects} - for ani, ani_data in srv_data["anime"].items(): - kolaborasi_data = [] - if "kolaborasi" in ani_data: - kolaborasi_data = ani_data["kolaborasi"] - if kolaborasi_data: - kolaborasi_data.remove(server_message) - fsani_id = str(ani_data["fsdb_data"]["ani_id"]) + for anime_data in srv_data["anime"]: + koleb_data = [] + if "kolaborasi" in anime_data: + koleb_data = anime_data["kolaborasi"] + if koleb_data and server_message in koleb_data: + koleb_data.remove(server_message) + fsani_id = str(anime_data["fsdb_data"]["ani_id"]) if fsani_id in existing_projects: self.logger.info(f"fsdb_ani{fsani_id}: using existing project in fansubdb...") - srv_data["anime"][ani]["fsdb_data"]["id"] = existing_projects[fsani_id] - if kolaborasi_data: - for osrv in kolaborasi_data: + anime_data["fsdb_data"]["id"] = existing_projects[fsani_id] + if koleb_data: + for osrv in koleb_data: if osrv == server_message: continue osrv_data = await self.showqueue.fetch_database(osrv) - if osrv_data is not None: - if "fsdb_data" not in osrv_data["anime"][ani]: - osrv_data["anime"][ani]["fsdb_data"] = { - "id": existing_projects[fsani_id], - "ani_id": srv_data["anime"][ani]["fsdb_data"]["ani_id"], - } - await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) - osrv_dumped[osrv] = osrv_data - else: - self.logger.warning(f"Unknown bang server ini: {osrv}") + if osrv_data is None: + self.logger.warning(f"Unknown server: {osrv}") + continue + find_oani_idx = self._search_data_index( + osrv_data["anime"], "id", anime_data["id"] + ) + if find_oani_idx is None: + self.logger.warning(f"Can't find anime index: {osrv}") + continue + osrv_data["anime"][find_oani_idx]["fsdb_data"] = { + "id": existing_projects[fsani_id], + "ani_id": anime_data["fsdb_data"]["ani_id"], + } + await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) + osrv_dumped[osrv] = osrv_data else: - first_ep = list(ani_data["status"].keys())[0] - final_ep = list(ani_data["status"].keys())[-1] status_add = "Tentatif" - if ani_data["status"][final_ep]["status"] == "released": + if anime_data["status"][-1]["is_done"]: status_add = "Tamat" - elif ani_data["status"][first_ep]["status"] == "released": + elif anime_data["status"][0]["is_done"]: status_add = "Jalan" + if len(anime_data["status"]) == 1: + status_add = "Tamat" self.logger.info(f"fsdb_ani{fsani_id}: creating new project in fansubdb...") scd, project_id = await self.fsdb_conn.add_new_project( fsani_id, json_tables["fs_id"], status_add ) if scd: self.logger.info(f"fsdb_ani{fsani_id}: appending proj{project_id} to database...") - srv_data["anime"][ani]["fsdb_data"]["id"] = project_id - if kolaborasi_data: - for osrv in kolaborasi_data: + anime_data["fsdb_data"]["id"] = project_id + if koleb_data: + for osrv in koleb_data: if osrv == server_message: continue osrv_data = await self.showqueue.fetch_database(osrv) - if osrv_data is not None: - if "fsdb_data" not in osrv_data["anime"][ani]: - osrv_data["anime"][ani]["fsdb_data"] = { - "id": project_id, - "ani_id": srv_data["anime"][ani]["fsdb_data"]["ani_id"], - } - await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) - osrv_dumped[osrv] = osrv_data - else: - self.logger.warning(f"Unknown bang server ini: {osrv}") + if osrv_data is None: + self.logger.warning(f"Unknown server: {osrv}") + continue + find_oani_idx = self._search_data_index( + osrv_data["anime"], "id", anime_data["id"] + ) + if find_oani_idx is None: + self.logger.warning(f"Can't find anime index: {osrv}") + continue + osrv_data["anime"][find_oani_idx]["fsdb_data"] = { + "id": existing_projects[fsani_id], + "ani_id": anime_data["fsdb_data"]["ani_id"], + } + await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) + osrv_dumped[osrv] = osrv_data embed = discord.Embed(title="Integrasi FansubDB", color=0x56ACF3) embed.add_field(name="Memproses!", value="Membuat data akhir...", inline=True) @@ -335,15 +342,12 @@ async def fsdb_binding(self, ctx, *, judul=None): msg = await ctx.send("Memulai proses binding...") - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) - + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -353,10 +357,14 @@ async def fsdb_binding(self, ctx, *, judul=None): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] res = await self.confirmation_dialog( - self.bot, ctx, f"Apakah yakin ingin binding untuk judul **{matches[0]}**" + self.bot, ctx, f"Apakah yakin ingin binding untuk judul **{ani_title}**" ) if not res: return await ctx.send("Dibatalkan.") @@ -396,18 +404,18 @@ async def fsdb_binding(self, ctx, *, judul=None): if anifs_id == 0: _, anifs_id = await self.fsdb_conn.import_mal(program_info["mal_id"]) - final_ep = list(program_info["status"].keys())[-1] - first_ep = list(program_info["status"].keys())[0] status_add = "Tentatif" - if program_info["status"][final_ep]["status"] == "released": + if program_info["status"][-1]["is_done"]: status_add = "Tamat" - elif program_info["status"][first_ep]["status"] == "released": + elif program_info["status"][0]["is_done"]: status_add = "Jalan" + if len(program_info["status"]) == 1: + status_add = "Tamat" res, project_id = await self.fsdb_conn.add_new_project(anifs_id, srv_data["fsdb_id"], status_add) if not res: return await ctx.send("Gagal menambahkan project baru ke FansubDB") - srv_data["anime"][matches[0]]["fsdb_data"] = { + program_info["fsdb_data"] = { "id": project_id, "ani_id": anifs_id, } @@ -423,7 +431,7 @@ async def fsdb_binding(self, ctx, *, judul=None): self.bot.showtimes_resync.append(server_message) self.logger.info(f"{server_message}: done processing!") - await ctx.send(f"Selesai membinding judul **{matches[0]}**") + await ctx.send(f"Selesai membinding judul **{ani_title}**") @fsdb_cmd.command(name="fansub") async def fsdb_fansub(self, ctx, *, pencarian): @@ -454,65 +462,8 @@ async def create_fsdb_embed(data): embed.add_field(name="URL", value="\n".join(url_link_info)) return embed - first_run = True - num = 1 - max_page = len(results) - while True: - if first_run: - self.logger.info("showing results...") - data = results[num - 1] - embed = await create_fsdb_embed(data) - - first_run = False - msg = await ctx.send(embed=embed) - - reactmoji = [] - if max_page == 1 and num == 1: - break - if num == 1: - reactmoji.append("⏩") - elif num == max_page: - reactmoji.append("⏪") - reactmoji.append("✅") - - self.logger.debug("reacting message...") - for react in reactmoji: - await msg.add_reaction(react) - - def check_react(reaction, user): - if reaction.message.id != msg.id: - return False - if user != ctx.message.author: - return False - if str(reaction.emoji) not in reactmoji: - return False - return True - - try: - self.logger.debug("now waiting for reaction...") - res, user = await self.bot.wait_for("reaction_add", timeout=30.0, check=check_react) - except asyncio.TimeoutError: - self.logger.warning("timeout, removing reactions...") - return await msg.clear_reactions() - if user != ctx.message.author: - pass - elif "⏪" in str(res.emoji): - self.logger.debug("going backward...") - num = num - 1 - data = results[num - 1] - embed = await create_fsdb_embed(data) - - await msg.clear_reactions() - await msg.edit(embed=embed) - elif "⏩" in str(res.emoji): - self.logger.debug("going forward...") - num = num + 1 - data = results[num - 1] - embed = await create_fsdb_embed(data) - - await msg.clear_reactions() - await msg.edit(embed=embed) - elif "✅" in str(res.emoji): - self.logger.warning("deleting embed...") - await msg.clear_reactions() - return await msg.delete() + paginate = DiscordPaginator(self.bot, ctx) + paginate.checker() + paginate.breaker() + paginate.set_generator(create_fsdb_embed) + await paginate.start(results, 30.0) diff --git a/cogs/showtimes_module/others.py b/cogs/showtimes_module/others.py index 223bf1f..1114fb6 100644 --- a/cogs/showtimes_module/others.py +++ b/cogs/showtimes_module/others.py @@ -51,7 +51,16 @@ async def alias(self, ctx): self.logger.warning(f"{server_message}: not the server admin") return await ctx.send("Hanya admin yang bisa menambah alias") - srv_anilist, _ = await self.collect_anime_with_alias(srv_data["anime"], srv_data["alias"]) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) + srv_anilist = [] + existing_aliases = [] + existing_aliases_dex = [] + for data in propagated_anilist: + if data["type"] == "real": + srv_anilist.append(data) + elif data["type"] == "alias": + existing_aliases.append(data["name"]) + existing_aliases_dex.append(data) if len(srv_anilist) < 1: self.logger.warning(f"{server_message}: no registered data on database.") @@ -65,7 +74,7 @@ async def alias(self, ctx): ) emb_msg = await ctx.send(embed=embed) msg_author = ctx.message.author - json_tables = {"alias_anime": "", "target_anime": ""} + json_tables = {"alias_anime": "", "target_anime": -1} def check_if_author(m): return m.author == msg_author @@ -84,7 +93,7 @@ async def process_anime(table, emb_msg, anime_list): await emb_msg.edit(embed=embed) await_msg = await self.bot.wait_for("message", check=check_if_author) - matches = self.get_close_matches(await_msg.content, anime_list) + matches = self.find_any_matches(await_msg.content, anime_list) await await_msg.delete() if not matches: await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -94,9 +103,12 @@ async def process_anime(table, emb_msg, anime_list): if not matches: return await ctx.send("**Dibatalkan!**") + matched = matches[0] + ani_title = matched["name"] + embed = discord.Embed(title="Alias", color=0x96DF6A) embed.add_field( - name="Apakah benar?", value="Judul: **{}**".format(matches[0]), inline=False, + name="Apakah benar?", value="Judul: **{}**".format(ani_title), inline=False, ) embed.set_footer( text="Dibawakan oleh naoTimes™®", icon_url="https://p.n4o.xyz/i/nao250px.png", @@ -121,7 +133,7 @@ def check_react(reaction, user): if user != msg_author: pass elif "✅" in str(res.emoji): - table["target_anime"] = matches[0] + table["target_anime"] = matched["index"] await emb_msg.clear_reactions() elif "❌" in str(res.emoji): await ctx.send("**Dibatalkan!**") @@ -158,14 +170,18 @@ async def process_alias(table, emb_msg): first_time = True cancel_toggled = False while True: + if json_tables["target_anime"] < 0: + anime_name = "*[Unknown]*" + else: + anime_name = srv_data["anime"][json_tables["target_anime"]]["title"] embed = discord.Embed( title="Alias", description="Periksa data!\nReact jika ingin diubah.", color=0xE7E363, ) embed.add_field( - name="1⃣ Anime/Garapan", value="{}".format(json_tables["target_anime"]), inline=False, + name="1⃣ Anime/Garapan", value=anime_name, inline=False, ) embed.add_field( - name="2⃣ Alias", value="{}".format(json_tables["alias_anime"]), inline=False, + name="2⃣ Alias", value=json_tables["alias_anime"], inline=False, ) embed.add_field( name="Lain-Lain", value="✅ Tambahkan!\n❌ Batalkan!", inline=False, @@ -224,12 +240,14 @@ def check_react(reaction, user): ) await emb_msg.edit(embed=embed) - if json_tables["alias_anime"] in srv_data["alias"]: + if json_tables["alias_anime"] in existing_aliases: + aindex = existing_aliases.index(json_tables["alias_anime"]) + anime_match = existing_aliases_dex[aindex] embed = discord.Embed(title="Alias", color=0xE24545) embed.add_field( name="Dibatalkan!", value="Alias **{}** sudah terdaftar untuk **{}**".format( - json_tables["alias_anime"], srv_data["alias"][json_tables["alias_anime"]], + json_tables["alias_anime"], anime_match["real_name"], ), inline=True, ) @@ -239,7 +257,10 @@ def check_react(reaction, user): await emb_msg.delete() return await ctx.send(embed=embed) - srv_data["alias"][json_tables["alias_anime"]] = json_tables["target_anime"] + program_info = srv_data["anime"][json_tables["target_anime"]] + if "aliases" not in program_info: + program_info["aliases"] = [] + program_info["aliases"].append(json_tables["alias_anime"]) embed = discord.Embed(title="Alias", color=0x56ACF3) embed.add_field(name="Memproses!", value="Mengirim data...", inline=True) @@ -254,7 +275,7 @@ def check_react(reaction, user): embed.add_field( name="Sukses!", value="Alias **{} ({})** telah ditambahkan ke database\nDatabase utama akan diupdate sebentar lagi".format( # noqa: E501 - json_tables["alias_anime"], json_tables["target_anime"] + json_tables["alias_anime"], program_info["title"] ), inline=True, ) @@ -274,7 +295,7 @@ def check_react(reaction, user): await ctx.send( "Berhasil menambahkan alias **{} ({})** ke dalam database utama naoTimes".format( # noqa: E501 - json_tables["alias_anime"], json_tables["target_anime"] + json_tables["alias_anime"], program_info["title"] ) ) @@ -288,16 +309,23 @@ async def alias_list(self, ctx, *, judul): return self.logger.info(f"{server_message}: data found.") - if not srv_data["alias"]: - return await ctx.send("Tidak ada alias yang terdaftar.") + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) + srv_anilist = [] + existing_aliases = [] + for data in propagated_anilist: + if data["type"] == "real": + srv_anilist.append(data) + elif data["type"] == "alias": + existing_aliases.append(data) - srv_anilist, _ = await self.collect_anime_with_alias(srv_data["anime"], srv_data["alias"]) + if not existing_aliases: + return await ctx.send("Tidak ada alias yang terdaftar.") if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = self.get_close_matches(judul, srv_anilist) + matches = self.find_any_matches(judul, srv_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -307,11 +335,14 @@ async def alias_list(self, ctx, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") + matched_anime = matches[0] + indx = matched_anime["index"] + + self.logger.info(f"{server_message}: matched {matched_anime}") srv_anilist_alias = [] - for k, v in srv_data["alias"].items(): - if v in matches: - srv_anilist_alias.append(k) + for aliases in existing_aliases: + if aliases["index"] == indx: + srv_anilist_alias.append(aliases["name"]) text_value = "" if not srv_anilist_alias: @@ -322,7 +353,7 @@ async def alias_list(self, ctx, *, judul): self.logger.info(f"{server_message}: sending alias!") embed = discord.Embed(title="Alias list", color=0x47E0A7) - embed.add_field(name=matches[0], value=text_value, inline=False) + embed.add_field(name=matched_anime["name"], value=text_value, inline=False) embed.set_footer( text="Dibawakan oleh naoTimes™®", icon_url="https://p.n4o.xyz/i/nao250px.png", ) @@ -342,15 +373,19 @@ async def alias_hapus(self, ctx, *, judul): self.logger.warning(f"{server_message}: not the server admin") return await ctx.send("Hanya admin yang bisa menghapus alias") - if not srv_data["alias"]: - return await ctx.send("Tidak ada alias yang terdaftar.") - - srv_anilist, _ = await self.collect_anime_with_alias(srv_data["anime"], srv_data["alias"]) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) + srv_anilist = [] + existing_aliases = [] + for data in propagated_anilist: + if data["type"] == "real": + srv_anilist.append(data) + elif data["type"] == "alias": + existing_aliases.append(data) if not judul: return await self.send_all_projects(ctx, srv_anilist, server_message) - matches = self.get_close_matches(judul, srv_anilist) + matches = self.find_any_matches(judul, srv_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -360,31 +395,40 @@ async def alias_hapus(self, ctx, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") + matched_anime = matches[0] + indx = matched_anime["index"] + + self.logger.info(f"{server_message}: matched {matched_anime}") srv_anilist_alias = [] - for k, v in srv_data["alias"].items(): - if v in matches: - srv_anilist_alias.append(k) + for aliases in existing_aliases: + if aliases["index"] == indx: + srv_anilist_alias.append(aliases) if not srv_anilist_alias: - self.logger.info(f"{matches[0]}: no registered alias.") - return await ctx.send("Tidak ada alias yang terdaftar untuk judul **{}**".format(matches[0])) + self.logger.info(f"{matched_anime['name']}: no registered alias.") + return await ctx.send( + "Tidak ada alias yang terdaftar untuk judul **{}**".format(matched_anime["name"]) + ) alias_chunked = [srv_anilist_alias[i : i + 5] for i in range(0, len(srv_anilist_alias), 5)] + def _create_naming_scheme(chunked_thing): + name_only = [] + for chunk in chunked_thing: + name_only.append(chunk["name"]) + return self.make_numbered_alias(name_only) + first_run = True n = 1 max_n = len(alias_chunked) while True: if first_run: self.logger.info(f"{server_message}: sending results...") - n = 1 + chunked = alias_chunked[n - 1] first_run = False embed = discord.Embed(title="Alias list", color=0x47E0A7) embed.add_field( - name="{}".format(matches[0]), - value=self.make_numbered_alias(alias_chunked[n - 1]), - inline=False, + name=matched_anime["name"], value=_create_naming_scheme(chunked), inline=False, ) embed.add_field( name="*Informasi*", @@ -429,12 +473,12 @@ def check_react(reaction, user): if user != ctx.message.author: pass elif "⏪" in str(res.emoji): - n = n - 1 + n -= 1 await emb_msg.clear_reactions() embed = discord.Embed(title="Alias list", color=0x47E0A7) embed.add_field( - name="{}".format(matches[0]), - value=self.make_numbered_alias(alias_chunked[n - 1]), + name=matched_anime["name"], + value=_create_naming_scheme(alias_chunked[n - 1]), inline=False, ) embed.add_field( @@ -446,12 +490,12 @@ def check_react(reaction, user): ) await emb_msg.edit(embed=embed) elif "⏩" in str(res.emoji): - n = n + 1 + n += 1 await emb_msg.clear_reactions() embed = discord.Embed(title="Alias list", color=0x47E0A7) embed.add_field( - name="{}".format(matches[0]), - value=self.make_numbered_alias(alias_chunked[n - 1]), + name=matched_anime["name"], + value=_create_naming_scheme(alias_chunked[n - 1]), inline=False, ) embed.add_field( @@ -471,11 +515,18 @@ def check_react(reaction, user): await emb_msg.clear_reactions() await emb_msg.delete() index_del = to_react.index(str(res.emoji)) - n_del = alias_chunked[n - 1][index_del] - del srv_data["alias"][n_del] + to_be_deleted = alias_chunked[n - 1][index_del] + try: + srv_data["anime"][indx]["aliases"].remove(to_be_deleted["name"]) + except (ValueError, IndexError, KeyError): + pass await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) - await ctx.send("Alias **{} ({})** telah dihapus dari database".format(n_del, matches[0])) + await ctx.send( + "Alias **{} ({})** telah dihapus dari database".format( + to_be_deleted["name"], matched_anime["name"] + ) + ) self.logger.info(f"{server_message}: updating database...") success, msg = await self.ntdb.update_data_server(server_message, srv_data) @@ -484,8 +535,7 @@ def check_react(reaction, user): self.logger.error(f"{server_message}: failed to update, reason: {msg}") if server_message not in self.bot.showtimes_resync: self.bot.showtimes_resync.append(server_message) - - await emb_msg.delete() + break class ShowtimesKolaborasi(commands.Cog, ShowtimesBase): @@ -560,15 +610,12 @@ async def kolaborasi_dengan(self, ctx, server_id, *, judul): self.logger.warning(f"{server_id}: can't find the server.") return await ctx.send("Tidak dapat menemukan server tersebut di database") - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) - + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -578,13 +625,15 @@ async def kolaborasi_dengan(self, ctx, server_id, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] - if ( - "kolaborasi" in srv_data["anime"][matches[0]] - and server_id in srv_data["anime"][matches[0]]["kolaborasi"] - ): - self.logger.info(f"{matches[0]}: already on collab.") + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] + + if "kolaborasi" in program_info and server_id in program_info["kolaborasi"]: + self.logger.info(f"{ani_title}: already on collab.") return await ctx.send("Server tersebut sudah diajak kolaborasi.") randomize_confirm = generate_custom_code(16) # nosec @@ -595,12 +644,12 @@ async def kolaborasi_dengan(self, ctx, server_id, *, judul): try: server_identd = self.bot.get_guild(int(server_id)) server_ident = server_identd.name - except AttributeError: + except (AttributeError, ValueError, TypeError): server_ident = server_id embed = discord.Embed( title="Kolaborasi", description="Periksa data!\nReact jika ingin diubah.", color=0xE7E363, ) - embed.add_field(name="Anime/Garapan", value=matches[0], inline=False) + embed.add_field(name="Anime/Garapan", value=ani_title, inline=False) embed.add_field(name="Server", value=server_ident, inline=False) embed.add_field( name="Lain-Lain", value="✅ Tambahkan!\n❌ Batalkan!", inline=False, @@ -634,7 +683,7 @@ def check_react(reaction, user): await emb_msg.clear_reactions() break elif "❌" in str(res.emoji): - self.logger.warning(f"{matches[0]}: cancelling...") + self.logger.warning(f"{ani_title}: cancelling...") cancel_toggled = True await emb_msg.clear_reactions() await emb_msg.delete() @@ -644,12 +693,13 @@ def check_react(reaction, user): return await ctx.send("**Dibatalkan!**") table_data = {} - table_data["anime"] = matches[0] - table_data["server"] = server_message + table_data["id"] = randomize_confirm + table_data["anime_id"] = matched_anime["id"] + table_data["server_id"] = server_message if "konfirmasi" not in target_server: - target_server["konfirmasi"] = {} - target_server["konfirmasi"][randomize_confirm] = table_data + target_server["konfirmasi"] = [] + target_server["konfirmasi"].append(table_data) embed = discord.Embed(title="Kolaborasi", color=0x56ACF3) embed.add_field(name="Memproses!", value="Mengirim data...", inline=True) @@ -660,13 +710,11 @@ def check_react(reaction, user): self.logger.info(f"{server_message}-{server_id}: storing data...") await self.showqueue.add_job(ShowtimesQueueData(target_server, server_id)) - # await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) # noqa: E501 embed = discord.Embed(title="Kolaborasi", color=0x96DF6A) embed.add_field( name="Sukses!", - value="Berikan kode berikut `{}` kepada fansub/server lain.\nDatabase utama akan diupdate sebentar lagi".format( # noqa: E501 - randomize_confirm - ), + value=f"Berikan kode berikut `{randomize_confirm}` kepada fansub/server lain.\n" + "Database utama akan diupdate sebentar lagi", inline=True, ) embed.set_footer( @@ -676,7 +724,7 @@ def check_react(reaction, user): await ctx.send(embed=embed) self.logger.info(f"{server_id}: updating database...") - success, msg = await self.ntdb.kolaborasi_dengan(server_id, randomize_confirm, table_data) + success, msg = await self.ntdb.kolaborasi_dengan(server_id, table_data) if not success: self.logger.error(f"{server_id}: failed to update, reason: {msg}") @@ -684,9 +732,9 @@ def check_react(reaction, user): self.bot.showtimes_resync.append(server_message) await ctx.send( - "Berikan kode berikut `{rand}` kepada fansub/server lain.\nKonfirmasi di server lain dengan `!kolaborasi konfirmasi {rand}`".format( # noqa: E501 - rand=randomize_confirm - ) + f"Berikan kode berikut `{randomize_confirm}` kepada fansub/server yang ditentukan tadi.\n" + f"Konfirmasi di server tersebut dengan `{self.bot.prefix}kolaborasi " + f"konfirmasi {randomize_confirm}`" ) @kolaborasi.command(name="konfirmasi", aliases=["confirm"]) @@ -706,20 +754,26 @@ async def kolaborasi_konfirmasi(self, ctx, konfirm_id): if "konfirmasi" not in srv_data: self.logger.warning(f"{server_message}: nothing to confirm.") return await ctx.send("Tidak ada kolaborasi yang harus dikonfirmasi.") - if konfirm_id not in srv_data["konfirmasi"]: + klb_data_dex = self._search_data_index(srv_data["konfirmasi"], "id", konfirm_id) + if klb_data_dex is None: self.logger.warning(f"{konfirm_id}: can't find that confirm id.") return await ctx.send("Tidak dapat menemukan kode kolaborasi yang diberikan.") - - klb_data = srv_data["konfirmasi"][konfirm_id] + klb_data = srv_data["konfirmasi"][klb_data_dex] try: - server_identd = self.bot.get_guild(int(klb_data["server"])) + server_identd = self.bot.get_guild(int(klb_data["server_id"])) server_ident = server_identd.name - except AttributeError: - server_ident = klb_data["server"] + except (AttributeError, ValueError, TypeError): + server_ident = klb_data["server_id"] + + source_srv = await self.showqueue.fetch_database(klb_data["server_id"]) + ani_idx = self._search_data_index(source_srv["anime"], "id", klb_data["anime_id"]) + if ani_idx is None: + return await ctx.send("Tidak dapat menemukan anime yang akan diajak kolaborasi.") + selected_anime = source_srv["anime"][ani_idx] embed = discord.Embed(title="Konfirmasi Kolaborasi", color=0xE7E363) - embed.add_field(name="Anime/Garapan", value=klb_data["anime"], inline=False) + embed.add_field(name="Anime/Garapan", value=selected_anime["title"], inline=False) embed.add_field(name="Server", value=server_ident, inline=False) embed.add_field(name="Lain-Lain", value="✅ Konfirmasi!\n❌ Batalkan!", inline=False) embed.set_footer( @@ -750,47 +804,57 @@ def check_react(reaction, user): await emb_msg.clear_reactions() return await ctx.send("**Dibatalkan!**") + find_target_id = self._search_data_index(srv_data["anime"], "id", selected_anime["id"]) + ani_srv_role = "" - if klb_data["anime"] in srv_data["anime"]: + old_koleb_data = [] + if find_target_id is not None: self.logger.warning(f"{server_message}: existing data, changing with source server") - ani_srv_role += srv_data["anime"][klb_data["anime"]]["role_id"] - del srv_data["anime"][klb_data["anime"]] + ani_srv_role += srv_data["anime"][find_target_id]["role_id"] + if "kolaborasi" in srv_data["anime"][find_target_id]: + old_koleb_data.extend(srv_data["anime"][find_target_id]["kolaborasi"]) + srv_data["anime"].pop(find_target_id) if not ani_srv_role: self.logger.info(f"{server_message}: creating roles...") c_role = await ctx.message.guild.create_role( - name=klb_data["anime"], colour=discord.Colour(0xDF2705), mentionable=True, + name=selected_anime["title"], colour=discord.Colour.random(), mentionable=True, ) ani_srv_role = str(c_role.id) - srv_source = klb_data["server"] - source_srv_data = await self.showqueue.fetch_database(srv_source) + copied_data = deepcopy(selected_anime) + copied_data["role_id"] = ani_srv_role - other_anime_data = source_srv_data["anime"][klb_data["anime"]] - copied_data = deepcopy(other_anime_data) - srv_data["anime"][klb_data["anime"]] = copied_data - srv_data["anime"][klb_data["anime"]]["role_id"] = ani_srv_role - - join_srv = [klb_data["server"], server_message] - if "kolaborasi" in srv_data["anime"][klb_data["anime"]]: - join_srv.extend(srv_data["anime"][klb_data["anime"]]["kolaborasi"]) + join_srv = [klb_data["server_id"], server_message] + if old_koleb_data: + join_srv.extend(old_koleb_data) join_srv = list(dict.fromkeys(join_srv)) - if "kolaborasi" in other_anime_data: - join_srv.extend(other_anime_data["kolaborasi"]) + if "kolaborasi" in selected_anime: + join_srv.extend(selected_anime["kolaborasi"]) join_srv = list(dict.fromkeys(join_srv)) - other_anime_data["kolaborasi"] = join_srv + source_srv["anime"][ani_idx]["kolaborasi"] = join_srv + copied_data["kolaborasi"] = join_srv + srv_data["anime"].append(copied_data) update_osrv_data = {} for osrv in join_srv: - if osrv in (srv_source, server_message): + if osrv in (klb_data["server_id"], server_message): continue osrv_data = await self.showqueue.fetch_database(osrv) - osrv_data["anime"][klb_data["anime"]]["kolaborasi"] = join_srv + osrv_id_anime = self._search_data_index(osrv_data["anime"], "id", copied_data["id"]) + if osrv_id_anime is None: + continue + osrv_data["anime"][osrv_id_anime]["kolaborasi"] = join_srv await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) update_osrv_data[osrv] = osrv_data - srv_data["anime"][klb_data["anime"]]["kolaborasi"] = join_srv - del srv_data["konfirmasi"][konfirm_id] + try: + srv_data["konfirmasi"].remove(klb_data) + except ValueError: + try: + srv_data["konfirmasi"].pop(klb_data_dex) + except IndexError: + pass embed = discord.Embed(title="Kolaborasi", color=0x56ACF3) embed.add_field(name="Memproses!", value="Mengirim data...", inline=True) @@ -799,14 +863,14 @@ def check_react(reaction, user): ) await emb_msg.edit(embed=embed) - self.logger.info(f"{server_message}-{srv_source}: storing data...") - await self.showqueue.add_job(ShowtimesQueueData(source_srv_data, srv_source)) + self.logger.info(f"{server_message}-{klb_data['server_id']}: storing data...") + await self.showqueue.add_job(ShowtimesQueueData(source_srv, klb_data["server_id"])) await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) embed = discord.Embed(title="Kolaborasi", color=0x96DF6A) embed.add_field( name="Sukses!", value="Berhasil konfirmasi dengan server **{}**.\nDatabase utama akan diupdate sebentar lagi".format( # noqa: E501 - klb_data["server"] + klb_data["server_id"] ), inline=True, ) @@ -818,7 +882,7 @@ def check_react(reaction, user): self.logger.info(f"{server_message}: updating database...") success, msg = await self.ntdb.kolaborasi_konfirmasi( - klb_data["server"], server_message, source_srv_data, srv_data, + klb_data["server_id"], server_message, source_srv, srv_data, ) if not success: @@ -827,7 +891,7 @@ def check_react(reaction, user): self.bot.showtimes_resync.append(server_message) for osrv, osrv_data in update_osrv_data.items(): - if osrv in (srv_source, server_message): + if osrv in (klb_data["server_id"], server_message): continue self.logger.info(f"{osrv}: updating database...") res2, msg2 = await self.ntdb.update_data_server(osrv, osrv_data) @@ -837,9 +901,8 @@ def check_react(reaction, user): self.logger.error(f"{osrv}: failed to update, reason: {msg2}") await ctx.send( - "Berhasil menambahkan kolaborasi dengan **{}** ke dalam database utama naoTimes\nBerikan role berikut agar bisa menggunakan perintah staff <@&{}>".format( # noqa: E501 - klb_data["server"], ani_srv_role - ) + f"Berhasil menambahkan kolaborasi dengan **{klb_data['server_id']}** ke dalam database utama" + f" naoTimes\nBerikan role berikut agar bisa menggunakan perintah staff <@&{ani_srv_role}>" ) @kolaborasi.command(name="batalkan") @@ -864,20 +927,28 @@ async def kolaborasi_batalkan(self, ctx, server_id, konfirm_id): if "konfirmasi" not in other_srv_data: self.logger.warning(f"{server_message}: nothing to confirm.") return await ctx.send("Tidak ada kolaborasi yang harus dikonfirmasi.") - if konfirm_id not in other_srv_data["konfirmasi"]: - self.logger.warning(f"{server_message}: can't find that confirm id.") + klb_data_dex = self._search_data_index(other_srv_data["konfirmasi"], "id", konfirm_id) + if klb_data_dex is None: + self.logger.warning(f"{konfirm_id}: can't find that confirm id.") return await ctx.send("Tidak dapat menemukan kode kolaborasi yang diberikan.") - - del other_srv_data["konfirmasi"][konfirm_id] + klb_data = other_srv_data["konfirmasi"][klb_data_dex] + if klb_data["server_id"] != server_message: + return await ctx.send("Anda tidak berhak untuk menghapus kode ini!") + try: + other_srv_data["konfirmasi"].remove(klb_data) + except ValueError: + try: + other_srv_data["konfirmasi"].pop(klb_data_dex) + except IndexError: + return await ctx.send("Gagal menghapus kode konfirmasi!") self.logger.info(f"{server_message}-{server_id}: storing data...") await self.showqueue.add_job(ShowtimesQueueData(other_srv_data, server_id)) embed = discord.Embed(title="Kolaborasi", color=0x96DF6A) embed.add_field( name="Sukses!", - value="Berhasil membatalkan kode konfirmasi **{}**.\nDatabase utama akan diupdate sebentar lagi".format( # noqa: E501 - konfirm_id - ), + value=f"Berhasil membatalkan kode konfirmasi **{konfirm_id}**.\n" + "Database utama akan diupdate sebentar lagi", inline=True, ) embed.set_footer( @@ -893,11 +964,7 @@ async def kolaborasi_batalkan(self, ctx, server_id, konfirm_id): if server_message not in self.bot.showtimes_resync: self.bot.showtimes_resync.append(server_message) - await ctx.send( - "Berhasil membatalkan kode konfirmasi **{}** dari database utama naoTimes".format( # noqa: E501 - konfirm_id - ) - ) + await ctx.send(f"Berhasil membatalkan kode konfirmasi **{konfirm_id}** dari database utama naoTimes") @kolaborasi.command() async def putus(self, ctx, *, judul): @@ -912,15 +979,12 @@ async def putus(self, ctx, *, judul): if str(ctx.message.author.id) not in srv_data["serverowner"]: return await ctx.send("Hanya admin yang bisa memputuskan kolaborasi") - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) - + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -930,29 +994,32 @@ async def putus(self, ctx, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] if "kolaborasi" not in program_info: - self.logger.warning(f"{server_message}: no registered collaboration on this title.") + self.logger.warning(f"{server_message}-{ani_title}: no registered collaboration on this title.") return await ctx.send("Tidak ada kolaborasi sama sekali pada judul ini.") - self.logger.warning(f"{matches[0]}: start removing server from other server...") + self.logger.warning(f"{ani_title}: start removing server from other server...") for osrv in program_info["kolaborasi"]: if osrv == server_message: continue osrv_data = await self.showqueue.fetch_database(osrv) - klosrv = deepcopy(osrv_data["anime"][matches[0]]["kolaborasi"]) - klosrv.remove(server_message) - - remove_all = False - if len(klosrv) == 1 and klosrv[0] == osrv: - remove_all = True + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + if indx_other is None: + continue + osrv_anime = osrv_data["anime"][indx_other] + if "kolaborasi" in osrv_anime and osrv_anime["kolaborasi"]: + try: + osrv_data["anime"][indx_other]["kolaborasi"].remove(server_message) + except ValueError: + pass - if remove_all: - del osrv_data["anime"][matches[0]]["kolaborasi"] - else: - osrv_data["anime"][matches[0]]["kolaborasi"] = klosrv await self.showqueue.add_job(ShowtimesQueueData(osrv_data, osrv)) self.logger.info(f"{osrv}: updating database...") res2, msg2 = await self.ntdb.update_data_server(osrv, osrv_data) @@ -964,17 +1031,16 @@ async def putus(self, ctx, *, judul): fsdb_binded = False if "fsdb_data" in program_info: fsdb_binded = True - del srv_data["anime"][matches[0]]["fsdb_data"] + del srv_data["anime"][indx]["fsdb_data"] self.logger.info(f"{server_message}: storing data...") - del srv_data["anime"][matches[0]]["kolaborasi"] + srv_data["anime"][indx]["kolaborasi"] = [] await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) embed = discord.Embed(title="Kolaborasi", color=0x96DF6A) embed.add_field( name="Sukses!", - value="Berhasil memputuskan kolaborasi **{}**.\nDatabase utama akan diupdate sebentar lagi".format( # noqa: E501 - matches[0] - ), + value=f"Berhasil memputuskan kolaborasi **{ani_title}**.\n" + "Database utama akan diupdate sebentar lagi", inline=True, ) embed.set_footer( @@ -990,13 +1056,9 @@ async def putus(self, ctx, *, judul): if server_message not in self.bot.showtimes_resync: self.bot.showtimes_resync.append(server_message) - await ctx.send( - "Berhasil memputuskan kolaborasi **{}** dari database utama naoTimes".format( # noqa: E501 - matches[0] - ) - ) + await ctx.send(f"Berhasil memputuskan kolaborasi **{ani_title}** dari database utama naoTimes") if fsdb_binded: await ctx.send( "Binding FansubDB untuk anime terputus, " - f"silakan hubungkan ulang dengan: `{self.bot.prefixes(ctx)}fsdb bind {matches[0]}`" + f"silakan hubungkan ulang dengan: `{self.bot.prefixes(ctx)}fsdb bind {ani_title}`" ) diff --git a/cogs/showtimes_module/owner.py b/cogs/showtimes_module/owner.py index 7e39f14..3d4a8b3 100644 --- a/cogs/showtimes_module/owner.py +++ b/cogs/showtimes_module/owner.py @@ -13,7 +13,7 @@ from nthelper.bot import naoTimesBot from nthelper.showtimes_helper import ShowtimesQueueData -from nthelper.utils import HelpGenerator, write_files +from nthelper.utils import HelpGenerator, confirmation_dialog, write_files from .base import ShowtimesBase @@ -31,8 +31,7 @@ def __init__(self, bot: naoTimesBot): self.bot = bot self.ntdb = bot.ntdb self.bot_config = bot.botconf - self.showqueue = bot.showqueue - self.srv_lists = partial(self.fetch_servers, cwd=bot.fcwd) + self.srv_lists = partial(self.fetch_servers, redisdb=bot.redisdb) self.logger = logging.getLogger("cogs.showtimes_module.owner.ShowtimesOwner") def __str__(self): @@ -83,6 +82,16 @@ async def ntadmin(self, ctx): # noqa: D102 await helpcmd.generate_aliases(["naotimesadmin", "naoadmin"]) await ctx.send(embed=helpcmd.get()) + @ntadmin.command(name="showui") + async def _showui_owner_cmd(self, ctx: commands.Context): + do_continue = await confirmation_dialog( + self.bot, ctx, "Perintah ini akan memperlihatkan kode rahasia untuk login di WebUI, lanjutkan?" + ) + if not do_continue: + return await ctx.send("Dibatalkan!") + _, return_msg = await self.ntdb.generate_login_info(str(ctx.message.author.id), True) + await ctx.send(return_msg) + @ntadmin.command() async def listserver(self, ctx): # noqa: D102 if self.ntdb is None: @@ -137,17 +146,17 @@ async def fetchdb(self, ctx): # noqa: D102 return async def _internal_fetch(srv_id): - data_res = await self.showqueue.fetch_database(srv_id) + data_res = await self.bot.showqueue.fetch_database(srv_id) return data_res, srv_id channel = ctx.message.channel fetch_jobs = [_internal_fetch(srv) for srv in srv_lists] - final_dataset = {} + final_dataset = {"servers": []} for fjob in asyncio.as_completed(fetch_jobs): - data_res, srv_id = await fjob + data_res, _ = await fjob if data_res is not None: - final_dataset[srv_id] = data_res + final_dataset["servers"].append(data_res) super_admin = await self.fetch_super_admins(self.bot.redisdb) final_dataset["supermod"] = super_admin @@ -168,13 +177,13 @@ async def forcepull(self, ctx): # noqa: D102 self.logger.info("Forcing local database with remote database.") channel = ctx.message.channel - json_d = await self.ntdb.fetch_all_as_json() - for srv, srv_data in json_d.items(): - if srv == "supermod": - await self.dumps_super_admins(srv_data, self.bot.redisdb) - else: - self.logger.info(f"{srv}: saving...") - await self.showqueue.add_job(ShowtimesQueueData(srv_data, srv)) + js_data = await self.bot.ntdb.fetch_all_as_json() + for admins in js_data["supermod"]: + self.logger.info(f"saving admin {admins['id']} data to redis") + await self.bot.redisdb.set(f"showadmin_{admins['id']}", admins) + for server in js_data["servers"]: + self.logger.info(f"saving server {server['id']} data to redis") + await self.bot.redisdb.set("showtimes_" + server["id"], server) await channel.send("Newest database has been pulled and saved to local save") @ntadmin.command() @@ -236,11 +245,12 @@ def check_react(reaction, user): pass elif "✅" in str(res.emoji): self.logger.info("Patching database...") - for srv, srv_data in json_to_patch.items(): - if srv == "supermod": - await self.dumps_super_admins(srv_data, self.bot.redisdb) - else: - await self.showqueue.add_job(ShowtimesQueueData(srv_data, srv)) + for admins in json_to_patch["supermod"]: + self.logger.info(f"saving admin {admins['id']} data to redis") + await self.bot.redisdb.set(f"showadmin_{admins['id']}", admins) + for server in json_to_patch["servers"]: + self.logger.info(f"saving server {server['id']} data to redis") + await self.bot.redisdb.set("showtimes_" + server["id"], server) self.logger.info("Patching remote database...") success = await self.ntdb.patch_all_from_json(json_to_patch) await preview_msg.clear_reactions() @@ -266,7 +276,7 @@ async def tambah(self, ctx, srv_id, adm_id, prog_chan=None): # noqa: D102 if adm_id is None: return await ctx.send("Tidak ada input admin dari user") - new_server = await self.showqueue.fetch_database(str(srv_id)) + new_server = await self.bot.showqueue.fetch_database(str(srv_id)) if new_server is not None: return await ctx.send("Server `{}` tersebut telah terdaftar di database".format(srv_id)) @@ -275,8 +285,11 @@ async def tambah(self, ctx, srv_id, adm_id, prog_chan=None): # noqa: D102 new_srv_data["serverowner"] = [str(adm_id)] if prog_chan: new_srv_data["announce_channel"] = str(prog_chan) - new_srv_data["anime"] = {} - new_srv_data["alias"] = {} + else: + new_srv_data["announce_channel"] = None + new_srv_data["anime"] = [] + new_srv_data["konfirmasi"] = [] + new_srv_data["id"] = str(srv_id) if "announce_channel" in new_srv_data and new_srv_data["announce_channel"] == "": del new_srv_data["announce_channel"] @@ -287,7 +300,7 @@ async def tambah(self, ctx, srv_id, adm_id, prog_chan=None): # noqa: D102 self.logger.info(f"Created new server data for: {srv_id}") self.logger.info(f"{srv_id}: dumping to local db") - await self.showqueue.add_job(ShowtimesQueueData(new_srv_data, str(srv_id))) + await self.bot.showqueue.add_job(ShowtimesQueueData(new_srv_data, str(srv_id))) await self.dumps_super_admins(supermod_lists, self.bot.redisdb) if not prog_chan: prog_chan = None @@ -313,7 +326,7 @@ async def hapus(self, ctx, srv_id): # noqa: D102 self.logger.info("Initiated server removal from database...") if srv_id is None: return await ctx.send("Tidak ada input server dari user") - srv_data = await self.showqueue.fetch_database(str(srv_id)) + srv_data = await self.bot.showqueue.fetch_database(str(srv_id)) if srv_data is None: self.logger.warning(f"{srv_id}: Unknown server") @@ -356,7 +369,7 @@ async def tambahadmin(self, ctx, srv_id: str, adm_id: str): # noqa: D102 if adm_id is None: return await ctx.send("Tidak ada input admin dari user") - srv_data = await self.showqueue.fetch_database(srv_id) + srv_data = await self.bot.showqueue.fetch_database(srv_id) if srv_data is None: return await ctx.send("Server tidak dapat ditemukan dalam database.") @@ -367,7 +380,7 @@ async def tambahadmin(self, ctx, srv_id: str, adm_id: str): # noqa: D102 srv_data["serverowner"].append(adm_id) self.logger.info(f"{srv_id}: Commiting to database...") - await self.showqueue.add_job(ShowtimesQueueData(srv_data, srv_id)) + await self.bot.showqueue.add_job(ShowtimesQueueData(srv_data, srv_id)) success, msg = await self.ntdb.update_data_server(srv_id, srv_data) if not success: self.logger.error(f"{srv_id}: Failed to update main database, reason: {msg}") @@ -388,8 +401,7 @@ async def hapusadmin(self, ctx, srv_id: str, adm_id: str): # noqa: D102 if adm_id is None: return await ctx.send("Tidak ada input admin dari user") - srv_data = await self.showqueue.fetch_database(srv_id) - super_admins = await self.fetch_super_admins(self.bot.redisdb) + srv_data = await self.bot.showqueue.fetch_database(srv_id) if srv_data is None: return await ctx.send("Server tidak dapat ditemukan dalam database.") @@ -400,7 +412,7 @@ async def hapusadmin(self, ctx, srv_id: str, adm_id: str): # noqa: D102 srv_data["serverowner"].remove(adm_id) self.logger.info(f"{srv_id}: Commiting to database...") - await self.showqueue.add_job(ShowtimesQueueData(srv_data, srv_id)) + await self.bot.showqueue.add_job(ShowtimesQueueData(srv_data, srv_id)) success, msg = await self.ntdb.update_data_server(srv_id, srv_data) if not success: self.logger.error(f"{srv_id}: Failed to update main database, reason: {msg}") @@ -408,14 +420,6 @@ async def hapusadmin(self, ctx, srv_id: str, adm_id: str): # noqa: D102 self.bot.showtimes_resync.append(srv_id) self.logger.info(f"{srv_id}: Admin `{adm_id}` removed from database...") await ctx.send("Sukses menghapus admin `{a}` dari server `{s}`".format(s=srv_id, a=adm_id)) - if str(adm_id) in super_admins: - self.logger.info(f"{srv_id}: Commiting to top admin database...") - super_admins.remove(str(adm_id)) - await self.dumps_super_admins(super_admins, self.bot.redisdb) - success, msg = await self.ntdb.remove_top_admin(adm_id) - if not success: - self.logger.error(f"{srv_id}: Failed to update top admin database, reason: {msg}") - await ctx.send("Tetapi gagal menghapus admin dari top_admin.") @ntadmin.command(name="initialisasi", aliases=["init", "initialize"]) async def ntadmin_initialisasi(self, ctx): @@ -427,58 +431,3 @@ async def ntadmin_initialisasi(self, ctx): server_lists = await self.srv_lists() if server_lists: return await ctx.send("naoTimes Showtimes already initialized.") - - @ntadmin.command() - async def modify_db(self, ctx): # noqa: D102 - if self.ntdb is None: - self.logger.info("owner hasn't enabled naoTimesDB yet.") - return - self.logger.info("Batch modifying db, this time: mal_id addition...") - - async def _internal_fetch(srv_id): - data_res = await self.showqueue.fetch_database(srv_id) - return data_res, srv_id - - srv_lists = await self.srv_lists() - fetch_jobs = [_internal_fetch(srv) for srv in srv_lists] - - msg_dc = await ctx.send("Fetching all database...") - final_dataset = {} - for fjob in asyncio.as_completed(fetch_jobs): - data_res, srv_id = await fjob - if data_res is not None: - final_dataset[srv_id] = data_res - - def get_username_of_user(user_id): - try: - user_data = self.bot.get_user(int(user_id)) - return user_data.name - except AttributeError: - return None - - await msg_dc.edit(content="Monkey-patching DB...") - for srv_id, srv_data in final_dataset.items(): - for ani, ani_data in srv_data["anime"].items(): - staffes = ani_data["staff_assignment"] - new_staffes = {} - for staff_pos, staff_id in staffes.items(): - if isinstance(staff_id, dict): - new_staffes[staff_pos] = staff_id - else: - new_staffes[staff_pos] = {"id": staff_id, "name": get_username_of_user(staff_id)} - final_dataset[srv_id]["anime"][ani]["staff_assignment"] = new_staffes - - super_admins = await self.fetch_super_admins(self.bot.redisdb) - final_dataset["supermod"] = super_admins - - self.logger.info("Patching database...") - await msg_dc.edit(content="Patching to database...") - for srv, srv_data in final_dataset.items(): - if srv == "supermod": - await self.dumps_super_admins(srv_data, self.bot.redisdb) - else: - await self.showqueue.add_job(ShowtimesQueueData(srv_data, srv)) - self.logger.info("Patching remote database...") - success = await self.ntdb.patch_all_from_json(final_dataset) - if success: - await ctx.send("Success!") diff --git a/cogs/showtimes_module/staff.py b/cogs/showtimes_module/staff.py index 784d983..f4ff18e 100644 --- a/cogs/showtimes_module/staff.py +++ b/cogs/showtimes_module/staff.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import logging -import time import discord from discord.ext import commands, tasks @@ -68,16 +67,14 @@ async def rilis(self, ctx, *, data): self.logger.info(f"{server_message}: data found.") srv_owner = srv_data["serverowner"] - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) - if len(srv_anilist) < 1: + if len(propagated_anilist) < 1: self.logger.warning(f"{server_message}: no registered data on database.") return await ctx.send("**Tidak ada anime yang terdaftar di database**") - if not data or data == []: - return await self.send_all_projects(ctx, srv_anilist, server_message) + if not data: + return await self.send_all_projects(ctx, srv_data["anime"], server_message) koleb_list = [] osrv_dumped = {} @@ -90,10 +87,10 @@ async def rilis(self, ctx, *, data): judul = " ".join(data) if judul == " " or judul == "" or judul == " " or not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -103,8 +100,14 @@ async def rilis(self, ctx, *, data): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = ( + matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + ) + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] status_list = program_info["status"] if "kolaborasi" in program_info: @@ -116,24 +119,24 @@ async def rilis(self, ctx, *, data): koleb_list.append(ko_data) current = self.get_current_ep(status_list) - episode_set = list(status_list.keys()) + episode_set = status_list[0] if not current: - self.logger.warning(f"{matches[0]}: no episode left to be worked on.") + self.logger.warning(f"{ani_title}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") - if current == episode_set[0]: + if current["episode"] == episode_set["episode"]: should_update_fsdb = True fsdb_update_to = "Jalan" if ( - str(ctx.message.author.id) != program_info["staff_assignment"]["QC"]["id"] + str(ctx.message.author.id) != program_info["assignments"]["QC"]["id"] and str(ctx.message.author.id) not in srv_owner ): - self.logger.warning(f"{matches[0]}: user not allowed.") + self.logger.warning(f"{ani_title}: user not allowed.") return await ctx.send("**Tidak secepat itu ferguso, yang bisa rilis cuma admin atau QCer**") if koleb_list: - self.logger.info(f"{matches[0]}: setting collab status...") + self.logger.info(f"{ani_title}: setting collab status...") for other_srv in koleb_list: if other_srv == server_message: continue @@ -141,29 +144,32 @@ async def rilis(self, ctx, *, data): if srv_o_data is None: continue self.logger.debug(f"{server_message}: {other_srv} processing...") - srv_o_data["anime"][matches[0]]["status"][current]["status"] = "released" - srv_o_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + indx_other = self._search_data_index(srv_o_data["anime"], "id", program_info["id"]) + progoinfo = srv_o_data["anime"][indx_other] + indxo_ep = self._search_data_index(progoinfo["status"], "episode", current["episode"]) + srv_o_data["anime"][indx_other]["status"][indxo_ep]["is_done"] = True + srv_o_data["anime"][indx_other]["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(srv_o_data, other_srv)) osrv_dumped[other_srv] = srv_o_data - self.logger.info(f"{matches[0]}: setting status...") - srv_data["anime"][matches[0]]["status"][current]["status"] = "released" - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + self.logger.info(f"{ani_title}: setting status...") + current["is_done"] = True + program_info["last_update"] = self.get_unix() - text_data = "**{} - #{}** telah dirilis".format(matches[0], current) - embed_text_data = "{} #{} telah dirilis!".format(matches[0], current) + text_data = "**{} - #{}** telah dirilis".format(ani_title, current["episode"]) + embed_text_data = "{} #{} telah dirilis!".format(ani_title, current["episode"]) elif data[0] == "batch": self.logger.info(f"{server_message}: using batch mode.") if not data[1].isdigit(): - await self.send_all_projects(ctx, srv_anilist, server_message) + await self.send_all_projects(ctx, srv_data["anime"], server_message) return await ctx.send("Lalu tulis jumlah terlebih dahulu baru judul") if len(data) < 3: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) jumlah = data[1] judul = " ".join(data[2:]) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -172,9 +178,15 @@ async def rilis(self, ctx, *, data): matches = await self.choose_anime(bot=self.bot, ctx=ctx, matches=matches) if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = ( + matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + ) + self.logger.info(f"{server_message}: matched {matched_anime}") + + program_info = srv_data["anime"][indx] status_list = program_info["status"] if "kolaborasi" in program_info: @@ -187,18 +199,19 @@ async def rilis(self, ctx, *, data): current = self.get_current_ep(status_list) if not current: - self.logger.warning(f"{matches[0]}: no episode left to be worked on.") + self.logger.warning(f"{ani_title}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") + indx_ep = self._search_data_index(status_list, "episode", current["episode"]) if ( - str(ctx.message.author.id) != program_info["staff_assignment"]["QC"]["id"] + str(ctx.message.author.id) != program_info["assignments"]["QC"]["id"] and str(ctx.message.author.id) not in srv_owner ): - self.logger.warning(f"{matches[0]}: user not allowed.") + self.logger.warning(f"{ani_title}: user not allowed.") return await ctx.send("**Tidak secepat itu ferguso, yang bisa rilis cuma admin atau QCer**") if koleb_list: - self.logger.info(f"{matches[0]}: setting collab status...") + self.logger.info(f"{ani_title}: setting collab status...") for other_srv in koleb_list: if other_srv == server_message: continue @@ -206,28 +219,39 @@ async def rilis(self, ctx, *, data): if srv_o_data is None: continue self.logger.debug(f"{server_message}: {other_srv} processing...") - for x in range(int(current), int(current) + int(jumlah)): # range(int(c), int(c)+int(x)) - srv_o_data["anime"][matches[0]]["status"][str(x)]["status"] = "released" - srv_o_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + indx_other = self._search_data_index(srv_o_data["anime"], "id", program_info["id"]) + progoinfo = srv_o_data["anime"][indx_other] + indxo_ep = self._search_data_index(progoinfo["status"], "episode", current["episode"]) + for x in range(indxo_ep, indxo_ep + int(jumlah)): # range(int(c), int(c)+int(x)) + try: + srv_o_data["anime"][indx_other]["status"][x]["is_done"] = True + except IndexError: + break + srv_o_data["anime"][indx_other]["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(srv_o_data, other_srv)) osrv_dumped[other_srv] = srv_o_data - self.logger.info(f"{matches[0]}: setting status...") - for x in range(int(current), int(current) + int(jumlah)): # range(int(c), int(c)+int(x)) - srv_data["anime"][matches[0]]["status"][str(x)]["status"] = "released" + self.logger.info(f"{ani_title}: setting status...") + last_ep_tick = current["episode"] + for x in range(indx_ep, indx_ep + int(jumlah)): # range(int(c), int(c)+int(x)) + try: + program_info["status"][x]["is_done"] = True + last_ep_tick = program_info["status"][x]["episode"] + except IndexError: + break should_update_fsdb = True fsdb_update_to = "Jalan" - last_ep = int(list(status_list.keys())[-1]) - if last_ep == int(current) + int(jumlah): + last_ep = len(status_list) - 1 + if indx_ep + int(jumlah) >= last_ep: fsdb_update_to = "Tamat" - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + program_info["last_update"] = self.get_unix() text_data = "**{} - #{} sampai #{}** telah dirilis".format( - matches[0], current, int(current) + int(jumlah) - 1 + ani_title, current["episode"], last_ep_tick ) embed_text_data = "{} #{} sampai #{} telah dirilis!".format( - matches[0], current, int(current) + int(jumlah) - 1 + ani_title, current["episode"], last_ep_tick ) elif data[0] == "semua": should_update_fsdb = True @@ -236,10 +260,10 @@ async def rilis(self, ctx, *, data): judul = " ".join(data[1:]) if judul == " " or judul == "" or judul == " " or not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -249,7 +273,14 @@ async def rilis(self, ctx, *, data): if not matches: return await ctx.send("**Dibatalkan!**") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = ( + matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + ) + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] status_list = program_info["status"] if "kolaborasi" in program_info: @@ -262,40 +293,44 @@ async def rilis(self, ctx, *, data): all_status = self.get_not_released_ep(status_list) if not all_status: - self.logger.warning(f"{matches[0]}: no episode left to be worked on.") + self.logger.warning(f"{ani_title}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") if ( - str(ctx.message.author.id) != program_info["staff_assignment"]["QC"]["id"] + str(ctx.message.author.id) != program_info["assignments"]["QC"]["id"] and str(ctx.message.author.id) not in srv_owner ): - self.logger.warning(f"{matches[0]}: user not allowed.") + self.logger.warning(f"{ani_title}: user not allowed.") return await ctx.send("**Tidak secepat itu ferguso, yang bisa rilis cuma admin atau QCer**") if koleb_list: - self.logger.info(f"{matches[0]}: setting collab status...") + self.logger.info(f"{ani_title}: setting collab status...") for other_srv in koleb_list: if other_srv == server_message: continue srv_o_data = await self.showqueue.fetch_database(other_srv) if srv_o_data is None: continue - for x in all_status: - srv_o_data["anime"][matches[0]]["status"][x]["status"] = "released" - srv_o_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + self.logger.debug(f"{server_message}: {other_srv} processing...") + indx_other = self._search_data_index(srv_o_data["anime"], "id", program_info["id"]) + progoinfo = srv_o_data["anime"][indx_other] + alls_other = self.get_not_released_ep(status_list) + for ep_other in alls_other: + ep_other["is_done"] = True + progoinfo["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(srv_o_data, other_srv)) osrv_dumped[other_srv] = srv_o_data - self.logger.info(f"{matches[0]}: setting status...") - for x in all_status: - srv_data["anime"][matches[0]]["status"][x]["status"] = "released" + self.logger.info(f"{ani_title}: setting status...") + for ep_stat in all_status: + ep_stat["is_done"] = True - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + program_info["last_update"] = self.get_unix() text_data = "**{} - #{} sampai #{}** telah dirilis".format( - matches[0], all_status[0], all_status[-1] + ani_title, all_status[0]["episode"], all_status[-1]["episode"] ) embed_text_data = "{} #{} sampai #{} telah dirilis!".format( - matches[0], all_status[0], all_status[-1] + ani_title, all_status[0]["episode"], all_status[-1]["episode"] ) await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) @@ -326,34 +361,34 @@ async def rilis(self, ctx, *, data): for osrv, osrv_data in osrv_dumped.items(): if osrv == server_message: continue - if "announce_channel" in osrv_data: + if "announce_channel" in osrv_data and osrv_data["announce_channel"]: self.logger.info(f"{osrv}: sending progress to everyone...") announce_chan = osrv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning(f"{osrv}: failed to convert announce channel to integer, ignoring...") continue target_chan = self.bot.get_channel(announce_chan) if not target_chan: self.logger.warning(f"{announce_chan}: unknown channel.") continue - embed = discord.Embed(title="{}".format(matches[0]), color=0x1EB5A6) + embed = discord.Embed(title=ani_title, color=0x1EB5A6) embed.add_field(name="Rilis!", value=embed_text_data, inline=False) embed.set_footer(text=f"Pada: {get_current_time()}") await target_chan.send(embed=embed) - if "announce_channel" in srv_data: + if "announce_channel" in srv_data and srv_data["announce_channel"]: self.logger.info(f"{server_message}: sending progress to everyone...") announce_chan = srv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning( f"{server_message}: failed to convert announce channel to integer, ignoring..." ) return target_chan = self.bot.get_channel(announce_chan) - embed = discord.Embed(title="{}".format(matches[0]), color=0x1EB5A6) + embed = discord.Embed(title=ani_title, color=0x1EB5A6) embed.add_field(name="Rilis!", value=embed_text_data, inline=False) embed.set_footer(text=f"Pada: {get_current_time()}") if target_chan: @@ -386,14 +421,12 @@ async def beres(self, ctx, posisi: str, *, judul: str): self.logger.info(f"{server_message}: data found.") srv_owner = srv_data["serverowner"] - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -403,8 +436,12 @@ async def beres(self, ctx, posisi: str, *, judul: str): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] status_list = program_info["status"] koleb_list = [] @@ -424,32 +461,32 @@ async def beres(self, ctx, posisi: str, *, judul: str): return except ValueError: return await ctx.send( - f"Gagal memeriksa role, mohon ubah dengan {self.bot.prefix}ubahdata {matches[0]}" + f"Gagal memeriksa role, mohon ubah dengan {self.bot.prefix}ubahdata {ani_title}" ) current = self.get_current_ep(status_list) if not current: - self.logger.warning(f"{matches[0]}: no episode left to be worked on.") + self.logger.warning(f"{ani_title}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") - current_stat = status_list[current]["staff_status"][posisi] - if current_stat == "y": - self.logger.warning(f"{matches[0]}: position already set to done.") + current_stat = current["progress"][posisi] + if current_stat: + self.logger.warning(f"{ani_title}: position already set to done.") return await ctx.send(f"**{posisi_asli}** sudah ditandakan sebagai beres.") poster_data = program_info["poster_data"] poster_image = poster_data["url"] if ( - str(ctx.message.author.id) != str(program_info["staff_assignment"][posisi]["id"]) + str(ctx.message.author.id) != str(program_info["assignments"][posisi]["id"]) and str(ctx.message.author.id) not in srv_owner ): - self.logger.warning(f"{matches[0]}: no access to set to done.") + self.logger.warning(f"{ani_title}: no access to set to done.") return await ctx.send("**Bukan posisi situ untuk mengubahnya!**") - self.logger.info(f"{matches[0]}: setting episode {current} to done.") - srv_data["anime"][matches[0]]["status"][current]["staff_status"][posisi] = "y" - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + self.logger.info(f"{ani_title}: setting episode {current} to done.") + current["progress"][posisi] = True + program_info["last_update"] = self.get_unix() osrv_dumped = {} if koleb_list: for other_srv in koleb_list: @@ -458,16 +495,19 @@ async def beres(self, ctx, posisi: str, *, judul: str): osrv_data = await self.showqueue.fetch_database(other_srv) if osrv_data is None: continue - osrv_data["anime"][matches[0]]["status"][current]["staff_status"][posisi] = "y" - osrv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + progoinfo = osrv_data["anime"][indx_other] + indxo_ep = self._search_data_index(progoinfo["status"], "episode", current["episode"]) + osrv_data["anime"][indx_other]["status"][indxo_ep]["progress"][posisi] = True + osrv_data["anime"][indx_other]["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(osrv_data, other_srv)) osrv_dumped[other_srv] = osrv_data - current_ep_status = status_list[current]["staff_status"] + current_ep_status = current["progress"] await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) - self.logger.info(f"{matches[0]}: sending progress info to staff...") - await ctx.send("Berhasil mengubah status garapan {} - #{}".format(matches[0], current)) + self.logger.info(f"{ani_title}: sending progress info to staff...") + await ctx.send("Berhasil mengubah status garapan {} - #{}".format(ani_title, current["episode"])) self.logger.info(f"{server_message}: updating database...") success, msg = await self.ntdb.update_data_server(server_message, srv_data) @@ -490,12 +530,12 @@ async def beres(self, ctx, posisi: str, *, judul: str): for osrv, osrv_data in osrv_dumped.items(): if osrv == server_message: continue - if "announce_channel" in osrv_data: + if "announce_channel" in osrv_data and osrv_data["announce_channel"]: self.logger.info(f"{osrv}: sending progress to everyone...") announce_chan = osrv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning( f"{osrv}: failed to convert announce channel to integer, ignoring..." ) @@ -504,22 +544,24 @@ async def beres(self, ctx, posisi: str, *, judul: str): if not target_chan: self.logger.warning(f"{announce_chan}: unknown channel.") continue - embed = discord.Embed(title="{} - #{}".format(matches[0], current), color=0x1EB5A6) + embed = discord.Embed( + title="{} - #{}".format(ani_title, current["episode"]), color=0x1EB5A6 + ) embed.description = f"✅ {self.normalize_role_to_name(posisi)}" embed.add_field( name="Status", value=self.parse_status(current_ep_status), inline=False, ) embed.set_footer(text=f"Pada: {get_current_time()}") await target_chan.send(embed=embed) - embed = discord.Embed(title="{} - #{}".format(matches[0], current), color=0x1EB5A6) + embed = discord.Embed(title="{} - #{}".format(ani_title, current["episode"]), color=0x1EB5A6) embed.add_field( name="Status", value=self.parse_status(current_ep_status), inline=False, ) - if "announce_channel" in srv_data: + if "announce_channel" in srv_data and srv_data["announce_channel"]: announce_chan = srv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning( f"{server_message}: failed to convert announce channel to integer, ignoring..." ) @@ -552,15 +594,12 @@ async def batalrilis(self, ctx, *, judul=None): return self.logger.info(f"{server_message}: data found.") - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) - + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -570,13 +609,17 @@ async def batalrilis(self, ctx, *, judul=None): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] status_list = program_info["status"] srv_owner = srv_data["serverowner"] if ( - str(ctx.message.author.id) != program_info["staff_assignment"]["QC"]["id"] + str(ctx.message.author.id) != program_info["assignments"]["QC"]["id"] and str(ctx.message.author.id) not in srv_owner ): return await ctx.send( @@ -586,16 +629,14 @@ async def batalrilis(self, ctx, *, judul=None): current = self.get_current_ep(status_list) reset_fsdb = False if not current: - current = int(list(status_list.keys())[-1]) + current = status_list[-1] reset_fsdb = True else: - current = int(current) - 1 - - if current < 1: - self.logger.info(f"{matches[0]}: no episode have been released.") - return await ctx.send("Tidak ada episode yang dirilis untuk judul ini.") - - current = str(current) + indx_ep = self._search_data_index(status_list, "episode", current["episode"]) + try: + current = status_list[indx_ep - 1] + except IndexError: + return await ctx.send("Tidak dapat membatalkan rilis jika episode pertama belum di rilis!") koleb_list = [] if "kolaborasi" in program_info: @@ -606,9 +647,9 @@ async def batalrilis(self, ctx, *, judul=None): continue koleb_list.append(ko_data) - self.logger.info(f"{matches[0]}: unreleasing episode {current}") - srv_data["anime"][matches[0]]["status"][current]["status"] = "not_released" - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + self.logger.info(f"{ani_title}: unreleasing episode {current['episode']}") + current["is_done"] = False + program_info["last_update"] = self.get_unix() osrv_dumped = {} if koleb_list: for other_srv in koleb_list: @@ -617,14 +658,17 @@ async def batalrilis(self, ctx, *, judul=None): osrv_data = await self.showqueue.fetch_database(other_srv) if osrv_data is None: continue - osrv_data["anime"][matches[0]]["status"][current]["status"] = "not_released" - osrv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + progoinfo = osrv_data["anime"][indx_other] + indxo_ep = self._search_data_index(progoinfo["status"], "episode", current["episode"]) + osrv_data["anime"][indx_other]["status"][indxo_ep]["is_done"] = False + osrv_data["anime"][indx_other]["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(osrv_data, other_srv)) osrv_dumped[other_srv] = osrv_data await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) - self.logger.info(f"{matches[0]}: sending progress info to staff...") - await ctx.send("Berhasil membatalkan rilisan **{}** episode {}".format(matches[0], current)) + self.logger.info(f"{ani_title}: sending progress info to staff...") + await ctx.send("Berhasil membatalkan rilisan **{}** episode {}".format(ani_title, current["episode"])) if "fsdb_data" in program_info and reset_fsdb and self.fsdb is not None: self.logger.info("Re-Updating back FSDB project to Not done.") @@ -657,16 +701,16 @@ async def batalrilis(self, ctx, *, judul=None): announce_chan = osrv_data["announce_channel"] try: target_chan = self.bot.get_channel(int(announce_chan)) - except ValueError: + except (AttributeError, ValueError, TypeError): continue if not target_chan: self.logger.warning(f"{announce_chan}: unknown channel.") continue - embed = discord.Embed(title="{}".format(matches[0]), color=0xB51E1E) + embed = discord.Embed(title=ani_title, color=0xB51E1E) embed.add_field( name="Batal rilis...", value="Rilisan **episode #{}** dibatalkan dan sedang dikerjakan kembali".format( # noqa: E501 - current + current["episode"] ), inline=False, ) @@ -676,17 +720,17 @@ async def batalrilis(self, ctx, *, judul=None): announce_chan = srv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning( f"{server_message}: failed to convert announce channel to integer, ignoring..." ) return target_chan = self.bot.get_channel(announce_chan) - embed = discord.Embed(title="{}".format(matches[0]), color=0xB51E1E) + embed = discord.Embed(title=ani_title, color=0xB51E1E) embed.add_field( name="Batal rilis...", value="Rilisan **episode #{}** dibatalkan dan sedang dikerjakan kembali".format( # noqa: E501 - current + current["episode"] ), inline=False, ) @@ -722,15 +766,12 @@ async def gakjadi(self, ctx, posisi, *, judul): self.logger.info(f"{server_message}: data found.") srv_owner = srv_data["serverowner"] - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) - + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -740,8 +781,12 @@ async def gakjadi(self, ctx, posisi, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] status_list = program_info["status"] koleb_list = [] @@ -761,32 +806,32 @@ async def gakjadi(self, ctx, posisi, *, judul): return except ValueError: return await ctx.send( - f"Gagal memeriksa role, mohon ubah dengan {self.bot.prefix}ubahdata {matches[0]}" + f"Gagal memeriksa role, mohon ubah dengan {self.bot.prefix}ubahdata {ani_title}" ) current = self.get_current_ep(status_list) if not current: - self.logger.warning(f"{matches[0]}: no episode left to be worked on.") + self.logger.warning(f"{ani_title}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") - current_stat = status_list[current]["staff_status"][posisi] - if current_stat == "x": - self.logger.warning(f"{matches[0]}: position already set to undone.") + current_stat = current["progress"][posisi] + if not current_stat: + self.logger.warning(f"{ani_title}: position already set to undone.") return await ctx.send(f"**{posisi_asli}** sudah ditandakan sebagai tidak beres.") poster_data = program_info["poster_data"] poster_image = poster_data["url"] if ( - str(ctx.message.author.id) != str(program_info["staff_assignment"][posisi]["id"]) + str(ctx.message.author.id) != str(program_info["assignments"][posisi]["id"]) and str(ctx.message.author.id) not in srv_owner ): - self.logger.warning(f"{matches[0]}: no access to set to undone.") + self.logger.warning(f"{ani_title}: no access to set to undone.") return await ctx.send("**Bukan posisi situ untuk mengubahnya!**") - self.logger.info(f"{matches[0]}: setting episode {current} to undone.") - srv_data["anime"][matches[0]]["status"][current]["staff_status"][posisi] = "x" - srv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + self.logger.info(f"{ani_title}: setting episode {current} to undone.") + current["progress"][posisi] = False + program_info["last_update"] = self.get_unix() osrv_dumped = {} if koleb_list: for other_srv in koleb_list: @@ -795,16 +840,19 @@ async def gakjadi(self, ctx, posisi, *, judul): osrv_data = await self.showqueue.fetch_database(other_srv) if osrv_data is None: continue - osrv_data["anime"][matches[0]]["status"][current]["staff_status"][posisi] = "x" - osrv_data["anime"][matches[0]]["last_update"] = str(int(round(time.time()))) + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + progoinfo = osrv_data["anime"][indx_other] + indxo_ep = self._search_data_index(progoinfo["status"], "episode", current["episode"]) + osrv_data["anime"][indx_other]["status"][indxo_ep]["progress"][posisi] = False + osrv_data["anime"][indx_other]["last_update"] = self.get_unix() await self.showqueue.add_job(ShowtimesQueueData(osrv_data, other_srv)) osrv_dumped[other_srv] = osrv_data - current_ep_status = status_list[current]["staff_status"] + current_ep_status = current["progress"] await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) - self.logger.info(f"{matches[0]}: sending progress info to staff...") - await ctx.send("Berhasil mengubah status garapan {} - #{}".format(matches[0], current)) + self.logger.info(f"{ani_title}: sending progress info to staff...") + await ctx.send("Berhasil mengubah status garapan {} - #{}".format(ani_title, current["episode"])) self.logger.info(f"{server_message}: updating database...") success, msg = await self.ntdb.update_data_server(server_message, srv_data) @@ -831,7 +879,7 @@ async def gakjadi(self, ctx, posisi, *, judul): announce_chan = osrv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning(f"{osrv}: failed to convert announce channel to integer, ignoring...") continue try: @@ -841,14 +889,14 @@ async def gakjadi(self, ctx, posisi, *, judul): if not target_chan: self.logger.warning(f"{announce_chan}: unknown channel.") continue - embed = discord.Embed(title="{} - #{}".format(matches[0], current), color=0xB51E1E,) + embed = discord.Embed(title="{} - #{}".format(ani_title, current["episode"]), color=0xB51E1E,) embed.description = f"❌ {self.normalize_role_to_name(posisi)}" embed.add_field( name="Status", value=self.parse_status(current_ep_status), inline=False, ) embed.set_footer(text=f"Pada: {get_current_time()}") await target_chan.send(embed=embed) - embed = discord.Embed(title="{} - #{}".format(matches[0], current), color=0xB51E1E) + embed = discord.Embed(title="{} - #{}".format(ani_title, current["episode"]), color=0xB51E1E) embed.add_field( name="Status", value=self.parse_status(current_ep_status), inline=False, ) @@ -856,7 +904,7 @@ async def gakjadi(self, ctx, posisi, *, judul): announce_chan = srv_data["announce_channel"] try: announce_chan = int(announce_chan) - except ValueError: + except (AttributeError, ValueError, TypeError): self.logger.warning( f"{server_message}: failed to convert announce channel to integer, ignoring..." ) @@ -901,15 +949,12 @@ async def tandakan(self, ctx, posisi: str, episode_n: str, *, judul): self.logger.info(f"{server_message}: data found.") srv_owner = srv_data["serverowner"] - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) - + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -919,17 +964,23 @@ async def tandakan(self, ctx, posisi: str, episode_n: str, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] status_list = program_info["status"] - if episode_n not in status_list: - self.logger.warning(f"{matches[0]}: episode out of range.") + ep_index = self._search_data_index(status_list, "episode", episode_n) + + if ep_index is None: + self.logger.warning(f"{ani_title}: episode out of range.") return await ctx.send("Episode tersebut tidak ada di database.") current = self.get_current_ep(status_list) if not current: - self.logger.warning(f"{matches[0]}: no episode left to be worked on.") + self.logger.warning(f"{ani_title}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") koleb_list = [] @@ -943,16 +994,17 @@ async def tandakan(self, ctx, posisi: str, episode_n: str, *, judul): # Toggle status section if ( - str(ctx.message.author.id) != str(program_info["staff_assignment"][posisi]["id"]) + str(ctx.message.author.id) != str(program_info["assignments"][posisi]["id"]) and str(ctx.message.author.id) not in srv_owner ): - self.logger.warning(f"{matches[0]}: no access to set to mark it.") + self.logger.warning(f"{ani_title}: no access to set to mark it.") return await ctx.send("**Bukan posisi situ untuk mengubahnya!**") - pos_status = status_list[str(episode_n)]["staff_status"] + pos_status = status_list[ep_index]["progress"] + reverse_stat = not pos_status[posisi] osrv_dumped = {} - self.logger.info(f"{matches[0]}: marking episode {current}...") + self.logger.info(f"{ani_title}: marking episode {current}...") if koleb_list: for other_srv in koleb_list: if other_srv == server_message: @@ -960,24 +1012,27 @@ async def tandakan(self, ctx, posisi: str, episode_n: str, *, judul): osrv_data = await self.showqueue.fetch_database(other_srv) if osrv_data is None: continue - if pos_status[posisi] == "x": - osrv_data["anime"][matches[0]]["status"][episode_n]["staff_status"][posisi] = "y" - elif pos_status[posisi] == "y": - osrv_data["anime"][matches[0]]["status"][episode_n]["staff_status"][posisi] = "x" + indx_other = self._search_data_index(osrv_data["anime"], "id", program_info["id"]) + progoinfo = osrv_data["anime"][indx_other] + indxo_ep = self._search_data_index(progoinfo["status"], "episode", current["episode"]) + if indxo_ep is None: + continue + osrv_data["anime"][indx_other]["status"][indxo_ep]["progress"][posisi] = reverse_stat await self.showqueue.add_job(ShowtimesQueueData(osrv_data, other_srv)) osrv_dumped[other_srv] = osrv_data - if pos_status[posisi] == "x": - srv_data["anime"][matches[0]]["status"][episode_n]["staff_status"][posisi] = "y" - txt_msg = ( - "Berhasil mengubah status **{st}** **{an}** episode **#{ep}** ke **beres**" # noqa: E501 - ) - elif pos_status[posisi] == "y": - srv_data["anime"][matches[0]]["status"][episode_n]["staff_status"][posisi] = "x" - txt_msg = "Berhasil mengubah status **{st}** **{an}** episode **#{ep}** ke **belum beres**" # noqa: E501 + pos_status[posisi] = reverse_stat + txt_msg = "Berhasil mengubah status **{st}** **{an}** episode **#{ep}** ke **{x}**" await self.showqueue.add_job(ShowtimesQueueData(srv_data, server_message)) - await ctx.send(txt_msg.format(st=posisi, an=matches[0], ep=episode_n)) + await ctx.send( + txt_msg.format( + st=posisi, + an=ani_title, + ep=status_list[ep_index]["episode"], + x="beres" if reverse_stat else "belum beres", + ) + ) self.logger.info(f"{server_message}: updating database...") success, msg = await self.ntdb.update_data_server(server_message, srv_data) diff --git a/cogs/showtimes_module/user.py b/cogs/showtimes_module/user.py index a5dae66..03e8371 100644 --- a/cogs/showtimes_module/user.py +++ b/cogs/showtimes_module/user.py @@ -5,6 +5,7 @@ from copy import deepcopy from datetime import datetime, timezone from functools import partial +from typing import Union import discord from discord.ext import commands @@ -50,15 +51,13 @@ async def tagih(self, ctx, *, judul=None): return self.logger.info(f"{server_message}: data found.") - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -68,21 +67,25 @@ async def tagih(self, ctx, *, judul=None): if not matches: return await ctx.send("**Dibatalkan!**") - self.logger.info(f"{server_message}: matched {matches[0]}") - program_info = srv_data["anime"][matches[0]] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] last_update = int(program_info["last_update"]) status_list = program_info["status"] current = self.get_current_ep(status_list) - if not current: + if current is None: self.logger.info(f"{matches[0]}: no episode left to be worked on.") return await ctx.send("**Sudah beres digarap!**") poster_data = program_info["poster_data"] poster_image, poster_color = poster_data["url"], poster_data["color"] - if self.any_progress(status_list[current]["staff_status"]): - anilist_data = await fetch_anilist(program_info["anilist_id"], current) + if not self.is_progressing(current["progress"]): + anilist_data = await fetch_anilist(program_info["id"], current["episode"]) if isinstance(anilist_data, str): last_status = "Tidak diketahui..." else: @@ -92,10 +95,10 @@ async def tagih(self, ctx, *, judul=None): last_status = get_last_updated(last_update) last_text = "Update Terakhir" - current_ep_status = self.parse_status(status_list[current]["staff_status"]) + current_ep_status = self.parse_status(current["progress"]) self.logger.info(f"{matches[0]} sending current episode progress...") - embed = discord.Embed(title="{} - #{}".format(matches[0], current), color=poster_color) + embed = discord.Embed(title="{} - #{}".format(ani_title, current["episode"]), color=poster_color) embed.set_thumbnail(url=poster_image) embed.add_field(name="Status", value=current_ep_status, inline=False) embed.add_field(name=last_text, value=last_status, inline=False) @@ -122,22 +125,21 @@ async def jadwal(self, ctx): self.logger.info(f"{server_message}: data found.") time_data_list = {} - total_anime = len(list(srv_data["anime"].keys())) + total_anime = len(srv_data["anime"]) self.logger.info(f"{server_message}: collecting {total_anime} jadwal...") def calculate_needed(status_list): - all_ep = list(status_list.keys())[-1] - return 7 * int(all_ep) * 24 * 60 * 60 + final_ep = status_list[-1] + return 7 * final_ep["episode"] * 24 * 60 * 60 simple_queue = asyncio.Queue() fetch_anime_jobs = [] current_date = datetime.now(tz=timezone.utc).timestamp() - for ani, ani_data in srv_data["anime"].items(): - if ani == "alias": - continue + for ani_data in srv_data["anime"]: + ani = ani_data["title"] current = self.get_current_ep(ani_data["status"]) if current is None: - self.logger.warning(f"{ani}: anime already done worked on.") + self.logger.warning(f"{ani_data['title']}: anime already done worked on.") continue try: start_time = ani_data["start_time"] @@ -150,7 +152,7 @@ def calculate_needed(status_list): self.logger.warning(f"{ani}: anime already ended, skipping...") continue self.logger.info(f"{server_message}: requesting {ani}") - fetch_anime_jobs.append(fetch_anilist(ani_data["anilist_id"], jadwal_only=True)) + fetch_anime_jobs.append(fetch_anilist(ani_data["id"], jadwal_only=True)) self.logger.info(f"{server_message}: running jobs...") is_error = False @@ -195,9 +197,9 @@ def calculate_needed(status_list): if is_error: await ctx.send("Ada kemungkinan Anilist gagal dihubungi, mohon coba lagi nanti.") - @commands.command(aliases=["tukangdelay", "pendelay"]) + @commands.command(aliases=["tukangdelay", "pendelay", "staf"]) @commands.guild_only() - async def staff(self, ctx, *, judul): + async def staff(self, ctx: commands.Context, *, judul): """ Menagih utang fansub tukang diley maupun tidak untuk memberikan mereka tekanan @@ -216,15 +218,13 @@ async def staff(self, ctx, *, judul): self.logger.info(f"{server_message}: data found.") srv_owner = srv_data["serverowner"] - srv_anilist, srv_anilist_alias = await self.collect_anime_with_alias( - srv_data["anime"], srv_data["alias"] - ) + propagated_anilist = self.propagate_anime_with_aliases(srv_data["anime"]) if not judul: - return await self.send_all_projects(ctx, srv_anilist, server_message) + return await self.send_all_projects(ctx, srv_data["anime"], server_message) self.logger.info(f"{server_message}: getting close matches...") - matches = await self.find_any_matches(judul, srv_anilist, srv_anilist_alias, srv_data["alias"]) + matches = self.find_any_matches(judul, propagated_anilist) if not matches: self.logger.warning(f"{server_message}: no matches.") return await ctx.send("Tidak dapat menemukan judul tersebut di database") @@ -234,17 +234,23 @@ async def staff(self, ctx, *, judul): if not matches: return await ctx.send("**Dibatalkan!**") - staff_assignment = srv_data["anime"][matches[0]]["staff_assignment"] + matched_anime = matches[0] + indx = matched_anime["index"] + ani_title = matched_anime["name"] if matched_anime["type"] == "real" else matched_anime["real_name"] + + self.logger.info(f"{server_message}: matched {matched_anime}") + program_info = srv_data["anime"][indx] + staff_assignment = program_info["assignments"] self.logger.info(f"{server_message}: parsing staff data...") - rtext = "Staff yang mengerjakaan **{}**\n**Admin**: ".format(matches[0]) + rtext = "Staff yang mengerjakaan **{}**\n**Admin**: ".format(ani_title) rtext += "" async def get_user_name(user_id): try: user_data = self.bot.get_user(int(user_id)) return "{}#{}".format(user_data.name, user_data.discriminator) - except AttributeError: + except (AttributeError, ValueError, TypeError): return "[Rahasia]" new_srv_owner = [] @@ -254,13 +260,20 @@ async def get_user_name(user_id): rtext += ", ".join(new_srv_owner) - rtext += "\n**Role**: {}".format( - self.get_role_name(srv_data["anime"][matches[0]]["role_id"], ctx.message.guild.roles,) - ) + guild: discord.Guild = ctx.message.guild + role_name = "Tidak Diketahui" + try: + realrole: Union[discord.Role, None] = guild.get_role(int(program_info["role_id"])) + if realrole is not None: + role_name = realrole.name + except ValueError: + role_name = "Tidak Dikethui" + + rtext += f"\n**Role**: {role_name}" - if "kolaborasi" in srv_data["anime"][matches[0]]: + if "kolaborasi" in program_info: k_list = [] - for other_srv in srv_data["anime"][matches[0]]["kolaborasi"]: + for other_srv in program_info["kolaborasi"]: if server_message == other_srv: continue server_data = self.bot.get_guild(int(other_srv)) diff --git a/cogs/vote.py b/cogs/vote.py index bcc64ff..ded99ff 100644 --- a/cogs/vote.py +++ b/cogs/vote.py @@ -34,7 +34,7 @@ "action": "store", "help": "Waktu sebelum voting ditutup (Format time string seperti: " "'30m 30s' untuk 30 menit 30 detik, minimal 30 detik, default 1 menit)\n" - "Referensi time string: https://naoti.me/#/vote_cmd?id=time-string-format", + "Referensi time string: https://naoti.me/docs/perintah/vote#time-string-format", } vote_opsi_args = ["--opsi", "-O"] vote_opsi_kwargs = { @@ -53,7 +53,7 @@ vote_timer_kwargs["help"] = ( "Waktu sebelum voting ditutup (Format time string seperti: " "'30m 30s' untuk 30 menit 30 detik, minimal 3 menit, default 5 menit)\n" - "Referensi time string: https://naoti.me/#/vote_cmd?id=time-string-format" + "Referensi time string: https://naoti.me/docs/perintah/vote#time-string-format" ) giveaway_timer_kwargs = deepcopy(kickban_timer_kwargs) @@ -61,7 +61,7 @@ giveaway_timer_kwargs["help"] = ( "Waktu sebelum voting ditutup (Format time string seperti: " "'30m 30s' untuk 30 menit 30 detik, minimal 5 menit, default 1 jam)\n" - "Referensi time string: https://naoti.me/#/vote_cmd?id=time-string-format" + "Referensi time string: https://naoti.me/docs/perintah/vote#time-string-format" ) ban_args = Arguments("voteban") diff --git a/cogs/vtuber.py b/cogs/vtuber.py index c094656..56a0c83 100644 --- a/cogs/vtuber.py +++ b/cogs/vtuber.py @@ -427,7 +427,6 @@ async def _generate_channel_embed(self, dataset: dict, pos: int, total: int): @vtuber_main.command(name="live", aliases=["lives"]) async def vtuber_live(self, ctx: commands.Context, *, args=""): - print(args) args = await live_converter.convert(ctx, args) if isinstance(args, str): args = f"```py\n{args}\n```" diff --git a/nthelper/fsdb.py b/nthelper/fsdb.py index e5bd0b8..5a598ca 100644 --- a/nthelper/fsdb.py +++ b/nthelper/fsdb.py @@ -294,16 +294,3 @@ async def delete_project(self, project_id: Union[int, str]) -> Tuple[bool, str]: if results["type"] == "success": return True, "Success" return False, results["message"] - - -if __name__ == "__main__": - loop = asyncio.get_event_loop() - fsdb = FansubDBBridge("", "", loop) - # success, fs_id = loop.run_until_complete(fsdb.import_mal()) - loop.run_until_complete(fsdb.authorize()) - res, _ = loop.run_until_complete(fsdb.fetch_fansub_projects(18)) - with open("delima_projects.json", "w", encoding="utf-8") as fp: - ujson.dump( - res, fp, indent=4, ensure_ascii=False, encode_html_chars=False, escape_forward_slashes=False - ) - loop.run_until_complete(fsdb.close()) diff --git a/nthelper/kateglo.py b/nthelper/kateglo.py new file mode 100644 index 0000000..ac3111e --- /dev/null +++ b/nthelper/kateglo.py @@ -0,0 +1,111 @@ +import aiohttp +import logging +from typing import Any, Dict, List, NamedTuple, Union +from enum import Enum + +from .utils import __version__, traverse + +logger = logging.getLogger("nthelper.kateglo") + + +class KategloError(Exception): + pass + + +class KategloTidakDitemukan(KategloError): + def __init__(self, kata: str) -> None: + super().__init__(f"Kata `{kata}` tidak dapat ditemukan") + + +class KategloTidakAdaRelasi(KategloError): + def __init__(self, kata: str) -> None: + super().__init__(f"Tidak ada relasi untuk kata `{kata}`") + + +class KategloTipe(Enum): + Sinonim = "s" + Antonim = "a" + Turunan = "d" + Gabungan = "c" + Peribahasa = "pb" + Berkaitan = "r" + TidakDiketahui = "td" + + +class KategloRelasi(NamedTuple): + id: Union[str, None] + tipe: KategloTipe + kata: str + + def to_dict(self): + return {"id": self.id, "tipe": self.tipe, "kata": self.kata} + + +def cherry_pick_safe(dataset: dict, note: str) -> Union[Any, None]: + try: + return traverse(dataset, note) + except (ValueError, KeyError, AttributeError): + return None + + +def collect_relations(dataset, tipe: KategloTipe) -> List[KategloRelasi]: + relasidata: Union[Dict[str, dict], None] = cherry_pick_safe(dataset, tipe.value) + if not isinstance(relasidata, dict): + return [] + collected: List[KategloRelasi] = [] + for key, relasi in relasidata.items(): + if not key.isdigit(): + continue + uuid = cherry_pick_safe(relasi, "rel_uid") + if uuid is None: + uuid = key + phrase = cherry_pick_safe(relasi, "related_phrase") + collected.append(KategloRelasi(uuid, tipe, phrase)) + return collected + + +async def kateglo_relasi(kata: str) -> List[KategloRelasi]: + query_params = {"format": "json", "phrase": kata} + logger.info(f"searching for {kata}") + async with aiohttp.ClientSession( + headers={"User-Agent": f"naoTimes/v{__version__} (https://github.com/naoTimesdev)"} + ) as sesi: + try: + async with sesi.get("https://kateglo.com/api.php", params=query_params) as resp: + if resp.status != 200: + logger.error(f"{kata}: Got non-200 code: {resp.status}") + raise KategloError(f"Error, Mendapatkan kode status {resp.status} dari API") + if "application/json" not in resp.headers["content-type"]: + logger.warning( + f"{kata}: expected JSON data, but got {resp.headers['content-type']} instead" + ) + raise KategloTidakDitemukan(kata) + res = await resp.json() + except aiohttp.ClientError: + logger.error(f"{kata}: failed to fetch to API") + raise KategloError("Tidak dapat menghubungi API Kateglo untuk mendapatkan hasil.") + + logger.info(f"{kata}: traversing to kateglo relation") + relasi_kata = cherry_pick_safe(res, "kateglo.relation") + + if relasi_kata is None: + logger.warning(f"{kata}: failed to fetch relation data") + raise KategloTidakAdaRelasi(kata) + + antonim = collect_relations(relasi_kata, KategloTipe.Antonim) + gabungan_kata = collect_relations(relasi_kata, KategloTipe.Gabungan) + sinonim = collect_relations(relasi_kata, KategloTipe.Sinonim) + turunan = collect_relations(relasi_kata, KategloTipe.Turunan) + peribahasa = collect_relations(relasi_kata, KategloTipe.Peribahasa) + berkaitan = collect_relations(relasi_kata, KategloTipe.Berkaitan) + + bulk_results: List[KategloRelasi] = [] + bulk_results.extend(antonim) + bulk_results.extend(gabungan_kata) + bulk_results.extend(sinonim) + bulk_results.extend(turunan) + bulk_results.extend(peribahasa) + bulk_results.extend(berkaitan) + logger.info(f"{kata}: got {len(bulk_results)} results") + bulk_results.sort(key=lambda x: x.id) + return bulk_results diff --git a/nthelper/redis.py b/nthelper/redis.py index 6314bef..867093c 100644 --- a/nthelper/redis.py +++ b/nthelper/redis.py @@ -5,6 +5,14 @@ from typing import Any, Dict, List, Optional import aioredis +from bson import ObjectId + + +class BSONEncoderExtended(json.JSONEncoder): + def default(self, o: Any) -> Any: + if isinstance(o, ObjectId): + return str(o) + return super().default(o) class RedisBridge: @@ -61,7 +69,15 @@ def __init__(self, host: str, port: int, password: str = None, loop: asyncio.Abs self._is_stopping = False @staticmethod - def stringify(data: Any) -> str: + def _clean_bson_objectid(objects: dict) -> dict: + readjusted_data = {} + for key, value in objects.items(): + if isinstance(value, ObjectId): + continue + readjusted_data[key] = value + return readjusted_data + + def stringify(self, data: Any) -> str: """Stringify `data` :param data: data to be turn into a string @@ -76,7 +92,17 @@ def stringify(data: Any) -> str: elif isinstance(data, int): data = str(data) elif isinstance(data, (list, tuple, dict)): - data = json.dumps(data, ensure_ascii=False) + if isinstance(data, dict): + data = self._clean_bson_objectid(data) + elif isinstance(data, list): + _data = [] + for d in data: + if isinstance(d, dict): + _data.append(self._clean_bson_objectid(d)) + else: + _data.append(d) + data = _data + data = json.dumps(data, ensure_ascii=False, cls=BSONEncoderExtended) return data @staticmethod diff --git a/nthelper/showtimes_helper.py b/nthelper/showtimes_helper.py index 5b67603..b5c1508 100644 --- a/nthelper/showtimes_helper.py +++ b/nthelper/showtimes_helper.py @@ -3,18 +3,73 @@ import logging import time import traceback -from copy import deepcopy -from typing import Union +from typing import Dict, List, Union import aioredis import motor.motor_asyncio import pymongo import pymongo.errors +import schema as sc from nthelper.redis import RedisBridge +from nthelper.utils import generate_custom_code showtimes_log = logging.getLogger("showtimes_helper") +NoneStr = sc.Or(str, None) +NoneInt = sc.Or(int, None) + +ShowtimesSchemas = sc.Schema( + { + "id": str, + "serverowner": [str], + sc.Optional("announce_channel"): NoneStr, + sc.Optional("fsdb_id"): sc.Or(int, str), + "anime": [ + { + sc.Optional("aliases"): [sc.Optional(str)], + sc.Optional("kolaborasi"): [sc.Optional(str)], + "id": str, + "mal_id": str, + "title": str, + "role_id": NoneStr, + "start_time": NoneInt, + "assignments": { + "TL": {"id": NoneStr, "name": NoneStr}, + "TLC": {"id": NoneStr, "name": NoneStr}, + "ENC": {"id": NoneStr, "name": NoneStr}, + "ED": {"id": NoneStr, "name": NoneStr}, + "TM": {"id": NoneStr, "name": NoneStr}, + "TS": {"id": NoneStr, "name": NoneStr}, + "QC": {"id": NoneStr, "name": NoneStr}, + }, + "status": [ + { + "episode": int, + "is_done": bool, + "progress": { + "TL": bool, + "TLC": bool, + "ENC": bool, + "ED": bool, + "TM": bool, + "TS": bool, + "QC": bool, + }, + sc.Optional("airtime"): int, + } + ], + "poster_data": {"url": str, "color": int}, + "last_update": int, + sc.Optional("fsdb_data"): {"id": int, "ani_id": int}, + } + ], + sc.Optional("konfirmasi"): [{"id": str, "anime_id": int, "server_id": str}], + }, + name="ShowtimesData", + description="A schema for server Showtimes data.", +) + def safe_asynclock(func): """A thread-safe/async-safe decorator lock mechanism to fight race-condition @@ -41,7 +96,7 @@ async def safelock(self, *args, **kwargs): except Exception as error: # skipcq: PYL-W0703 showtimes_log.error("Exception occured, releasing lock...") tb = traceback.format_exception(type(error), error, error.__traceback__) - showtimes_log.error("traceback\n{}".format("".join(tb))) + showtimes_log.error("{}".format("".join(tb))) await self._release_lock() # skipcq: PYL-W0212 ret = None if args and len(args) == 1: @@ -73,8 +128,6 @@ def __init__(self, ip_hostname, port, dbname="naotimesdb", auth_string=None, tls self.client = motor.motor_asyncio.AsyncIOMotorClient(self._url) self.db = self.client[dbname] - self.clientsync = pymongo.MongoClient(self._url) - self.dbsync = self.clientsync[dbname] self.srv_re = {"name": {"$regex": r"^srv"}} self.__locked = False @@ -134,56 +187,65 @@ async def validate_connection(self): await self.db.command({"ping": 1}) @safe_asynclock - async def fetch_data(self, collection: str, **kwargs) -> tuple: - self.logger.info(f"Fetching {collection} data...") - coll = self.db[collection] - coll_cur = coll.find({}) - res = list(await coll_cur.to_list(length=100)) - res = res[0] - del res["_id"] # type: ignore - return res, collection + async def fetch_data(self, server_id: Union[str, int], remove_id=True, **kwargs) -> tuple: + self.logger.info(f"Fetching server {server_id} data...") + result = await self.db["showtimesdatas"].find_one({"id": str(server_id)}) + if result is None: + return {}, server_id + if remove_id: + try: + del result["_id"] + except KeyError: + pass + return result, server_id @safe_asynclock - async def update_data(self, coll_key: str, data: dict, **kwargs) -> bool: + async def update_data(self, server_id: str, data: dict, **kwargs) -> bool: upd = {"$set": data} - self.logger.info(f"{coll_key}: Updating collection...") - coll = self.db[coll_key] - res = await coll.update_one({}, upd) + self.logger.info(f"{server_id}: validating schematics...") + try: + ShowtimesSchemas.validate(data) + except sc.SchemaError as error: + self.logger.error(f"{server_id}: failed to validate the server schemas") + tb = traceback.format_exception(type(error), error, error.__traceback__) + self.logger.error("Exception occured\n" + "".join(tb)) + self.logger.info(f"{server_id}: Updating collection...") + coll = self.db["showtimesdatas"] + res = await coll.update_one({"id": str(server_id)}, upd) if res.acknowledged: - self.logger.info(f"{coll_key}: data updated.") + self.logger.info(f"{server_id}: data updated.") return True - self.logger.error(f"{coll_key}: Failed to update.") + self.logger.error(f"{server_id}: Failed to update.") return False @safe_asynclock - async def insert_new(self, coll_key: str, data: dict, **kwargs) -> bool: - coll = self.db[coll_key] - self.logger.info(f"{coll_key}: adding new data...") + async def insert_new(self, server_id: str, data: dict, **kwargs) -> bool: + coll = self.db["showtimesdatas"] + self.logger.info(f"{server_id}: validating schematics...") + try: + ShowtimesSchemas.validate(data) + except sc.SchemaError as error: + self.logger.error(f"{server_id}: failed to validate the server schemas") + tb = traceback.format_exception(type(error), error, error.__traceback__) + self.logger.error("Exception occured\n" + "".join(tb)) + self.logger.info(f"{server_id}: adding new data...") result = await coll.insert_one(data) if result.acknowledged: ids_res = result.inserted_id - self.logger.info(f"{coll_key}: inserted with IDs {ids_res}") + self.logger.info(f"{server_id}: inserted with IDs {ids_res}") return True - self.logger.error(f"{coll_key}: Failed to insert new data.") + self.logger.error(f"{server_id}: Failed to insert new data.") return False @safe_asynclock - async def insert_new_sync(self, coll_key: str, data: dict, **kwargs) -> bool: - srv = self.dbsync[coll_key] - self.logger.info(f"{coll_key}: adding new data...") - res = srv.insert(data, check_keys=False) - if res: - self.logger.info(f"{coll_key}: data inserted.") - return True - self.logger.error(f"{coll_key}: Failed to insert new data.") - return False - - async def _precheck_server_name(self, namae): - if not isinstance(namae, str): - namae = str(namae) - if not namae.startswith("srv_"): - namae = "srv_" + namae - return namae + async def fetch_available_servers(self) -> List[str]: + self.logger.info("Fetching available keys...") + cursor = self.db["showtimesdatas"].find({}, {"id": 1}) + results_keys = await cursor.to_list(length=250) + finalized_data = [] + for key in results_keys: + finalized_data.append(key["id"]) + return finalized_data async def ping_server(self): t1_ping = time.perf_counter() @@ -202,38 +264,26 @@ async def fetch_all_as_json(self): json_data = {} admin_list = await self.get_top_admin() json_data["supermod"] = admin_list - server_collection = await self.db.list_collection_names(filter=self.srv_re) - self.logger.info("Creating tasks...") - server_tasks = [self.fetch_data(coll) for coll in server_collection] - for srv_task in asyncio.as_completed(server_tasks): - srv, name = await srv_task - self.logger.info("Fetching server: {}".format(name)) - json_data[name[4:]] = srv + showtimes_coll = self.db["showtimesdatas"] + showtimes_cur = showtimes_coll.find({}) + self.logger.info("Fetching all showtimes servers!") + server_collection = await showtimes_cur.to_list(length=100) + json_data["servers"] = server_collection self.logger.info("dumping data...") return json_data async def patch_all_from_json(self, dataset: dict): self.logger.info("patching database with current local save.") - kunci = list(dataset.keys()) - kunci.remove("supermod") - - server_collection = await self.db.list_collection_names(filter=self.srv_re) - for ksrv in kunci: - self.logger.info(f"patching collection: {ksrv}") - ksrv = await self._precheck_server_name(ksrv) - if ksrv in server_collection: - while True: - self.logger.info(f"{ksrv}: Updating collection...") - res = await self.update_data(ksrv, dataset[ksrv[4:]]) - if res: - self.logger.info(f"{ksrv}: Updated.") - break + server_collection = await self.fetch_available_servers() + for server in dataset["servers"]: + if server["id"] in server_collection: + res = await self.update_data(server["id"], server) + if res: + self.logger.info(f"updated {server['id']}") else: - while True: - self.logger.info(f"{ksrv}: adding as new data...") - res = await self.insert_new_sync(ksrv, dataset[ksrv[4:]]) - if res: - break + res = await self.insert_new(server["id"], server) + if res: + self.logger.info(f"inserted {server['id']}") self.logger.info("updating top admin...") while True: @@ -246,9 +296,12 @@ async def patch_all_from_json(self, dataset: dict): async def get_top_admin(self): self.logger.info("fetching...") - admin, _ = await self.fetch_data("server_admin") - admin = admin["server_admin"] - return admin + curr = self.db["showtimesadmin"].find({}) + admin_coll = await curr.to_list(length=100) + removed_ids_contents = [] + for res in admin_coll: + removed_ids_contents.append({"id": res["id"], "servers": res["servers"]}) + return removed_ids_contents async def add_top_admin(self, adm_id): self.logger.info(f"trying to add {adm_id}...") @@ -278,19 +331,10 @@ async def remove_top_admin(self, adm_id): self.logger.error("failed to remove top admin.") return False, "Gagal menghapus top admin." - @safe_asynclock - async def get_server_list(self, clean_result=False): - self.logger.info("fetching...") - srv_list = await self.db.list_collection_names(filter=self.srv_re) - if clean_result: - self.logger.info("cleaning results...") - srv_list = [s[s.find("_") + 1 :] for s in srv_list] - return srv_list - async def get_server(self, server): - server = await self._precheck_server_name(server) + server = str(server) self.logger.info(f"fetching server set: {server}") - srv_list = await self.get_server_list() + srv_list = await self.fetch_available_servers() if server not in srv_list: self.logger.warning(f"cant find {server} on database.") return {} @@ -299,9 +343,9 @@ async def get_server(self, server): return srv async def update_data_server(self, server, dataset): - server = await self._precheck_server_name(server) + server = str(server) self.logger.info(f"updating data for {server}") - srv_list = await self.get_server_list() + srv_list = await self.fetch_available_servers() if server not in srv_list: self.logger.warning(f"cant find {server} on database.") return ( @@ -315,7 +359,7 @@ async def update_data_server(self, server, dataset): return False, "Gagal mengupdate server data." async def add_admin(self, server, adm_id): - server = await self._precheck_server_name(server) + server = str(server) adm_id = str(adm_id) self.logger.info(f"trying to add {adm_id} to {server}...") srv_data = await self.get_server(server) @@ -324,13 +368,13 @@ async def add_admin(self, server, adm_id): return False, "Server tidak terdaftar di naoTimes." if adm_id not in srv_data["serverowner"]: - srv_data["serverowner"].append(adm_id) + srv_data["serverowner"].append(int(adm_id)) res, msg = await self.update_data_server(server, srv_data) return res, msg async def remove_admin(self, server, adm_id): - server = await self._precheck_server_name(server) + server = str(server) adm_id = str(adm_id) self.logger.info(f"trying to remove {adm_id} from {server}...") srv_data = await self.get_server(server) @@ -339,15 +383,15 @@ async def remove_admin(self, server, adm_id): return False, "Server tidak terdaftar di naoTimes." if adm_id in srv_data["serverowner"]: - srv_data["serverowner"].remove(adm_id) + srv_data["serverowner"].remove(int(adm_id)) res, msg = await self.update_data_server(server, srv_data) return res, msg async def new_server(self, server, admin_id, announce_channel=None): - server = await self._precheck_server_name(server) + server = str(server) self.logger.info(f"trying to add {server} to database...") - srv_list = await self.get_server_list() + srv_list = await self.fetch_available_servers() if server in srv_list: self.logger.warning(f"{server} already exists on database.") return ( @@ -356,17 +400,14 @@ async def new_server(self, server, admin_id, announce_channel=None): ) dataset = { - "serverowner": [str(admin_id)], - "announce_channel": "", - "anime": {}, - "alias": {}, - "konfirmasi": {}, + "id": str(server), + "serverowner": [admin_id], + "announce_channel": announce_channel, + "anime": [], + "konfirmasi": [], } - if announce_channel: - dataset["announce_channel"] = announce_channel - - res = await self.insert_new_sync(server, dataset) + res = await self.insert_new(server, dataset) if res: res, msg = await self.add_top_admin(str(admin_id)) self.logger.info(f"{server} added to database.") @@ -375,9 +416,9 @@ async def new_server(self, server, admin_id, announce_channel=None): return False, "Gagal mengupdate server data." async def remove_server(self, server, admin_id): - server = await self._precheck_server_name(server) + server = str(server) self.logger.info(f"yeeting {server} from database...") - srv_list = await self.get_server_list() + srv_list = await self.fetch_available_servers() if server not in srv_list: self.logger.warning(f"cant find {server} on database.") return True @@ -390,14 +431,16 @@ async def remove_server(self, server, admin_id): self.logger.warning("server doesn't exist on database when dropping, ignoring...") return True, "Success anyway" - async def kolaborasi_dengan(self, target_server, confirm_id, target_data): + async def kolaborasi_dengan(self, target_server, target_data): self.logger.info(f"new collaboration with {target_server}") - target_server = await self._precheck_server_name(target_server) + target_server = str(target_server) srv_data = await self.get_server(target_server) if not srv_data: self.logger.warning(f"server {target_server} doesn't exist.") return False, "Server tidak terdaftar di naoTimes." - srv_data["konfirmasi"][confirm_id] = target_data + if "konfirmasi" not in srv_data: + srv_data["konfirmasi"] = [] + srv_data["konfirmasi"].append(target_data) res, msg = await self.update_data_server(target_server, srv_data) self.logger.info(f"{target_server}: collaboration initiated") @@ -405,8 +448,8 @@ async def kolaborasi_dengan(self, target_server, confirm_id, target_data): async def kolaborasi_konfirmasi(self, source_server, target_server, srv1_data, srv2_data): self.logger.info(f"confirming between {source_server}" f" and {target_server}") - target_server = await self._precheck_server_name(target_server) - source_server = await self._precheck_server_name(source_server) + target_server = str(target_server) + source_server = str(source_server) target_srv_data = await self.get_server(target_server) source_srv_data = await self.get_server(source_server) if not target_srv_data: @@ -418,62 +461,56 @@ async def kolaborasi_konfirmasi(self, source_server, target_server, srv1_data, s res, msg = await self.update_data_server(source_server, srv1_data) self.logger.info(f"{source_server}: is acknowledged? {res}") - self.logger.info(f"{source_server}: message> {msg}") + self.logger.info(f"{source_server}: message? {msg}") res, msg = await self.update_data_server(target_server, srv2_data) self.logger.info(f"{target_server}: is acknowledged? {res}") - self.logger.info(f"{target_server}: message> {msg}") + self.logger.info(f"{target_server}: message? {msg}") return res, msg + @staticmethod + def _find_confirm_id(konfirm_id, kolaborasi_data): + index = None + for n, koleb in enumerate(kolaborasi_data): + if konfirm_id == koleb["id"]: + index = n + break + return index + async def kolaborasi_batalkan(self, server, confirm_id): self.logger.info("cancelling " f"collaboration with {server}") - server = await self._precheck_server_name(server) + server = str(server) srv_data = await self.get_server(server) if not srv_data: self.logger.warning(f"{server} doesn't exist.") return False, "Server tidak terdaftar di naoTimes." - if confirm_id in srv_data["kolaborasi"]: - del srv_data["kolaborasi"][confirm_id] - res, msg = await self.update_data_server(server, srv_data) - return res, msg - - async def kolaborasi_putuskan(self, server, anime): - self.logger.info("aborting " f"collaboration with {server}") - server = await self._precheck_server_name(server) - srv_data = await self.get_server(server) - if not srv_data: - return False, "Server tidak terdaftar di naoTimes." - - if anime not in srv_data["anime"]: - return False, "Anime tidak dapat ditemukan." - - if "kolaborasi" not in srv_data["anime"][anime]: - return False, "Tidak ada kolaborasi yang terdaftar" - - for osrv in srv_data["anime"][anime]["kolaborasi"]: - self.logger.info(f"removing {server} " f"from {osrv} data") - osrv = await self._precheck_server_name(osrv) - osrvd = await self.get_server(osrv) - klosrv = deepcopy(osrvd["anime"][anime]["kolaborasi"]) - klosrv.remove(server[4:]) - - remove_all = False - if len(klosrv) == 1 and klosrv[0] == osrv[4:]: - remove_all = True - - if remove_all: - del osrvd["anime"][anime]["kolaborasi"] - else: - osrvd["anime"][anime]["kolaborasi"] = klosrv - res, msg = await self.update_data_server(osrv, osrvd) - self.logger.info(f"{osrv}: is acknowledged? {res}") - self.logger.info(f"{osrv}: message> {msg}") - - del srv_data["anime"][anime]["kolaborasi"] + del_index = self._find_confirm_id(confirm_id, srv_data["kolaborasi"]) + if del_index is None: + return True, "IDs tidak dapat ditemukan" + srv_data["kolaborasi"].pop(del_index) res, msg = await self.update_data_server(server, srv_data) return res, msg + # WebUI Mechanism + async def generate_login_info(self, server_id: str, is_owner=False) -> str: + randomized_password = generate_custom_code(16, True, True) + self.logger.info("Checking existing login information!") + collection = self.db["showtimesuilogin"] + server_id = str(server_id) + old_data = await collection.find_one({"id": server_id}) + if old_data is not None: + self.logger.info("Existing login info exist, returning!") + return False, f"Login sudah ada, passwordnya adalah: `{old_data['secret']}`" + self.logger.info(f"Generating new secret for {server_id}") + login_type = "server" if not is_owner else "owner" + result = await collection.insert_one( + {"id": server_id, "secret": randomized_password, "privilege": login_type} + ) + if result.acknowledged: + return True, f"Silakan gunakan password/secret berikut untuk login: `{randomized_password}`" + return False, "Gagal membuat informasi login, mohon coba lagi nanti." + class ShowtimesQueueData: """A queue data of save state @@ -487,10 +524,51 @@ def __init__(self, dataset: Union[list, dict], server_id: str): self._type = "dumps" + @property + def info(self): + return f"Server: {self.server_id}" + def job_type(self): return self._type +class ShowtimesLock: + def __init__(self, server_id: Union[str, int]): + self._log = logging.getLogger(f"ShowtimesLock[{server_id}]") + self._id = str(server_id) + self._lock = False + + @property + def id(self): + return self._id + + async def __aenter__(self, *args, **kwargs): + await self.hold() + return self._id + + async def __aexit__(self, *args, **kwargs): + await self.release() + + async def hold(self): + timeout_max = 10 # In seconds + current_time = 0 + increment = 0.2 + while self._lock: + if not self._lock: + break + if current_time > timeout_max: + self._log.warning("Waiting timeout occured, relocking!") + break + await asyncio.sleep(increment) + current_time += increment + self._log.info("Holding access to lock!") + self._lock = True + + async def release(self): + self._log.info("Releasing lock...") + self._lock = False + + class ShowtimesQueue: """A helper to queue save local showtimes database. @@ -507,7 +585,7 @@ def __init__(self, redis_client: RedisBridge, loop=None): self._showtasks: asyncio.Task = asyncio.Task(self.background_jobs(), loop=self._loop) # self._showdata: dict = {} - self._lock = False + self._lock_collection: Dict[str, ShowtimesLock] = {} async def shutdown(self): """ @@ -515,56 +593,36 @@ async def shutdown(self): """ self._logger.info("Cancelling all tasks...") self._showtasks.cancel() + for _, locked in self._lock_collection.items(): + await locked.release() self._logger.info("finished awaiting cancelled tasks, stopping...") - # async def get_data(self, server_id: str, retry_time: int = 5): - # if retry_time <= 0: - # retry_time = 1 - # dataset = {} - # is_success = False - # while retry_time > 0: - # _temp_data = self._showdata.get(server_id, "no data in showdata") - # if not isinstance(_temp_data, str): - # dataset = self._showdata.pop(server_id) - # is_success = True - # break - # retry_time -= 1 - # await asyncio.sleep(0.2) - # return dataset, is_success + def _get_lock(self, server_id: str) -> ShowtimesLock: + server_id = str(server_id) + if server_id not in self._lock_collection: + self._lock_collection[server_id] = ShowtimesLock(server_id) + return self._lock_collection[server_id] async def _dumps_data(self, dataset: Union[list, dict], server_id: str): self._logger.info(f"dumping db {server_id}") - await self._lock_job() - try: - await self._db.set(f"showtimes_{server_id}", dataset) - except aioredis.RedisError as e: - self._logger.error("Failed to dumps database...") - self._logger.error(e) - pass - await self._job_done() + async with self._get_lock(server_id) as locked_id: + try: + await self._db.set(f"showtimes_{locked_id}", dataset) + except aioredis.RedisError as e: + self._logger.error("Failed to dumps database...") + self._logger.error(e) async def fetch_database(self, server_id: str): self._logger.info(f"opening db {server_id}") - await self._lock_job() - try: - json_data = await self._db.get(f"showtimes_{server_id}") - except aioredis.RedisError as e: - self._logger.error("Failed to read database...") - self._logger.error(e) - json_data = None - # self._logger.info("Adding to data part...") - # self._showdata[server_id] = json_data - await self._job_done() + async with self._get_lock(server_id) as locked_id: + try: + json_data = await self._db.get(f"showtimes_{locked_id}") + except aioredis.RedisError as e: + self._logger.error("Failed to read database...") + self._logger.error(e) + json_data = None return json_data - async def _lock_job(self): - while self._lock: - await asyncio.sleep(0.2) - self._lock = True - - async def _job_done(self): - self._lock = False - async def background_jobs(self): self._logger.info("Starting ShowtimesQueue Task...") while True: diff --git a/nthelper/utils.py b/nthelper/utils.py index d296ccf..5915204 100644 --- a/nthelper/utils.py +++ b/nthelper/utils.py @@ -916,3 +916,15 @@ def check_react(reaction, user): await message.delete() return is_timeout return is_timeout + + +def hex_to_color(hex_str: str) -> discord.Colour: + hex_str = hex_str.replace("#", "").upper() + r = int(hex_str[0:2], 16) + g = int(hex_str[2:4], 16) + b = int(hex_str[4:6], 16) + return discord.Colour.from_rgb(r, g, b) + + +def rgb_to_color(r: int, g: int, b: int) -> discord.Colour: + return discord.Colour.from_rgb(r, g, b) diff --git a/nthelper/wolfram.py b/nthelper/wolfram.py index 05cdc8f..3f8c99b 100644 --- a/nthelper/wolfram.py +++ b/nthelper/wolfram.py @@ -30,6 +30,7 @@ """ import asyncio +import json from typing import Any, Dict, List, Mapping, NamedTuple, Union import aiohttp @@ -143,10 +144,9 @@ async def _request(self, question: str) -> Mapping[str, Any]: elif resp.status == 404: return {"error": "Tidak dapat hasil"} return {"error": f"Mendapatkan error status {resp.status}"} - if "application/json" not in resp.headers["content-type"]: - return {"error": "Mendapatkan jawaban selain JSON dari API"} + raw_resp = await resp.text() try: - responses = await resp.json() + responses = json.loads(raw_resp) return responses except ValueError: return {"error": "Tidak dapat memproses hasil dari API"} diff --git a/requirements.txt b/requirements.txt index 6520c17..67b304b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ aiohttp>=3.6.2 aiofiles==0.5.0 ujson==1.35 -discord.py[voice]>=1.5 +discord.py[voice]>=1.6 discord-py-slash-command==1.0.9.5 motor==2.2.0 beautifulsoup4==4.8.0 @@ -22,6 +22,7 @@ PyNaCl==1.4.0 schema==0.7.4 aiographql-client==1.0.2 pyparsing==2.4.7 +timeago==1.0.15 youtube-dl dnspython \ No newline at end of file