Compare commits

..

52 commits

Author SHA1 Message Date
99e7d90185
changed all instances of SeaswimmerTheFsh to cswimr
All checks were successful
Actions / Lint Code (Pylint) (push) Successful in 45s
2024-09-16 09:20:13 -04:00
a81f578756
fix(actions): changed workflow container tag 2024-08-27 15:34:49 -04:00
32e1ffd942
feat(poetry): updated deps
Some checks failed
Actions / Lint Code (Pylint) (push) Failing after 46s
2024-08-27 15:34:00 -04:00
a5b9cffcaf
removed [p]nslookup command
Some checks failed
Actions / Lint Code (Pylint) (push) Failing after 35s
use https://www.coastalcommits.com/Seaswimmer/SeaCogs/src/branch/main/seautils instead, that uses dig instead of nslookup and looks far nicer
2024-05-30 23:53:37 +00:00
40fdf469e6
fix(repo): poetry fixes
All checks were successful
Actions / Lint Code (Pylint) (push) Successful in 39s
2024-04-11 17:22:55 -04:00
6e44fd9235
feat(workflow): changed workflow from the old manual installs to using a docker image for faster runs
All checks were successful
Actions / Lint Code (Pylint) (push) Successful in 22s
2024-01-29 15:29:50 -05:00
435cc2d72d
fix(shortmute): fixed using incorrect context for fetching embed colors
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 1m44s
2024-01-29 15:12:43 -05:00
f7be30dcd8
fix(youtubedownloader): fixed file not found error if the video name includes / or \
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 1m50s
2024-01-29 12:18:31 -05:00
4e4c16f278
repo(moderation): moving moderation cog to seacogs
All checks were successful
Pylint / Pylint (3.10) (push) Successful in 50s
2023-12-14 18:33:47 -05:00
be11b12517
fix(moderation): added messages to the import buttons
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-14 18:03:30 -05:00
51d4dbb4cb
fix(moderation): guild should be a parent of history, not moderationset
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 55s
2023-12-14 17:27:20 -05:00
88d6d86522
fix(moderation): fixed import group
Some checks failed
Pylint / Pylint (3.10) (push) Has been cancelled
2023-12-14 17:26:31 -05:00
c99d8c5bf1
fix(moderation): fixed moderationset
Some checks failed
Pylint / Pylint (3.10) (push) Has been cancelled
2023-12-14 17:26:06 -05:00
fa2c7d5b9c
feat(moderation): added default setting configuration for the history command
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-14 17:23:25 -05:00
b27397c040
fix(moderation): make inline default to a pagesize of 6
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-14 17:05:21 -05:00
47d359718c
misc(moderation): added docstring for inline argument
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-14 17:02:51 -05:00
24f933f6f6
feat(moderation): add a bool value for inline fields
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-14 17:01:02 -05:00
e411e4b942
misc(moderation): revert inline fields in history command
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-14 16:57:00 -05:00
b435441356
fix(moderation): use json instead of a list object
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 52s
2023-12-14 16:54:58 -05:00
d88986cf0d
fix(moderation): fixed incorrect mysql syntax
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-14 16:51:57 -05:00
1b999f386b
feat(moderation): added timestamps to history embeds
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-14 16:50:01 -05:00
fc0dc8aefe
misc(moderation): improved formatting
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 59s
2023-12-14 16:05:47 -05:00
15ef2216ce
feat(moderation): added changes column to mysql tables
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 56s
2023-12-14 15:50:48 -05:00
adbe4b4d85
fix(moderation): only reduce length of reason in /history if it is actually below 150 characters
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 55s
2023-12-14 14:57:28 -05:00
9cd63ce92b
fix(moderation): added caching for history command
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 56s
2023-12-14 14:41:38 -05:00
5babdb74c2
fix(moderation): fixed formatting in mute command
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 1m0s
2023-12-14 14:26:37 -05:00
be565f9458
fix(moderation): fixed history embeds taking 3 years to process 2023-12-14 14:24:00 -05:00
a67b1f8ccb
fix(moderation): fixed editing the case number into the message
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 51s
2023-12-13 16:59:36 -05:00
7d147bc027
fix(moderation): I forgot about notes 💀
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 52s
2023-12-13 16:55:53 -05:00
a0b77d6fcf
feat(moderation): added case numbers to the moderation response messages
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 52s
2023-12-13 16:54:25 -05:00
f9a9f07b39
fix(moderation): fix malformed sql query
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 16:04:21 -05:00
33ac32c086
fix(moderation): removed case 0 from the export
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 16:01:42 -05:00
dd778587bb
fix(moderation): added encoding type to open() in history command (pylint fix)
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 15:53:08 -05:00
b7193ae2bf
fix(moderation): beautify export json
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-13 15:46:28 -05:00
781b283135
fix(moderation): fixed discord.File
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 51s
2023-12-13 15:45:00 -05:00
b7200210ba
fix(moderation): fixed filename in discord upload function
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 15:43:23 -05:00
9d73e46749
fix(moderation): fixed ephemeral argument's docstring in history 2023-12-13 15:42:28 -05:00
3402ea5553
fix(moderation): fixed filename AGAIN
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 15:41:55 -05:00
d5486aa011
fix(moderation): fixed filename again
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 15:40:39 -05:00
4d97d6722d
fix(moderation): fixed export filename
Some checks failed
Pylint / Pylint (3.10) (push) Has been cancelled
2023-12-13 15:39:53 -05:00
5740ba851f
fix(moderation): export temp writes to a directory it actually has access to
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 55s
2023-12-13 15:38:40 -05:00
6681a7d938
feat(moderation): added ability to export moderation history
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 1m2s
2023-12-13 15:35:17 -05:00
d4e0425e0a
fix(moderation): pylint fixes
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 53s
2023-12-13 15:15:30 -05:00
cb5c1e32eb
fix(moderation): use bool() instead of ==
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-13 15:07:04 -05:00
09d9a5239b
fix(moderation): history now properly checks the epheremal argument
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-13 15:00:36 -05:00
71d7329696
fix(moderation): fixed history command (hopefully)
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 55s
2023-12-13 14:48:44 -05:00
9b68892b67
bunch of goofy/broken changes
Some checks failed
Pylint / Pylint (3.10) (push) Failing after 54s
2023-12-13 03:06:26 -05:00
15d03a944b
modified pylint workflow config 2023-12-13 03:05:37 -05:00
8ca56695e3
fix(youtubedownloader): awaited a corotuine
All checks were successful
Pylint / Pylint (3.10) (push) Successful in 49s
2023-11-26 13:25:24 -05:00
fc9e6fd273
fix(youtubedownloader): fixed blacklist
All checks were successful
Pylint / Pylint (3.10) (push) Successful in 50s
2023-11-26 13:24:02 -05:00
f3fd4a8311
fix(youtubedownloader): fixed erroring when deleting a downloaded file
All checks were successful
Pylint / Pylint (3.10) (push) Successful in 53s
2023-11-26 12:33:52 -05:00
85585d4988
misc(youtubedownloader): delete argument in download command now set to True by default 2023-11-26 12:31:53 -05:00
27 changed files with 1249 additions and 2681 deletions

View file

@ -1,5 +1,6 @@
[MESSAGES CONTROL]
disable=
too-many-lines,
missing-module-docstring,
missing-function-docstring,
missing-class-docstring,

View file

