From 852f190fbdac225f525bac7ba98de7ce3750b628 Mon Sep 17 00:00:00 2001 From: Noelle Wang <73260931+No767@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:44:44 -0700 Subject: [PATCH] Entirely remove Tonetags (#183) Sorry for some of the folks that use this feature, but this is a necessary evil. --- README.md | 1 - bot/cogs/tonetags.py | 281 ------------------------ bot/libs/cog_utils/tonetags/__init__.py | 18 -- bot/libs/cog_utils/tonetags/db_utils.py | 131 ----------- bot/libs/cog_utils/tonetags/structs.py | 27 --- bot/libs/cog_utils/tonetags/utils.py | 41 ---- bot/libs/ui/tonetags/__init__.py | 12 - bot/libs/ui/tonetags/embed_entries.py | 85 ------- bot/libs/ui/tonetags/modals.py | 81 ------- bot/libs/ui/tonetags/pages.py | 79 ------- bot/libs/ui/tonetags/structs.py | 28 --- bot/libs/ui/tonetags/views.py | 76 ------- docs/source/index.rst | 1 - docs/source/terms-of-service/tos.rst | 2 +- old_tests/cog_utils/test_tonetags.py | 231 ------------------- 15 files changed, 1 insertion(+), 1093 deletions(-) delete mode 100644 bot/cogs/tonetags.py delete mode 100644 bot/libs/cog_utils/tonetags/__init__.py delete mode 100644 bot/libs/cog_utils/tonetags/db_utils.py delete mode 100644 bot/libs/cog_utils/tonetags/structs.py delete mode 100644 bot/libs/cog_utils/tonetags/utils.py delete mode 100644 bot/libs/ui/tonetags/__init__.py delete mode 100644 bot/libs/ui/tonetags/embed_entries.py delete mode 100644 bot/libs/ui/tonetags/modals.py delete mode 100644 bot/libs/ui/tonetags/pages.py delete mode 100644 bot/libs/ui/tonetags/structs.py delete mode 100644 bot/libs/ui/tonetags/views.py delete mode 100644 old_tests/cog_utils/test_tonetags.py diff --git a/README.md b/README.md index 185f5dc..1171334 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ Catherine-Chan is the all in one tookit for LGBTQ+ folks and allies. Catherine-C - [x] Pride Profile creator (create profiles to let others know about your pronouns and more) - [x] Informative pronouns and LGBTQ+ terms dictionary - [x] Resources for support -- [x] ToneTags support - [x] HRT Measurement Conversion Calculator (consult your doctor first to ensure that it is accurate for you if you need to use this) And so much more! If you want to suggest a feature, please open up a feature requests report on GitHub! diff --git a/bot/cogs/tonetags.py b/bot/cogs/tonetags.py deleted file mode 100644 index 08c638d..0000000 --- a/bot/cogs/tonetags.py +++ /dev/null @@ -1,281 +0,0 @@ -from datetime import timezone -from io import BytesIO -from typing import Optional - -import discord -import orjson -from catherinecore import Catherine -from discord import app_commands -from discord.ext import commands -from libs.cog_utils.tonetags import ( - TonetagInfo, - edit_tonetag, - format_similar_tonetags, - get_exact_and_similar_tonetags, - get_tonetag, - get_tonetag_info, - get_top_tonetags, - parse_tonetag, -) -from libs.ui.tonetags import ( - BareToneTagsPages, - CreateToneTagModal, - EditToneTagModal, - ESToneTagsPages, - SimpleToneTagsPages, - StatsBareToneTagsPages, - ToneTagPages, -) -from libs.ui.tonetags.views import DeleteTagView -from libs.utils import ConfirmEmbed, Embed - -NO_TONETAGS_FOUND = "No tonetags were found" -TONETAG_NOT_FOUND = "The tonetag requested was not found" -INDICATOR_DESCRIPTION = "The indicator to look for. Can be in both forms (/j or j)" - - -class ToneTags(commands.GroupCog, name="tonetags"): - """The to-go cog for tone tags""" - - def __init__(self, bot: Catherine) -> None: - self.bot = bot - self.pool = self.bot.pool - super().__init__() - - async def _build_tonetag_info(self, tonetag: TonetagInfo) -> Embed: - query = """SELECT ( - SELECT COUNT(*) - FROM tonetags second - WHERE (second.uses, second.id) >= (first.uses, first.id) - ) AS rank - FROM tonetags first - WHERE first.id=$1 - """ - rank = await self.pool.fetchrow(query, tonetag["tonetags_id"]) - - owner_id = tonetag["author_id"] - author = self.bot.get_user(owner_id) or (await self.bot.fetch_user(owner_id)) - - embed = Embed() - embed.title = f"/{tonetag['indicator']}" - embed.set_author(name=author.name, icon_url=author.display_avatar.url) - embed.add_field(name="Author", value=f"<@{owner_id}>") - embed.add_field(name="Uses", value=tonetag["uses"]) - if rank is not None: - embed.add_field(name="Rank", value=rank["rank"]) - return embed - - def _build_tonetag_embed(self, tonetag: TonetagInfo) -> Embed: - embed = Embed() - embed.title = f"/{tonetag['indicator']}" - embed.description = tonetag["definition"] - embed.timestamp = tonetag["created_at"].replace(tzinfo=timezone.utc) - embed.set_footer(text="Created At") - return embed - - @app_commands.command(name="get") - @app_commands.describe(indicator=INDICATOR_DESCRIPTION) - async def get(self, interaction: discord.Interaction, indicator: str) -> None: - """Gets a tonetag""" - tonetag = await get_tonetag(indicator, self.pool) - - if isinstance(tonetag, list): - output_str = format_similar_tonetags(tonetag) - await interaction.response.send_message(output_str) - elif tonetag is None: - await interaction.response.send_message(TONETAG_NOT_FOUND) - else: - await interaction.response.send_message( - embed=self._build_tonetag_embed(tonetag) - ) - - @app_commands.command(name="info") - @app_commands.describe(indicator=INDICATOR_DESCRIPTION) - async def info(self, interaction: discord.Interaction, indicator: str) -> None: - """Provides info about the given tonetag""" - - tonetag = await get_tonetag_info(indicator, self.pool) - if tonetag is None: - await interaction.response.send_message(TONETAG_NOT_FOUND) - return - - embed = await self._build_tonetag_info(tonetag) - await interaction.response.send_message(embed=embed) - - @app_commands.command(name="all") - @app_commands.describe(json="Whether to output the tonetags in JSON format") - async def _all( - self, interaction: discord.Interaction, json: Optional[bool] = False - ) -> None: - """Displays all of the tonetags available""" - query = """ - SELECT tonetags_lookup.indicator, tonetags.definition, tonetags.created_at, tonetags.author_id, tonetags.uses, tonetags_lookup.tonetags_id - FROM tonetags_lookup - INNER JOIN tonetags ON tonetags.id = tonetags_lookup.tonetags_id - ORDER BY uses DESC - LIMIT 100; - """ - records = await self.pool.fetch(query) - - if len(records) == 0: - await interaction.response.send_message(NO_TONETAGS_FOUND) - return - - if json: - buffer = BytesIO( - orjson.dumps([dict(row) for row in records], option=orjson.OPT_INDENT_2) - ) - file = discord.File(fp=buffer, filename="tonetags.json") - await interaction.response.send_message(file=file) - return - - pages = ToneTagPages(entries=records, interaction=interaction) - await pages.start() - - @app_commands.command(name="lookup") - @app_commands.describe(indicator=INDICATOR_DESCRIPTION) - async def lookup(self, interaction: discord.Interaction, indicator: str) -> None: - """Looks for the exact and/or similar tonetags to the one given""" - tonetags = await get_exact_and_similar_tonetags(indicator, self.pool) - - if tonetags is None: - await interaction.response.send_message(NO_TONETAGS_FOUND) - return - - pages = ESToneTagsPages(entries=tonetags, interaction=interaction) - await pages.start() - - @app_commands.command(name="top") - async def top(self, interaction: discord.Interaction) -> None: - """Gets the top 100 tonetags by usage""" - top_tonetags = await get_top_tonetags(self.pool) - - if len(top_tonetags) == 0: - await interaction.response.send_message(NO_TONETAGS_FOUND) - return - - pages = StatsBareToneTagsPages(entries=top_tonetags, interaction=interaction) - await pages.start() - - @app_commands.command(name="search") - @app_commands.describe( - indicator="The indicator to search for. Can be in both forms (/j or j)" - ) - async def search(self, interaction: discord.Interaction, indicator: str) -> None: - """Searches for tonetags""" - sql = """ - SELECT tonetags_lookup.indicator, tonetags.author_id, tonetags_lookup.tonetags_id - FROM tonetags_lookup - INNER JOIN tonetags ON tonetags.id = tonetags_lookup.tonetags_id - WHERE tonetags_lookup.indicator % $1 - ORDER BY similarity(tonetags_lookup.indicator, $1) DESC - LIMIT 100; - """ - parsed_indicator = parse_tonetag(indicator) - records = await self.pool.fetch(sql, parsed_indicator) - if records: - pages = SimpleToneTagsPages(entries=records, interaction=interaction) - await pages.start() - else: - await interaction.response.send_message( - "The tonetag requested was not found" - ) - - @app_commands.command(name="list") - @app_commands.describe(user="The user to get all owned tonetags from") - async def list( - self, interaction: discord.Interaction, user: Optional[discord.User] = None - ): - """Lists either your owned tonetags or others""" - query = """ - SELECT tonetags_lookup.indicator, tonetags_lookup.tonetags_id - FROM tonetags_lookup - INNER JOIN tonetags ON tonetags.id = tonetags_lookup.tonetags_id - WHERE tonetags_lookup.author_id = $1 - LIMIT 100; - """ - author_id = user.id if user is not None else interaction.user.id - records = await self.pool.fetch(query, author_id) - if records: - # I'm aware that this doesn't show the user's name and icon - pages = BareToneTagsPages(entries=records, interaction=interaction) - await pages.start() - else: - await interaction.response.send_message("No tonetags found for this user") - - @app_commands.command(name="create") - async def create(self, interaction: discord.Interaction) -> None: - """Creates an new tonetag""" - modal = CreateToneTagModal(interaction, self.pool) - await interaction.response.send_modal(modal) - - @app_commands.command(name="edit") - @app_commands.describe( - indicator="The indicator of the tonetag to edit", - definition="The new definition to use. If left blank, an interactive session will open up.", - ) - async def edit( - self, - interaction: discord.Interaction, - indicator: str, - definition: Optional[str] = None, - ) -> None: - """Edits a tonetag""" - indicator = parse_tonetag(indicator) - if definition is None: - query = """ - SELECT definition - FROM tonetags - WHERE indicator = $1 AND author_id = $2; - """ - old_def = await self.pool.fetchval(query, indicator, interaction.user.id) - if old_def is None: - await interaction.response.send_message( - f"The tonetag `{indicator}` was not found. Please create it first.", - ephemeral=True, - ) - return - modal = EditToneTagModal(interaction, indicator, old_def, self.pool) - await interaction.response.send_modal(modal) - return - - status = await edit_tonetag( - indicator, definition, interaction.user.id, self.pool - ) - if status[-1] != "0": - await interaction.response.send_message( - f"Successfully edited tonetag `{indicator}`" - ) - return - - await interaction.response.send_message( - f"The tonetag `{indicator}` did not get edited" - ) - - @app_commands.command(name="delete") - @app_commands.describe(indicator="The tonetag indicator to delete") - async def delete(self, interaction: discord.Interaction, indicator: str) -> None: - """Deletes a tonetag""" - view = DeleteTagView(interaction, self.pool, indicator=indicator) - embed = ConfirmEmbed() - embed.description = ( - f"Are you sure you want to delete the tonetag `{indicator}`?" - ) - await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - view.original_response = await interaction.original_response() - - @app_commands.command(name="delete-id") - @app_commands.describe(id="The ID of the tonetag") - async def delete_id(self, interaction: discord.Interaction, id: int) -> None: - """Deletes a tonetag via the ID instead""" - view = DeleteTagView(interaction, self.pool, indicator_id=id) - embed = ConfirmEmbed() - embed.description = ( - f"Are you sure you want to delete the tonetag with ID `{id}`?" - ) - await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - view.original_response = await interaction.original_response() - - -async def setup(bot: Catherine) -> None: - await bot.add_cog(ToneTags(bot)) diff --git a/bot/libs/cog_utils/tonetags/__init__.py b/bot/libs/cog_utils/tonetags/__init__.py deleted file mode 100644 index 5d2ddb6..0000000 --- a/bot/libs/cog_utils/tonetags/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from .db_utils import ( - create_tonetag as create_tonetag, - edit_tonetag as edit_tonetag, - get_exact_and_similar_tonetags as get_exact_and_similar_tonetags, - get_tonetag as get_tonetag, - get_tonetag_info as get_tonetag_info, - get_top_tonetags as get_top_tonetags, -) -from .structs import ( - ExactAndSimilarTonetags as ExactAndSimilarTonetags, - SimilarTonetags as SimilarTonetags, - TonetagInfo as TonetagInfo, -) -from .utils import ( - format_similar_tonetags as format_similar_tonetags, - parse_tonetag as parse_tonetag, - validate_tonetag as validate_tonetag, -) diff --git a/bot/libs/cog_utils/tonetags/db_utils.py b/bot/libs/cog_utils/tonetags/db_utils.py deleted file mode 100644 index ac2bfb9..0000000 --- a/bot/libs/cog_utils/tonetags/db_utils.py +++ /dev/null @@ -1,131 +0,0 @@ -from typing import List, Union - -import asyncpg -from libs.cog_utils.commons import register_user - -from .structs import ( - ExactAndSimilarTonetags, - SimilarTonetags, - StatsBareToneTag, - TonetagInfo, -) -from .utils import parse_tonetag - - -async def get_exact_and_similar_tonetags( - indicator: str, pool: asyncpg.Pool -) -> Union[List[ExactAndSimilarTonetags], None]: - query = """ - SELECT indicator, definition - FROM tonetags - WHERE tonetags.indicator % $1 - ORDER BY similarity(indicator, $1) DESC - LIMIT 100; - """ - records = await pool.fetch(query, indicator) - if len(records) == 0: - return None - return [ExactAndSimilarTonetags(record) for record in records] - - -async def get_tonetag_info( - indicator: str, pool: asyncpg.Pool -) -> Union[TonetagInfo, None]: - query = """ - SELECT tonetags_lookup.indicator, tonetags.definition, tonetags.created_at, tonetags.author_id, tonetags.uses, tonetags_lookup.tonetags_id - FROM tonetags_lookup - INNER JOIN tonetags ON tonetags.id = tonetags_lookup.tonetags_id - WHERE LOWER(tonetags_lookup.indicator) = $1; - """ - parsed_tonetag = parse_tonetag(indicator) - result = await pool.fetchrow(query, parsed_tonetag) - if result is None: - return None - return TonetagInfo(result) - - -async def get_tonetag( - indicator: str, pool: asyncpg.Pool -) -> Union[TonetagInfo, List[SimilarTonetags], None]: - query = """ - SELECT tonetags_lookup.indicator, tonetags.definition, tonetags.created_at, tonetags.author_id, tonetags.uses, tonetags_lookup.tonetags_id - FROM tonetags_lookup - INNER JOIN tonetags ON tonetags.id = tonetags_lookup.tonetags_id - WHERE LOWER(tonetags_lookup.indicator) = $1; - """ - update_query = """ - UPDATE tonetags - SET uses = uses + 1 - WHERE id = $1; - """ - parsed_tonetag = parse_tonetag(indicator) - async with pool.acquire() as conn: - first_result = await conn.fetchrow(query, parsed_tonetag) - if first_result is None: - search_query = """ - SELECT tonetags_lookup.indicator - FROM tonetags_lookup - WHERE tonetags_lookup.indicator % $1 - ORDER BY similarity(tonetags_lookup.indicator, $1) DESC - LIMIT 5; - """ - search_result = await conn.fetch(search_query, parsed_tonetag) - if len(search_result) == 0: - return None - return [SimilarTonetags(row) for row in search_result] - - await conn.execute(update_query, first_result["tonetags_id"]) - return TonetagInfo(first_result) - - -async def get_top_tonetags(pool: asyncpg.Pool) -> List[StatsBareToneTag]: - query = """ - SELECT tonetags_lookup.indicator, tonetags.uses, tonetags_lookup.tonetags_id - FROM tonetags_lookup - INNER JOIN tonetags ON tonetags.id = tonetags_lookup.tonetags_id - ORDER BY tonetags.uses DESC - LIMIT 100; - """ - records = await pool.fetch(query) - return [StatsBareToneTag(row) for row in records] - - -async def edit_tonetag( - indicator: str, definition: str, author_id: int, pool: asyncpg.Pool -) -> str: - query = """ - UPDATE tonetags - SET definition = $2 - WHERE indicator = $1 AND author_id = $3; - """ - status = await pool.execute(query, indicator, definition, author_id) - return status - - -async def create_tonetag( - indicator: str, definition: str, author_id: int, pool: asyncpg.Pool -) -> str: - query = """ - WITH tonetag_insert AS ( - INSERT INTO tonetags (indicator, definition, author_id) - VALUES ($1, $2, $3) - RETURNING id - ) - INSERT INTO tonetags_lookup (indicator, author_id, tonetags_id) - VALUES ($1, $3, (SELECT id FROM tonetag_insert)); - """ - async with pool.acquire() as conn: - await register_user(author_id, conn) - tr = conn.transaction() - await tr.start() - - try: - await conn.execute( - query, parse_tonetag(indicator.lower()), definition.lower(), author_id - ) - except asyncpg.UniqueViolationError: - await tr.rollback() - return f"The tonetag `{indicator}` already exists" - else: - await tr.commit() - return f"Tonetag `{indicator}` successfully created" diff --git a/bot/libs/cog_utils/tonetags/structs.py b/bot/libs/cog_utils/tonetags/structs.py deleted file mode 100644 index c48dacf..0000000 --- a/bot/libs/cog_utils/tonetags/structs.py +++ /dev/null @@ -1,27 +0,0 @@ -import datetime -from typing import TypedDict - - -class TonetagInfo(TypedDict): - indicator: str - definition: str - created_at: datetime.datetime - author_id: int - uses: int - tonetags_id: int - - -class SimilarTonetags(TypedDict): - indicator: str - - -class ExactAndSimilarTonetags(TypedDict): - indicator: str - definition: str - - -# Here to avoid circular imports -class StatsBareToneTag(TypedDict): - indicator: str - uses: int - tonetags_id: int diff --git a/bot/libs/cog_utils/tonetags/utils.py b/bot/libs/cog_utils/tonetags/utils.py deleted file mode 100644 index 3ce6f2c..0000000 --- a/bot/libs/cog_utils/tonetags/utils.py +++ /dev/null @@ -1,41 +0,0 @@ -import re -from typing import List, Union - -from .structs import SimilarTonetags - - -def parse_tonetag(tonetag: str) -> str: - """Parses a tonetag properly and sends back the stripped results - - Args: - tonetag (str): Tonetag - - Returns: - str: Parsed and cleaned up result - """ - return re.sub(r"^/", "", tonetag, re.IGNORECASE) - - -def validate_tonetag(tonetag: str) -> bool: - """Validates a tonetag""" - return len(tonetag) < 4 and re.fullmatch(r"^[a-zA-Z]*$", tonetag) is not None - - -def format_similar_tonetags(rows: Union[List[SimilarTonetags], None]) -> str: - """Formats a list of similar tonetags into a string for the user to see - - Parameters - ---------- - rows : Union[List[SimilarTonetags], None] - List of similar tonetags or None - - Returns - ------- - str - Formatted string for the end user - """ - if rows is None or len(rows) == 0: - return "No tonetags found" - - names = "\n".join([row["indicator"] for row in rows]) - return f"Tonetag not found. Did you mean:\n{names}" diff --git a/bot/libs/ui/tonetags/__init__.py b/bot/libs/ui/tonetags/__init__.py deleted file mode 100644 index 137f2bc..0000000 --- a/bot/libs/ui/tonetags/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .modals import ( - CreateToneTagModal as CreateToneTagModal, - EditToneTagModal as EditToneTagModal, -) -from .pages import ( - BareToneTagsPages as BareToneTagsPages, - ESToneTagsPages as ESToneTagsPages, - SimpleToneTagsPages as SimpleToneTagsPages, - StatsBareToneTagsPages as StatsBareToneTagsPages, - ToneTagPages as ToneTagPages, -) -from .views import DeleteTagView as DeleteTagView diff --git a/bot/libs/ui/tonetags/embed_entries.py b/bot/libs/ui/tonetags/embed_entries.py deleted file mode 100644 index d4e296b..0000000 --- a/bot/libs/ui/tonetags/embed_entries.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Any, Dict - -from discord.utils import format_dt -from libs.cog_utils.tonetags import ExactAndSimilarTonetags - -from .structs import BareToneTag, SimpleToneTag, StatsBareToneTag, ToneTagInfo - - -class ToneTagInfoPageEntry: - __slots__ = ["indicator", "definition", "created_at", "author_id", "tonetags_id"] - - def __init__(self, entry: ToneTagInfo): - self.indicator = entry["indicator"] - self.definition = entry["definition"] - self.created_at = entry["created_at"] - self.author_id = entry["author_id"] - self.tonetags_id = entry["tonetags_id"] - - def to_dict(self) -> Dict[str, Any]: - data = { - "title": f"/{self.indicator}", - "description": self.definition, - "fields": [ - {"name": "ID", "value": self.tonetags_id, "inline": True}, - {"name": "Author ", "value": f"<@{self.author_id}>", "inline": True}, - { - "name": "Created At", - "value": format_dt(self.created_at), - "inline": True, - }, - ], - } - return data - - -class SimpleToneTagPageEntry: - __slots__ = ("indicator", "author_id", "tonetags_id") - - def __init__(self, entry: SimpleToneTag): - self.indicator = entry["indicator"] - self.author_id = entry["author_id"] - self.tonetags_id = entry["tonetags_id"] - - def __str__(self) -> str: - return ( - f"/{self.indicator} (ID: {self.tonetags_id}, Creator: <@{self.author_id}>)" - ) - - -class BareToneTagPageEntry: - __slots__ = ("indicator", "tonetags_id") - - def __init__(self, entry: BareToneTag): - self.indicator = entry["indicator"] - self.tonetags_id = entry["tonetags_id"] - - def __str__(self) -> str: - return f"{self.indicator} (ID: {self.tonetags_id})" - - -class ESTonetagsPageEntry: - __slots__ = ("indicator", "definition", "created_at") - - def __init__(self, entry: ExactAndSimilarTonetags): - self.indicator = entry["indicator"] - self.definition = entry["definition"] - - def to_dict(self) -> Dict[str, Any]: - data = { - "title": f"/{self.indicator}", - "description": self.definition, - } - return data - - -class StatsBareToneTagPageEntry: - __slots__ = ("indicator", "uses", "tonetags_id") - - def __init__(self, entry: StatsBareToneTag): - self.indicator = entry["indicator"] - self.uses = entry["uses"] - self.tonetags_id = entry["tonetags_id"] - - def __str__(self) -> str: - return f"{self.indicator} (Uses: {self.uses} | ID: {self.tonetags_id})" diff --git a/bot/libs/ui/tonetags/modals.py b/bot/libs/ui/tonetags/modals.py deleted file mode 100644 index 141588e..0000000 --- a/bot/libs/ui/tonetags/modals.py +++ /dev/null @@ -1,81 +0,0 @@ -from __future__ import annotations - -import asyncpg -import discord -from libs.cog_utils.tonetags import ( - create_tonetag, - edit_tonetag, - parse_tonetag, - validate_tonetag, -) -from libs.utils import CatherineModal - - -class CreateToneTagModal(CatherineModal, title="Create a ToneTag"): - def __init__(self, interaction: discord.Interaction, pool: asyncpg.Pool): - super().__init__(interaction) - self.pool = pool - self.indicator = discord.ui.TextInput( - label="Indicator", - placeholder="Enter the indicator name", - style=discord.TextStyle.short, - min_length=1, - max_length=25, - ) - self.definition = discord.ui.TextInput( - label="Definition", - placeholder="Enter the definition of the tonetag", - style=discord.TextStyle.long, - min_length=1, - max_length=500, - ) - self.add_item(self.indicator) - self.add_item(self.definition) - - async def on_submit(self, interaction: discord.Interaction) -> None: - parsed_tonetag = parse_tonetag(self.indicator.value) - if validate_tonetag(parsed_tonetag) is False: - await interaction.response.send_message("The tonetag is invalid.") - return - - status = await create_tonetag( - self.indicator.value, self.definition.value, interaction.user.id, self.pool - ) - await interaction.response.send_message(status, ephemeral=True) - - -class EditToneTagModal(CatherineModal, title="Edit a ToneTag"): - def __init__( - self, - interaction: discord.Interaction, - indicator: str, - old_definition: str, - pool: asyncpg.Pool, - ): - super().__init__(interaction=interaction) - self.pool = pool - self.indicator = indicator - self.old_definition = old_definition - self.definition = discord.ui.TextInput( - label="Definition", - placeholder="Enter the new definition of the tonetag", - style=discord.TextStyle.long, - min_length=1, - max_length=500, - default=self.old_definition, - ) - self.add_item(self.definition) - - async def on_submit(self, interaction: discord.Interaction) -> None: - parsed_tonetag = parse_tonetag(self.indicator) - status = await edit_tonetag( - parsed_tonetag, self.definition.value, interaction.user.id, self.pool - ) - if status[-1] != "0": - await interaction.response.send_message( - f"Tonetag `{self.indicator}` successfully edited" - ) - else: - await interaction.response.send_message( - f"The requested tonetag `{self.indicator}` was not edited" - ) diff --git a/bot/libs/ui/tonetags/pages.py b/bot/libs/ui/tonetags/pages.py deleted file mode 100644 index 614fc58..0000000 --- a/bot/libs/ui/tonetags/pages.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import List - -import discord -from libs.cog_utils.tonetags import ExactAndSimilarTonetags -from libs.utils.pages import CatherinePages, EmbedListSource, SimplePages - -from .embed_entries import ( - BareToneTagPageEntry, - ESTonetagsPageEntry, - SimpleToneTagPageEntry, - StatsBareToneTagPageEntry, - ToneTagInfoPageEntry, -) -from .structs import BareToneTag, SimpleToneTag, StatsBareToneTag, ToneTagInfo - - -class ToneTagPages(CatherinePages): - def __init__( - self, - entries: List[ToneTagInfo], - *, - interaction: discord.Interaction, - per_page: int = 1, - ): - converted = [ToneTagInfoPageEntry(entry).to_dict() for entry in entries] - super().__init__( - EmbedListSource(converted, per_page=per_page), interaction=interaction - ) - self.embed = discord.Embed(colour=discord.Colour.from_rgb(255, 125, 212)) - - -class ESToneTagsPages(CatherinePages): - def __init__( - self, - entries: List[ExactAndSimilarTonetags], - *, - interaction: discord.Interaction, - ): - converted = [ESTonetagsPageEntry(entry).to_dict() for entry in entries] - super().__init__( - EmbedListSource(converted, per_page=1), interaction=interaction - ) - self.embed = discord.Embed(colour=discord.Colour.from_rgb(255, 125, 212)) - - -class SimpleToneTagsPages(SimplePages): - def __init__( - self, - entries: List[SimpleToneTag], - *, - interaction: discord.Interaction, - per_page: int = 12, - ): - converted = [SimpleToneTagPageEntry(entry) for entry in entries] - super().__init__(converted, per_page=per_page, interaction=interaction) - - -class BareToneTagsPages(SimplePages): - def __init__( - self, - entries: List[BareToneTag], - *, - interaction: discord.Interaction, - per_page: int = 12, - ): - converted = [BareToneTagPageEntry(entry) for entry in entries] - super().__init__(converted, per_page=per_page, interaction=interaction) - - -class StatsBareToneTagsPages(SimplePages): - def __init__( - self, - entries: List[StatsBareToneTag], - *, - interaction: discord.Interaction, - per_page: int = 10, - ): - converted = [StatsBareToneTagPageEntry(entry) for entry in entries] - super().__init__(converted, per_page=per_page, interaction=interaction) diff --git a/bot/libs/ui/tonetags/structs.py b/bot/libs/ui/tonetags/structs.py deleted file mode 100644 index e55db3c..0000000 --- a/bot/libs/ui/tonetags/structs.py +++ /dev/null @@ -1,28 +0,0 @@ -import datetime -from typing import TypedDict - - -class ToneTagInfo(TypedDict): - indicator: str - definition: str - created_at: datetime.datetime - author_id: int - tonetags_id: int - - -class SimpleToneTag(TypedDict): - indicator: str - author_id: int - tonetags_id: int - - -class BareToneTag(TypedDict): - indicator: str - tonetags_id: int - - -# Here to avoid circular imports -class StatsBareToneTag(TypedDict): - indicator: str - uses: int - tonetags_id: int diff --git a/bot/libs/ui/tonetags/views.py b/bot/libs/ui/tonetags/views.py deleted file mode 100644 index 6157cee..0000000 --- a/bot/libs/ui/tonetags/views.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -import asyncpg -import discord -from libs.cog_utils.tonetags import parse_tonetag -from libs.utils import CatherineView, ErrorEmbed, SuccessEmbed, TimeoutEmbed - -NO_CONTROL_MSG = "This menu cannot be controlled by you, sorry!" - - -class DeleteTagView(CatherineView): - def __init__( - self, - interaction: discord.Interaction, - pool: asyncpg.Pool, - *, - indicator: Optional[str] = None, - indicator_id: Optional[int] = None, - ): - super().__init__(interaction=interaction) - self.indicator = indicator - self.indicator_id = indicator_id - self.pool = pool - - async def on_timeout(self) -> None: - if self.original_response and not self.triggered.is_set(): - await self.original_response.edit( - embed=TimeoutEmbed(), view=None, delete_after=15.0 - ) - - @discord.ui.button( - label="Confirm", - style=discord.ButtonStyle.green, - emoji="<:greenTick:596576670815879169>", - ) - async def confirm( - self, interaction: discord.Interaction, button: discord.ui.Button - ) -> None: - query = """ - DELETE FROM tonetags - WHERE indicator = $1 AND author_id = $2 OR id = $3; - """ - - if self.indicator: - self.indicator = parse_tonetag(self.indicator.lower()) - - status = await self.pool.execute( - query, self.indicator, interaction.user.id, self.indicator_id - ) - - if status[-1] != "0": - embed = SuccessEmbed() - embed.description = f"Successfully deleted the tonetag `{self.indicator or self.indicator_id}`" - else: - embed = ErrorEmbed(title="Doesn't exist") - embed.description = ( - "The tonetag that you are trying to delete doesn't exist" - ) - - if self.original_response: - self.triggered.set() - await self.original_response.edit(embed=embed, view=None, delete_after=15.0) - - @discord.ui.button( - label="Cancel", - style=discord.ButtonStyle.red, - emoji="<:redTick:596576672149667840>", - ) - async def cancel( - self, interaction: discord.Interaction, button: discord.ui.Button - ) -> None: - await interaction.response.defer() - await interaction.delete_original_response() - self.stop() diff --git a/docs/source/index.rst b/docs/source/index.rst index 6f86d0a..28eff02 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -51,7 +51,6 @@ Features * Pride Profile creator (create profiles to let others know about your pronouns and more) * Full pronouns.page integration * Resources for support -* ToneTags support * HRT Converter If there is a feature that you would like to see, please either suggest it within the `Discord Server `_ or preferably make a `feature request ticket `_ on GitHub. diff --git a/docs/source/terms-of-service/tos.rst b/docs/source/terms-of-service/tos.rst index 6ce65fc..7c7e417 100644 --- a/docs/source/terms-of-service/tos.rst +++ b/docs/source/terms-of-service/tos.rst @@ -7,7 +7,7 @@ By using the services provided by Catherine-Chan, you agree to follow these term 1. Don't make any content that discriminates against others based on age, ability, gender, sexuality, race, ethnicity, nationality, creed or any other trait. 2. Keep the content informative and appropriate for all ages. -3. Features such as the Tonetags and pronouns suggestions **are not places to advertise any form of information** +3. Features such as pronouns suggestions **are not places to advertise any form of information** 4. Don't abuse this bot. **I will track you down and ban you from the support server and blacklist you globally from using the bot if you do this** diff --git a/old_tests/cog_utils/test_tonetags.py b/old_tests/cog_utils/test_tonetags.py deleted file mode 100644 index e438522..0000000 --- a/old_tests/cog_utils/test_tonetags.py +++ /dev/null @@ -1,231 +0,0 @@ -import sys -from pathlib import Path - -another_path = Path(__file__).parents[2].joinpath("bot") -sys.path.append(str(another_path)) -import os - -import pytest -import pytest_asyncio -from dotenv import load_dotenv -from libs.cog_utils.tonetags import ( - create_tonetag, - edit_tonetag, - format_similar_tonetags, - get_exact_and_similar_tonetags, - get_tonetag, - get_tonetag_info, - get_top_tonetags, - parse_tonetag, - validate_tonetag, -) - -load_dotenv(dotenv_path=another_path.joinpath(".env")) - -POSTGRES_URI = os.environ["POSTGRES_URI"] - -import asyncpg - - -@pytest_asyncio.fixture -async def setup(): - async with asyncpg.create_pool(dsn=POSTGRES_URI) as pool: - yield pool - - -def test_validate_tonetag(): - tag = "/j" - parsed_tonetag = parse_tonetag(tag) - assert validate_tonetag(parsed_tonetag) is True - - second_tag = parse_tonetag("/srs") - assert validate_tonetag(second_tag) is True - - another_tag = "fsdoubfosydfbsiydfsf" - parsed_another_tag = parse_tonetag(another_tag) - assert validate_tonetag(parsed_another_tag) is False - - third_tag = "jf3" - assert validate_tonetag(parse_tonetag(third_tag)) is False - - fourth_tag = "j3." - assert validate_tonetag(parse_tonetag(fourth_tag)) is False - - sus_word_tag = "sex" - assert validate_tonetag(parse_tonetag(sus_word_tag)) is False - - -def test_parse_tonetag(): - tag = "/j" - parsed_tonetag = parse_tonetag(tag) - assert parsed_tonetag == "j" - - -def test_format_similar_tonetags(): - rows = [{"indicator": "j"}, {"indicator": "k"}] - assert ( - format_similar_tonetags(None) == "No tonetags found" - and format_similar_tonetags([]) == "No tonetags found" - ) - names = "\n".join([row["indicator"] for row in rows]) - final_str = f"Tonetag not found. Did you mean:\n{names}" - assert format_similar_tonetags(rows) == final_str - - -@pytest.mark.asyncio -async def test_create_tonetag(setup): - defs = {"indicator": "j", "definition": "joking", "author_id": 454357482102587393} - status = await create_tonetag( - defs["indicator"], defs["definition"], defs["author_id"], setup - ) - status_again = await create_tonetag( - defs["indicator"], defs["definition"], defs["author_id"], setup - ) - assert (status == "Tonetag `j` successfully created") or ( - status_again == "The tonetag `j` already exists" - ) - - -@pytest.mark.asyncio -async def test_edit_tonetag(setup): - query = """ - SELECT definition - FROM tonetags - WHERE indicator = $1 AND author_id = $2; - """ - exists_query = ( - "SELECT EXISTS(SELECT 1 FROM tonetags WHERE indicator = $1 AND author_id = $2);" - ) - defs = { - "indicator": "j", - "definition": "joking again", - "author_id": 454357482102587393, - } - - does_exists = await setup.fetchval( - exists_query, defs["indicator"], defs["author_id"] - ) - if does_exists is False: - await create_tonetag(defs["indicator"], "joking", defs["author_id"], setup) - old_def = await setup.fetchval(query, defs["indicator"], defs["author_id"]) - status = await edit_tonetag( - defs["indicator"], defs["definition"], defs["author_id"], setup - ) - assert (status[-1] != "0") or (old_def != defs["definition"]) - - -@pytest.mark.asyncio -async def test_get_tonetag(setup): - exists_query = ( - "SELECT EXISTS(SELECT 1 FROM tonetags WHERE indicator = $1 AND author_id = $2);" - ) - - defs = { - "indicator": "pkpk", - "definition": "pokpok", - "author_id": 454357482102587393, - } - new_def = { - "indicator": "pkpkf", - "definition": "something", - "author_id": 454357482102587393, - } - does_exists = await setup.fetchval( - exists_query, defs["indicator"], defs["author_id"] - ) - - if does_exists is False: - await create_tonetag( - defs["indicator"], defs["definition"], defs["author_id"], setup - ) - - new_def_res = await create_tonetag( - new_def["indicator"], new_def["definition"], new_def["author_id"], setup - ) - - assert ( - new_def_res == "Tonetag `pkpkf` successfully created" - or new_def_res == "The tonetag `pkpkf` already exists" - ) - - res = await get_tonetag("pkpk", setup) - assert ( - isinstance(res, dict) - and res["indicator"] == "pkpk" - and res["author_id"] == 454357482102587393 - ) - - second_res = await get_tonetag("pkpkd", setup) - - assert second_res[1]["indicator"] == new_def["indicator"] - - third_res = await get_tonetag("pfffsdfnsod", setup) - assert third_res is None - - -@pytest.mark.asyncio -async def test_get_tonetag_info(setup): - indicator = "pkpk" - author_id = 454357482102587393 - definition = "pokpok" - - tt_info = await get_tonetag_info(indicator, setup) - - assert ( - tt_info["indicator"] == indicator - and tt_info["author_id"] == author_id - and tt_info["definition"] == definition - ) - - tt_info_none = await get_tonetag_info("sfdosihfbsodubsoufdosudbfsoudfbsoub", setup) - assert tt_info_none is None - - -@pytest.mark.asyncio -async def test_get_exact_and_similar_tonetags(setup): - author_id = 454357482102587393 - exact_result = { - "indicator": "pkpk", - "definition": "pokpok", - } - similar_result = { - "indicator": "pkpkf", - "definition": "something", - } - - exists_query = ( - "SELECT EXISTS(SELECT 1 FROM tonetags WHERE indicator = $1 AND author_id = $2);" - ) - - async with setup.acquire() as conn: - exact_does_exists = await conn.fetchval( - exists_query, exact_result["indicator"], author_id - ) - similar_does_exists = await conn.fetchval( - exists_query, similar_result["indicator"], author_id - ) - - if exact_does_exists is False or similar_does_exists is False: - await create_tonetag( - exact_result["indicator"], exact_result["definition"], author_id, setup - ) - await create_tonetag( - similar_result["indicator"], - similar_result["definition"], - author_id, - setup, - ) - - exact_res = await get_exact_and_similar_tonetags( - exact_result["indicator"], setup - ) - assert exact_res[0] == exact_result and exact_res[1] == similar_result - - no_res = await get_exact_and_similar_tonetags("fspudfpsubufyeeeSOF", setup) - assert no_res is None - - -@pytest.mark.asyncio -async def test_get_top_tonetags(setup): - init_res = await get_top_tonetags(setup) - assert isinstance(init_res, list)