From 91c1312d35651bc0b879b74f50cf3eb2cbf5a995 Mon Sep 17 00:00:00 2001 From: dnns01 <git@dnns01.de> Date: Mon, 19 Dec 2022 17:17:20 +0100 Subject: [PATCH] Remove old stuff and update schedule to post a weekly calendar --- .gitignore | 1 + armin.py | 35 --------- extensions/schedule.py | 165 +++++++++++++++++++++++++++++++++++++++++ leaderboard.py | 73 ------------------ models.py | 41 ++++++++++ poll_cog.py | 118 ----------------------------- requirements.txt | 23 +++--- roll_cog.py | 19 ----- schedule.py | 69 ----------------- strolly.py | 42 ++++------- utils.py | 24 ------ 11 files changed, 233 insertions(+), 377 deletions(-) delete mode 100644 armin.py create mode 100644 extensions/schedule.py delete mode 100644 leaderboard.py create mode 100644 models.py delete mode 100644 poll_cog.py delete mode 100644 roll_cog.py delete mode 100644 schedule.py delete mode 100644 utils.py diff --git a/.gitignore b/.gitignore index 8512892..e67bb08 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,4 @@ GitHub.sublime-settings /news.json /highscores.json /schedule.json +/strolly.db diff --git a/armin.py b/armin.py deleted file mode 100644 index 357a087..0000000 --- a/armin.py +++ /dev/null @@ -1,35 +0,0 @@ -import random -from datetime import datetime, timedelta - -from discord.ext import commands - - -class Armin(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.a = ["ein", "zwei", "drei", "vier", "fünf", "sechs"] - self.b = ["tägige", "wöchige", "monatige", "fache", "malige", "hebige"] - self.c = ["harte", "softe", "optionale", "intransparente", "alternativlose", "unumkehrbare"] - self.d = ["Wellenbrecher-", "Brücken-", "Treppen-", "Wende-", "Impf-", "Ehren-"] - self.e = ["Lockdown", "Stopp", "Maßnahme", "Kampagne", "Sprint", "Matrix"] - self.f = ["zum Sommer", "auf Weiteres", "zur Bundestagswahl", "2030", "nach den Abiturprüfungen", - "in die Puppen"] - self.g = ["sofortigen", "nachhaltigen", "allmählichen", "unausweichlichen", "wirtschaftsschonenden", - "willkürlichen"] - self.h = ["Senkung", "Steigerung", "Beendigung", "Halbierung", "Vernichtung", "Beschönigung"] - self.i = ["Infektionszahlen", "privaten Treffen", "Wirtschaftsleistung", "Wahlprognosen", "dritten Welle", - "Bundeskanzlerin"] - self.arminsagt_cooldown = datetime.now() - - @commands.command(name="arminsagt") - async def cmd_arminsagt(self, ctx): - if datetime.now() > self.arminsagt_cooldown: - self.arminsagt_cooldown = datetime.now() + timedelta(minutes=1) - rNum = random.randint(0, 5) - n = "n" if rNum not in [2, 3, 5] else "" - await ctx.send(f"Was wir jetzt brauchen, ist eine{n} {random.choice(self.a)}{random.choice(self.b)}{n} " - f"{random.choice(self.c)}{n} {random.choice(self.d)}{self.e[rNum]} " - f"bis {random.choice(self.f)} zur {random.choice(self.g)} {random.choice(self.h)} " - f"der {random.choice(self.i)}.") - else: - await ctx.send("Sorry, aber Armin denkt noch nach...") diff --git a/extensions/schedule.py b/extensions/schedule.py new file mode 100644 index 0000000..d02487f --- /dev/null +++ b/extensions/schedule.py @@ -0,0 +1,165 @@ +import base64 +import json +import os +from datetime import datetime, timedelta + +import discord +from discord import app_commands, Interaction +from discord.ext import commands, tasks +from twitchio import Client + +from models import ScheduleSegment, TwitchChannel, Schedule + + +def get_calendar_week(): + now = datetime.now() + calendar = now.isocalendar() + + if calendar.weekday < 7 or now.hour < 12: + start_day = now - timedelta(days=calendar.weekday - 1) + return calendar.week, datetime(year=start_day.year, month=start_day.month, day=start_day.day) + + start_day = now + timedelta(days=1) + return start_day.isocalendar().week, datetime(year=start_day.year, month=start_day.month, day=start_day.day) + + +def get_weekday(curr_day): + weekdays = ["", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] + return weekdays[curr_day.isocalendar().weekday] + + +def remove_cancelled_streams(user, segments): + segments_dict = {segment.id: segment for segment in segments} + + for schedule_segment in ScheduleSegment.select().where(ScheduleSegment.channel == user.id).where( + ScheduleSegment.start_time > datetime.now()): + if schedule_segment.id not in segments_dict: + schedule_segment.delete_instance() + + +@app_commands.guild_only +@app_commands.default_permissions(manage_server=True) +class TwitchSchedule(commands.GroupCog, name="schedule"): + def __init__(self, bot): + self.bot = bot + self.twitch_client = Client.from_client_credentials(client_id=os.getenv("TWITCH_CLIENT_ID"), + client_secret=os.getenv("TWITCH_CLIENT_SECRET")) + self.update_task.start() + + @app_commands.command(name="add", description="Add Twitch channel to schedule") + @app_commands.describe(twitch_channel="Twitch channel to add", + emoji="Emoji to be used for this channels entries in schedule", + update_schedule="Define whether schedule should be updated or not.") + async def cmd_add(self, interaction: Interaction, twitch_channel: str, emoji: str, update_schedule: bool = False): + await interaction.response.defer(ephemeral=True) + user = await self.twitch_client.fetch_users(names=[twitch_channel]) + if len(user) != 1: + await interaction.edit_original_response( + content="Twitch Kanal nicht gefunden. Bitte überprüfe, ob du den Kanal richtig geschrieben hast.") + return + + if TwitchChannel.get_or_none(TwitchChannel.user_id == user[0].id): + await interaction.edit_original_response( + content="Der angegebene Kanal ist schon Teil des Kalenders.") + return + + TwitchChannel.create(user_id=user[0].id, emoji=emoji, display_name=user[0].display_name) + await interaction.edit_original_response( + content=f"Twitch Kanal {twitch_channel} erfolgreich hinzugefügt.") + if update_schedule: + await self.update_schedule() + + @app_commands.command(name="remove", description="Remove Twitch Channel from Schedule") + @app_commands.describe(twitch_channel="Twitch Channel to remove", + update_schedule="Define whether Schedule should be updated or not.") + async def cmd_remove(self, interaction: Interaction, twitch_channel: str, update_schedule: bool = False): + await interaction.response.defer(ephemeral=True) + user = await self.twitch_client.fetch_users(names=[twitch_channel]) + if len(user) != 1: + await interaction.edit_original_response( + content="Twitch Kanal nicht gefunden. Bitte überprüfe, ob du den Kanal richtig geschrieben hast.") + return + + if channel := TwitchChannel.get_or_none(TwitchChannel.user_id == user[0].id): + channel.delete_instance(recursive=True) + await interaction.edit_original_response( + content=f"Twitch Kanal {twitch_channel} erfolgreich entfernt.") + if update_schedule: + await self.update_schedule() + return + + await interaction.edit_original_response( + content=f"Twitch Kanal ist kein Teil des Kalenders.") + + @app_commands.command(name="update", description="Force to update the schedule") + async def cmd_update(self, interaction: Interaction): + await interaction.response.defer(ephemeral=True) + await self.update_schedule() + await interaction.edit_original_response(content="Update abgeschlossen.") + + @tasks.loop(hours=1) + async def update_task(self): + await self.update_schedule() + + async def update_schedule(self): + await self.update_database() + calendar_week, start_day = get_calendar_week() + end_day = start_day + timedelta(days=6) + + schedule_channel = await self.bot.fetch_channel(int(os.getenv("SCHEDULE_CHANNEL"))) + + week_schedule = Schedule.get_or_none(calendar_week=calendar_week, calendar_year=start_day.year) + embed = discord.Embed(title=f"Contentvorhersage für die {calendar_week}. Kalenderwoche", + description=f"Strolchige Streams in der Woche vom {start_day.strftime('%d.%m.%Y')} " + f"bis {end_day.strftime('%d.%m.%Y')}") + embed.set_thumbnail(url="https://strolchibot.dnns01.dev/static/images/logo.png") + + curr_day = start_day + while curr_day <= end_day: + name = f"{get_weekday(curr_day)} {curr_day.strftime('%d.%m.%Y')}" + value = "" + for segment in ScheduleSegment.select() \ + .where(ScheduleSegment.start_time.between(curr_day, (curr_day + timedelta(days=1)))) \ + .order_by(ScheduleSegment.start_time): + value += f"{segment.channel.emoji} {segment.timeframe()}\n{segment.title}\n\n" + + if len(value) > 0: + embed.add_field(name=name, value=value, inline=False) + curr_day += timedelta(days=1) + + if week_schedule.message_id: + try: + message = await schedule_channel.fetch_message(week_schedule.message_id) + await message.edit(embed=embed) + return + except: + pass + + message = await schedule_channel.send("", embed=embed) + week_schedule.update(message_id=message.id).where(Schedule.id == week_schedule.id).execute() + + async def update_database(self): + twitch_channels = [twitch_channel.user_id for twitch_channel in TwitchChannel.select()] + for user in await self.twitch_client.fetch_users(ids=twitch_channels): + schedule = await user.fetch_schedule() + for segment in schedule.segments: + segment_id = json.loads(base64.b64decode(segment.id)) + calendar_year = segment_id["isoYear"] + calendar_week = segment_id["isoWeek"] + week_schedule = Schedule.get_or_create(calendar_week=calendar_week, calendar_year=calendar_year) + if segment.start_time < datetime.now().astimezone(): + continue + if schedule_segment := ScheduleSegment.get_or_none( + ScheduleSegment.id == segment.id): + schedule_segment.update(start_time=segment.start_time, end_time=segment.end_time, + title=segment.title, schedule=week_schedule[0].id) \ + .where(ScheduleSegment.id == segment.id).execute() + else: + ScheduleSegment.create(id=segment.id, start_time=segment.start_time, end_time=segment.end_time, + title=segment.title, channel=user.id, schedule=week_schedule[0].id) + + remove_cancelled_streams(user, schedule.segments) + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(TwitchSchedule(bot)) diff --git a/leaderboard.py b/leaderboard.py deleted file mode 100644 index b4bc893..0000000 --- a/leaderboard.py +++ /dev/null @@ -1,73 +0,0 @@ -import json - -import discord -from discord.ext import commands - - -class Leaderboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.highscores = self.load() - - def load(self): - """ Load highscores from json file """ - with open("highscores.json", mode="r") as highscore_file: - return json.load(highscore_file) - - def save(self): - """ Save highscores to json file """ - with open("highscores.json", mode="w") as highscore_file: - json.dump(self.highscores, highscore_file) - - @commands.command(name="highscore") - async def cmd_highscore(self, ctx, score: int): - """ Add highscore for Dorfromantik leaderboard """ - - if score > 50: - if highscore := self.highscores.get(str(ctx.author.id)): - self.highscores[str(ctx.author.id)] = max(highscore, score) - else: - self.highscores[str(ctx.author.id)] = score - self.save() - - await ctx.send( - f"Vielen Dank für deine Einreichung. Du bist damit auf Platz {self.get_place(ctx.author.id)} der Rangliste gelandet.") - - @commands.command(name="romantikboard", aliases=["dorfpranger"]) - async def cmd_romantikboard(self, ctx, all=None): - - embed = discord.Embed(title="Dorfromantik Leaderboard", - description="Offizielles inoffizielles Leaderborad des kultigen Karten-Lege-Spiels Dorfromantik der geilsten Powercommunity! Highscores, die HIER nicht eingetragen sind, zählen nicht!") - embed.set_thumbnail(url="https://img.itch.zone/aW1nLzQ2ODEyMTUuanBn/original/SVutRj.jpg") - - places = scores = "" - place = 0 - max = 0 if all == "all" else 10 - ready = False - for key, value in sorted(self.highscores.items(), key=lambda item: item[1], reverse=True): - try: - place += 1 - - if 0 < max < place: - if ready: - break - elif str(ctx.author.id) != key: - continue - places += f"{place}: <@!{key}>\n" - scores += f"{value:,}\n".replace(",", ".") - - if str(ctx.author.id) == key: - ready = True - except: - pass - - embed.add_field(name=f"Romantiker", value=places) - embed.add_field(name=f"Punkte", value=scores) - await ctx.send("", embed=embed) - - def get_place(self, id): - place = 0 - for key, value in sorted(self.highscores.items(), key=lambda item: item[1], reverse=True): - place += 1 - if key == str(id): - return place diff --git a/models.py b/models.py new file mode 100644 index 0000000..9428adb --- /dev/null +++ b/models.py @@ -0,0 +1,41 @@ +from datetime import datetime, timedelta + +from peewee import * + +db = SqliteDatabase("strolly.db") + + +class BaseModel(Model): + class Meta: + database = db + + +class TwitchChannel(BaseModel): + user_id = IntegerField(primary_key=True) + display_name = CharField() + emoji = CharField() + + +class Schedule(BaseModel): + calendar_year = IntegerField() + calendar_week = IntegerField() + message_id = IntegerField(null=True) + + +class ScheduleSegment(BaseModel): + id = CharField(primary_key=True) + start_time = DateTimeField() + end_time = DateTimeField(null=True) + title = CharField() + channel = ForeignKeyField(TwitchChannel, backref="schedule_segments") + schedule = ForeignKeyField(Schedule, backref="schedule_segments") + + def timeframe(self): + tf = f"<t:{int(datetime.fromisoformat(self.start_time).timestamp())}:t> - " + end_time = datetime.fromisoformat(self.end_time) if self.end_time else datetime.fromisoformat( + self.start_time) + timedelta(hours=4) + tf += f"<t:{int(end_time.timestamp())}:t>" + return tf + + +db.create_tables([TwitchChannel, Schedule, ScheduleSegment], safe=True) diff --git a/poll_cog.py b/poll_cog.py deleted file mode 100644 index 50fc600..0000000 --- a/poll_cog.py +++ /dev/null @@ -1,118 +0,0 @@ -import discord -from discord.ext import commands - -OPTIONS = ["🇦", "🇧", "🇨", "🇩", "🇪", "🇫", "🇬", "ðŸ‡", "🇮", "🇯", "🇰", "🇱", "🇲", "🇳", "🇴", "🇵", "🇶", "🇷"] - - -class PollCog(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.command(name="poll") - async def cmd_poll(self, ctx, question, *answers): - """ Create poll """ - - await Poll(self.bot, question, answers, ctx.author.id).send_poll(ctx) - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload): - if payload.user_id == self.bot.user.id: - return - - if payload.emoji.name in ["🗑ï¸", "🛑"]: - channel = await self.bot.fetch_channel(payload.channel_id) - message = await channel.fetch_message(payload.message_id) - if len(message.embeds) > 0 and message.embeds[0].title == "Umfrage": - poll = Poll(self.bot, message=message) - if str(payload.user_id) == poll.author: - if payload.emoji.name == "🗑ï¸": - await poll.delete_poll() - else: - await poll.close_poll() - - -class Poll: - - def __init__(self, bot, question=None, answers=None, author=None, message=None): - self.bot = bot - self.question = question - self.answers = answers - self.author = author - - if message: - self.message = message - self.answers = [] - embed = message.embeds[0] - self.author = embed.fields[0].value[3:-1] - self.question = embed.description - for i in range(2, len(embed.fields)): - self.answers.append(embed.fields[i].value) - - async def send_poll(self, channel, result=False, message=None): - option_ctr = 0 - title = "Umfrage" - - if result: - title += " Ergebnis" - - if len(self.answers) > len(OPTIONS): - await channel.send( - f"Fehler beim Erstellen der Umfrage! Es werden nicht mehr als {len(OPTIONS)} Optionen unterstützt!") - return - - embed = discord.Embed(title=title, description=self.question) - embed.add_field(name="Erstellt von", value=f'<@!{self.author}>', inline=False) - embed.add_field(name="\u200b", value="\u200b", inline=False) - - for i in range(0, len(self.answers)): - name = f'{OPTIONS[i]}' - value = f'{self.answers[i]}' - - if result: - reaction = self.get_reaction(name) - if reaction: - name += f' : {reaction.count - 1}' - # value += f'\nStimmen: ' - - # async for user in reaction.users(): - # if self.bot.user == user: - # continue - # ping = f'<@!{str(user.id)}> ' - # - # if len(value) + len(ping) > 1024: - # embed.add_field(name=name, value=value, inline=False) - # answer = f'' - # name = "\u200b" - # elif - # - # value += ping - - embed.add_field(name=name, value=value, inline=False) - option_ctr += 1 - - if message: - await message.edit(embed=embed) - else: - message = await channel.send("", embed=embed) - - if not result: - for i in range(0, len(self.answers)): - await message.add_reaction(OPTIONS[i]) - - await message.add_reaction("🗑ï¸") - await message.add_reaction("🛑") - - async def close_poll(self): - await self.send_poll(self.message.channel, result=True) - await self.delete_poll() - - async def delete_poll(self): - await self.message.delete() - - def get_reaction(self, reaction): - if self.message: - reactions = self.message.reactions - - for react in reactions: - if react.emoji == reaction: - return react diff --git a/requirements.txt b/requirements.txt index 07ab343..4f3f15e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,13 @@ -aiohttp==3.7.4.post0 -async-timeout==3.0.1 -attrs==20.3.0 -chardet==4.0.0 -discord.py==1.6.0 -idna==3.1 -multidict==5.1.0 -python-dotenv==0.16.0 -typing-extensions==3.7.4.3 -yarl==1.6.3 +aiohttp==3.8.3 +aiosignal==1.3.1 +async-timeout==4.0.2 +attrs==22.1.0 +charset-normalizer==2.1.1 +discord.py==2.1.0 +frozenlist==1.3.3 +idna==3.4 +multidict==6.0.3 +peewee==3.15.4 +python-dotenv==0.21.0 +yarl==1.8.2 +twitchio \ No newline at end of file diff --git a/roll_cog.py b/roll_cog.py deleted file mode 100644 index 6bf3111..0000000 --- a/roll_cog.py +++ /dev/null @@ -1,19 +0,0 @@ -import random - -from discord.ext import commands - - -class RollCog(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.command(name="roll") - async def cmd_roll(self, ctx, dice="w6", qty=1): - """ Roll a/multiple dice """ - - eyes = int(dice[1:]) - answer = f"Es wurden {qty} {dice.upper()} geworfen, mit folgenden Ergebnissen:\n" - for i in range(qty): - answer += f"{i + 1}. Wurf: {random.randrange(1, eyes + 1)}\n" - - await ctx.send(answer) diff --git a/schedule.py b/schedule.py deleted file mode 100644 index 5e0f8e8..0000000 --- a/schedule.py +++ /dev/null @@ -1,69 +0,0 @@ -import json -import os -from datetime import datetime - -import discord -from aiohttp import ClientSession -from discord.ext import commands, tasks - - -async def get_schedule(): - async with ClientSession() as session: - auth = "kimne78kx3ncx6brgo4mv6wki5h1ko" - headers = {"client-id": f"{auth}", "Content-Type": "application/json"} - - async with session.post("https://gql.twitch.tv/gql", - headers=headers, - json={ - "query": "query {\r\n channel(name: \"indiestrolche\") {\r\n schedule {\r\n segments(includeFutureSegments: true) {\r\n id\r\n startAt\r\n title\r\n categories {\r\n displayName\r\n boxArtURL\r\n }\r\n }\r\n }\r\n }\r\n}"}) as r: - if r.status == 200: - return {segment["id"]: segment for segment in - (await r.json())["data"]["channel"]["schedule"]["segments"]} - return None - - -class Schedule(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.update_schedule.start() - self.schedule_file = "schedule.json" - self.schedule = self.load() - - def load(self): - schedule_file = open(self.schedule_file, mode="r") - return json.load(schedule_file) - - def save(self): - schedule_file = open(self.schedule_file, mode="w") - json.dump(self.schedule, schedule_file) - - @tasks.loop(hours=1) - async def update_schedule(self): - new_schedule = await get_schedule() - - for id, segment in new_schedule.items(): - if id in self.schedule.keys(): - old_segment = self.schedule.get(id) - if old_segment["startAt"] != segment["startAt"]: - self.schedule[id] = segment - await self.announce_segment(segment, new=False) - else: - self.schedule[id] = segment - await self.announce_segment(segment) - self.save() - - async def announce_segment(self, segment, new=True): - channel = await self.bot.fetch_channel(int(os.getenv("DURCHSAGEN_CHANNEL"))) - start_at = datetime.fromisoformat(f"{segment['startAt'][:-1]}+00:00").astimezone().strftime("%d.%m.%Y %H:%M") - title = "<:ja:836282702248411217> <:aa:836282738709233675> <:aa:836282738709233675> <:aa:836282738709233675> <:aa:836282738709233675>" if new else "Achtung Leute aufgepasst!!!" - description = "Wie geil ist es? Ein neuer Stream ist in den Kalender geglitten\n" if new else "Es gibt eine kleine Änderung im Programmablauf!\n" - game = "Lass dich einfach überraschen!" - url = "https://static-cdn.jtvnw.net/ttv-static/404_boxart-144x192.jpg" - if categories := segment.get("categories"): - game = categories[0]['displayName'] - url = categories[0]['boxArtURL'].replace('-{width}x{height}', '').replace("/./", "/") - embed = discord.Embed(title=title, description=description) - embed.set_thumbnail(url=url) - embed.add_field(name=segment["title"], value=game) - embed.add_field(name="Wann?", value=start_at) - await channel.send(embed=embed) diff --git a/strolly.py b/strolly.py index a77318e..aff9bd3 100644 --- a/strolly.py +++ b/strolly.py @@ -1,42 +1,26 @@ import os +from typing import List import discord from discord.ext import commands from dotenv import load_dotenv -from armin import Armin -from leaderboard import Leaderboard -from poll_cog import PollCog -from roll_cog import RollCog -from schedule import Schedule - # .env file is necessary in the same directory, that contains several strings. load_dotenv() -TOKEN = os.getenv('DISCORD_TOKEN') -ACTIVITY = os.getenv('DISCORD_ACTIVITY') - -intents = discord.Intents.all() -bot = commands.Bot(command_prefix='!', help_command=None, activity=discord.Game(ACTIVITY), intents=intents) -bot.add_cog(PollCog(bot)) -bot.add_cog(RollCog(bot)) -bot.add_cog(Leaderboard(bot)) -bot.add_cog(Armin(bot)) -bot.add_cog(Schedule(bot)) - -@bot.event -async def on_ready(): - print("Client started!") +class Strolly(commands.Bot): + def __init__(self, *args, initial_extensions: List[str], **kwargs): + super(Strolly, self).__init__(*args, **kwargs) + self.initial_extensions: List[str] = initial_extensions -@bot.event -async def on_member_update(before, after): - if before.id == 250613346653569025: - if after.activity and "Blender" in after.activity.name: - if not before.activity or (before.activity and "Blender" not in before.activity.name): - channel = await bot.fetch_channel(811980578621620235) - await channel.send( - "Achtung <@!490167202625093634>!!! Er tut es schon wieder! Marcus hat gerade die Kultsoftware Blender gestartet!") + async def setup_hook(self) -> None: + for extension in self.initial_extensions: + await self.load_extension(f"extensions.{extension}") + await self.tree.sync() -bot.run(TOKEN) +extensions = ["schedule"] +bot = Strolly(command_prefix="!", help_command=None, activity=discord.Game(os.getenv('DISCORD_ACTIVITY')), + intents=discord.Intents.all(), initial_extensions=extensions) +bot.run(os.getenv('DISCORD_TOKEN')) diff --git a/utils.py b/utils.py deleted file mode 100644 index 74c7dc5..0000000 --- a/utils.py +++ /dev/null @@ -1,24 +0,0 @@ -import os - -import discord - - -async def send_dm(user, message, embed=None): - """ Send DM to a user/member """ - - if type(user) is discord.User or type(user) is discord.Member: - if user.dm_channel is None: - await user.create_dm() - - await user.dm_channel.send(message, embed=embed) - - -def is_mod(ctx): - author = ctx.author - roles = author.roles - - for role in roles: - if role.id == int(os.getenv("DISCORD_MOD_ROLE")): - return True - - return False -- GitLab