@ -1,30 +1,20 @@
name: Pylint
on: [push]
name: Actions
on:
push:
branches:
- 'main'
pull_request:
jobs:
Pylint:
Lint Code (Pylint):
runs-on: docker
strategy:
matrix:
python-version: ["3.10"]
container: catthehacker/ubuntu:act-latest
container: www.coastalcommits.com/cswimr/actions:galaxycogs
steps:
- name: Checkout
uses: actions/checkout@v3.6.0
with:
token: ${{ secrets.COASTALCOMMITSTOKEN}}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
run: curl -sSL https://cdn.seaswimmer.cc/go/poetry | python${{ matrix.python-version }} -
uses: actions/checkout@v3
- name: Install dependencies
run: |
export PATH="$HOME/.local/bin:$PATH"
poetry env use ${{ matrix.python-version }}
poetry install --with dev
- name: Analysing the code with Pylint
run: |
export PATH="$HOME/.local/bin:$PATH"
poetry run pylint $(git ls-files '*.py')
run: poetry install --with dev --no-root
- name: Analysing code with Pylint
run: pylint --rcfile .forgejo/workflows/config/.pylintrc $(git ls-files '*.py')

View file

@ -1,11 +1,13 @@
import asyncio
import os
import discord
from redbot.core import Config, checks, commands, data_manager
class ExportChannels(commands.Cog):
"""Custom cog to export channels to JSON and HTML formats using Discord Chat Exporter.
Developed by SeaswimmerTheFsh and yname."""
Developed by cswimr and yname."""
def __init__(self, bot):
self.bot = bot

View file

@ -5,7 +5,7 @@ from redbot.core import Config, commands
class Forums(commands.Cog):
"""Custom cog intended for use on the Galaxy discord server.
Developed by SeaswimmerTheFsh."""
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Forums!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Forums!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Forums",
"short" : "Custom cog intended for use on the Galaxy discord server.",
"description" : "Custom cog intended for use on the Galaxy discord server.",

View file

@ -1,15 +1,15 @@
from random import randint
import re
import subprocess
from datetime import datetime
from random import randint
import discord
from redbot.core import Config, app_commands, checks, commands
from redbot.core import Config, app_commands, commands
from redbot.core.app_commands import Choice
class Galaxy(commands.Cog):
"""Custom cog intended for use on the Galaxy discord server.
Developed by SeaswimmerTheFsh."""
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot
@ -19,18 +19,6 @@ class Galaxy(commands.Cog):
autoreact_emoji = '💀'
)
@commands.command()
@checks.is_owner()
async def nslookup(self, ctx: commands.Context, *, website: str):
"""This command uses `nslookup` to check the IP Address of any given website."""
try:
result = subprocess.run(['nslookup', website], capture_output=True, text=True, check=True)
await ctx.send(f"```\n{result.stdout}\n```")
except subprocess.CalledProcessError as e:
await ctx.send(f"Error executing `nslookup`: `{e}`")
except FileNotFoundError:
await ctx.send("`nslookup` command not found. Make sure you have `nslookup` installed and it's in your system PATH.")
@commands.command()
async def carnagerefund(self, ctx: commands.Context, message_id: str):
"""This command generates a link to refund carnage of killed ships."""

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Galaxy!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Galaxy!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Galaxy",
"short" : "Custom cog intended for use on the Galaxy discord server.",
"description" : "Custom cog intended for use on the Galaxy discord server.",

View file

@ -1,8 +1,8 @@
{
"author": [
"SeaswimmerTheFsh, yname, meelyman"
"cswimr, yname, meelyman"
],
"install_msg": "Thanks for installing my repo!\n\nIf you have any issues with any of the cogs, please create an issue here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs/issues.",
"install_msg": "Thanks for installing my repo!\n\nIf you have any issues with any of the cogs, please create an issue here: https://coastalcommits.com/cswimr/GalaxyCogs/issues.",
"name": "Galaxy",
"short": "Cogs intended for use on the Galaxy discord server.",
"description": "Custom cogs/cog modifications intended for the Galaxy discord server."

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Info!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Info!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Info",
"short" : "Provides information on Discord objects.",
"description" : "Provides information on Discord objects. Most of this code is shamelessly ripped from <https://github.com/Cog-Creators/Red-DiscordBot/tree/V3/develop/redbot/cogs>.",

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Issues!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Issues!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Issues",
"short" : "This cog allows you to create Gitea issues through a Discord modal.",
"description" : "This cog allows you to create Gitea issues through a Discord modal.",

View file

@ -1,10 +1,12 @@
import discord
from redbot.core import Config, app_commands, commands, checks
from redbot.core import Config, app_commands, checks, commands
from . import modals
class Issues(commands.Cog):
"""This cog allows you to create Gitea issues through a Discord modal.
Developed by SeaswimmerTheFsh."""
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot
@ -31,8 +33,8 @@ class Issues(commands.Cog):
"""Found a bug or have a suggestion for the Galaxy bot? Use this command."""
color = await self.bot.get_embed_color(None)
embed = discord.Embed(title="Issue Reporting & Suggestions", color=await self.bot.get_embed_color(None), description="Have a problem or a suggestion for the Galaxy bot or GalaxyCogs? Read this!")
embed.add_field(name="Bot Issues & Suggestions", value="If you'd like to submit a suggestion or a bug report to the developers of the Galaxy bot, please do so with the buttons below or by going [here](https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs/issues/new/choose).\n**Please make sure whatever you're suggesting or reporting doesn't have an existing issue! If it does, you can comment on that issue with additional details if necessary.**")
embed.add_field(name="Cog Issues & Suggestions", value="If you'd like to submit a suggestion or a bug report to the developers of GalaxyCogs, please do so with the buttons below or by going [here](https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs/issues/new/choose).\n**Please make sure whatever you're suggesting or reporting doesn't have an existing issue! If it does, you can comment on that issue with additional details if necessary.**")
embed.add_field(name="Bot Issues & Suggestions", value="If you'd like to submit a suggestion or a bug report to the developers of the Galaxy bot, please do so with the buttons below or by going [here](https://coastalcommits.com/cswimr/GalaxyCogs/issues/new/choose).\n**Please make sure whatever you're suggesting or reporting doesn't have an existing issue! If it does, you can comment on that issue with additional details if necessary.**")
embed.add_field(name="Cog Issues & Suggestions", value="If you'd like to submit a suggestion or a bug report to the developers of GalaxyCogs, please do so with the buttons below or by going [here](https://coastalcommits.com/cswimr/GalaxyCogs/issues/new/choose).\n**Please make sure whatever you're suggesting or reporting doesn't have an existing issue! If it does, you can comment on that issue with additional details if necessary.**")
await interaction.response.send_message(embed=embed, view=self.IssueButtons(color, self, interaction), ephemeral=True)
async def submit_issue_request(self, interaction: discord.Interaction, original_interaction: discord.Interaction, embed: discord.Embed):

View file

@ -1,5 +0,0 @@
from .moderation import Moderation
async def setup(bot):
await bot.add_cog(Moderation(bot))

View file

@ -1,11 +0,0 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Moderation!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"name" : "Moderation",
"short" : "Custom cog intended for use on the Galaxy discord server.",
"description" : "Custom cog intended for use on the Galaxy discord server.",
"end_user_data_statement" : "This cog does not store any End User Data.",
"requirements": ["sqlalchemy", "humanize", "pytimeparse2"],
"hidden": false,
"disabled": false
}

View file

@ -1,986 +0,0 @@
import logging
import time
from datetime import datetime, timedelta, timezone
from typing import Union
import discord
import humanize
from sqlalchemy import MetaData, Table, Column, Integer, String, Boolean, DateTime, Text, BigInteger, func, or_, and_
from sqlalchemy.exc import OperationalError, NoSuchTableError
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from discord.ext import tasks
from pytimeparse2 import disable_dateutil, parse
from redbot.core import app_commands, checks, Config, commands
from redbot.core.app_commands import Choice
class Moderation(commands.Cog):
"""Custom moderation cog.
Developed by SeaswimmerTheFsh."""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=481923957134912)
self.config.register_global(
db_use_sqlite = True,
db_connection_string = " "
)
self.config.register_guild(
ignore_other_bots = True,
dm_users = True,
log_channel = " "
)
disable_dateutil()
self.handle_expiry.start() # pylint: disable=no-member
self.logger = logging.getLogger('red.seaswimmerthefsh.moderation')
async def cog_load(self):
"""This method prepares the database schema for all of the guilds the bot is currently in."""
conf = await self.check_conf([
'mysql_address',
'mysql_database',
'mysql_username',
'mysql_password'
])
if conf:
self.logger.fatal("Failed to create tables, due to MySQL connection configuration being unset.")
return
guilds: list[discord.Guild] = self.bot.guilds
try:
for guild in guilds:
if not await self.bot.cog_disabled_in_guild(self, guild):
await self.create_guild_table(guild)
except ConnectionRefusedError:
return
async def cog_unload(self):
self.handle_expiry.cancel() # pylint: disable=no-member
@commands.Cog.listener('on_guild_join')
async def db_generate_guild_join(self, guild: discord.Guild):
"""This method prepares the database schema whenever the bot joins a guild."""
if not await self.bot.cog_disabled_in_guild(self, guild):
conf = await self.check_conf([
'mysql_address',
'mysql_database',
'mysql_username',
'mysql_password'
])
if conf:
self.logger.error("Failed to create a table for %s, due to MySQL connection configuration being unset.", guild.id)
return
try:
await self.create_guild_table(guild)
except ConnectionRefusedError:
return
@commands.Cog.listener('on_audit_log_entry_create')
async def autologger(self, entry: discord.AuditLogEntry):
"""This method automatically logs moderations done by users manually ("right clicks")."""
if not await self.bot.cog_disabled_in_guild(self, entry.guild):
if await self.config.guild(entry.guild.id).ignore_other_bots() is True:
if entry.user.bot or entry.target.bot:
return
else:
if entry.user.id == self.bot.user.id:
return
duration = "NULL"
if entry.reason:
reason = entry.reason + " (This action was performed without the bot.)"
else:
reason = "This action was performed without the bot."
if entry.action == discord.AuditLogAction.kick:
moderation_type = 'KICK'
elif entry.action == discord.AuditLogAction.ban:
moderation_type = 'BAN'
elif entry.action == discord.AuditLogAction.unban:
moderation_type = 'UNBAN'
elif entry.action == discord.AuditLogAction.member_update:
if entry.after.timed_out_until is not None:
timed_out_until_aware = entry.after.timed_out_until.replace(tzinfo=timezone.utc)
duration_datetime = timed_out_until_aware - datetime.now(tz=timezone.utc)
minutes = round(duration_datetime.total_seconds() / 60)
duration = timedelta(minutes=minutes)
moderation_type = 'MUTE'
else:
moderation_type = 'UNMUTE'
else:
return
await self.sql_log(entry.guild.id, entry.user.id, moderation_type, entry.target.id, duration, reason)
async def connect(self):
"""Connects to the MySQL database, and returns a connection object."""
try:
if await self.config.db_use_sqlite() is False and await self.config.db_connection_string() != " ":
engine = create_async_engine(await self.config.db_connection_string())
else:
engine = create_async_engine('sqlite:///moderation.db')
except OperationalError as e:
self.logger.fatal("Unable to access the database!\nError:\n%s", e.msg)
raise ConnectionRefusedError(f"Unable to access the Database!\n{e.msg}") from e
return engine
async def fetch_table(self, engine, guild_id):
table = Table(f'moderation_{guild_id}', MetaData(), autoload_with=engine)
return table
async def create_guild_table(self, guild: discord.Guild):
engine = await self.connect()
metadata = MetaData()
try:
await self.fetch_table(engine, guild.id)
self.logger.info("MySQL Table exists for server %s (%s)", guild.name, guild.id)
except NoSuchTableError:
moderation = Table(
f'moderation_{guild.id}', metadata,
Column('moderationId', Integer, primary_key=True, autoincrement=True, index=True),
Column('timestamp', Integer, nullable=False),
Column('moderationType', String(20), nullable=False),
Column('targetId', BigInteger, nullable=False, index=True),
Column('moderatorId', BigInteger, nullable=False, index=True),
Column('roleId', BigInteger),
Column('duration', String(40)),
Column('endTimestamp', Integer),
Column('reason', Text),
Column('resolved', Boolean, nullable=False),
Column('resolvedBy', BigInteger),
Column('resolveReason', Text),
Column('expired', Boolean, nullable=False)
)
ins = moderation.insert()
ins = moderation.insert().values(
moderationId=0,
timestamp=0,
moderationType="NULL",
targetId=0,
moderatorId=0,
roleId=0,
duration="NULL",
endTimestamp=0,
reason="NULL",
resolved=0,
resolvedBy=0,
resolveReason="NULL",
expired=0
)
async with engine.begin() as conn:
await conn.run_sync(metadata.create_all)
await conn.execute(ins)
conn.commit()
conn.close()
self.logger.info("Database Table (moderation_%s) created for %s (%s)", guild.id, guild.name, guild.id)
def check_permissions(self, user: discord.User, permissions: list, ctx: Union[commands.Context, discord.Interaction] = None, guild: discord.Guild = None):
"""Checks if a user has a specific permission (or a list of permissions) in a channel."""
if ctx:
member = ctx.guild.get_member(user.id)
resolved_permissions = ctx.channel.permissions_for(member)
elif guild:
member = guild.get_member(user.id)
resolved_permissions = member.guild_permissions
else:
raise(KeyError)
for permission in permissions:
if not getattr(resolved_permissions, permission, False) and not resolved_permissions.administrator is True:
return permission
return False
async def sql_log(self, guild_id: str, author_id: str, moderation_type: str, target_id: int, duration, reason: str):
timestamp = int(time.time())
if duration != "NULL":
end_timedelta = datetime.fromtimestamp(timestamp) + duration
end_timestamp = int(end_timedelta.timestamp())
else:
end_timestamp = 0
engine = await self.connect()
table = await self.fetch_table(engine, guild_id)
moderation_id = await self.get_next_case_number(guild_id=guild_id, engine=engine)
sql = table.insert().values(
moderation_id=moderation_id,
timestamp=timestamp,
moderation_type=moderation_type,
target_id=target_id,
moderator_id=author_id,
duration=duration,
end_timestamp=end_timestamp,
reason=reason,
resolved=0,
resolved_by="NULL",
resolve_reason="NULL",
expired=0
)
async with engine.connect() as conn:
conn.execute(sql)
conn.commit()
conn.close()
self.logger.debug("Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, 0, NULL, NULL, 0", guild_id, moderation_id, timestamp, moderation_type, target_id, author_id, duration, end_timestamp, reason)
return moderation_id
async def get_next_case_number(self, guild_id: str, engine: AsyncEngine):
"""This method returns the next case number from the database table for a specific guild."""
table = await self.fetch_table(engine, guild_id)
async with engine.connect() as conn:
result = await conn.execute(table.select().order_by(table.c.moderationId.desc()).limit(1))
result = result.fetchone()
conn.close()
return result[0] + 1 if result else 1
def generate_dict(self, result):
case: dict = {
"moderation_id": result[0],
"timestamp": result[1],
"moderation_type": result[2],
"target_id": result[3],
"moderator_id": result[4],
"role_id": result[5],
"duration": result[6],
"end_timestamp": result[7],
"reason": result[8],
"resolved": result[9],
"resolved_by": result[10],
"resolve_reason": result[11],
"expired": result[12]
}
return case
async def fetch_user_dict(self, interaction: discord.Interaction, user_id: str):
"""This method returns a dictionary containing either user information or a standard deleted user template."""
try:
user = await interaction.client.fetch_user(user_id)
user_dict = {
'id': user.id,
'name': user.name,
'discriminator': user.discriminator
}
except discord.errors.NotFound:
user_dict = {
'id': user_id,
'name': 'Deleted User',
'discriminator': '0'
}
return user_dict
async def embed_factory(self, embed_type: str, /, interaction: discord.Interaction = None, case_dict: dict = None, guild: discord.Guild = None, reason: str = None, moderation_type: str = None, response: discord.InteractionMessage = None, duration: timedelta = None, resolved: bool = False):
"""This method creates an embed from set parameters, meant for either moderation logging or contacting the moderated user.
Valid arguments for 'embed_type':
- 'message'
- 'log' - WIP
- 'case'
Required arguments for 'message':
- guild
- reason
- moderation_type
- response
- duration (optional)
Required arguments for 'log':
- interaction
- case_dict
- resolved (optional)
Required arguments for 'case':
- interaction
- case_dict"""
if embed_type == 'message':
if moderation_type in ["kicked", "banned", "tempbanned", "unbanned"]:
guild_name = guild.name
else:
guild_name = f"[{guild.name}]({response.jump_url})"
if moderation_type in ["tempbanned", "muted"] and duration:
embed_duration = f" for {humanize.precisedelta(duration)}"
else:
embed_duration = ""
if moderation_type == "note":
embed_desc = "recieved a"
else:
embed_desc = "been"
embed = discord.Embed(title=str.title(moderation_type), description=f"You have {embed_desc} {moderation_type}{embed_duration} in {guild_name}.", color=await self.bot.get_embed_color(None), timestamp=datetime.now())
embed.add_field(name='Reason', value=f"`{reason}`")
embed.set_author(name=guild.name, icon_url=guild.icon.url)
embed.set_footer(text=f"Case #{await self.get_next_case_number(guild.id)}", icon_url="https://cdn.discordapp.com/attachments/1070822161389994054/1159469476773904414/arrow-right-circle-icon-512x512-2p1e2aaw.png?ex=65312319&is=651eae19&hm=3cebdd28e805c13a79ec48ef87c32ca532ffa6b9ede2e48d0cf8e5e81f3a6818&")
return embed
if embed_type == 'case':
target_user = await self.fetch_user_dict(interaction, case_dict['target_id'])
moderator_user = await self.fetch_user_dict(interaction, case_dict['moderator_id'])
target_name = f"`{target_user['name']}`" if target_user['discriminator'] == "0" else f"`{target_user['name']}#{target_user['discriminator']}`"
moderator_name = moderator_user['name'] if moderator_user['discriminator'] == "0" else f"{moderator_user['name']}#{moderator_user['discriminator']}"
embed = discord.Embed(title=f"📕 Case #{case_dict['moderation_id']}", color=await self.bot.get_embed_color(None))
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Resolved:** {bool(case_dict['resolved'])}\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
if case_dict['duration'] != 'NULL':
td = timedelta(**{unit: int(val) for unit, val in zip(["hours", "minutes", "seconds"], case_dict["duration"].split(":"))})
duration_embed = f"{humanize.precisedelta(td)} | <t:{case_dict['end_timestamp']}:R>" if case_dict["expired"] == '0' else str(humanize.precisedelta(td))
embed.description = embed.description + f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}"
embed.add_field(name='Reason', value=f"```{case_dict['reason']}```", inline=False)
if case_dict['resolved'] == 1:
resolved_user = await self.fetch_user_dict(interaction, case_dict['resolved_by'])
resolved_name = resolved_user['name'] if resolved_user['discriminator'] == "0" else f"{resolved_user['name']}#{resolved_user['discriminator']}"
embed.add_field(name='Resolve Reason', value=f"Resolved by {resolved_name} ({resolved_user['id']}) for:\n```{case_dict['resolve_reason']}```", inline=False)
return embed
if embed_type == 'log':
if resolved:
target_user = await self.fetch_user_dict(interaction, case_dict['target_id'])
moderator_user = await self.fetch_user_dict(interaction, case_dict['moderator_id'])
target_name = f"`{target_user['name']}`" if target_user['discriminator'] == "0" else f"`{target_user['name']}#{target_user['discriminator']}`"
moderator_name = moderator_user['name'] if moderator_user['discriminator'] == "0" else f"{moderator_user['name']}#{moderator_user['discriminator']}"
embed = discord.Embed(title=f"📕 Case #{case_dict['moderation_id']} Resolved", color=await self.bot.get_embed_color(None))
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
if case_dict['duration'] != 'NULL':
td = timedelta(**{unit: int(val) for unit, val in zip(["hours", "minutes", "seconds"], case_dict["duration"].split(":"))})
duration_embed = f"{humanize.precisedelta(td)} | <t:{case_dict['end_timestamp']}:R>" if case_dict["expired"] == '0' else str(humanize.precisedelta(td))
embed.description = embed.description + f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}"
embed.add_field(name='Reason', value=f"```{case_dict['reason']}```", inline=False)
resolved_user = await self.fetch_user_dict(interaction, case_dict['resolved_by'])
resolved_name = resolved_user['name'] if resolved_user['discriminator'] == "0" else f"{resolved_user['name']}#{resolved_user['discriminator']}"
embed.add_field(name='Resolve Reason', value=f"Resolved by {resolved_name} ({resolved_user['id']}) for:\n```{case_dict['resolve_reason']}```", inline=False)
else:
target_user = await self.fetch_user_dict(interaction, case_dict['target_id'])
moderator_user = await self.fetch_user_dict(interaction, case_dict['moderator_id'])
target_name = f"`{target_user['name']}`" if target_user['discriminator'] == "0" else f"`{target_user['name']}#{target_user['discriminator']}`"
moderator_name = moderator_user['name'] if moderator_user['discriminator'] == "0" else f"{moderator_user['name']}#{moderator_user['discriminator']}"
embed = discord.Embed(title=f"📕 Case #{case_dict['moderation_id']}", color=await self.bot.get_embed_color(None))
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
if case_dict['duration'] != 'NULL':
td = timedelta(**{unit: int(val) for unit, val in zip(["hours", "minutes", "seconds"], case_dict["duration"].split(":"))})
embed.description = embed.description + f"\n**Duration:** {humanize.precisedelta(td)} | <t:{case_dict['end_timestamp']}:R>"
embed.add_field(name='Reason', value=f"```{case_dict['reason']}```", inline=False)
return embed
raise(TypeError("'type' argument is invalid!"))
async def fetch_case(self, moderation_id: int, guild_id: str):
"""This method fetches a case from the database and returns the case's dictionary."""
engine = await self.connect()
table = await self.fetch_table(engine, guild_id)
async with engine.connect() as conn:
result = await conn.execute(table.select().where(table.c.moderation_id == moderation_id))
result = result.fetchone()
conn.close()
return self.generate_dict(result)
async def log(self, interaction: discord.Interaction, moderation_id: int, resolved: bool = False):
"""This method sends a message to the guild's configured logging channel when an infraction takes place."""
logging_channel_id = await self.config.guild(interaction.guild).log_channel()
if logging_channel_id != " ":
logging_channel = interaction.guild.get_channel(logging_channel_id)
case = await self.fetch_case(moderation_id, interaction.guild.id)
if case:
embed = await self.embed_factory('log', interaction=interaction, case_dict=case, resolved=resolved)
try:
await logging_channel.send(embed=embed)
except discord.errors.Forbidden:
return
@app_commands.command(name="note")
async def note(self, interaction: discord.Interaction, target: discord.User, reason: str, silent: bool = None):
"""Add a note to a user.
Parameters
-----------
target: discord.User
Who are you noting?
reason: str
Why are you noting this user?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has recieved a note!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='note', response=await interaction.original_response())
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'NOTE', target.id, 'NULL', reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="warn")
async def warn(self, interaction: discord.Interaction, target: discord.Member, reason: str, silent: bool = None):
"""Warn a user.
Parameters
-----------
target: discord.Member
Who are you warning?
reason: str
Why are you warning this user?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has been warned!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='warned', response=await interaction.original_response())
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'WARN', target.id, 'NULL', reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="mute")
async def mute(self, interaction: discord.Interaction, target: discord.Member, duration: str, reason: str, silent: bool = None):
"""Mute a user.
Parameters
-----------
target: discord.Member
Who are you unbanning?
duration: str
How long are you muting this user for?
reason: str
Why are you unbanning this user?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['moderate_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
if target.is_timed_out() is True:
await interaction.response.send_message(f"{target.mention} is already muted!", allowed_mentions=discord.AllowedMentions(users=False), ephemeral=True)
return
try:
parsed_time = parse(sval=duration, as_timedelta=True, raise_exception=True)
except ValueError:
await interaction.response.send_message("Please provide a valid duration!", ephemeral=True)
return
if parsed_time.total_seconds() / 1000 > 2419200000:
await interaction.response.send_message("Please provide a duration that is less than 28 days.")
return
await target.timeout(parsed_time, reason=f"Muted by {interaction.user.id} for: {reason}")
await interaction.response.send_message(content=f"{target.mention} has been muted for {humanize.precisedelta(parsed_time)}!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='muted', response=await interaction.original_response(), duration=parsed_time)
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'MUTE', target.id, parsed_time, reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="unmute")
async def unmute(self, interaction: discord.Interaction, target: discord.Member, reason: str = None, silent: bool = None):
"""Unmute a user.
Parameters
-----------
target: discord.user
Who are you unmuting?
reason: str
Why are you unmuting this user?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['moderate_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
if target.is_timed_out() is False:
await interaction.response.send_message(f"{target.mention} is not muted!", allowed_mentions=discord.AllowedMentions(users=False), ephemeral=True)
return
if reason:
await target.timeout(None, reason=f"Unmuted by {interaction.user.id} for: {reason}")
else:
await target.timeout(None, reason=f"Unbanned by {interaction.user.id}")
reason = "No reason given."
await interaction.response.send_message(content=f"{target.mention} has been unmuted!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='unmuted', response=await interaction.original_response())
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'UNMUTE', target.id, 'NULL', reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="kick")
async def kick(self, interaction: discord.Interaction, target: discord.Member, reason: str, silent: bool = None):
"""Kick a user.
Parameters
-----------
target: discord.user
Who are you kicking?
reason: str
Why are you kicking this user?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['kick_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has been kicked!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='kicked', response=await interaction.original_response())
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
await target.kick(f"Kicked by {interaction.user.id} for: {reason}")
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'KICK', target.id, 'NULL', reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="ban")
@app_commands.choices(delete_messages=[
Choice(name="None", value=0),
Choice(name='1 Hour', value=3600),
Choice(name='12 Hours', value=43200),
Choice(name='1 Day', value=86400),
Choice(name='3 Days', value=259200),
Choice(name='7 Days', value=604800),
])
async def ban(self, interaction: discord.Interaction, target: discord.User, reason: str, duration: str = None, delete_messages: Choice[int] = 0, silent: bool = None):
"""Ban a user.
Parameters
-----------
target: discord.user
Who are you banning?
duration: str
How long are you banning this user for?
reason: str
Why are you banning this user?
delete_messages: Choices[int]
How many days of messages to delete?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['ban_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
try:
await interaction.guild.fetch_ban(target)
await interaction.response.send_message(content=f"{target.mention} is already banned!", ephemeral=True)
return
except discord.errors.NotFound:
pass
if duration:
try:
parsed_time = parse(sval=duration, as_timedelta=True, raise_exception=True)
except ValueError:
await interaction.response.send_message("Please provide a valid duration!", ephemeral=True)
return
await interaction.response.send_message(content=f"{target.mention} has been banned for {humanize.precisedelta(parsed_time)}!\n**Reason** - `{reason}`")
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='tempbanned', response=await interaction.original_response(), duration=parsed_time)
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
await interaction.guild.ban(target, reason=f"Tempbanned by {interaction.user.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages)
await self.sql_log(interaction.guild.id, interaction.user.id, 'TEMPBAN', target.id, parsed_time, reason)
else:
await interaction.response.send_message(content=f"{target.mention} has been banned!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='banned', response=await interaction.original_response())
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
await interaction.guild.ban(target, reason=f"Banned by {interaction.user.id} for: {reason}", delete_message_seconds=delete_messages)
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'BAN', target.id, 'NULL', reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="unban")
async def unban(self, interaction: discord.Interaction, target: discord.User, reason: str = None, silent: bool = None):
"""Unban a user.
Parameters
-----------
target: discord.user
Who are you unbanning?
reason: str
Why are you unbanning this user?
silent: bool
Should the user be messaged?"""
if interaction.guild.get_member(target.id):
target_member = interaction.guild.get_member(target.id)
if interaction.guild.get_member(interaction.client.user.id).top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a role higher than the bot!", ephemeral=True)
return
if interaction.user.top_role <= target_member.top_role:
await interaction.response.send_message(content="You cannot moderate members with a higher role than you!", ephemeral=True)
return
permissions = self.check_permissions(interaction.client.user, ['ban_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
try:
await interaction.guild.fetch_ban(target)
except discord.errors.NotFound:
await interaction.response.send_message(content=f"{target.mention} is not banned!", ephemeral=True)
return
if reason:
await interaction.guild.unban(target, reason=f"Unbanned by {interaction.user.id} for: {reason}")
else:
await interaction.guild.unban(target, reason=f"Unbanned by {interaction.user.id}")
reason = "No reason given."
await interaction.response.send_message(content=f"{target.mention} has been unbanned!\n**Reason** - `{reason}`")
if silent is None:
silent = not await self.config.guild(interaction.guild).dm_users()
if silent is False:
try:
embed = await self.embed_factory('message', guild=interaction.guild, reason=reason, moderation_type='unbanned', response=await interaction.original_response())
await target.send(embed=embed)
except discord.errors.HTTPException:
pass
moderation_id = await self.sql_log(interaction.guild.id, interaction.user.id, 'UNBAN', target.id, 'NULL', reason)
await self.log(interaction, moderation_id)
@app_commands.command(name="history")
async def history(self, interaction: discord.Interaction, target: discord.User = None, moderator: discord.User = None, pagesize: app_commands.Range[int, 1, 25] = 5, page: int = 1, epheremal: bool = False):
"""List previous infractions.
Parameters
-----------
target: discord.User
User whose infractions to query, overrides moderator if both are given
moderator: discord.User
Query by moderator
pagesize: app_commands.Range[int, 1, 25]
Amount of infractions to list per page
page: int
Page to select
epheremal: bool
Hide the command response"""
permissions = self.check_permissions(interaction.client.user, ['embed_links'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
start_index = (page - 1) * pagesize
end_index = page * pagesize
engine = await self.connect()
table = await self.fetch_table(engine, interaction.guild.id)
async with engine.connect() as conn:
if target:
results = conn.execute(table.select().where(table.c.targetId == target.id).order_by(table.c.moderationId.desc()).offset(start_index).limit(end_index - start_index + 1))
case_quantity = conn.execute(table.select(func.count()).where(table.c.targetId == target.id))
elif moderator:
results = conn.execute(table.select().where(table.c.moderatorId == moderator.id).order_by(table.c.moderationId.desc()).offset(start_index).limit(end_index - start_index + 1))
case_quantity = conn.execute(table.select(func.count()).where(table.c.moderatorId == moderator.id))
else:
results = conn.execute(table.select().order_by(table.c.moderationId.desc()).offset(start_index).limit(pagesize))
case_quantity = conn.execute(table.select(func.count())) - 1 # account for case 0 techincally existing
conn.close()
result_dict_list = []
for result in results:
case_dict = self.generate_dict(result)
result_dict_list.append(case_dict)
page_quantity = round(case_quantity / pagesize)
embed = discord.Embed(color=await self.bot.get_embed_color(None))
embed.set_author(icon_url=interaction.guild.icon.url, name='Infraction History')
embed.set_footer(text=f"Page {page}/{page_quantity} | {case_quantity} Results")
for case in result_dict_list[start_index:end_index]:
if str(case['moderation_id']) == '0':
continue
target_user = await self.fetch_user_dict(interaction, case['target_id'])
moderator_user = await self.fetch_user_dict(interaction, case['moderator_id'])
target_name = f"`{target_user['name']}`" if target_user['discriminator'] == "0" else f"`{target_user['name']}#{target_user['discriminator']}`"
moderator_name = moderator_user['name'] if moderator_user['discriminator'] == "0" else f"{moderator_user['name']}#{moderator_user['discriminator']}"
field_name = f"Case #{case['moderation_id']} ({str.title(case['moderation_type'])})"
field_value = f"**Target:** `{target_name}` ({target_user['id']})\n**Moderator:** `{moderator_name}` ({moderator_user['id']})\n**Reason:** `{str(case['reason'])[:150]}`"
if case['duration'] != 'NULL':
td = timedelta(**{unit: int(val) for unit, val in zip(["hours", "minutes", "seconds"], case["duration"].split(":"))})
duration_embed = f"{humanize.precisedelta(td)} | <t:{case['end_timestamp']}:R>" if case["expired"] == '0' else f"{humanize.precisedelta(td)} | Expired"
field_value = field_value + f"\n**Duration:** {duration_embed}"
if bool(case['resolved']):
field_value = field_value + "\n**Resolved:** True"
embed.add_field(name=field_name, value=field_value, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=epheremal)
@app_commands.command(name="resolve")
async def resolve(self, interaction: discord.Interaction, case_number: int, reason: str = None):
"""Resolve a specific case.
Parameters
-----------
case_number: int
Case number of the case you're trying to resolve
reason: str
Reason for resolving case"""
permissions = self.check_permissions(interaction.client.user, ['embed_links', 'moderate_members', 'ban_members'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
engine = await self.connect()
table = await self.fetch_table(engine, interaction.guild.id)
async with engine.connect() as conn:
result_1 = conn.execute(table.select().where(table.c.moderationId == case_number))[0]
if result_1 is None or case_number == 0:
await interaction.response.send_message(content=f"There is no moderation with a case number of {case_number}.", ephemeral=True)
conn.close()
return
result_2 = conn.execute(table.select().where(table.c.moderationId == case_number).where(table.c.resolved == 0))[0]
if result_2 is None:
await interaction.response.send_message(content=f"This moderation has already been resolved!\nUse `/case {case_number}` for more information.", ephemeral=True)
conn.close()
return
case = self.generate_dict(result_2)
if reason is None:
reason = "No reason given."
if case['moderation_type'] in ['UNMUTE', 'UNBAN']:
await interaction.response.send_message(content="You cannot resolve this type of moderation!", ephemeral=True)
conn.close()
return
if case['moderation_type'] in ['MUTE', 'TEMPBAN', 'BAN']:
if case['moderation_type'] == 'MUTE':
try:
member = await interaction.guild.fetch_member(case['target_id'])
await member.timeout(None, reason=f"Case #{case_number} resolved by {interaction.user.id}")
except discord.NotFound:
pass
if case['moderation_type'] in ['TEMPBAN', 'BAN']:
try:
user = await interaction.client.fetch_user(case['target_id'])
await interaction.guild.unban(user, reason=f"Case #{case_number} resolved by {interaction.user.id}")
except discord.NotFound:
pass
conn.execute(table.update().where(table.c.moderationId == case_number).values(resolved=1, expired=1, resolvedBy=interaction.user.id, resolveReason=reason))
else:
conn.execute(table.update().where(table.c.moderationId == case_number).values(resolved=1, resolvedBy=interaction.user.id, resolveReason=reason))
conn.commit()
result = conn.execute(table.select().where(table.c.moderationId == case_number))[0]
conn.close()
case_dict = self.generate_dict(result)
embed = await self.embed_factory('case', interaction=interaction, case_dict=case_dict)
await interaction.response.send_message(content=f"✅ Moderation #{case_number} resolved!", embed=embed)
await self.log(interaction, case_number, True)
@app_commands.command(name="case")
async def case(self, interaction: discord.Interaction, case_number: int, ephemeral: bool = False):
"""Check the details of a specific case.
Parameters
-----------
case_number: int
What case are you looking up?
ephemeral: bool
Hide the command response"""
permissions = self.check_permissions(interaction.client.user, ['embed_links'], interaction)
if permissions:
await interaction.response.send_message(f"I do not have the `{permissions}` permission, required for this action.", ephemeral=True)
return
if case_number != 0:
case = await self.fetch_case(case_number, interaction.guild.id)
if case:
embed = await self.embed_factory('case', interaction=interaction, case_dict=case)
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
return
await interaction.response.send_message(content=f"No case with case number `{case_number}` found.", ephemeral=True)
@tasks.loop(minutes=1)
async def handle_expiry(self):
engine = await self.connect()
db = await self.config.mysql_database()
guilds: list[discord.Guild] = self.bot.guilds
async with engine.connect() as conn:
for guild in guilds:
if not await self.bot.cog_disabled_in_guild(self, guild):
table = await self.fetch_table(engine, guild.id)
result = conn.execute(
table.select().where(table.c.moderationType == 'TEMPBAN').where(table.c.expired == 0).where(table.c.endTimestamp != 0).where(table.c.endTimestamp <= time.time()))
target_ids = [row[0] for row in result]
moderation_ids = [row[1] for row in result]
for target_id, moderation_id in zip(target_ids, moderation_ids):
user: discord.User = await self.bot.fetch_user(target_id)
await guild.unban(user, reason=f"Automatic unban from case #{moderation_id}")
embed = await self.embed_factory('message', guild, f'Automatic unban from case #{moderation_id}', 'unbanned')
try:
await user.send(embed=embed)
except discord.errors.HTTPException:
pass
conn.execute(
table.update().
where(
or_(
and_(
table.c.end_timestamp != 0,
table.c.end_timestamp <= time.time(),
table.c.expired == 0
),
and_(
table.c.expired == 0,
table.c.resolved == 1
)
)
).
values(expired=1)
)
conn.commit()
conn.close()
@commands.group(autohelp=True)
@checks.admin()
async def moderationset(self, ctx: commands.Context):
"""Manage moderation commands."""
@moderationset.command(name="ignorebots")
@checks.admin()
async def moderationset_ignorebots(self, ctx: commands.Context):
"""Toggle if the cog should ignore other bots' moderations."""
await self.config.guild(ctx.guild).ignore_other_bots.set(not await self.config.guild(ctx.guild).ignore_other_bots())
await ctx.send(f"Ignore bots setting set to {await self.config.guild(ctx.guild).ignore_other_bots()}")
@moderationset.command(name="dm")
@checks.admin()
async def moderationset_dm(self, ctx: commands.Context):
"""Toggle automatically messaging moderated users.
This option can be overridden by specifying the `silent` argument in any moderation command."""
await self.config.guild(ctx.guild).dm_users.set(not await self.config.guild(ctx.guild).dm_users())
await ctx.send(f"DM users setting set to {await self.config.guild(ctx.guild).dm_users()}")
@moderationset.command(name="logchannel")
@checks.admin()
async def moderationset_logchannel(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""Set a channel to log infractions to."""
if channel:
await self.config.guild(ctx.guild).log_channel.set(channel.id)
await ctx.send(f"Logging channel set to {channel.mention}.")
else:
await self.config.guild(ctx.guild).log_channel.set(" ")
await ctx.send("Logging channel disabled.")
@moderationset.command(name="mysql")
@checks.is_owner()
async def moderationset_mysql(self, ctx: commands.Context):
"""Configure MySQL connection details."""
await ctx.message.add_reaction("")
await ctx.author.send(content="Click the button below to configure your MySQL connection details.", view=self.ConfigButtons(60))
class ConfigButtons(discord.ui.View):
def __init__(self, timeout):
super().__init__()
self.config = Config.get_conf(None, cog_name='Moderation', identifier=481923957134912)
@discord.ui.button(label="Edit", style=discord.ButtonStyle.success)
async def config_button(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(Moderation.MySQLConfigModal(self.config))
class MySQLConfigModal(discord.ui.Modal, title="MySQL Database Configuration"):
def __init__(self, config):
super().__init__()
self.config = config
address = discord.ui.TextInput(
label="Address",
placeholder="Input your MySQL address here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
database = discord.ui.TextInput(
label="Database",
placeholder="Input the name of your database here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
username = discord.ui.TextInput(
label="Username",
placeholder="Input your MySQL username here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
password = discord.ui.TextInput(
label="Password",
placeholder="Input your MySQL password here.",
style=discord.TextStyle.short,
required=False,
max_length=300
)
async def on_submit(self, interaction: discord.Interaction):
message = ""
if self.address.value != "":
await self.config.mysql_address.set(self.address.value)
message += f"- Address set to\n - `{self.address.value}`\n"
if self.database.value != "":
await self.config.mysql_database.set(self.database.value)
message += f"- Database set to\n - `{self.database.value}`\n"
if self.username.value != "":
await self.config.mysql_username.set(self.username.value)
message += f"- Username set to\n - `{self.username.value}`\n"
if self.password.value != "":
await self.config.mysql_password.set(self.password.value)
trimmed_password = self.password.value[:8]
message += f"- Password set to\n - `{trimmed_password}` - Trimmed for security\n"
if message == "":
trimmed_password = str(await self.config.mysql_password())[:8]
send = f"No changes were made.\nCurrent configuration:\n- Address:\n - `{await self.config.mysql_address()}`\n- Database:\n - `{await self.config.mysql_database()}`\n- Username:\n - `{await self.config.mysql_username()}`\n- Password:\n - `{trimmed_password}` - Trimmed for security"
else:
send = f"Configuration changed:\n{message}"
await interaction.response.send_message(send, ephemeral=True)
@commands.command(aliases=["tdc"])
async def timedeltaconvert(self, ctx: commands.Context, *, duration: str):
"""This command converts a duration to a [`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) Python object.
**Example usage**
`[p]timedeltaconvert 1 day 15hr 82 minutes 52s`
**Output**
`1 day, 16:22:52`"""
try:
parsed_time = parse(duration, as_timedelta=True, raise_exception=True)
await ctx.send(f"`{str(parsed_time)}`")
except ValueError:
await ctx.send("Please provide a convertible value!")

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Podcast!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Podcast!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Podcast",
"short" : "Provides a questions submission system.",
"description" : "Provies a questions submission system.",

View file

@ -1,8 +1,9 @@
from redbot.core import commands, checks, Config
from redbot.core import Config, checks, commands
class Podcast(commands.Cog):
"""Provides a questions submission system for podcasts.
Developed by SeaswimmerTheFsh."""
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot

2769
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@ description = "Custom cogs/cog modifications intended for the Galaxy discord ser
authors = ["Galaxy Discord Management Team"]
license = "MPL 2"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = ">=3.9,<3.12"
@ -14,7 +15,7 @@ prisma = "^0.10.0"
mysql-connector-python = "^8.1.0"
humanize = "^4.8.0"
pytube = "^15.0.0"
sqlalchemy = {extras = ["asyncio"], version = "^2.0.22"}
ruff = "^0.3.6"
[tool.poetry.group.dev]
optional = true

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Send!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Send!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Send",
"short" : "Allows you to send messages as the bot user!",
"description" : "Allows you to send messages as the bot user!.",

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Shortmute!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing Shortmute!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "Shortmute",
"short" : "Allows staff members to shortmute individuals for up to 30 minutes.",
"description" : "Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.",

View file

@ -56,7 +56,7 @@ class Shortmute(commands.Cog):
"timedelta": timedelta,
"reason": reason,
"interaction": interaction,
"color": await self.bot.get_embed_color(None),
"color": await self.bot.get_embed_color(interaction.guild),
"evidence": evidence
}
blacklisted_users_list = await self.config.guild(interaction.guild).blacklisted_users()
@ -91,13 +91,13 @@ class Shortmute(commands.Cog):
await interaction.response.send_message(content=f"Please shortmute the user for longer than {readable_duration}! The maximum duration is 30 minutes.", ephemeral=True)
return
if skip_confirmation is False:
embed = discord.Embed(title="Are you sure?", description=f"**Moderator:** {interaction.user.mention}\n**Target:** {target.mention}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=await self.bot.get_embed_color(None))
embed = discord.Embed(title="Are you sure?", description=f"**Moderator:** {interaction.user.mention}\n**Target:** {target.mention}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=await self.bot.get_embed_color(interaction.guild))
embed.set_footer(text="/shortmute")
if evidence:
embed.set_image(url=evidence)
await interaction.response.send_message(embed=embed, view=self.ShortmuteButtons(timeout=180, passed_info=passed_info), ephemeral=True)
elif skip_confirmation is True:
edit_embed = discord.Embed(title="Shortmute confirmed!", description=f"**Moderator:** {interaction.user.mention}\n**Target:** {target.mention}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=await self.bot.get_embed_color(None))
edit_embed = discord.Embed(title="Shortmute confirmed!", description=f"**Moderator:** {interaction.user.mention}\n**Target:** {target.mention}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=await self.bot.get_embed_color(interaction.guild))
edit_embed.set_footer(text="/shortmute")
if evidence:
edit_embed.set_image(url=evidence)
@ -105,7 +105,7 @@ class Shortmute(commands.Cog):
await target.timeout(timedelta, reason=f"User shortmuted for {readable_duration} by {interaction.user.name} ({interaction.user.id}) for: {reason}")
shortmute_msg = await interaction.channel.send(content=f"{target.mention} was shortmuted for {readable_duration} by {interaction.user.mention} for: `{reason}`")
if await self.config.guild(interaction.guild).dm() is True:
dm_embed = discord.Embed(title=f"You've been shortmuted in {interaction.guild.name}!", description=f"**Moderator:** {interaction.user.mention}\n**Target:** {target.mention}\n**Message:** {shortmute_msg.jump_url}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=await self.bot.get_embed_color(None))
dm_embed = discord.Embed(title=f"You've been shortmuted in {interaction.guild.name}!", description=f"**Moderator:** {interaction.user.mention}\n**Target:** {target.mention}\n**Message:** {shortmute_msg.jump_url}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=await self.bot.get_embed_color(interaction.guild))
dm_embed.set_footer(text="/shortmute")
if evidence:
dm_embed.set_image(url=evidence)
@ -115,7 +115,7 @@ class Shortmute(commands.Cog):
await message.edit(content="Could not message the target, user most likely has Direct Messages disabled.")
logging_channels_list = await self.config.guild(interaction.guild).logging_channels()
if logging_channels_list:
logging_embed = discord.Embed(title="User Shortmuted", description=f"**Moderator:** {interaction.user.mention} ({interaction.user.id})\n**Target:** {target.mention} ({target.id})\n**Message:** {shortmute_msg.jump_url}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`\n**Confirmation Skipped:** True", color=await self.bot.get_embed_color(None))
logging_embed = discord.Embed(title="User Shortmuted", description=f"**Moderator:** {interaction.user.mention} ({interaction.user.id})\n**Target:** {target.mention} ({target.id})\n**Message:** {shortmute_msg.jump_url}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`\n**Confirmation Skipped:** True", color=await self.bot.get_embed_color(interaction.guild))
logging_embed.set_footer(text="/shortmute")
if evidence:
logging_embed.set_image(url=evidence)

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh, meelyman"],
"install_msg" : "Thank you for installing Suggestions!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs\nYou can find the original source code from SauriCogs here: <https://github.com/elijabesu/SauriCogs>",
"author" : ["cswimr, meelyman"],
"install_msg" : "Thank you for installing Suggestions!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs\nYou can find the original source code from SauriCogs here: <https://github.com/elijabesu/SauriCogs>",
"name" : "Suggestions",
"short" : "Simple suggestions system.",
"description" : "Per guild suggestions system.",

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing SugonCredit!\n**Please load the Economy cog before loading this cog.**\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs.",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing SugonCredit!\n**Please load the Economy cog before loading this cog.**\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs.",
"name" : "SugonCredit",
"short" : "Simple points system.",
"description" : "Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community.",

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing World Zero!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing World Zero!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "World Zero",
"short" : "This cog is meant to provide random functions for my crippling World Zero addiction!",
"description" : "This cog is meant to provide random functions for my crippling World Zero addiction!",

View file

@ -1,9 +1,10 @@
from redbot.core import commands
import discord
from redbot.core import commands
class WorldZero(commands.Cog):
"""This cog is meant to provide random functions for my crippling World Zero addiction!
Developed by SeaswimmerTheFsh."""
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot

View file

@ -1,6 +1,6 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing YouTubeDownloader!\nYou can find the source code of this cog here: https://coastalcommits.com/SeaswimmerTheFsh/GalaxyCogs",
"author" : ["cswimr"],
"install_msg" : "Thank you for installing YouTubeDownloader!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs",
"name" : "YouTubeDownloader",
"short" : "Custom cog intended for use on the Galaxy discord server.",
"description" : "Custom cog intended for use on the Galaxy discord server.",

View file

@ -23,7 +23,7 @@ class YouTubeDownloader(commands.Cog):
blacklisted_users = await self.config.blacklisted_users()
for blacklisted_user_id in blacklisted_users:
if blacklisted_user_id == user_id:
return self.UserBlacklisted
raise self.UserBlacklisted
@commands.command()
@checks.is_owner()
@ -43,7 +43,7 @@ class YouTubeDownloader(commands.Cog):
await ctx.send("The path you've provided doesn't exist!")
@commands.command(aliases=["dl"])
async def download(self, ctx: commands.Context, url: str, audio_only: bool = True, delete: bool = False, *, subfolder: str = None):
async def download(self, ctx: commands.Context, url: str, audio_only: bool = True, delete: bool = True, *, subfolder: str = None):
"""This command downloads a YouTube Video as an `m4a` (or `mp4`) and uploads the file to discord.
If you're considered a bot owner, you will be able to save downloaded files to the data path set in the `[p]change_data_path` command.
@ -58,14 +58,15 @@ class YouTubeDownloader(commands.Cog):
- The `subfolder` argument only does anything if `delete` is set to False, but it allows you to save to a subfolder in the data path you've set previously without having to change said data path manually."""
try:
self.blacklist_checker(ctx.author.id)
await self.blacklist_checker(ctx.author.id)
except self.UserBlacklisted:
await ctx.send("You are blacklisted from running this command!")
return
def youtube_download(url: str, path: str):
"""This function does the actual downloading of the YouTube Video."""
yt = YouTube(url=url)
filename = f"{yt.title} ({yt.video_id})"
translation_table = dict.fromkeys(map(ord, r'<>:"/\|?*'), None)
filename = f"{yt.title.translate(translation_table)} ({yt.video_id})"
if audio_only:
stream = yt.streams.filter(only_audio=True, mime_type='audio/mp4')
stream = stream.order_by('abr')
@ -120,7 +121,7 @@ class YouTubeDownloader(commands.Cog):
file.close()
if delete is True or await self.bot.is_owner(ctx.author) is False:
if output[1] is False:
os.remove(output)
os.remove(output[0])
await complete_message.edit(content="YouTube Downloader completed!\nFile has been deleted.\nDownloaded file:")
if output[1] is True:
await complete_message.edit(content="YouTube Downloader completed!\nFile has not been deleted, as it was previously downloaded and saved.\nDownloaded file:")