660 lines
22 KiB
Python
Executable File
660 lines
22 KiB
Python
Executable File
import pickle
|
|
import json
|
|
from typing import Callable, Awaitable
|
|
from dataclasses import dataclass, field
|
|
import discord
|
|
from discord.ext import commands
|
|
import traceback
|
|
|
|
bot = commands.Bot(command_prefix="\0", intents=discord.Intents.all())
|
|
|
|
|
|
@dataclass
|
|
class ReactionRole:
|
|
emoji: str
|
|
role_id: int
|
|
description: str
|
|
|
|
def __str__(self):
|
|
return f"{self.emoji} - {self.description}"
|
|
|
|
|
|
@dataclass
|
|
class TrackedMessage:
|
|
guild_id: int
|
|
channel_id: int
|
|
message_id: int
|
|
header: str
|
|
own_message: bool
|
|
reactions: list[ReactionRole] = field(default_factory=list)
|
|
|
|
def __str__(self):
|
|
return f"message at https://discord.com/channels/{self.guild_id}/{self.channel_id}/{self.message_id} with {len(self.reactions)} reaction roles"
|
|
|
|
def __repr__(self):
|
|
return (self.header or " ") + '\n' + ("\n".join(
|
|
str(rr) for rr in self.reactions) or f"-# waiting for reactions, message id is {self.message_id}")
|
|
|
|
|
|
class Database:
|
|
def __init__(self, d: str):
|
|
self.__tracked_messages: dict[int, TrackedMessage] = {}
|
|
self.__file: str = d
|
|
self.__load()
|
|
|
|
def __load(self) -> None:
|
|
try:
|
|
self.__tracked_messages = pickle.load(open(self.__file, "rb"))
|
|
except FileNotFoundError:
|
|
self.__tracked_messages = {}
|
|
|
|
def __save(self) -> None:
|
|
pickle.dump(self.__tracked_messages, open(self.__file, "wb"))
|
|
|
|
def track_message(self, guild_id: int, channel_id: int, message_id: int, header: str, own_message: bool) -> bool:
|
|
if message_id in self.__tracked_messages:
|
|
return True
|
|
self.__tracked_messages[message_id] = TrackedMessage(guild_id, channel_id, message_id, header, own_message, [])
|
|
self.__save()
|
|
return False
|
|
|
|
def edit_header(self, message_id: int, header: str) -> None:
|
|
self.__tracked_messages[message_id].header = header
|
|
|
|
def untrack_message(self, message_id: int):
|
|
del self.__tracked_messages[message_id]
|
|
self.__save()
|
|
|
|
def add_reaction_role(self, message_id: int, emoji: str, role_id: int, description: str) -> bool:
|
|
if message_id not in self.__tracked_messages:
|
|
return True
|
|
|
|
if any(rr.emoji == emoji or rr.role_id == role_id for rr in self.__tracked_messages[message_id].reactions):
|
|
return True
|
|
|
|
self.__tracked_messages[message_id].reactions.append(ReactionRole(emoji, role_id, description))
|
|
self.__save()
|
|
return False
|
|
|
|
def edit_reaction_role_description(self, message_id: int, emoji: str, description: str) -> str | None:
|
|
if message_id not in self.__tracked_messages:
|
|
return None
|
|
|
|
for rr in self.__tracked_messages[message_id].reactions:
|
|
if rr.emoji == emoji:
|
|
old_desc = rr.description
|
|
rr.description = description
|
|
self.__save()
|
|
return old_desc
|
|
|
|
return None
|
|
|
|
def remove_reaction_role(self, message_id: int, emoji: str) -> None:
|
|
self.__tracked_messages[message_id].reactions = [x for x in self.__tracked_messages[message_id].reactions if
|
|
x.emoji != emoji]
|
|
self.__save()
|
|
|
|
def embed_message(self, message_id: int) -> discord.Embed:
|
|
m = self.__tracked_messages[message_id]
|
|
return discord.Embed(
|
|
title=f"message with {len(m.reactions)} reactions",
|
|
description="\n".join(f"{x.emoji}" + (f" ({x.emoji.removesuffix('>').split(':')[2]})" if ':' in x.emoji else "") + f" - <@&{x.role_id}>" for x in m.reactions)
|
|
)
|
|
|
|
def format_message(self, message_id: int) -> str:
|
|
return repr(self.__tracked_messages[message_id])
|
|
|
|
def is_tracked_already(self, message_id: int):
|
|
return message_id in self.__tracked_messages
|
|
|
|
def is_my_message(self, message_id: int):
|
|
return self.__tracked_messages[message_id].own_message
|
|
|
|
async def get_message(self, message_id: int) -> discord.Message | None:
|
|
if message_id not in self.__tracked_messages:
|
|
return None
|
|
tm = self.__tracked_messages[message_id]
|
|
channel = bot.get_channel(tm.channel_id)
|
|
if channel is None:
|
|
return channel
|
|
message = await channel.fetch_message(tm.message_id)
|
|
return message
|
|
|
|
def get_reaction_role(self, message_id: int, emoji: str) -> ReactionRole | None:
|
|
if message_id not in self.__tracked_messages:
|
|
return None
|
|
for rr in self.__tracked_messages[message_id].reactions:
|
|
if rr.emoji == emoji:
|
|
return rr
|
|
return None
|
|
|
|
|
|
class Config:
|
|
def __init__(self, d: str):
|
|
self.__file = d
|
|
self.__load()
|
|
|
|
def __load(self):
|
|
try:
|
|
c: dict[str, str | list[int]] = json.load(open(self.__file))
|
|
self.__prefix: str = c["prefix"]
|
|
self.__trusted: list[int] = c["trusted"]
|
|
self.__token: str = c["token"]
|
|
except FileNotFoundError:
|
|
json.dumps({
|
|
"prefix": "hey rc ",
|
|
"trusted": [],
|
|
"token": "https://youtu.be/dQw4w9WgXcQ"
|
|
}, open(self.__file, "w"), indent=4)
|
|
raise FileNotFoundError("Config not found")
|
|
|
|
@property
|
|
def prefix(self):
|
|
return self.__prefix
|
|
|
|
@property
|
|
def trusted(self):
|
|
return self.__trusted
|
|
|
|
def run_bot(self):
|
|
bot.run(self.__token)
|
|
|
|
|
|
class EmojiStorage:
|
|
def __init__(self, d: str):
|
|
self.__emoji_storage: dict[int, dict[str, int | list[str]]] = {}
|
|
self.db_file: str = d
|
|
self.__load()
|
|
|
|
|
|
def __load(self) -> None:
|
|
try:
|
|
self.__emoji_storage = pickle.load(open(self.db_file, "rb"))
|
|
except (FileNotFoundError, EOFError):
|
|
self.__emoji_storage = {}
|
|
|
|
def __save(self) -> None:
|
|
pickle.dump(self.__emoji_storage, open(self.db_file, "wb"))
|
|
|
|
def add_emoji_storage(self, server_id: int) -> bool:
|
|
if server_id in self.__emoji_storage:
|
|
return True
|
|
|
|
self.__emoji_storage[server_id] = {"emoji_message_channel": 0, "emoji_message": 0, "emojis": []}
|
|
self.__save()
|
|
return False
|
|
|
|
def set_emoji_message(self, server_id: int, channel_id: int, message_id: int):
|
|
self.__emoji_storage[server_id]["emoji_message_channel"] = channel_id
|
|
self.__emoji_storage[server_id]["emoji_message"] = message_id
|
|
self.__save()
|
|
|
|
def remove_emoji_storage(self, server_id: int) -> bool:
|
|
if server_id not in self.__emoji_storage:
|
|
return True
|
|
|
|
del self.__emoji_storage[server_id]
|
|
self.__save()
|
|
return False
|
|
|
|
async def __add_emoji(self, server_id: int, emoji: str):
|
|
self.__emoji_storage[server_id]["emojis"].append(emoji)
|
|
self.__save()
|
|
channel = bot.get_channel(self.__emoji_storage[server_id]["emoji_message_channel"])
|
|
message = await channel.fetch_message(self.__emoji_storage[server_id]["emoji_message"])
|
|
await message.edit(content=''.join(self.__emoji_storage[server_id]["emojis"] or "-# dust"))
|
|
|
|
async def try_add_emoji(self, name: str, image: bytes) -> discord.Emoji | None:
|
|
for x in self.__emoji_storage:
|
|
g = bot.get_guild(x)
|
|
if len(g.emojis) == g.emoji_limit:
|
|
continue
|
|
emoji = await g.create_custom_emoji(name=name, image=image)
|
|
await self.__add_emoji(x, str(emoji))
|
|
return emoji
|
|
return None
|
|
|
|
DATABASE: Database = Database("db.dat")
|
|
EMOJI_STORAGE: EmojiStorage = EmojiStorage("emojis.dat")
|
|
CONFIG: Config = Config("config.json")
|
|
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
print(f"{bot.user} is ready")
|
|
await bot.change_presence(activity=discord.CustomActivity("gaming"))
|
|
await bot.tree.sync()
|
|
|
|
|
|
rc_func = Callable[[list[str], discord.Message], Awaitable[tuple[str | discord.Embed, bool]]]
|
|
registry: list[tuple[str, rc_func, int]] = []
|
|
|
|
def register(name: str, args_amount: int):
|
|
def decorator(func: rc_func):
|
|
registry.append((name, func, args_amount))
|
|
return func
|
|
return decorator
|
|
|
|
|
|
@register("show", 1)
|
|
async def show(args: list[str], message: discord.Message) -> tuple[str | discord.Embed, bool]:
|
|
if len(args) != 1:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if not DATABASE.is_tracked_already(int(args[0])):
|
|
return "not tracked", True
|
|
|
|
return DATABASE.embed_message(int(args[0])), False
|
|
|
|
@register("send", 2)
|
|
async def send(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if not 1 <= len(args) <= 2:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid channel id", True
|
|
|
|
channel = bot.get_channel(int(args[0]))
|
|
if channel is None:
|
|
return "channel not found", True
|
|
|
|
try:
|
|
sent_message = await channel.send("hold on")
|
|
except discord.Forbidden:
|
|
return "no permission", True
|
|
|
|
DATABASE.track_message(sent_message.guild.id, sent_message.channel.id, sent_message.id,
|
|
args[1] if len(args) == 2 else "", True)
|
|
await sent_message.edit(content=DATABASE.format_message(sent_message.id))
|
|
return "<:thumbsup:1486044381784834168>", True
|
|
|
|
|
|
@register("track", 2)
|
|
async def track(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if len(args) != 2:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid channel id", True
|
|
|
|
if not args[1].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if DATABASE.is_tracked_already(int(args[1])):
|
|
return "already tracked", True
|
|
|
|
channel = bot.get_channel(int(args[0]))
|
|
if channel is None:
|
|
return "channel not found", True
|
|
|
|
try:
|
|
tracked_msg = await channel.fetch_message(int(args[1]))
|
|
DATABASE.track_message(tracked_msg.guild.id, tracked_msg.channel.id, tracked_msg.id, "", False)
|
|
return f"successfully tracked message {args[1]}", True
|
|
except discord.NotFound:
|
|
return "message not found", True
|
|
except discord.Forbidden:
|
|
return "no permission to access that message", True
|
|
|
|
|
|
@register("add", 4)
|
|
async def add(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if not 3 <= len(args) <= 4:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if not DATABASE.is_tracked_already(int(args[0])):
|
|
return "that message is not tracked. use `track` or `send` command first", True
|
|
|
|
tracked_msg = await DATABASE.get_message(int(args[0]))
|
|
if tracked_msg is None:
|
|
return "could not fetch the message", True
|
|
|
|
my = DATABASE.is_my_message(int(args[0]))
|
|
|
|
if my and len(args) != 4:
|
|
return "that's MY message and i want a description for it", True
|
|
|
|
role = discord.utils.get(tracked_msg.guild.roles, name=args[2])
|
|
if role is None:
|
|
if args[2].isdigit():
|
|
role = tracked_msg.guild.get_role(int(args[2]))
|
|
else:
|
|
return "not a role", True
|
|
|
|
if args[1].isdigit():
|
|
args[1] = bot.get_emoji(int(args[1]))
|
|
if args[1] is None:
|
|
return "emoji not found", True
|
|
args[1] = str(args[1])
|
|
|
|
if DATABASE.add_reaction_role(int(args[0]), args[1], role.id, args[3] if len(args) == 4 else ""):
|
|
return "that emoji already has a reaction role on this message", True
|
|
|
|
try:
|
|
await tracked_msg.add_reaction(args[1])
|
|
except discord.NotFound:
|
|
return "invalid emoji", True
|
|
except discord.Forbidden:
|
|
return "missing permissions to add reactions", True
|
|
except discord.HTTPException as e:
|
|
return f"failed to add reaction: {e}", True
|
|
|
|
if my:
|
|
await tracked_msg.edit(content=DATABASE.format_message(int(args[0])))
|
|
|
|
return f"successfully added reaction role: {args[1]} -> {role.name}" + (f" ({args[3]})" if len(args) == 4 else ""), True
|
|
|
|
|
|
@register("remove", 2)
|
|
async def remove(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if len(args) != 2:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if not DATABASE.is_tracked_already(int(args[0])):
|
|
return "that message is not tracked", True
|
|
|
|
if args[1].isdigit():
|
|
args[1] = bot.get_emoji(int(args[1]))
|
|
if args[1] is None:
|
|
return "emoji not found", True
|
|
args[1] = str(args[1])
|
|
|
|
reaction_role = DATABASE.get_reaction_role(int(args[0]), args[1])
|
|
if reaction_role is None:
|
|
return "no reaction role found with that emoji on this message", True
|
|
|
|
tracked_msg = await DATABASE.get_message(int(args[0]))
|
|
if tracked_msg is None:
|
|
return "could not fetch the message", True
|
|
|
|
try:
|
|
await tracked_msg.clear_reaction(args[1])
|
|
except discord.Forbidden:
|
|
return "missing permissions to remove reactions", True
|
|
except discord.NotFound:
|
|
pass
|
|
except discord.HTTPException as e:
|
|
return f"failed to remove reaction: {e}", True
|
|
|
|
DATABASE.remove_reaction_role(int(args[0]), args[1])
|
|
|
|
if DATABASE.is_my_message(int(args[0])):
|
|
await tracked_msg.edit(content=DATABASE.format_message(int(args[0])))
|
|
|
|
return f"successfully removed reaction role: {args[1]}", True
|
|
|
|
|
|
@register("delete", 1)
|
|
async def delete(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if len(args) != 1:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if not DATABASE.is_tracked_already(int(args[0])):
|
|
return "that message is not tracked", True
|
|
|
|
if not DATABASE.is_my_message(int(args[0])):
|
|
return "i can only delete messages that i own (messages created with the `send` command)", True
|
|
|
|
tracked_msg = await DATABASE.get_message(int(args[0]))
|
|
|
|
if tracked_msg:
|
|
try:
|
|
await tracked_msg.delete()
|
|
except discord.Forbidden:
|
|
return "missing permissions to delete this message", True
|
|
except discord.NotFound:
|
|
pass
|
|
except discord.HTTPException as e:
|
|
return f"failed to delete message: {e}", True
|
|
|
|
DATABASE.untrack_message(int(args[0]))
|
|
|
|
return f"successfully deleted tracked message {int(args[0])}", True
|
|
|
|
|
|
@register("kill yourself", 0)
|
|
async def kill_yourself(args: list[str], message: discord.Message):
|
|
raise Exception("ok")
|
|
|
|
|
|
@register("add this server to emoji storage", 0)
|
|
async def add_this_server_to_emoji_storage(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if message.author.id not in CONFIG.trusted:
|
|
return "no", True
|
|
|
|
if EMOJI_STORAGE.add_emoji_storage(message.guild.id):
|
|
return "already added", True
|
|
|
|
sent_message = await message.channel.send("ok")
|
|
EMOJI_STORAGE.set_emoji_message(sent_message.guild.id, sent_message.channel.id, sent_message.id)
|
|
return "should've worked", True
|
|
|
|
|
|
@register("addemoji", 1)
|
|
async def addemoji(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if message.guild.id != 1480697029780045875:
|
|
return "this command cannot be ran in this server", True
|
|
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if len(args) != 1:
|
|
return "i dont understand this command", True
|
|
|
|
if len(message.attachments) != 1:
|
|
return "nothing to add", True
|
|
|
|
attachment = message.attachments[0]
|
|
b: bytes = await attachment.read()
|
|
emoji = await EMOJI_STORAGE.try_add_emoji(args[0], b)
|
|
if emoji is None:
|
|
return "failed to add emoji", True
|
|
|
|
if args[0] == "arch":
|
|
await message.channel.send("should've alpined")
|
|
return f"added {emoji} with id {emoji.id}", False
|
|
|
|
|
|
@register("editdesc", 3)
|
|
async def editdesc(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if len(args) != 3:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if not DATABASE.is_tracked_already(int(args[0])):
|
|
return "that message is not tracked. use `track` or `send` command first", True
|
|
|
|
tracked_msg = await DATABASE.get_message(int(args[0]))
|
|
if tracked_msg is None:
|
|
return "could not fetch the message", True
|
|
|
|
my = DATABASE.is_my_message(int(args[0]))
|
|
if not my:
|
|
return "can't help it it's not my message", True
|
|
|
|
if args[1].isdigit():
|
|
args[1] = bot.get_emoji(int(args[1]))
|
|
if args[1] is None:
|
|
return "emoji not found", True
|
|
args[1] = str(args[1])
|
|
|
|
result = DATABASE.edit_reaction_role_description(int(args[0]), args[1], args[2])
|
|
if result is None:
|
|
return "reaction role not found", True
|
|
await tracked_msg.edit(content=DATABASE.format_message(int(args[0])))
|
|
return f"changed reaction role description from {result} to {args[2]}", True
|
|
|
|
|
|
|
|
@register("edithdr", 2)
|
|
async def edithdr(args: list[str], message: discord.Message) -> tuple[str, bool]:
|
|
if not message.author.guild_permissions.manage_guild:
|
|
return "no permission", True
|
|
|
|
if len(args) != 2:
|
|
return "i dont understand your command", True
|
|
|
|
if not args[0].isdigit():
|
|
return "not a valid message id", True
|
|
|
|
if not DATABASE.is_tracked_already(int(args[0])):
|
|
return "that message is not tracked. use `track` or `send` command first", True
|
|
|
|
tracked_msg = await DATABASE.get_message(int(args[0]))
|
|
if tracked_msg is None:
|
|
return "could not fetch the message", True
|
|
|
|
my = DATABASE.is_my_message(int(args[0]))
|
|
if not my:
|
|
return "can't help it it's not my message", True
|
|
|
|
DATABASE.edit_header(int(args[0]), args[1])
|
|
await tracked_msg.edit(content=DATABASE.format_message(int(args[0])))
|
|
return "i have done the thing", True
|
|
|
|
|
|
async def handle_command(msg: str, message: discord.Message) -> tuple[str | discord.Embed, bool]:
|
|
for reg in registry:
|
|
if msg.lower().startswith(reg[0] + ' '):
|
|
return await (reg[1])(
|
|
msg.removeprefix(reg[0] + ' ').split(' ', max(0, reg[2] - 1)) if msg != reg[0] else [],
|
|
message
|
|
)
|
|
|
|
return "i dont know this command", True
|
|
|
|
|
|
@bot.event
|
|
async def on_message(message: discord.Message):
|
|
try:
|
|
msg = message.content
|
|
if not msg.lower().startswith(CONFIG.prefix):
|
|
return
|
|
|
|
result, d = await handle_command(msg.removeprefix(CONFIG.prefix), message)
|
|
|
|
if isinstance(result, str):
|
|
if d:
|
|
await message.reply(result, delete_after=60)
|
|
else:
|
|
await message.reply(result)
|
|
elif isinstance(result, discord.Embed):
|
|
await message.reply(embed=result)
|
|
except Exception as e:
|
|
await message.reply("```py\n" + '\n'.join(traceback.format_exception(e)) + "\n```")
|
|
|
|
|
|
@bot.event
|
|
async def on_raw_reaction_add(payload: discord.RawReactionActionEvent):
|
|
if payload.user_id == bot.user.id:
|
|
return
|
|
|
|
reaction_role = DATABASE.get_reaction_role(payload.message_id, str(payload.emoji))
|
|
if reaction_role is None:
|
|
return
|
|
|
|
guild = bot.get_guild(payload.guild_id)
|
|
if guild is None:
|
|
return
|
|
|
|
member = guild.get_member(payload.user_id)
|
|
if member is None:
|
|
return
|
|
|
|
role = guild.get_role(reaction_role.role_id)
|
|
if role is None:
|
|
return
|
|
|
|
try:
|
|
await member.add_roles(role, reason="reaction role")
|
|
except discord.Forbidden:
|
|
print(f"missing permissions to add role {role.name} to {member.name}")
|
|
except discord.HTTPException as e:
|
|
print(f"failed to add role: {e}")
|
|
|
|
|
|
@bot.event
|
|
async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent):
|
|
if payload.user_id == bot.user.id:
|
|
return
|
|
|
|
reaction_role = DATABASE.get_reaction_role(payload.message_id, str(payload.emoji))
|
|
if reaction_role is None:
|
|
return
|
|
|
|
guild = bot.get_guild(payload.guild_id)
|
|
if guild is None:
|
|
return
|
|
|
|
member = guild.get_member(payload.user_id)
|
|
if member is None:
|
|
return
|
|
|
|
role = guild.get_role(reaction_role.role_id)
|
|
if role is None:
|
|
return
|
|
|
|
try:
|
|
await member.remove_roles(role, reason="reaction role")
|
|
except discord.Forbidden:
|
|
print(f"missing permissions to remove role {role.name} from {member.name}")
|
|
except discord.HTTPException as e:
|
|
print(f"failed to remove role: {e}")
|
|
|
|
|
|
@bot.tree.command(name="help", description="this description is here to help you use your help hope it helps")
|
|
async def help_slash_command(interaction: discord.Interaction):
|
|
await interaction.response.send_message(embed=discord.Embed(
|
|
title="reaction bot",
|
|
description=rf""" made by tema5002
|
|
hosted by `[insert your name here at line {__import__('inspect').currentframe().f_lineno} at {__import__('os').path.abspath(__import__('os').path.abspath(__file__))}]`
|
|
|
|
"rc" stands for reaction clanker
|
|
|
|
Usage:
|
|
- {CONFIG.prefix}show message-id: Shows info about the message
|
|
- {CONFIG.prefix}send channel-id header (optional): Sends a message with `header` as it's top contents to be used for reaction roles
|
|
- {CONFIG.prefix}track channel-id message-id: Track an existing message for reaction roles
|
|
- {CONFIG.prefix}add message-id emoji/emoji-id role-id/role-name description: Adds a reaction role to message with `message-id` displayed as `emoji` `description` that gives you `role`
|
|
- {CONFIG.prefix}remove message-id;emoji: Removes a reaction role from a tracked message
|
|
- {CONFIG.prefix}delete message-id: Deletes a tracked message that the bot owns
|
|
- {CONFIG.prefix}addemoji emoji-name + attachment: Adds emoji to the emoji storage. Only available for the "fuck alpinedevs,systemd,... server"
|
|
- {CONFIG.prefix}editdesc message-id emoji header: Edit description for a reaction roles emoji
|
|
- {CONFIG.prefix}edithdr message-id header: Edit header for a reaction roles message""",
|
|
color=discord.Color(0xa2d2df)
|
|
))
|
|
|
|
CONFIG.run_bot()
|