mirror of
https://github.com/neoarz/Syntrel.git
synced 2025-12-25 03:40:11 +01:00
feat(slop): tag system needs to be fixed
This commit is contained in:
14
bot.py
14
bot.py
@@ -163,6 +163,20 @@ class DiscordBot(commands.Bot):
|
|||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"Failed to load extension {folder}.{extension}\n{exception}"
|
f"Failed to load extension {folder}.{extension}\n{exception}"
|
||||||
)
|
)
|
||||||
|
elif os.path.isdir(os.path.join(folder_path, file)) and not file.startswith('__'):
|
||||||
|
if os.path.exists(os.path.join(folder_path, file, "__init__.py")):
|
||||||
|
full_name = f"{folder}.{file}".lower()
|
||||||
|
if file.lower() in disabled_cogs or full_name in disabled_cogs:
|
||||||
|
self.logger.info(f"Skipped disabled extension '{full_name}'")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
await self.load_extension(f"cogs.{folder}.{file}")
|
||||||
|
self.logger.info(f"Loaded extension '{folder}.{file}'")
|
||||||
|
except Exception as e:
|
||||||
|
exception = f"{type(e).__name__}: {e}"
|
||||||
|
self.logger.error(
|
||||||
|
f"Failed to load extension {folder}.{file}\n{exception}"
|
||||||
|
)
|
||||||
|
|
||||||
for file in os.listdir(cogs_path):
|
for file in os.listdir(cogs_path):
|
||||||
if file.endswith(".py") and not file.startswith('__'):
|
if file.endswith(".py") and not file.startswith('__'):
|
||||||
|
|||||||
145
cogs/general/tags/__init__.py
Normal file
145
cogs/general/tags/__init__.py
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
from discord import Interaction, Embed, Color
|
||||||
|
from discord.ui import View
|
||||||
|
from discord.app_commands import Choice, Group, autocomplete, describe
|
||||||
|
|
||||||
|
from .views.base import BaseCog
|
||||||
|
from .views.models import AsyncTagManager, Tag
|
||||||
|
from .views.tags import (
|
||||||
|
AddTagButtonModal,
|
||||||
|
TagSelectButton,
|
||||||
|
)
|
||||||
|
from .tagsend import TagSend
|
||||||
|
from .tagcreate import TagCreate
|
||||||
|
from .tagedit import TagEdit
|
||||||
|
from .tagdelete import TagDelete
|
||||||
|
|
||||||
|
|
||||||
|
class Tags(BaseCog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
super().__init__(bot)
|
||||||
|
self.description = "A cog for retrieving and setting tags."
|
||||||
|
|
||||||
|
self._conn: AsyncTagManager | None = None
|
||||||
|
|
||||||
|
# Initialize command classes
|
||||||
|
self.tag_send = TagSend(self)
|
||||||
|
self.tag_create = TagCreate(self)
|
||||||
|
self.tag_edit = TagEdit(self)
|
||||||
|
self.tag_delete = TagDelete(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conn(self) -> AsyncTagManager:
|
||||||
|
if self._conn is None: raise ValueError("Initialized improperly!")
|
||||||
|
return self._conn
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def setup(cls, bot):
|
||||||
|
import os
|
||||||
|
c = await super().setup(bot)
|
||||||
|
db_path = f"{os.path.realpath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))}/database/database.db"
|
||||||
|
c._conn = await AsyncTagManager.from_file(db_path)
|
||||||
|
return c
|
||||||
|
|
||||||
|
async def cog_unload(self):
|
||||||
|
if self._conn:
|
||||||
|
await self._conn.close()
|
||||||
|
await super().cog_unload()
|
||||||
|
|
||||||
|
async def tag_completer(self, inter: Interaction, current: str):
|
||||||
|
_ = inter
|
||||||
|
if self._conn is None or inter.guild is None:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
Choice(name=f"[G] {t.name}" if t.guild is None else t.name, value=str(t.tid))
|
||||||
|
for t in await self._conn.tags
|
||||||
|
if current.lower() in t.name.lower() and (inter.guild.id == t.guild or t.guild is None)
|
||||||
|
][:25]
|
||||||
|
|
||||||
|
async def tag_button_completer(self, inter: Interaction, current: str):
|
||||||
|
_ = inter
|
||||||
|
if self._conn is None or inter.guild is None:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
Choice(name=f"[G] {t.name}" if t.guild is None else t.name, value=str(t.tid))
|
||||||
|
for t in await self._conn.tags
|
||||||
|
if current.lower() in t.name.lower()
|
||||||
|
and (inter.guild.id == t.guild or t.guild is None)
|
||||||
|
and len(t.buttons) > 0
|
||||||
|
][:25]
|
||||||
|
|
||||||
|
tags = Group(name="tags", description="The parent for tag operations.", guild_only=True)
|
||||||
|
urls = Group(name="urls", description="Manage url buttons for tags.", parent=tags, guild_only=True)
|
||||||
|
|
||||||
|
async def check_conn_tag(self, name: str) -> str | Tag:
|
||||||
|
if self._conn is None:
|
||||||
|
return "Error: couldn't connect to the db file to get tags!"
|
||||||
|
if not name.isnumeric():
|
||||||
|
return f"Error: Tag {name!r} was not found!"
|
||||||
|
if (tag := await self._conn.tag(tid=int(name))) is None:
|
||||||
|
return f"Error: Tag with id {name!r} was not found!"
|
||||||
|
return tag
|
||||||
|
|
||||||
|
@tags.command(description="Send contents of a tag.")
|
||||||
|
@describe(name="The name of the tag you want to send.")
|
||||||
|
@autocomplete(name=tag_completer)
|
||||||
|
async def send(self, inter: Interaction, name: str):
|
||||||
|
await self.tag_send.send(inter, name)
|
||||||
|
|
||||||
|
@tags.command(description="Create a new tag.")
|
||||||
|
async def create(self, inter: Interaction):
|
||||||
|
await self.tag_create.create(inter)
|
||||||
|
|
||||||
|
@tags.command(description="Edit a tag.")
|
||||||
|
@describe(name="The name of the tag you want to edit.")
|
||||||
|
@autocomplete(name=tag_completer)
|
||||||
|
async def edit(self, inter: Interaction, name: str):
|
||||||
|
await self.tag_edit.edit(inter, name)
|
||||||
|
|
||||||
|
@tags.command(description="Delete a tag.")
|
||||||
|
@describe(name="The name of the tag you want to delete.")
|
||||||
|
@autocomplete(name=tag_completer)
|
||||||
|
async def delete(self, inter: Interaction, name: str):
|
||||||
|
await self.tag_delete.delete(inter, name)
|
||||||
|
|
||||||
|
@urls.command(name="add", description="Add a url button to tag.")
|
||||||
|
@describe(name="The name of the tag you want to add a url button to.")
|
||||||
|
@autocomplete(name=tag_completer)
|
||||||
|
async def button_add(self, inter: Interaction, name: str):
|
||||||
|
tag = await self.check_conn_tag(name)
|
||||||
|
if isinstance(tag, str) or self._conn is None:
|
||||||
|
return await inter.response.send_message(tag, ephemeral=True)
|
||||||
|
await inter.response.send_modal(AddTagButtonModal(self, tag))
|
||||||
|
|
||||||
|
@urls.command(name="edit", description="Edit a button for a tag.")
|
||||||
|
@describe(name="The name of the tag you want to edit button from.")
|
||||||
|
@autocomplete(name=tag_button_completer)
|
||||||
|
async def button_edit(self, inter: Interaction, name: str):
|
||||||
|
tag = await self.check_conn_tag(name)
|
||||||
|
if isinstance(tag, str) or self._conn is None:
|
||||||
|
return await inter.response.send_message(tag, ephemeral=True)
|
||||||
|
try:
|
||||||
|
button_sel = TagSelectButton(self, tag)
|
||||||
|
button_view = View()
|
||||||
|
button_view.add_item(button_sel)
|
||||||
|
await inter.response.send_message(view=button_view, ephemeral=True)
|
||||||
|
except:
|
||||||
|
button_sel = TagSelectButton(self, tag, safe=True)
|
||||||
|
button_view = View()
|
||||||
|
button_view.add_item(button_sel)
|
||||||
|
await inter.response.send_message(view=button_view, ephemeral=True)
|
||||||
|
|
||||||
|
@urls.command(name="delete", description="Delete button(s) for a tag.")
|
||||||
|
@describe(name="The name of the tag you want to delete button(s) from.")
|
||||||
|
@autocomplete(name=tag_button_completer)
|
||||||
|
async def button_delete(self, inter: Interaction, name: str):
|
||||||
|
tag = await self.check_conn_tag(name)
|
||||||
|
if isinstance(tag, str) or self._conn is None:
|
||||||
|
return await inter.response.send_message(tag, ephemeral=True)
|
||||||
|
button_sel = TagSelectButton(self, tag, True)
|
||||||
|
button_view = View()
|
||||||
|
button_view.add_item(button_sel)
|
||||||
|
await inter.response.send_message(view=button_view, ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
await bot.add_cog(await Tags.setup(bot))
|
||||||
16
cogs/general/tags/tagcreate.py
Normal file
16
cogs/general/tags/tagcreate.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
from .views.base import BaseCog
|
||||||
|
from .views.tags import CreateTagModal
|
||||||
|
|
||||||
|
|
||||||
|
class TagCreate:
|
||||||
|
def __init__(self, cog: BaseCog):
|
||||||
|
self.cog = cog
|
||||||
|
|
||||||
|
async def create(self, inter: Interaction):
|
||||||
|
"""Create a new tag."""
|
||||||
|
if self.cog._conn is None:
|
||||||
|
await inter.response.send_message(f"Error: DB connection was None!", ephemeral=True)
|
||||||
|
return
|
||||||
|
await inter.response.send_modal(CreateTagModal(self.cog))
|
||||||
38
cogs/general/tags/tagdelete.py
Normal file
38
cogs/general/tags/tagdelete.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import discord
|
||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
from .views.base import BaseCog
|
||||||
|
from .views.models import AsyncTagManager, Tag
|
||||||
|
from .views.tags import ConfirmDeleteTag
|
||||||
|
|
||||||
|
|
||||||
|
class TagDelete:
|
||||||
|
def __init__(self, cog: BaseCog):
|
||||||
|
self.cog = cog
|
||||||
|
|
||||||
|
async def check_conn_tag(self, name: str) -> str | Tag:
|
||||||
|
if self.cog._conn is None:
|
||||||
|
return "Error: couldn't connect to the db file to get tags!"
|
||||||
|
if not name.isnumeric():
|
||||||
|
return f"Error: Tag {name!r} was not found!"
|
||||||
|
if (tag := await self.cog._conn.tag(tid=int(name))) is None:
|
||||||
|
return f"Error: Tag with id {name!r} was not found!"
|
||||||
|
return tag
|
||||||
|
|
||||||
|
async def delete(self, inter: Interaction, name: str):
|
||||||
|
"""Delete a tag."""
|
||||||
|
tag = await self.check_conn_tag(name)
|
||||||
|
if isinstance(tag, str) or self.cog._conn is None:
|
||||||
|
return await inter.response.send_message(tag, ephemeral=True)
|
||||||
|
delete_tag = ConfirmDeleteTag(self.cog, tag)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Confirm Deletion",
|
||||||
|
description=f"Are you sure you want to delete `{tag.name}`?",
|
||||||
|
color=0xFF0000
|
||||||
|
)
|
||||||
|
embed.set_author(name="Tags", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||||
|
await inter.response.send_message(
|
||||||
|
embed=embed,
|
||||||
|
view=delete_tag,
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
36
cogs/general/tags/tagedit.py
Normal file
36
cogs/general/tags/tagedit.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from discord import Interaction, Embed
|
||||||
|
|
||||||
|
from .views.base import BaseCog
|
||||||
|
from .views.models import AsyncTagManager, Tag
|
||||||
|
from .views.tags import EditTagPreview
|
||||||
|
|
||||||
|
|
||||||
|
class TagEdit:
|
||||||
|
def __init__(self, cog: BaseCog):
|
||||||
|
self.cog = cog
|
||||||
|
|
||||||
|
async def check_conn_tag(self, name: str) -> str | Tag:
|
||||||
|
if self.cog._conn is None:
|
||||||
|
return "Error: couldn't connect to the db file to get tags!"
|
||||||
|
if not name.isnumeric():
|
||||||
|
return f"Error: Tag {name!r} was not found!"
|
||||||
|
if (tag := await self.cog._conn.tag(tid=int(name))) is None:
|
||||||
|
return f"Error: Tag with id {name!r} was not found!"
|
||||||
|
return tag
|
||||||
|
|
||||||
|
async def edit(self, inter: Interaction, name: str):
|
||||||
|
"""Edit a tag."""
|
||||||
|
tag = await self.check_conn_tag(name)
|
||||||
|
if isinstance(tag, str) or self.cog._conn is None:
|
||||||
|
return await inter.response.send_message(tag, ephemeral=True)
|
||||||
|
|
||||||
|
embed = Embed(
|
||||||
|
title=f"Edit Tag: {tag.name}",
|
||||||
|
description=f"```\n{tag.content}\n```",
|
||||||
|
color=0x7289DA
|
||||||
|
)
|
||||||
|
embed.set_author(name="Tags", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||||
|
embed.set_footer(text="Click Edit to modify this tag or Close to cancel")
|
||||||
|
|
||||||
|
view = EditTagPreview(self.cog, tag)
|
||||||
|
await inter.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||||
40
cogs/general/tags/tagsend.py
Normal file
40
cogs/general/tags/tagsend.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from discord import Interaction
|
||||||
|
|
||||||
|
from .views.base import BaseCog
|
||||||
|
from .views.models import AsyncTagManager, Tag
|
||||||
|
from .views.tags import TagEmbed, TagButtonView
|
||||||
|
|
||||||
|
|
||||||
|
class TagSend:
|
||||||
|
def __init__(self, cog: BaseCog):
|
||||||
|
self.cog = cog
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conn(self) -> AsyncTagManager:
|
||||||
|
return self.cog.conn
|
||||||
|
|
||||||
|
async def check_conn_tag(self, name: str) -> str | Tag:
|
||||||
|
if self.cog._conn is None:
|
||||||
|
return "Error: couldn't connect to the db file to get tags!"
|
||||||
|
if not name.isnumeric():
|
||||||
|
return f"Error: Tag {name!r} was not found!"
|
||||||
|
if (tag := await self.cog._conn.tag(tid=int(name))) is None:
|
||||||
|
return f"Error: Tag with id {name!r} was not found!"
|
||||||
|
return tag
|
||||||
|
|
||||||
|
async def send(self, inter: Interaction, name: str):
|
||||||
|
"""Send contents of a tag."""
|
||||||
|
tag = await self.check_conn_tag(name)
|
||||||
|
if isinstance(tag, str) or self.cog._conn is None:
|
||||||
|
return await inter.response.send_message(tag, ephemeral=True)
|
||||||
|
ephemeral = inter.guild is None
|
||||||
|
author = self.cog.bot.get_user(tag.author)
|
||||||
|
authname = author.name if author else str(tag.author)
|
||||||
|
await inter.response.send_message(
|
||||||
|
embeds=[TagEmbed(tag, authname)],
|
||||||
|
view=TagButtonView(tag.buttons),
|
||||||
|
ephemeral=ephemeral
|
||||||
|
)
|
||||||
|
tag.used += 1
|
||||||
|
await self.cog._conn.update(tag)
|
||||||
|
self.cog.logger.info(f"{inter.user.name!r} sent {tag.name!r}")
|
||||||
0
cogs/general/tags/views/__init__.py
Normal file
0
cogs/general/tags/views/__init__.py
Normal file
15
cogs/general/tags/views/base.py
Normal file
15
cogs/general/tags/views/base.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import logging
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCog(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.logger = logging.getLogger(f"discord_bot.{self.__class__.__name__}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def setup(cls, bot):
|
||||||
|
return cls(bot)
|
||||||
|
|
||||||
|
async def cog_unload(self):
|
||||||
|
pass
|
||||||
187
cogs/general/tags/views/models.py
Normal file
187
cogs/general/tags/views/models.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import aiosqlite
|
||||||
|
import asyncio
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TagButton:
|
||||||
|
id: int
|
||||||
|
tag_id: int
|
||||||
|
label: str
|
||||||
|
url: str
|
||||||
|
emoji: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Tag:
|
||||||
|
tid: int
|
||||||
|
name: str
|
||||||
|
content: str
|
||||||
|
author: int
|
||||||
|
guild: Optional[int]
|
||||||
|
used: int = 0
|
||||||
|
buttons: List[TagButton] = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.buttons is None:
|
||||||
|
self.buttons = []
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTagManager:
|
||||||
|
def __init__(self, db_path: str):
|
||||||
|
self.db_path = db_path
|
||||||
|
self._connection = None
|
||||||
|
self._tags_cache = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def from_file(cls, db_path: str):
|
||||||
|
manager = cls(db_path)
|
||||||
|
await manager._connect()
|
||||||
|
return manager
|
||||||
|
|
||||||
|
async def _connect(self):
|
||||||
|
self._connection = await aiosqlite.connect(self.db_path)
|
||||||
|
await self._load_tags()
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
if self._connection:
|
||||||
|
await self._connection.close()
|
||||||
|
self._connection = None
|
||||||
|
|
||||||
|
async def _load_tags(self):
|
||||||
|
if not self._connection:
|
||||||
|
return
|
||||||
|
|
||||||
|
cursor = await self._connection.execute("""
|
||||||
|
SELECT t.tid, t.name, t.content, t.author, t.guild, t.used,
|
||||||
|
b.id, b.label, b.url, b.emoji
|
||||||
|
FROM tags t
|
||||||
|
LEFT JOIN tag_buttons b ON t.tid = b.tag_id
|
||||||
|
ORDER BY t.tid
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
await cursor.close()
|
||||||
|
|
||||||
|
tags_dict = {}
|
||||||
|
for row in rows:
|
||||||
|
tid = row[0]
|
||||||
|
if tid not in tags_dict:
|
||||||
|
tags_dict[tid] = Tag(
|
||||||
|
tid=row[0],
|
||||||
|
name=row[1],
|
||||||
|
content=row[2],
|
||||||
|
author=row[3],
|
||||||
|
guild=row[4],
|
||||||
|
used=row[5],
|
||||||
|
buttons=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
if row[6] is not None:
|
||||||
|
button = TagButton(
|
||||||
|
id=row[6],
|
||||||
|
tag_id=tid,
|
||||||
|
label=row[7],
|
||||||
|
url=row[8],
|
||||||
|
emoji=row[9]
|
||||||
|
)
|
||||||
|
tags_dict[tid].buttons.append(button)
|
||||||
|
|
||||||
|
self._tags_cache = list(tags_dict.values())
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def tags(self) -> List[Tag]:
|
||||||
|
if self._tags_cache is None:
|
||||||
|
await self._load_tags()
|
||||||
|
return self._tags_cache
|
||||||
|
|
||||||
|
async def tag(self, tid: int = None, name: str = None) -> Optional[Tag]:
|
||||||
|
tags = await self.tags
|
||||||
|
if tid is not None:
|
||||||
|
return next((tag for tag in tags if tag.tid == tid), None)
|
||||||
|
if name is not None:
|
||||||
|
return next((tag for tag in tags if tag.name.lower() == name.lower()), None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def create_tag(self, name: str, content: str, author: int, guild: Optional[int] = None) -> Tag:
|
||||||
|
if not self._connection:
|
||||||
|
raise ValueError("Database not connected")
|
||||||
|
|
||||||
|
cursor = await self._connection.execute(
|
||||||
|
"INSERT INTO tags (name, content, author, guild) VALUES (?, ?, ?, ?)",
|
||||||
|
(name, content, author, guild)
|
||||||
|
)
|
||||||
|
await self._connection.commit()
|
||||||
|
|
||||||
|
tid = cursor.lastrowid
|
||||||
|
await cursor.close()
|
||||||
|
|
||||||
|
tag = Tag(tid=tid, name=name, content=content, author=author, guild=guild, used=0, buttons=[])
|
||||||
|
self._tags_cache.append(tag)
|
||||||
|
return tag
|
||||||
|
|
||||||
|
async def update(self, tag: Tag):
|
||||||
|
if not self._connection:
|
||||||
|
raise ValueError("Database not connected")
|
||||||
|
|
||||||
|
await self._connection.execute(
|
||||||
|
"UPDATE tags SET name=?, content=?, used=? WHERE tid=?",
|
||||||
|
(tag.name, tag.content, tag.used, tag.tid)
|
||||||
|
)
|
||||||
|
await self._connection.commit()
|
||||||
|
|
||||||
|
if self._tags_cache:
|
||||||
|
for i, cached_tag in enumerate(self._tags_cache):
|
||||||
|
if cached_tag.tid == tag.tid:
|
||||||
|
self._tags_cache[i] = tag
|
||||||
|
break
|
||||||
|
|
||||||
|
async def delete_tag(self, tag: Tag):
|
||||||
|
if not self._connection:
|
||||||
|
raise ValueError("Database not connected")
|
||||||
|
|
||||||
|
await self._connection.execute("DELETE FROM tags WHERE tid=?", (tag.tid,))
|
||||||
|
await self._connection.commit()
|
||||||
|
|
||||||
|
if self._tags_cache:
|
||||||
|
self._tags_cache = [t for t in self._tags_cache if t.tid != tag.tid]
|
||||||
|
|
||||||
|
async def add_button(self, tag: Tag, label: str, url: str, emoji: Optional[str] = None) -> TagButton:
|
||||||
|
if not self._connection:
|
||||||
|
raise ValueError("Database not connected")
|
||||||
|
|
||||||
|
cursor = await self._connection.execute(
|
||||||
|
"INSERT INTO tag_buttons (tag_id, label, url, emoji) VALUES (?, ?, ?, ?)",
|
||||||
|
(tag.tid, label, url, emoji)
|
||||||
|
)
|
||||||
|
await self._connection.commit()
|
||||||
|
|
||||||
|
button_id = cursor.lastrowid
|
||||||
|
await cursor.close()
|
||||||
|
|
||||||
|
button = TagButton(id=button_id, tag_id=tag.tid, label=label, url=url, emoji=emoji)
|
||||||
|
tag.buttons.append(button)
|
||||||
|
return button
|
||||||
|
|
||||||
|
async def update_button(self, button: TagButton):
|
||||||
|
if not self._connection:
|
||||||
|
raise ValueError("Database not connected")
|
||||||
|
|
||||||
|
await self._connection.execute(
|
||||||
|
"UPDATE tag_buttons SET label=?, url=?, emoji=? WHERE id=?",
|
||||||
|
(button.label, button.url, button.emoji, button.id)
|
||||||
|
)
|
||||||
|
await self._connection.commit()
|
||||||
|
|
||||||
|
async def delete_button(self, button: TagButton):
|
||||||
|
if not self._connection:
|
||||||
|
raise ValueError("Database not connected")
|
||||||
|
|
||||||
|
await self._connection.execute("DELETE FROM tag_buttons WHERE id=?", (button.id,))
|
||||||
|
await self._connection.commit()
|
||||||
|
|
||||||
|
for tag in await self.tags:
|
||||||
|
if tag.tid == button.tag_id:
|
||||||
|
tag.buttons = [b for b in tag.buttons if b.id != button.id]
|
||||||
|
break
|
||||||
398
cogs/general/tags/views/tags.py
Normal file
398
cogs/general/tags/views/tags.py
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
import discord
|
||||||
|
from discord import Interaction, ButtonStyle, SelectOption
|
||||||
|
from discord.ui import Modal, TextInput, View, Button, Select
|
||||||
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .models import Tag, TagButton
|
||||||
|
|
||||||
|
class TagEmbed(discord.Embed):
|
||||||
|
def __init__(self, tag: "Tag", author_name: str):
|
||||||
|
super().__init__(
|
||||||
|
description=tag.content,
|
||||||
|
color=0x7289DA,
|
||||||
|
timestamp=discord.utils.utcnow()
|
||||||
|
)
|
||||||
|
self.set_author(name="Tags", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||||
|
self.set_footer(text=f"Created by {author_name} | Used {tag.used} times")
|
||||||
|
|
||||||
|
|
||||||
|
class TagButtonView(View):
|
||||||
|
def __init__(self, buttons: List["TagButton"]):
|
||||||
|
super().__init__(timeout=None)
|
||||||
|
for button in buttons[:5]:
|
||||||
|
self.add_item(TagUrlButton(button))
|
||||||
|
|
||||||
|
|
||||||
|
class TagUrlButton(Button):
|
||||||
|
def __init__(self, tag_button: "TagButton"):
|
||||||
|
super().__init__(
|
||||||
|
label=tag_button.label,
|
||||||
|
url=tag_button.url,
|
||||||
|
emoji=tag_button.emoji,
|
||||||
|
style=ButtonStyle.link
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateTagModal(Modal):
|
||||||
|
def __init__(self, cog):
|
||||||
|
super().__init__(title="Create New Tag")
|
||||||
|
self.cog = cog
|
||||||
|
|
||||||
|
self.name_input = TextInput(
|
||||||
|
label="Tag Name",
|
||||||
|
placeholder="Enter the name for your tag...",
|
||||||
|
max_length=100,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
self.content_input = TextInput(
|
||||||
|
label="Tag Content",
|
||||||
|
placeholder="Enter the content for your tag...",
|
||||||
|
style=discord.TextStyle.paragraph,
|
||||||
|
max_length=2000,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_item(self.name_input)
|
||||||
|
self.add_item(self.content_input)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: Interaction):
|
||||||
|
name = self.name_input.value
|
||||||
|
content = self.content_input.value
|
||||||
|
author = interaction.user.id
|
||||||
|
guild = interaction.guild.id if interaction.guild else None
|
||||||
|
|
||||||
|
existing_tag = await self.cog.conn.tag(name=name)
|
||||||
|
if existing_tag and (existing_tag.guild == guild or existing_tag.guild is None):
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"A tag with the name '{name}' already exists!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
tag = await self.cog.conn.create_tag(name, content, author, guild)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Tag Created!",
|
||||||
|
description=f"Successfully created tag `{name}`!",
|
||||||
|
color=0x7289DA
|
||||||
|
)
|
||||||
|
embed.set_author(name="Tags", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
self.cog.logger.info(f"Tag '{name}' created by {interaction.user.name}")
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"An error occurred while creating the tag: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EditTagPreview(View):
|
||||||
|
def __init__(self, cog, tag: "Tag"):
|
||||||
|
super().__init__(timeout=300)
|
||||||
|
self.cog = cog
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
@discord.ui.button(label="Edit", style=ButtonStyle.primary)
|
||||||
|
async def edit_button(self, interaction: Interaction, button: Button):
|
||||||
|
if interaction.user.id != self.tag.author and not interaction.user.guild_permissions.manage_messages:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"You don't have permission to edit this tag!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
await interaction.response.send_modal(EditTagModal(self.cog, self.tag))
|
||||||
|
|
||||||
|
@discord.ui.button(label="Close", style=ButtonStyle.danger)
|
||||||
|
async def close_button(self, interaction: Interaction, button: Button):
|
||||||
|
await interaction.response.edit_message(content="Tag edit cancelled.", embed=None, view=None)
|
||||||
|
|
||||||
|
|
||||||
|
class EditTagModal(Modal):
|
||||||
|
def __init__(self, cog, tag: "Tag"):
|
||||||
|
super().__init__(title=f"Edit Tag: {tag.name}")
|
||||||
|
self.cog = cog
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
self.name_input = TextInput(
|
||||||
|
label="Tag Name",
|
||||||
|
default=tag.name,
|
||||||
|
max_length=100,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
self.content_input = TextInput(
|
||||||
|
label="Tag Content",
|
||||||
|
default=tag.content,
|
||||||
|
style=discord.TextStyle.paragraph,
|
||||||
|
max_length=2000,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_item(self.name_input)
|
||||||
|
self.add_item(self.content_input)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: Interaction):
|
||||||
|
if interaction.user.id != self.tag.author and not interaction.user.guild_permissions.manage_messages:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"You don't have permission to edit this tag!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
name = self.name_input.value
|
||||||
|
content = self.content_input.value
|
||||||
|
|
||||||
|
guild = interaction.guild.id if interaction.guild else None
|
||||||
|
existing_tag = await self.cog.conn.tag(name=name)
|
||||||
|
if existing_tag and existing_tag.tid != self.tag.tid and (existing_tag.guild == guild or existing_tag.guild is None):
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"A tag with the name '{name}' already exists!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.tag.name = name
|
||||||
|
self.tag.content = content
|
||||||
|
await self.cog.conn.update(self.tag)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Success!",
|
||||||
|
description=f"Successfully updated tag `{name}`!",
|
||||||
|
color=0x00FF00
|
||||||
|
)
|
||||||
|
embed.set_author(name="Tags", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
self.cog.logger.info(f"Tag '{name}' updated by {interaction.user.name}")
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"An error occurred while updating the tag: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmDeleteTag(View):
|
||||||
|
def __init__(self, cog, tag: "Tag"):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.cog = cog
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
@discord.ui.button(label="Yes", style=ButtonStyle.danger)
|
||||||
|
async def confirm_delete(self, interaction: Interaction, button: Button):
|
||||||
|
if interaction.user.id != self.tag.author and not interaction.user.guild_permissions.manage_messages:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"You don't have permission to delete this tag!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.cog.conn.delete_tag(self.tag)
|
||||||
|
await interaction.response.edit_message(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title="Success!",
|
||||||
|
description=f"Tag `{self.tag.name}` has been deleted.",
|
||||||
|
color=0x00FF00
|
||||||
|
),
|
||||||
|
view=None
|
||||||
|
)
|
||||||
|
self.cog.logger.info(f"Tag '{self.tag.name}' deleted by {interaction.user.name}")
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"An error occurred while deleting the tag: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@discord.ui.button(label="No", style=ButtonStyle.secondary)
|
||||||
|
async def cancel_delete(self, interaction: Interaction, button: Button):
|
||||||
|
await interaction.response.edit_message(
|
||||||
|
content="Tag deletion cancelled.",
|
||||||
|
view=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AddTagButtonModal(Modal):
|
||||||
|
def __init__(self, cog, tag: "Tag"):
|
||||||
|
super().__init__(title=f"Add Button to: {tag.name}")
|
||||||
|
self.cog = cog
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
self.label_input = TextInput(
|
||||||
|
label="Button Label",
|
||||||
|
placeholder="Enter the button label...",
|
||||||
|
max_length=80,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
self.url_input = TextInput(
|
||||||
|
label="Button URL",
|
||||||
|
placeholder="https://example.com",
|
||||||
|
max_length=512,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
self.emoji_input = TextInput(
|
||||||
|
label="Button Emoji (Optional)",
|
||||||
|
placeholder="Enter emoji...",
|
||||||
|
max_length=100,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_item(self.label_input)
|
||||||
|
self.add_item(self.url_input)
|
||||||
|
self.add_item(self.emoji_input)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: Interaction):
|
||||||
|
if interaction.user.id != self.tag.author and not interaction.user.guild_permissions.manage_messages:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"You don't have permission to modify this tag!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(self.tag.buttons) >= 5:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"Tags can only have up to 5 buttons!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
label = self.label_input.value
|
||||||
|
url = self.url_input.value
|
||||||
|
emoji = self.emoji_input.value if self.emoji_input.value else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.cog.conn.add_button(self.tag, label, url, emoji)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"Successfully added button '{label}' to tag '{self.tag.name}'!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
self.cog.logger.info(f"Button '{label}' added to tag '{self.tag.name}' by {interaction.user.name}")
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"An error occurred while adding the button: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TagSelectButton(Select):
|
||||||
|
def __init__(self, cog, tag: "Tag", delete_mode: bool = False, safe: bool = False):
|
||||||
|
self.cog = cog
|
||||||
|
self.tag = tag
|
||||||
|
self.delete_mode = delete_mode
|
||||||
|
|
||||||
|
options = []
|
||||||
|
for i, button in enumerate(tag.buttons[:25]):
|
||||||
|
option_label = button.label
|
||||||
|
if len(option_label) > 100:
|
||||||
|
option_label = option_label[:97] + "..."
|
||||||
|
|
||||||
|
options.append(SelectOption(
|
||||||
|
label=option_label,
|
||||||
|
value=str(button.id),
|
||||||
|
description=button.url[:100] if len(button.url) <= 100 else button.url[:97] + "...",
|
||||||
|
emoji=button.emoji if not safe else None
|
||||||
|
))
|
||||||
|
|
||||||
|
if not options:
|
||||||
|
options.append(SelectOption(
|
||||||
|
label="No buttons available",
|
||||||
|
value="none",
|
||||||
|
description="This tag has no buttons"
|
||||||
|
))
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
placeholder="Select a button to edit..." if not delete_mode else "Select a button to delete...",
|
||||||
|
options=options,
|
||||||
|
disabled=len(tag.buttons) == 0
|
||||||
|
)
|
||||||
|
|
||||||
|
async def callback(self, interaction: Interaction):
|
||||||
|
if interaction.user.id != self.tag.author and not interaction.user.guild_permissions.manage_messages:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"You don't have permission to modify this tag!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.values[0] == "none":
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"No buttons available to modify.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
button_id = int(self.values[0])
|
||||||
|
button = next((b for b in self.tag.buttons if b.id == button_id), None)
|
||||||
|
|
||||||
|
if not button:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"Button not found!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.delete_mode:
|
||||||
|
try:
|
||||||
|
await self.cog.conn.delete_button(button)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"Successfully deleted button '{button.label}' from tag '{self.tag.name}'!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
self.cog.logger.info(f"Button '{button.label}' deleted from tag '{self.tag.name}' by {interaction.user.name}")
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"An error occurred while deleting the button: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_modal(EditTagButtonModal(self.cog, self.tag, button))
|
||||||
|
|
||||||
|
|
||||||
|
class EditTagButtonModal(Modal):
|
||||||
|
def __init__(self, cog, tag: "Tag", button: "TagButton"):
|
||||||
|
super().__init__(title=f"Edit Button: {button.label}")
|
||||||
|
self.cog = cog
|
||||||
|
self.tag = tag
|
||||||
|
self.button = button
|
||||||
|
|
||||||
|
self.label_input = TextInput(
|
||||||
|
label="Button Label",
|
||||||
|
default=button.label,
|
||||||
|
max_length=80,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
self.url_input = TextInput(
|
||||||
|
label="Button URL",
|
||||||
|
default=button.url,
|
||||||
|
max_length=512,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
self.emoji_input = TextInput(
|
||||||
|
label="Button Emoji (Optional)",
|
||||||
|
default=button.emoji or "",
|
||||||
|
max_length=100,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_item(self.label_input)
|
||||||
|
self.add_item(self.url_input)
|
||||||
|
self.add_item(self.emoji_input)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: Interaction):
|
||||||
|
label = self.label_input.value
|
||||||
|
url = self.url_input.value
|
||||||
|
emoji = self.emoji_input.value if self.emoji_input.value else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.button.label = label
|
||||||
|
self.button.url = url
|
||||||
|
self.button.emoji = emoji
|
||||||
|
await self.cog.conn.update_button(self.button)
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"Successfully updated button '{label}' on tag '{self.tag.name}'!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
self.cog.logger.info(f"Button '{label}' updated on tag '{self.tag.name}' by {interaction.user.name}")
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"An error occurred while updating the button: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
@@ -8,3 +8,22 @@ CREATE TABLE IF NOT EXISTS `warns` (
|
|||||||
`reason` varchar(255) NOT NULL,
|
`reason` varchar(255) NOT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tags` (
|
||||||
|
`tid` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
`author` varchar(20) NOT NULL,
|
||||||
|
`guild` varchar(20),
|
||||||
|
`used` INTEGER DEFAULT 0,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tag_buttons` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`tag_id` INTEGER NOT NULL,
|
||||||
|
`label` varchar(80) NOT NULL,
|
||||||
|
`url` TEXT NOT NULL,
|
||||||
|
`emoji` varchar(100),
|
||||||
|
FOREIGN KEY (tag_id) REFERENCES tags(tid) ON DELETE CASCADE
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user