Best Python code snippet using tempest_python
app.py
Source:app.py  
1import asyncio2from base64 import urlsafe_b64decode, urlsafe_b64encode3import datetime4import json5import uuid6import aiohttp7import discord8import secrets as _secrets9from discord.ext import commands10from fastapi.responses import HTMLResponse, ORJSONResponse, RedirectResponse11from fastapi.security.api_key import APIKeyHeader12from fastapi import Depends, FastAPI, Request, WebSocket, WebSocketDisconnect13from fastapi.routing import Mount14import orjson15from piccolo.engine import engine_finder16from piccolo_admin.endpoints import create_admin17import piccolo18import pydantic19from . import tables20import inspect21_tables = []22tables_dict = vars(tables)23for obj in tables_dict.values():24    if obj == tables.Table:25        continue26    if inspect.isclass(obj) and isinstance(obj, piccolo.table.TableMetaclass):27        _tables.append(obj)28app = FastAPI(29    routes=[30        Mount(31            "/admin/",32            create_admin(33                tables=_tables,34                site_name="BRC Admin",35                production=True,36                # Required when running under HTTPS:37                allowed_hosts=['catnip.metrobots.xyz']38            ),39        ),40    ],41)42with open("site.html") as site:43    site_html = site.read()44bot = commands.Bot(intents=discord.Intents.all(), command_prefix="%")45with open("secrets.json") as f:46    secrets = json.load(f)47    secrets["gid"] = int(secrets["gid"])48    secrets["reviewer"] = int(secrets["reviewer"])49    secrets["queue_channel"] = int(secrets["queue_channel"])50@app.on_event("startup")51async def open_database_connection_pool():52    engine = engine_finder()53    asyncio.create_task(bot.start(secrets["token"]))54    await bot.load_extension("jishaku")55    await engine.start_connnection_pool()56@app.on_event("shutdown")57async def close_database_connection_pool():58    engine = engine_finder()59    await engine.close_connnection_pool()60@app.get("/list/{id}")61async def get_list(id: uuid.UUID):62    return await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.description, tables.BotList.domain, tables.BotList.state, tables.BotList.icon).where(tables.BotList.id == id).first()63@app.get("/lists")64async def get_all_lists():65    return await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.description, tables.BotList.domain, tables.BotList.state, tables.BotList.icon).order_by(tables.BotList.id, ascending=True)66class BotPost(pydantic.BaseModel):67    bot_id: str68    username: str69    banner: str | None = None70    description: str 71    long_description: str72    website: str | None = None73    invite: str | None = None74    owner: str75    extra_owners: list[str]76    support: str | None = None77    donate: str | None = None78    library: str | None = None79    nsfw: bool | None = False80    prefix: str | None = None81    tags: list[str] | None = None82    review_note: str | None = None83    cross_add: bool | None = True84class Bot(BotPost):85    state: tables.State86    list_source: uuid.UUID87    added_at: datetime.datetime88    reviewer: str | None = None89    invite_link: str | None = None90class ListUpdate(pydantic.BaseModel):91    name: str | None = None92    description: str | None = None93    domain: str | None = None94    claim_bot_api: str | None = None95    unclaim_bot_api: str | None = None96    approve_bot_api: str | None = None97    deny_bot_api: str | None = None98    reset_secret_key: bool = False99    icon: str | None = None100auth_header = APIKeyHeader(name='Authorization')101@app.patch("/lists/{list_id}")102async def update_list(list_id: uuid.UUID, update: ListUpdate, auth: str = Depends(auth_header)):103    if (auth := await _auth(list_id, auth)):104        return auth 105    106    has_updated = []107    if update.name:108        await tables.BotList.update(name=update.name).where(tables.BotList.id == list_id)109        has_updated.append("name")110    if update.description:111        await tables.BotList.update(name=update.name).where(tables.BotList.id == list_id)112        has_updated.append("description")113    if update.icon:114        if update.icon.startswith("https://"):115            await tables.BotList.update(icon=update.icon).where(tables.BotList.id == list_id)116            has_updated.append("icon")117    if update.claim_bot_api:118        await tables.BotList.update(claim_bot_api=update.claim_bot_api).where(tables.BotList.id == list_id)119        has_updated.append("claim_bot_api")120    if update.unclaim_bot_api:121        await tables.BotList.update(unclaim_bot_api=update.unclaim_bot_api).where(tables.BotList.id == list_id)122        has_updated.append("unclaim_bot_api")123    if update.approve_bot_api:124        await tables.BotList.update(approve_bot_api=update.approve_bot_api).where(tables.BotList.id == list_id)125        has_updated.append("approve_bot_api")126    if update.deny_bot_api:127        await tables.BotList.update(deny_bot_api=update.deny_bot_api).where(tables.BotList.id == list_id)128        has_updated.append("deny_bot_api")129    if update.domain:130        # Remove trailing /131        if update.domain.endswith("/"):132            update.domain = update.domain[:-1]133        await tables.BotList.update(domain=update.domain).where(tables.BotList.id == list_id)134        has_updated.append("domain")135    136    if has_updated and update.reset_secret_key:137        return ORJSONResponse({"error": "Cannot reset secret key while updating other fields"}, status_code=400)138    elif update.reset_secret_key:139        key = _secrets.token_urlsafe()140        await tables.BotList.update(secret_key=key).where(tables.BotList.id == list_id)141        return {"secret_key": key}142    return {"has_updated": has_updated}143@app.get("/bots/{id}", response_model=Bot)144async def get_bot(id: int) -> Bot:145    return await tables.BotQueue.select().where(tables.BotQueue.bot_id == id).first()146@app.get("/bots", response_model=list[Bot])147async def get_bots() -> list[Bot]:148    return await tables.BotQueue.select().order_by(tables.BotQueue.bot_id, ascending=True)149@app.get("/team")150async def our_team():151    guild = bot.get_guild(int(secrets["gid"]))152    if not guild:153        return {"detail": "Guild not found"}154    155    team = []156    for member in guild.members:157        if member.id in []:158            continue159        list_roles = []160        is_list_owner = False161        sudo = False162        is_reviewer = False163        for role in member.roles:164            if "list" in role.name.lower() and not role.name.lower().startswith("list"):165                list_roles.append(role.name)166            if role.id == int(secrets["list_owner"]):167                is_list_owner = True168            elif role.id == int(secrets["sudo"]):169                sudo = True170            elif role.id == int(secrets["reviewer"]):171                is_reviewer = True172        if is_reviewer:173            team.append({174                "username": member.name, 175                "id": str(member.id), 176                "avatar": member.avatar.url,177                "is_list_owner": is_list_owner,178                "sudo": sudo,179                "roles": list_roles180            })181    return team182class Action(pydantic.BaseModel):183    id: int184    bot_id: str185    action: tables.Action186    reason: str187    reviewer: str 188    action_time: datetime.datetime189    list_source: uuid.UUID190@app.get("/actions", response_model=list[Action])191async def get_actions(offset: int = 0, limit: int = 50) -> list[Action]:192    """193Returns a list of review action (such as claim bot, unclaim bot, approve bot and deny bot etc.)194``list_source`` will not be present in all cases.195**This is purely to allow Metro Review lists to debug their code**196Paginated using ``limit`` (how many rows to return at maximum) and ``offset`` (how many rows to skip). Maximum limit is 200197    """198    if limit > 200:199        return []200    return await tables.BotAction.select().limit(limit).offset(offset).order_by(tables.BotAction.action_time, ascending=False)201    202good_states = (tables.ListState.PENDING_API_SUPPORT, tables.ListState.SUPPORTED)203async def _auth(list_id: uuid.UUID, key: str) -> ORJSONResponse | None:204    list = await tables.BotList.select(tables.BotList.secret_key, tables.BotList.state).where(tables.BotList.id == list_id).first()205    if not list:206        return ORJSONResponse({"error": "List not found"}, status_code=404)207    if key != list["secret_key"]:208        return ORJSONResponse({"error": "Invalid secret key"}, status_code=401)209    210    if list["state"] not in good_states:211        return ORJSONResponse({"error": "List blacklisted, defunct or in an unknown state"}, status_code=401)212emotes = {213    "id": "<:idemote:912034927443320862>",214    "bot": "<:bot:970349895829561420>",215    "crown": "<:owner:912356178833596497>",216    "invite": "<:plus:912363980490702918>",217    "note": "<:activity:912031377422172160>" 218}219@app.post("/bots")220async def post_bots(request: Request, _bot: BotPost, list_id: uuid.UUID, auth: str = Depends(auth_header)):221    """222All optional fields are actually *optional* and does not need to be posted223``extra_owners`` should be a empty list if you do not support it224    """225    if (auth := await _auth(list_id, auth)):226        return auth 227    228    rem = []229    230    try:231        bot_id = int(_bot.bot_id)232        owner = int(_bot.owner)233        try:234            extra_owners = [int(v) for v in _bot.extra_owners]235        except:236            extra_owners = []237            rem.append("extra_owners")238    except:239        return ORJSONResponse({"error": "Invalid bot fields"}, status_code=400)240   241    if owner in extra_owners:242        flag = True243        while flag:244            try:245                extra_owners.remove(owner)246            except:247                flag = False248    extra_owners = list(set(extra_owners))249    if _bot.banner:250        if not _bot.banner.startswith("https://"):251            _bot.banner = bot.banner.replace("http://", "https://")252            if not _bot.banner.startswith("https://"):253                # We tried working around it, now we just remove the bad banner254                _bot.banner = None255                rem.append("banner")256    if _bot.website:257        if not _bot.website.startswith("https://"):258            _bot.website = _bot.website.replace("http://", "https://")259            if not _bot.website.startswith("https://"):260                # We tried working around it, now we just remove the bad website261                _bot.website = None262                rem.append("website")263    264    if _bot.support:265        if not _bot.support.startswith("https://"):266            _bot.support = _bot.support.replace("http://", "https://")267            if not _bot.support.startswith("https://"):268                # We tried working around it, now we just remove the bad support269                _bot.support = None270                rem.append("support")271    272    # Ensure add bot across lists *works*273    if _bot.tags:274        _bot.tags = [tag.lower() for tag in _bot.tags]275        if "utility" not in _bot.tags:276            _bot.tags.append("utility")277    else:278        _bot.tags = ["utility"]279    curr_bot = await tables.BotQueue.select(tables.BotQueue.bot_id).where(tables.BotQueue.bot_id == bot_id)280    if len(curr_bot) > 0:281        print("Bot already exists")282        return ORJSONResponse({"error": "Bot already in queue"}, status_code=409)283    if _bot.invite and not _bot.invite.startswith("https://"):284        # Just remove bad invite285        _bot.invite = None286        rem.append("invite")287    await tables.BotQueue.insert(288        tables.BotQueue(289            bot_id=bot_id, 290            username=_bot.username, 291            banner=_bot.banner,292            list_source=list_id,293            description=_bot.description,294            long_description=_bot.long_description,295            website=_bot.website,296            invite=_bot.invite,297            owner=owner,298            support=_bot.support,299            donate=_bot.donate,300            library=_bot.library,301            nsfw=_bot.nsfw,302            prefix=_bot.prefix,303            tags=_bot.tags,304            review_note=_bot.review_note,305            extra_owners=extra_owners,306            cross_add=_bot.cross_add,307            state=tables.State.PENDING308        )309    )310    if not _bot.invite:311        invite = f"https://discordapp.com/oauth2/authorize?client_id={bot_id}&scope=bot%20applications.commands&permissions=0"312    else:313        invite = _bot.invite314    # TODO: Add bot add propogation in final scope plans if this is successful315    embed = discord.Embed(url=f"https://metrobots.xyz/bots/{bot_id}", title="Bot Added To Queue", description=f"{emotes['id']} {bot_id}\n{emotes['bot']} {_bot.username}\n{emotes['crown']} {_bot.owner} (<@{_bot.owner}>)\n{emotes['invite']} [Invite]({invite})\n{emotes['note']} {_bot.review_note or 'No review notes for this bot'}", color=discord.Color.green())316    c = bot.get_channel(secrets["queue_channel"])317    await c.send(f"<@&{secrets['test_ping_role'] or secrets['reviewer']}>", embed=embed)318    return {"removed": rem}319class PrefixSupport(discord.ui.View):320    def __init__(self, modal: discord.ui.Modal):321        self.modal = modal()322        super().__init__(timeout=180)323    @discord.ui.button(label="Click Here")324    async def click_here(self, interaction: discord.Interaction, _: discord.ui.Button):325        await interaction.response.send_modal(self.modal)326# Decorator to auto add legacy prefix support327def mr_command(modal_class):328    def wrapper(f):329        @bot.command(name=f.__name__, help=f.__doc__)330        async def _(ctx: commands.Context):331            await ctx.send("To continue, click the below button", view=PrefixSupport(modal_class))332        return f333    return wrapper334class FSnowflake():335    """Blame discord"""336    def __init__(self, id):337        self.id: int = id338@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))339async def invite(interaction: discord.Interaction, bot_id: str):340    if not bot_id.isdigit():341        return await interaction.response.send_message("Invalid bot id")342    return await interaction.response.send_message(f"https://discord.com/oauth2/authorize?client_id={bot_id}&scope=bot%20applications.commands&permissions=0")343@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))344async def queue(interaction: discord.Interaction, show_all: bool = False):345    """Bot queue"""346    states = {}347    for state in list(tables.State):348        if not show_all and state not in (tables.State.PENDING, tables.State.UNDER_REVIEW):349            continue350        states[state] = await tables.BotQueue.select(tables.BotQueue.bot_id, tables.BotQueue.username).where(tables.BotQueue.state == state)351    msg = []352    msg_index = -1353    for key, bots in states.items():354        if not len(bots):355            continue356        msg.append(f"**{key.name} ({len(bots)})**\n\n")357        msg_index += 1358        for bot in bots:359            if len(msg) > 1900:360                msg.append("")361                msg_index += 1362            msg[msg_index] += f"{bot['bot_id']} ({bot['username']})\n"363    await interaction.response.send_message(msg[0])364    for msg in msg[1:]:365        await interaction.followup.send(msg)366@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))367async def sync(interaction: discord.Interaction):368    """Syncs all commands"""369    await bot.tree.sync(guild=FSnowflake(id=secrets["gid"]))370    return await interaction.response.send_message("Done syncing")371@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))372async def support(interaction: discord.Interaction):373    """Show the link to support"""374    await interaction.response.send_message("https://github.com/MetroReviews/support")375async def post_act(376    interaction: discord.Interaction, 377    list_info: dict, 378    action: tables.Action,379    key: str,380    bot_id: int,381    reason: str,382    resend: bool = False383):384    if len(reason) < 5:385        return await interaction.response.send_message("Reason too short (5 characters minimum)")386    if not interaction.guild:387        return388   389    if interaction.guild.id != secrets["gid"]:390        guild = bot.get_guild(secrets["gid"])391        try:392            roles = guild.get_member(interaction.user.id).roles393        except Exception as exc:394            return await interaction.response.send_message(f"You must have the `Reviewer` role to use this command. {exc}")395    else:396        roles = interaction.user.roles397    if not discord.utils.get(roles, id=secrets["reviewer"]):398        return await interaction.response.send_message("You must have the `Reviewer` role to use this command.")399    400    bot_data = await tables.BotQueue.select().where(tables.BotQueue.bot_id == bot_id).first()401    if not bot_data:402        return await interaction.response.send_message(f"This bot (`{bot_id}`) cannot be found")        403    if not resend:404        if action == tables.Action.CLAIM and bot_data["state"] != tables.State.PENDING:405            return await interaction.response.send_message("This bot cannot be claimed as it is not pending review? Maybe someone is testing it right noe?")406        elif action == tables.Action.UNCLAIM and bot_data["state"] != tables.State.UNDER_REVIEW:407            return await interaction.response.send_message("This bot cannot be unclaimed as it is not under review?")408        elif action == tables.Action.APPROVE and bot_data["state"] != tables.State.UNDER_REVIEW:409            return await interaction.response.send_message("This bot cannot be approved as it is not under review?")410        elif action == tables.Action.DENY and bot_data["state"] != tables.State.UNDER_REVIEW:411            return await interaction.response.send_message("This bot cannot be denied as it is not under review?")412    if action == tables.Action.CLAIM:413        cls = tables.State.UNDER_REVIEW414        if not resend:415            await tables.BotQueue.update(reviewer=interaction.user.id).where(tables.BotQueue.bot_id == bot_id)416            417            # Make new server using discord api418            try:419                created_guild = await bot.create_guild(name=f"{bot_id} testing")420                channel = await created_guild.create_text_channel(name="do-not-delete-this-channel")421                invite = await channel.create_invite(reason="Bot Reviewer invite")422                await interaction.channel.send(f"**{interaction.user.mention}\nPlease join the following server to test the bot. If you do not do so within 1 minute (will increase, just for testing), this server will be deleted and the bot will be unclaimed!**\n\n{invite.url}\n\nYou can add the bot to the server using this link: https://discord.com/oauth2/authorize?client_id={bot_id}&permissions=0&guild_id={created_guild.id}&scope=bot%20applications.commands&disable_guild_select=true")423                await tables.BotQueue.update(invite_link=invite.url).where(tables.BotQueue.bot_id == bot_id)424                async def _task(guild: discord.Guild, bot_id: int):425                    await asyncio.sleep(60)426                    cached_guild = bot.get_guild(guild.id)427                    if not cached_guild:428                        return # All good429                    if not cached_guild.owner_id:430                        return await _task(guild, bot_id)431                    if cached_guild.owner_id == bot.user.id:432                        # Then the user has not joined the server in time433                        await tables.BotQueue.update(invite_link=None).where(tables.BotQueue.bot_id == bot_id)434                        try:435                            await guild.delete()436                        except discord.errors.Forbidden:437                            return438                        except Exception as exc:439                            print(f"Failed to delete guild: {exc}")440                        await interaction.channel.send(content=f"**{interaction.user.mention}\nYou have not joined the server in time. The bot will be unclaimed and the server has been deleted!**")441                        await tables.BotQueue.update(state=tables.State.PENDING, reviewer=None).where(tables.BotQueue.bot_id == bot_id)442                asyncio.create_task(_task(created_guild, bot_id))443                print("Got here")444            except Exception as exc:445                return await interaction.response.send_message(f"Failed to create new server for testing: {exc}")446    elif action == tables.Action.UNCLAIM:447        cls = tables.State.PENDING448    elif action == tables.Action.APPROVE:449        cls = tables.State.APPROVED450    elif action == tables.Action.DENY:451        cls = tables.State.DENIED452    await tables.BotQueue.update(state=cls).where(tables.BotQueue.bot_id == bot_id)453    msg = f"**{action.name.title()}!**\n"454    await interaction.response.defer()455    for list in list_info:456        if list["state"] not in good_states:457            continue458        if list["id"] != bot_data["list_source"] and not bot_data["cross_add"]:459            # Send limited 460            data = {461                "bot_id": str(bot_data["bot_id"]), 462                "owner": str(bot_data["owner"]), 463                "extra_owners": bot_data["extra_owners"],464                "cross_add": False,465                "prefix": None, # Dont send prefix466                "description": "", # Dont send description467                "long_description": "", # Dont send ld468                "list_source": bot_data["list_source"],469                "nsfw": True, # Dont send nsfw470                "tags": ["this-shouldnt-be-set"], # Dont send tags471                "username": bot_data["username"], # Username is needed for approve 472                "added_at": bot_data["added_at"], 473            }474        else:475            data = bot_data476        try:477            async with aiohttp.ClientSession() as sess:478                async with sess.post(479                    list[key], 480                    headers={"Authorization": list["secret_key"], "User-Agent": "Frostpaw/0.1"}, 481                    json=data | {482                        "bot_id": str(bot_data["bot_id"]), 483                        "owner": str(bot_data["owner"]), 484                        "reason": reason or "STUB_REASON", 485                        "reviewer": str(interaction.user.id), 486                        "added_at": str(bot_data["added_at"]), 487                        "list_source": str(bot_data["list_source"]),488                        "owner": str(bot_data["owner"]),489                        "extra_owners": [str(v) for v in bot_data["extra_owners"]]490                    }491                ) as resp:492                    msg += f"{list['name']} -> {resp.status}"493                    try:494                        json_d = await resp.text()495                        if resp.headers.get("content-type", "").startswith("application/json"):496                            json_d = orjson.loads(json_d)497                    except Exception as exc:498                        json_d = f"JSON deser failed {exc}"499                    msg += f" ({json_d})\n"500        except Exception as exc:501            msg += f"{list['name']} -> {type(exc).__name__}: {exc}\n"502    503    # Post intent to actions504    await tables.BotAction.insert(505        tables.BotAction(bot_id=bot_id, reason=reason, reviewer=str(interaction.user.id), action=action, action_time=datetime.datetime.now(), list_source=bot_data["list_source"])506    )507    508    embed = discord.Embed(title="Bot Info", description=f"**Bot ID**: {bot_id}\n\n**Reason:** {reason}", color=discord.Color.green())509    class ResendView(discord.ui.View):510        def __init__(self, *args, **kwargs):511            super().__init__(timeout=120)512        513        @discord.ui.button(label="Resend")514        async def resend(self, interaction: discord.Interaction, _: discord.ui.Button):515            return await post_act(516                interaction, 517                list_info, 518                action,519                key,520                bot_id,521                reason,522                resend = True523            )524    await interaction.followup.send(msg, embeds=[embed], view=ResendView())525class Claim(discord.ui.Modal, title='Claim Bot'):526    bot_id = discord.ui.TextInput(label='Bot ID')527    resend = discord.ui.TextInput(label='Resend to other lists (owner only, T/F)', default="F")528    async def on_submit(self, interaction: discord.Interaction):529        try:530            bot_id = int(self.bot_id.value)531        except:532            return await interaction.response.send_message("Bot ID invalid") # Don't respond, so it gives error on their side533        534        list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.claim_bot_api, tables.BotList.secret_key)535        resend = self.resend.value.lower() in ("t", "true")536        if resend and interaction.user.id not in secrets["owners"]:537            return await interaction.response.send_message("You are not an owner")538        return await post_act(interaction, list_info, tables.Action.CLAIM, "claim_bot_api", bot_id, "STUB_REASON", resend=resend)539class Unclaim(discord.ui.Modal, title='Unclaim Bot'):540    bot_id = discord.ui.TextInput(label='Bot ID')541    reason = discord.ui.TextInput(label='Reason', style=discord.TextStyle.paragraph, max_length=4000, min_length=5)542    resend = discord.ui.TextInput(label='Resend to other lists (owner only, T/F)', default="F")543    async def on_submit(self, interaction: discord.Interaction):544        try:545            bot_id = int(self.bot_id.value)546        except:547            return await interaction.response.send_message("Bot ID invalid") # Don't respond, so it gives error on their side548        list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.unclaim_bot_api, tables.BotList.secret_key)549        resend = self.resend.value.lower() in ("t", "true")550        if resend and interaction.user.id not in secrets["owners"]:551            return await interaction.response.send_message("You are not an owner")552        return await post_act(interaction, list_info, tables.Action.UNCLAIM, "unclaim_bot_api", bot_id, self.reason.value, resend=resend)553class Approve(discord.ui.Modal, title='Approve Bot'):554    bot_id = discord.ui.TextInput(label='Bot ID')555    reason = discord.ui.TextInput(label='Reason', style=discord.TextStyle.paragraph, max_length=4000, min_length=5)556    resend = discord.ui.TextInput(label='Resend to other lists (owner only, T/F)', default="F")557    async def on_submit(self, interaction: discord.Interaction):558        try:559            bot_id = int(self.bot_id.value)560        except:561            return await interaction.response.send_message("Bot ID invalid") # Don't respond, so it gives error on their side562        list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.approve_bot_api, tables.BotList.secret_key)563        resend = self.resend.value.lower() in ("t", "true")564        if resend and interaction.user.id not in secrets["owners"]:565            return await interaction.response.send_message("You are not an owner")566        return await post_act(interaction, list_info, tables.Action.APPROVE, "approve_bot_api", bot_id, self.reason.value, resend=resend)567class Deny(discord.ui.Modal, title='Deny Bot'):568    bot_id = discord.ui.TextInput(label='Bot ID')569    reason = discord.ui.TextInput(label='Reason', style=discord.TextStyle.paragraph, max_length=4000, min_length=5)570    resend = discord.ui.TextInput(label='Resend to other lists (owner only, T/F)', default="F")571    async def on_submit(self, interaction: discord.Interaction):572        try:573            bot_id = int(self.bot_id.value)574        except:575            return await interaction.response.send_message("Bot ID invalid") # Don't respond, so it gives error on their side576        list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.deny_bot_api, tables.BotList.secret_key)577        resend = self.resend.value.lower() in ("t", "true")578        if resend and interaction.user.id not in secrets["owners"]:579            return await interaction.response.send_message("You are not an owner")580        return await post_act(interaction, list_info, tables.Action.DENY, "deny_bot_api", bot_id, self.reason.value, resend=resend)581@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))582@mr_command(modal_class=Claim)583async def claim(interaction: discord.Interaction):584    """Claim a bot"""585    return await interaction.response.send_modal(Claim())586@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))587@mr_command(modal_class=Unclaim)588async def unclaim(interaction: discord.Interaction):589    """Unclaims a bot"""590    return await interaction.response.send_modal(Unclaim())591@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))592@mr_command(modal_class=Approve)593async def approve(interaction: discord.Interaction):594    """Approves a bot"""595    return await interaction.response.send_modal(Approve())596@bot.tree.command(guild=FSnowflake(id=secrets["gid"]))597@mr_command(modal_class=Deny)598async def deny(interaction: discord.Interaction):599    """Denies a bot"""600    return await interaction.response.send_modal(Deny())601@bot.event602async def on_ready():603    print("Client is now ready and up")604    await bot.tree.sync()605    await bot.tree.sync(guild=FSnowflake(id=secrets["gid"]))606    for cmd in bot.tree.walk_commands():607        print(cmd.name)608    609    for guild in bot.guilds:610        if guild.owner_id == bot.user.id:611            # Transfer ownership or delete612            if len(guild.members) == 1:613                await guild.delete()614            else:615                try:616                    bot_id = int(guild.name.split(" ")[0])617                    c = await guild.create_text_channel("never-remove-this")618                    invite = await c.create_invite()619                    await tables.BotQueue.update(invite_link=invite.url).where(tables.BotQueue.bot_id == bot_id)620                except Exception as e:621                    print(e)622                await guild.edit(owner=guild.members[0])623                await guild.leave()624@bot.event625async def on_member_join(member: discord.Member):626    if member.guild.owner_id == bot.user.id:627        # This is a bot owned guild, transfer ownership and leave628        await member.guild.edit(owner=member)629        await member.guild.leave()630# Panel code631class FakeResponse():632    def __init__(self, ws: WebSocket):633        self.ws = ws634    async def send_message(self, 635        content, 636        *, 637        embed = None,638        embeds = [],639        **_,640    ):641        if embed:642            embeds.append(embed)643        await self.ws.send_json({"content": content, "embeds": [e.to_dict() for e in embeds]})644    async def defer(self, *args, **kwargs):645        await self.ws.send_json({"defer": True})646class FakeState:647    ...648class FakeWs():649    def __init__(self):650        self.resp = []651        self.state = FakeState()652    653    async def send_json(self, data):654        self.resp.append(data)655class FakeChannel():656    def __init__(self, ws: WebSocket):657        self.ws = ws658        self.id = 0659    async def send(self, 660        content, 661        *, 662        embed = None,663        embeds = [],664        **_,665    ):666        if embed:667            embeds.append(embed)668        await self.ws.send_json({"content": content, "embeds": [e.to_dict() for e in embeds]})669class FakeUser():670    def __init__(self, ws: WebSocket, id: int):671        self.ws = ws672        self.id = id673        self.roles = [FSnowflake(id=secrets["reviewer"])]674        self.mention = f"<@{id}>"675class FakeInteraction():676    def __init__(self, ws: WebSocket):677        self.response = FakeResponse(ws)678        self.followup = FakeChannel(ws)679        self.channel = FakeChannel(ws)680        self.user = FakeUser(ws, ws.state.user["user_id"])681        self.guild_id = secrets["gid"]682        self.guild = FSnowflake(id=secrets["gid"])683@app.get("/littlecloud/{bot_id}")684async def reapprove_bot(bot_id: int):685    list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.approve_bot_api, tables.BotList.secret_key)686    687    bot = await tables.BotQueue.select(tables.BotQueue.state).where(tables.BotQueue.bot_id == bot_id).first()688    if not bot or bot["state"] != tables.State.APPROVED:689        return HTMLResponse("Bot is not approved and cannot be reapproved!")690    ws = FakeWs()691    ws.state.user = {692        "user_id": 968734728465289248 # Reapprove as system693    }694    await post_act(FakeInteraction(ws), list_info, tables.Action.APPROVE, "approve_bot_api", bot_id, "Already approved, readding due to errors (Automated Action)", resend=True)695    return ws.resp696@app.get("/_panel/strikestone", tags=["Panel (Internal)"])697def get_oauth2():698    return ORJSONResponse({"url": f"https://discord.com/api/oauth2/authorize?client_id={bot.application_id}&permissions=0&scope=identify%20guilds&response_type=code&redirect_uri=https://catnip.metrobots.xyz/_panel/frostpaw"})699class StarClan():700    def __init__(self):701        self.ws_list = []702    703    def add(self, ws: WebSocket):704        self.ws_list.append(ws)705    def remove(self, ws: WebSocket):706        self.ws_list.remove(ws)707sc = StarClan()708class SPL:709    unsuppprted = "U"710    auth_fail = "AF"711    out_of_date = "OD"712    done = "D"713class Reason(pydantic.BaseModel):714    reason: str715@app.post("/bots/{bot_id}/approve")716async def approve_bot(bot_id: int, reviewer: int, reason: Reason, list_id: uuid.UUID, auth: str = Depends(auth_header)):717    if (auth := await _auth(list_id, auth)):718        return auth 719    list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.approve_bot_api, tables.BotList.secret_key)720    721    ws = FakeWs()722    ws.state.user = {723        "user_id": reviewer724    }725    await tables.BotQueue.update(state = tables.State.UNDER_REVIEW).where(tables.BotQueue.bot_id == bot_id)726    await post_act(FakeInteraction(ws), list_info, tables.Action.APPROVE, "approve_bot_api", bot_id, reason.reason)727    return ws.resp728@app.post("/bots/{bot_id}/deny")729async def deny_bot(bot_id: int, reviewer: int, reason: Reason, list_id: uuid.UUID, auth: str = Depends(auth_header)):730    if (auth := await _auth(list_id, auth)):731        return auth732    list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.deny_bot_api, tables.BotList.secret_key)733    ws = FakeWs()734    ws.state.user = {735        "user_id": reviewer736    }737    await tables.BotQueue.update(state = tables.State.UNDER_REVIEW).where(tables.BotQueue.bot_id == bot_id)738    await post_act(FakeInteraction(ws), list_info, tables.Action.DENY, "deny_bot_api", bot_id, reason.reason)739    return ws.resp740@app.websocket("/_panel/starclan")741async def starclan_panel(ws: WebSocket, ticket: str, nonce: str):742    if ws.headers.get("Origin") != "https://metrobots.xyz":743        await ws.accept()744        await ws.send_json({"resp": "spld", "e": SPL.unsuppprted})745        await ws.close(code=4009)746        return747    await ws.accept()748    if nonce != "ashfur-v1":749        await ws.send_json({"resp": "spld", "e": SPL.out_of_date})750        return751    try:752        ws.state.ticket = orjson.loads(urlsafe_b64decode(ticket))753        if "nonce" not in ws.state.ticket.keys() or "user_id" not in ws.state.ticket.keys():754            await ws.send_json({"resp": "spld", "e": SPL.auth_fail, "hint": "Invalid ticket"})755            await ws.close(code=4009)756            return757        758        ws.state.ticket["user_id"] = int(ws.state.ticket["user_id"])759        user = await tables.Users.select().where(tables.Users.user_id==ws.state.ticket["user_id"], tables.Users.nonce==ws.state.ticket["nonce"]).first()760        if not user:761            await ws.send_json({"resp": "spld", "e": SPL.auth_fail})762            await ws.close(code=4009)763            return764        765        ws.state.user = user766    except Exception as exc:767        await ws.send_json({"resp": "spld", "e": SPL.auth_fail, "hint": str(exc)})768        await ws.close(code=4009)769        return770    sc.add(ws)771    try:772        while True:773            data = await ws.receive_json()774            if data["act"] == "claim":775                list_info = await tables.BotList.select(tables.BotList.id, tables.BotList.name, tables.BotList.state, tables.BotList.claim_bot_api, tables.BotList.secret_key)776                await post_act(FakeInteraction(ws), list_info, tables.Action.CLAIM, "claim_bot_api", int(data["id"]), "STUB_REASON")777    except WebSocketDisconnect:778        sc.remove(ws)779        await ws.close(code=4008)780    except Exception as exc:781        print(exc)782        sc.remove(ws)783        await ws.close(code=4009)784@app.get("/_panel/mapleshade", tags=["Panel (Internal)"])785async def get_panel_access(ticket: str):786    try:787        ticket = orjson.loads(urlsafe_b64decode(ticket))788    except:789        return {"access": False}790    if "nonce" not in ticket.keys() or "user_id" not in ticket.keys():791        return {"access": False}792    ticket["user_id"] = int(ticket["user_id"])793    user = await tables.Users.select().where(tables.Users.user_id==ticket["user_id"], tables.Users.nonce==ticket["nonce"]).first()794    if not user:795        return {"access": False}796    guild = bot.get_guild(secrets["gid"])797    if not guild:798        return {"access": False}799    member = guild.get_member(user["user_id"])800    if not member:801        return {"access": False}802    if discord.utils.get(member.roles, id=secrets["reviewer"]):803        return {"access": True}804    return {"access": False}805@app.get("/_panel/frostpaw", tags=["Panel (Internal)"])806async def complete_oauth2(request: Request, code: str):807    payload = {808        'grant_type': 'authorization_code',809        'code': code,810        'client_id': bot.application_id,811        'redirect_uri': "https://catnip.metrobots.xyz/_panel/frostpaw",812        'client_secret': secrets["client_secret"],813    }814    async with aiohttp.ClientSession() as sess:815        async with sess.post("https://discord.com/api/v10/oauth2/token", data=payload) as resp:816            if resp.status != 200:817                return await resp.json()818            data = await resp.json()819            scope = data["scope"].split(" ")820            if "identify" not in scope or "guilds" not in scope:821                return {"error": f"Invalid scopes, got {data['scope']} but have {scope}"}822            823            if data["token_type"] != "Bearer":824                return {"error": f"Invalid token type, got {data['token_type']}"}825            826            # Fetch user info827            async with sess.get(f"https://discord.com/api/v10/users/@me", headers={"Authorization": f"Bearer {data['access_token']}"}) as resp:828                if resp.status != 200:829                    return await resp.json()830                user = await resp.json()831            # Save the token832            nonce = _secrets.token_urlsafe()833            try:834                await tables.Users.insert(835                    tables.Users(836                        user_id=int(user["id"]),837                        nonce=nonce838                    )839                )840            except Exception:841                pass842            old_nonce = await tables.Users.select(tables.Users.nonce).where(tables.Users.user_id == int(user["id"])).first()843            ticket = {844                "nonce": old_nonce["nonce"],845                "user_id": str(user["id"]),846                "username": user["username"], # Ignored during actual auth847                "disc": user["discriminator"],848                "avatar": user["avatar"],849            }850            ticket = urlsafe_b64encode(orjson.dumps(ticket)).decode()851            return RedirectResponse(852                f"https://metrobots.xyz/curlfeather?ticket={ticket}"...tableUpdater.js
Source:tableUpdater.js  
1// if xajax has not yet been initialized, wait a second and try again2// once xajax has been initialized, then install the table command3// handlers.			4installTableUpdater = function() {5	var xjxReady = false;6	try {7		if (xajax) xjxReady = true;8	} catch (e) {9	}10	if (false == xjxReady) {11		setTimeout('installTableUpdater();', 1000);12		return;13	}14	try {15		if ('undefined' == typeof xajax.ext.tables)16			xajax.ext.tables = {};17	} catch (e) {18		xajax.ext = {};19		xajax.ext.tables = {};20	}21	// internal helper functions22	xajax.ext.tables.internal = {};23	xajax.ext.tables.internal.createTable = function(table) {24		if ('string' != typeof (table))25			throw { name: 'TableError', message: 'Invalid table name specified.' }26		var newTable = document.createElement('table');27		newTable.id = table;28		// save the column configuration29		xajax.ext.tables.appendHeader(table + '_header', newTable);30		xajax.ext.tables.appendBody(table + '_body', newTable);31		xajax.ext.tables.appendFooter(table + '_footer', newTable);32		return newTable;33	}34	xajax.ext.tables.internal.createRow = function(objects, id) {35		var row = document.createElement('tr');36		if (null != id)37			row.id = id;38		return row;39	}40	xajax.ext.tables.internal.createCell = function(objects, id) {41		var cell = document.createElement('td');42		if (null != id)43			cell.id = id;44		cell.innerHTML = '...';45		return cell;46	}47	xajax.ext.tables.internal.getColumnNumber = function(objects, cell) {48		var position;49		var columns = objects.header.getElementsByTagName('td');50		for (var column = 0; column < columns.length; ++column)51			if (columns[column].id == cell)52				return column;53		throw { name: 'TableError', message: 'Column not found. (getColumnNumber)' }54		return undefined;55	}56	xajax.ext.tables.internal.objectify = function(params, required) {57		if ('undefined' == typeof params.source)58			return false;59		var source = params.source;60		if ('string' == typeof (source))61			source = xajax.$(source);62		if ('TBODY' == source.nodeName) {63			params.table = source.parentNode;64		} else if ('TABLE' == source.nodeName) {65			params.table = source;66		} else if ('TR' == source.nodeName) {67			params.row = source;68			params.body = source.parentNode;69			params.table = source.parentNode.parentNode;70		} else if ('TD' == source.nodeName) {71			params.cell = source;72			params.row = source.parentNode;73			params.columns = params.row.getElementsByTagName('TD');74			for (var column = 0; 'undefined' == typeof params.column && column < params.columns.length; ++column)75				if (params.cell.id == params.columns[column].id)76					params.column = column;77			params.table = source.parentNode.parentNode.parentNode;78		} else if ('THEAD' == source.nodeName) {79			params.table = source.parentNode;80		} else if ('TFOOT' == source.nodeName) {81			params.table = source.parentNode;82		} else83			params.source = source;84		85		var bodies = params.table.getElementsByTagName('TBODY');86		if (0 < bodies.length)87			params.body = bodies[0];88		var headers = params.table.getElementsByTagName('THEAD');89		if (0 < headers.length)90			params.header = headers[0];91		var feet = params.table.getElementsByTagName('TFOOT');92		if (0 < feet.length)93			params.footer = feet[0];94		if ('undefined' != typeof params.body)95			params.rows = params.body.getElementsByTagName('TR');96		if ('undefined' != typeof params.row)97			params.cells = params.row.getElementsByTagName('TD');98		if ('undefined' != typeof params.header)99			params.columns = params.header.getElementsByTagName('TD');100		101		if ('undefined' == typeof required)102			return true;103		104		for (var index = 0; index < required.length; ++index) {105			var require = required[index];106			var is_defined = false;107			eval('is_defined = (undefined != params.' + require + ');');108			if (false == is_defined)109				throw { name: 'TableError', message: 'Unable to locate required object [' + require + '].' };110		}111		112		return true;113	}114	// table115	xajax.ext.tables.append = function(table, parent) {116		if ('string' == typeof (parent))117			parent = xajax.$(parent);118		parent.appendChild(xajax.ext.tables.internal.createTable(table));119	}120	xajax.ext.tables.insert = function(table, parent, before) {121		if ('string' == typeof (parent))122			parent = xajax.$(parent);123		if ('string' == typeof (before))124			before = xajax.$(before);125		parent.insertBefore(xajax.ext.tables.internal.createTable(table), before);126	}127	xajax.ext.tables.remove = function(table) {128		var objects = { source: table };129		xajax.ext.tables.internal.objectify(objects, ['table']);130		objects.table.parentNode.removeChild(objects.table);131	}132	xajax.ext.tables.appendHeader = function(id, table) {133		var objects = { source: table };134		xajax.ext.tables.internal.objectify(objects, ['table']);135		if ('undefined' == typeof objects.header) {136			var thead = document.createElement('thead');137			if (null != id)138				thead.id = id;139			objects.header = thead;140			thead.appendChild(xajax.ext.tables.internal.createRow(objects, null));141			if ('undefined' == typeof objects.table.firstChild)142				table.appendChild(thead);143			else144				table.insertBefore(thead, table.firstChild);145		}146	}147	xajax.ext.tables.appendBody = function(id, table) {148		var objects = { source: table };149		xajax.ext.tables.internal.objectify(objects, ['table']);150		if ('undefined' == typeof objects.body) {151			var tbody = document.createElement('tbody');152			if (null != id)153				tbody.id = id;154			objects.body = tbody;155		}156		if ('undefined' != typeof objects.rows) {157			for (var rn = 0; rn < objects.rows.length; ++rn) {158				var row = objects.rows[rn];159				objects.table.removeChild(row);160				objects.body.appendChild(row);161			}162		}163		if ('undefined' != typeof objects.footer)164			objects.table.insertBefore(objects.body, objects.footer);165		else166			objects.table.appendChild(objects.body);167	}168	xajax.ext.tables.appendFooter = function(id, table) {169		var objects = { source: table }170		xajax.ext.tables.internal.objectify(objects, ['table']);171		if ('undefined' == typeof objects.footer) {172			var tfoot = document.createElement('tfoot');173			if (null != id)174				tfoot.id = id;175			objects.footer = tfoot;176			tfoot.appendChild(xajax.ext.tables.internal.createRow(objects, null));177			objects.table.appendChild(tfoot);178		}179	}180	// rows181	xajax.ext.tables.rows = {}182	xajax.ext.tables.rows.internal = {}183	xajax.ext.tables.rows.internal.calculateRow = function(objects, position) {184		if ('undefined' == typeof position)185			throw { name: 'TableError', message: 'Missing row number / id.' }186		if ('undefined' == typeof objects.row)187			if ('undefined' != typeof objects.rows)188				if ('undefined' != typeof objects.rows[position])189					objects.row = objects.rows[position];190		if ('undefined' == typeof objects.row)191			objects.row = xajax.$(position);192		if ('undefined' == typeof objects.row)193			throw { name: 'TableError', message: 'Invalid row number / row id specified.' }194	}195	xajax.ext.tables.rows.append = function(id, table) {196		var objects = { source: table }197		xajax.ext.tables.internal.objectify(objects, ['table', 'body']);198		var row = xajax.ext.tables.internal.createRow(objects, id);199		if ('undefined' != typeof objects.columns) {200			for (var column = 0; column < objects.columns.length; ++column) {201				var cell = xajax.ext.tables.internal.createCell(objects, null);202				cell.innerHTML = '...';203				row.appendChild(cell);204			}205		}206		objects.body.appendChild(row);207	}208	xajax.ext.tables.rows.insert = function(id, source, position) {209		var objects = { source: source }210		xajax.ext.tables.internal.objectify(objects, ['table', 'body']);211		if ('undefined' == typeof objects.row)212			xajax.ext.tables.rows.internal.calculateRow(objects, position);213		var row = xajax.ext.tables.internal.createRow(objects, id);214		if ('undefined' != typeof objects.columns) {215			for (var column = 0; column < objects.columns.length; ++column) {216				var cell = xajax.ext.tables.internal.createCell(objects, null);217				cell.innerHTML = '...';218				row.appendChild(cell);219			}220		}221		objects.body.insertBefore(row, objects.row);222	}223	xajax.ext.tables.rows.replace = function(id, source, position) {224		var objects = { source: source }225		xajax.ext.tables.internal.objectify(objects, ['table', 'body']);226		if ('undefined' == typeof objects.row)227			xajax.ext.tables.rows.internal.calculateRow(objects, position);228		var row = xajax.ext.tables.internal.createRow(objects, id);229		if ('undefined' != typeof objects.columns) {230			for (var column = 0; column < objects.columns.length; ++column) {231				var cell = xajax.ext.tables.internal.createCell(objects, null);232				cell.innerHTML = '...';233				row.appendChild(cell);234			}235		}236		objects.body.insertBefore(row, objects.row);237		objects.body.removeChild(objects.row);238	}239	xajax.ext.tables.rows.remove = function(source, position) {240		var objects = { source: source }241		xajax.ext.tables.internal.objectify(objects, ['table', 'body']);242		if ('undefined' == typeof objects.row)243			xajax.ext.tables.rows.internal.calculateRow(objects, position);244		objects.body.removeChild(objects.row);245	}246	xajax.ext.tables.rows.assignProperty = function(value, source, position, property) {247		var objects = { source: source }248		xajax.ext.tables.internal.objectify(objects, ['table', 'body', 'header']);249		if ('undefined' == typeof objects.row)250			xajax.ext.tables.rows.internal.calculateRow(objects, position);251		if ('undefined' != typeof property)252			eval('objects.row.' + property + ' = value;');253	}254	xajax.ext.tables.rows.assign = function(values, source, position, start_column) {255		var objects = { source: source }256		xajax.ext.tables.internal.objectify(objects, ['table', 'body', 'header']);257		if ('undefined' == typeof objects.row)258			xajax.ext.tables.rows.internal.calculateRow(objects, position);259		if ('undefined' == typeof start_column)260			start_column = 0;261		for (var column = 0; column < values.length; ++column)262			xajax.ext.tables.cells.assign(values[column], objects.row, start_column + column);263	}264	// columns265	xajax.ext.tables.columns = {}266	xajax.ext.tables.columns.internal = {}267	xajax.ext.tables.columns.internal.calculateColumn = function(objects, position) {268		if ('undefined' == typeof position)269			throw { name: 'TableError', message: 'Missing column number / id.' }270		if ('undefined' == typeof objects.column)271			if ('undefined' != typeof objects.columns)272				if ('undefined' != typeof objects.columns[position])273					objects.column = position;274		if ('undefined' == typeof objects.column)275			for (var column = 0; 'undefined' == typeof objects.column && column < objects.columns.length; ++column)276				if (objects.columns[column].id == position)277					objects.column = column;278		if ('undefined' == typeof objects.column)279			throw { name: 'TableError', message: 'Invalid column number / row id specified.' }280	}281	xajax.ext.tables.columns.append = function(column_definition, table) {282		var objects = { source: table }283		xajax.ext.tables.internal.objectify(objects, ['table', 'header', 'body']);284		var cell = xajax.ext.tables.internal.createCell(objects, column_definition.id);285		if ('undefined' != typeof column_definition.name)286			cell.innerHTML = column_definition.name;287		objects.header.firstChild.appendChild(cell);288		if ('undefined' != typeof objects.rows)289			for (var i = 0; i < objects.rows.length; ++i)290				xajax.ext.tables.cells.append({id: null}, objects.rows[i]);291	}292	xajax.ext.tables.columns.insert = function(column_definition, source, position) {293		var objects = { source: source }294		xajax.ext.tables.internal.objectify(objects, ['table', 'header']);295		if ('undefined' == typeof objects.column)296			xajax.ext.tables.columns.internal.calculateColumn(objects, position);297		var column = xajax.ext.tables.internal.createCell(objects, column_definition.id);298		if ('undefined' != typeof column_definition.name)299			column.innerHTML = column_definition.name;300		objects.header.firstChild.insertBefore(column, objects.columns[objects.column]);301		if ('undefined' != typeof objects.rows)302			for (var i = 0; i < objects.rows.length; ++i)303				xajax.ext.tables.cells.insert({id: null}, objects.rows[i], objects.column);304	}305	xajax.ext.tables.columns.replace = function(column_definition, source, position) {306		var objects = { source: source }307		xajax.ext.tables.internal.objectify(objects, ['table', 'header', 'columns']);308		if ('undefined' == typeof objects.column)309			xajax.ext.tables.columns.internal.calculateColumn(objects, position);310		var before = objects.columns[objects.column];311		var column = xajax.ext.tables.internal.createCell(objects, column_definition.id);312		if ('undefined' != typeof column_definition.name)313			column.innerHTML = column_definition.name;314		objects.header.firstChild.insertBefore(column, before);315		objects.header.firstChild.removeChild(before);316		if ('undefined' != typeof objects.rows)317			for (var i = 0; i < objects.rows.length; ++i)318				xajax.ext.tables.cells.replace({id: null}, objects.rows[i], objects.column);319	}320	xajax.ext.tables.columns.remove = function(source, position) {321		var objects = { source: source }322		xajax.ext.tables.internal.objectify(objects, ['table', 'header']);323		if ('undefined' == typeof objects.column)324			xajax.ext.tables.columns.internal.calculateColumn(objects, position);325		objects.header.firstChild.removeChild(objects.columns[objects.column]);326		if ('undefined' != typeof objects.rows)327			for (var i = 0; i < objects.rows.length; ++i)328				xajax.ext.tables.cells.remove(objects.rows[i], objects.column);329	}330	xajax.ext.tables.columns.assign = function(values, source, position, start_row) {331		var objects = { source: source }332		xajax.ext.tables.internal.objectify(objects, ['table', 'cell']);333		if ('undefined' == typeof objects.column)334			xajax.ext.tables.columns.internal.calculateColumn(objects, position);335		for (var row = 0; row < values.length; ++row)336			xajax.ext.tables.cells.assign(values[row], objects.rows[start_row + row], objects.column);337	}338	xajax.ext.tables.columns.assignProperty = function(value, source, position, property) {339		var objects = { source: source }340		xajax.ext.tables.internal.objectify(objects, ['table', 'cell']);341		if ('undefined' == typeof objects.column)342			xajax.ext.tables.columns.internal.calculateColumn(objects, position);343		for (var row = 0; row < objects.rows.length; ++row)344			xajax.ext.tables.cells.assignProperty(value, objects.rows[row], objects.column, property);345	}346	// cells347	xajax.ext.tables.cells = {}348	xajax.ext.tables.cells.internal = {}349	xajax.ext.tables.cells.internal.calculateCell = function(objects, position) {350		if ('undefined' == typeof position)351			throw { name: 'TableError', message: 'Missing cell number / id.' }352		if ('undefined' == typeof objects.cell)353			if ('undefined' != typeof objects.cells)354				if ('undefined' != typeof objects.cells[position])355					objects.cell = objects.cells[position];356		if ('undefined' == typeof objects.cell)357			if ('undefined' != typeof objects.columns)358				for (var column = 0; 'undefined' == typeof objects.cell && column < objects.columns.length; ++column)359					if (objects.columns[column].id == position)360						objects.cell = objects.cells[column];361		if ('undefined' == typeof objects.cell)362			throw { name: 'TableError', message: 'Invalid cell number / id specified.' }363	}364	xajax.ext.tables.cells.append = function(cell_definition, source) {365		var objects = { source: source }366		xajax.ext.tables.internal.objectify(objects, ['table', 'row']);367		var cell = xajax.ext.tables.internal.createCell(objects, cell_definition.id);368		if ('undefined' != typeof cell_definition.name)369			cell.innerHTML = cell_definition.name;370		objects.row.appendChild(cell);371	}372	xajax.ext.tables.cells.insert = function(cell_definition, source, position) {373		var objects = { source: source }374		xajax.ext.tables.internal.objectify(objects, ['table', 'row']);375		if ('undefined' == typeof objects.cell)376			xajax.ext.tables.cells.internal.calculateCell(objects, position);377		var cell = xajax.ext.tables.internal.createCell(objects, cell_definition.id);378		if ('undefined' != typeof cell_definition.name)379			cell.innerHTML = cell_definition.name;380		objects.row.insertBefore(cell, objects.cell);381	}382	xajax.ext.tables.cells.replace = function(cell_definition, source, position) {383		var objects = { source: source }384		xajax.ext.tables.internal.objectify(objects, ['table', 'row']);385		if ('undefined' == typeof objects.cell)386			xajax.ext.tables.cells.internal.calculateCell(objects, position);387		var cell = xajax.ext.tables.internal.createCell(objects, cell_definition.id);388		if ('undefined' != typeof cell_definition.name)389			cell.innerHTML = cell_definition.name;390		objects.row.insertBefore(cell, objects.cell);391		objects.row.removeChild(objects.cell);392	}393	xajax.ext.tables.cells.remove = function(source, position) {394		var objects = { source: source }395		xajax.ext.tables.internal.objectify(objects, ['table', 'row']);396		if ('undefined' == typeof objects.cell)397			xajax.ext.tables.cells.internal.calculateCell(objects, position);398		objects.row.removeChild(objects.cell);399	}400	xajax.ext.tables.cells.assign = function(value, source, position) {401		var objects = { source: source }402		xajax.ext.tables.internal.objectify(objects, ['table', 'row']);403		if ('undefined' == typeof objects.cell)404			xajax.ext.tables.cells.internal.calculateCell(objects, position);405		objects.cell.innerHTML = value;406	}407	xajax.ext.tables.cells.assignProperty = function(value, source, position, property) {408		var objects = { source: source }409		xajax.ext.tables.internal.objectify(objects, ['table', 'row']);410		if ('undefined' == typeof objects.cell)411			xajax.ext.tables.cells.internal.calculateCell(objects, position);412		eval('objects.cell.' + property + ' = value;');413	}414	// command handlers415	// tables416	xajax.command.handler.register('et_at', function(args) {417		args.fullName = 'ext.tables.append';418		xajax.ext.tables.append(args.data, args.id);419		return true;420	});421	xajax.command.handler.register('et_it', function(args) {422		args.fullName = 'ext.tables.insert';423		xajax.ext.tables.insert(args.data, args.id, args.pos);424		return true;425	});426	xajax.command.handler.register('et_dt', function(args) {427		args.fullName = 'ext.tables.remove';428		xajax.ext.tables.remove(args.data);429		return true;430	});431	// rows432	xajax.command.handler.register('et_ar', function(args) {433		args.fullName = 'ext.tables.rows.append';434		xajax.ext.tables.rows.append(args.data, args.id);435		return true;436	});437	xajax.command.handler.register('et_ir', function(args) {438		args.fullName = 'ext.tables.rows.insert';439		xajax.ext.tables.rows.insert(args.data, args.id, args.pos);440		return true;441	});442	xajax.command.handler.register('et_rr', function(args) {443		args.fullName = 'ext.tables.rows.replace';444		xajax.ext.tables.rows.replace(args.data, args.id, args.pos);445		return true;446	});447	xajax.command.handler.register('et_dr', function(args) {448		args.fullName = 'ext.tables.rows.remove';449		xajax.ext.tables.rows.remove(args.id, args.pos);450		return true;451	});452	xajax.command.handler.register('et_asr', function(args) {453		args.fullName = 'ext.tables.rows.assign';454		xajax.ext.tables.rows.assign(args.data, args.id, args.pos);455		return true;456	});457	xajax.command.handler.register('et_asrp', function(args) {458		args.fullName = 'ext.tables.rows.assignProperty';459		xajax.ext.tables.rows.assignProperty(args.data, args.id, args.pos, args.prop);460	});461	// columns462	xajax.command.handler.register('et_acol', function(args) {463		args.fullName = 'ext.tables.columns.append';464		xajax.ext.tables.columns.append(args.data, args.id);465		return true;466	});467	xajax.command.handler.register('et_icol', function(args) {468		args.fullName = 'ext.tables.columns.insert';469		xajax.ext.tables.columns.insert(args.data, args.id, args.pos);470		return true;471	});472	xajax.command.handler.register('et_rcol', function(args) {473		args.fullName = 'ext.tables.columns.replace';474		xajax.ext.tables.columns.replace(args.data, args.id, args.pos);475		return true;476	});477	xajax.command.handler.register('et_dcol', function(args) {478		args.fullName = 'ext.tables.columns.remove';479		xajax.ext.tables.columns.remove(args.id, args.pos);480		return true;481	});482	xajax.command.handler.register('et_ascol', function(args) {483		args.fullName = 'ext.tables.columns.assign';484		xajax.ext.tables.columns.assign(args.data, args.id, args.pos, args.type);485		return true;486	});487	xajax.command.handler.register('et_ascolp', function(args) {488		args.fullName = 'ext.tables.columns.assignProperty';489		xajax.ext.tables.columns.assignProperty(args.data, args.id, args.pos, args.prop);490		return true;491	});492	// cells493	xajax.command.handler.register('et_ac', function(args) {494		args.fullName = 'ext.tables.cells.append';495		xajax.ext.tables.cells.append(args.data, args.id);496		return true;497	});498	xajax.command.handler.register('et_ic', function(args) {499		args.fullName = 'ext.tables.cells.insert';500		xajax.ext.tables.cells.insert(args.data, args.id, args.pos);501		return true;502	});503	xajax.command.handler.register('et_rc', function(args) {504		args.fullName = 'ext.tables.cells.replace';505		xajax.ext.tables.cells.replace(args.data, args.id, args.pos);506	});507	xajax.command.handler.register('et_dc', function(args) {508		args.fullName = 'ext.tables.cells.remove';509		xajax.ext.tables.cells.remove(args.id, args.pos);510		return true;511	});512	xajax.command.handler.register('et_asc', function(args) {513		args.fullName = 'ext.tables.cells.assign';514		xajax.ext.tables.cells.assign(args.data, args.id, args.pos);515		return true;516	});517	xajax.command.handler.register('et_ascp', function(args) {518		args.fullName = 'ext.tables.cells.assignProperty';519		xajax.ext.tables.cells.assignProperty(args.data, args.id, args.pos, args.prop);520		return true;521	});522}...test_common.py
Source:test_common.py  
...30    assert df.equals(tables[0].df)31    filename = os.path.join(testdir, "anticlockwise_table_2.pdf")32    tables = camelot.read_pdf(filename, flavor="stream")33    assert df.equals(tables[0].df)34def test_stream_two_tables():35    df1 = pd.DataFrame(data_stream_two_tables_1)36    df2 = pd.DataFrame(data_stream_two_tables_2)37    filename = os.path.join(testdir, "tabula/12s0324.pdf")38    tables = camelot.read_pdf(filename, flavor="stream")39    assert len(tables) == 240    assert df1.equals(tables[0].df)41    assert df2.equals(tables[1].df)42def test_stream_table_regions():43    df = pd.DataFrame(data_stream_table_areas)44    filename = os.path.join(testdir, "tabula/us-007.pdf")45    tables = camelot.read_pdf(46        filename, flavor="stream", table_regions=["320,460,573,335"]47    )48    assert df.equals(tables[0].df)49def test_stream_table_areas():50    df = pd.DataFrame(data_stream_table_areas)51    filename = os.path.join(testdir, "tabula/us-007.pdf")52    tables = camelot.read_pdf(53        filename, flavor="stream", table_areas=["320,500,573,335"]54    )55    assert df.equals(tables[0].df)56def test_stream_columns():57    df = pd.DataFrame(data_stream_columns)58    filename = os.path.join(testdir, "mexican_towns.pdf")59    tables = camelot.read_pdf(60        filename, flavor="stream", columns=["67,180,230,425,475"], row_tol=1061    )62    assert df.equals(tables[0].df)63def test_stream_split_text():64    df = pd.DataFrame(data_stream_split_text)65    filename = os.path.join(testdir, "tabula/m27.pdf")66    tables = camelot.read_pdf(67        filename,68        flavor="stream",69        columns=["72,95,209,327,442,529,566,606,683"],70        split_text=True,71    )72    assert df.equals(tables[0].df)73def test_stream_flag_size():74    df = pd.DataFrame(data_stream_flag_size)75    filename = os.path.join(testdir, "superscript.pdf")76    tables = camelot.read_pdf(filename, flavor="stream", flag_size=True)77    assert df.equals(tables[0].df)78def test_stream_strip_text():79    df = pd.DataFrame(data_stream_strip_text)80    filename = os.path.join(testdir, "detect_vertical_false.pdf")81    tables = camelot.read_pdf(filename, flavor="stream", strip_text=" ,\n")82    assert df.equals(tables[0].df)83def test_stream_edge_tol():84    df = pd.DataFrame(data_stream_edge_tol)85    filename = os.path.join(testdir, "edge_tol.pdf")86    tables = camelot.read_pdf(filename, flavor="stream", edge_tol=500)87    assert df.equals(tables[0].df)88def test_stream_layout_kwargs():89    df = pd.DataFrame(data_stream_layout_kwargs)90    filename = os.path.join(testdir, "detect_vertical_false.pdf")91    tables = camelot.read_pdf(92        filename, flavor="stream", layout_kwargs={"detect_vertical": False}93    )94    assert df.equals(tables[0].df)95def test_lattice():96    df = pd.DataFrame(data_lattice)97    filename = os.path.join(98        testdir, "tabula/icdar2013-dataset/competition-dataset-us/us-030.pdf"99    )100    tables = camelot.read_pdf(filename, pages="2")101    assert df.equals(tables[0].df)102def test_lattice_table_rotated():103    df = pd.DataFrame(data_lattice_table_rotated)104    filename = os.path.join(testdir, "clockwise_table_1.pdf")105    tables = camelot.read_pdf(filename)106    assert df.equals(tables[0].df)107    filename = os.path.join(testdir, "anticlockwise_table_1.pdf")108    tables = camelot.read_pdf(filename)109    assert df.equals(tables[0].df)110def test_lattice_two_tables():111    df1 = pd.DataFrame(data_lattice_two_tables_1)112    df2 = pd.DataFrame(data_lattice_two_tables_2)113    filename = os.path.join(testdir, "twotables_2.pdf")114    tables = camelot.read_pdf(filename)115    assert len(tables) == 2116    assert df1.equals(tables[0].df)117    assert df2.equals(tables[1].df)118def test_lattice_table_regions():119    df = pd.DataFrame(data_lattice_table_regions)120    filename = os.path.join(testdir, "table_region.pdf")121    tables = camelot.read_pdf(filename, table_regions=["170,370,560,270"])122    assert df.equals(tables[0].df)123def test_lattice_table_areas():124    df = pd.DataFrame(data_lattice_table_areas)...processor.test.ts
Source:processor.test.ts  
1/**2 * Licensed to Cloudera, Inc. under one3 * or more contributor license agreements.  See the NOTICE file4 * distributed with this work for additional information5 * regarding copyright ownership.  Cloudera, Inc. licenses this file6 * to you under the Apache License, Version 2.0 (the7 * "License"); you may not use this file except in compliance8 * with the License.  You may obtain a copy of the License at9 *10 *     http://www.apache.org/licenses/LICENSE-2.011 *12 * Unless required by applicable law or agreed to in writing, software13 * distributed under the License is distributed on an "AS IS" BASIS,14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15 * See the License for the specific language governing permissions and16 * limitations under the License.17 */18import { groupEntities } from './processor';19import { createTables } from '../test/utils';20import { Table } from './entities';21import { IEntity } from './interfaces';22describe('processor UTs', () => {23  test('Multiple unrelated entities (t0, t1, t2, t3, t4)', () => {24    const tableCount = 5;25    const entityGroups: Array<Array<IEntity>> = groupEntities(createTables(tableCount, 0), []);26    expect(entityGroups).toHaveLength(5);27    expect(entityGroups.flat()).toHaveLength(tableCount);28  });29  test('Related entities - Linked list (t0-t1-t2-t3-t4)', () => {30    const tableCount = 5;31    const tables: Array<Table> = createTables(tableCount, 2);32    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [33      {34        desc: '',35        left: tables[0].columns[1],36        right: tables[1].columns[0]37      },38      {39        desc: '',40        left: tables[1].columns[1],41        right: tables[2].columns[0]42      },43      {44        desc: '',45        left: tables[2].columns[1],46        right: tables[3].columns[0]47      },48      {49        desc: '',50        left: tables[3].columns[1],51        right: tables[4].columns[0]52      }53    ]);54    expect(entityGroups).toHaveLength(5);55    expect(entityGroups.flat()).toHaveLength(tableCount);56  });57  test('Related entities - Binary tree (t0-t1, t0-t2, t2-t3, t2-t4)', () => {58    const tableCount = 5;59    const tables: Array<Table> = createTables(tableCount, 3);60    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [61      {62        desc: '',63        left: tables[0].columns[1],64        right: tables[1].columns[0]65      },66      {67        desc: '',68        left: tables[0].columns[2],69        right: tables[2].columns[0]70      },71      {72        desc: '',73        left: tables[2].columns[1],74        right: tables[3].columns[0]75      },76      {77        desc: '',78        left: tables[2].columns[2],79        right: tables[4].columns[0]80      }81    ]);82    expect(entityGroups).toHaveLength(3);83    expect(entityGroups.flat()).toHaveLength(tableCount);84  });85  test('Related entities - Graph (t0-t1, t0-t2, t2-t3, t2-t4, t3-t0, t4-t0)', () => {86    // Adding back links in the above binary tree to simulate graph87    const tableCount = 5;88    const tables: Array<Table> = createTables(tableCount, 3);89    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [90      {91        desc: '',92        left: tables[0].columns[1],93        right: tables[1].columns[0]94      },95      {96        desc: '',97        left: tables[0].columns[2],98        right: tables[2].columns[0]99      },100      {101        desc: '',102        left: tables[2].columns[1],103        right: tables[3].columns[0]104      },105      {106        // Back link - 1107        desc: '',108        left: tables[3].columns[1],109        right: tables[0].columns[0]110      },111      {112        desc: '',113        left: tables[2].columns[2],114        right: tables[4].columns[0]115      },116      {117        // Back link - 2118        desc: '',119        left: tables[4].columns[2],120        right: tables[0].columns[0]121      }122    ]);123    expect(entityGroups).toHaveLength(3);124    expect(entityGroups.flat()).toHaveLength(tableCount);125  });126  test('Related entities - Self relation (t0-t0, t1)', () => {127    const tableCount = 2;128    const tables: Array<Table> = createTables(tableCount, 2);129    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [130      {131        desc: '',132        left: tables[0].columns[1],133        right: tables[0].columns[0]134      }135    ]);136    expect(entityGroups).toHaveLength(2);137    expect(entityGroups.flat()).toHaveLength(tableCount);138  });139  test('Related entities - Self relation + external reference (t0-t0, t0-t1)', () => {140    const tableCount = 2;141    const tables: Array<Table> = createTables(tableCount, 3);142    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [143      {144        desc: '',145        left: tables[0].columns[1],146        right: tables[0].columns[0]147      },148      {149        desc: '',150        left: tables[0].columns[2],151        right: tables[1].columns[0]152      }153    ]);154    expect(entityGroups).toHaveLength(2);155    expect(entityGroups.flat()).toHaveLength(tableCount);156  });157  test('Related entities - Cyclic relation (t0-t1, t1-t0)', () => {158    const tableCount = 2;159    const tables: Array<Table> = createTables(tableCount, 3);160    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [161      {162        desc: '',163        left: tables[0].columns[1],164        right: tables[1].columns[0]165      },166      {167        desc: '',168        left: tables[1].columns[1],169        right: tables[0].columns[0]170      }171    ]);172    expect(entityGroups).toHaveLength(2);173    expect(entityGroups.flat()).toHaveLength(tableCount);174  });175  test('Unrelated entity groups (t0-t1, t2-t3, t2-t4)', () => {176    const tableCount = 5;177    const tables: Array<Table> = createTables(tableCount, 2);178    const entityGroups: Array<Array<IEntity>> = groupEntities(tables, [179      {180        desc: '',181        left: tables[0].columns[1],182        right: tables[1].columns[0]183      },184      {185        desc: '',186        left: tables[2].columns[1],187        right: tables[3].columns[0]188      },189      {190        desc: '',191        left: tables[2].columns[1],192        right: tables[4].columns[0]193      }194    ]);195    expect(entityGroups).toHaveLength(4);196    expect(entityGroups.flat()).toHaveLength(tableCount);197  });...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
