Compare commits

..

No commits in common. "main" and "musicdownloader_async" have entirely different histories.

67 changed files with 937 additions and 4157 deletions

View file

@ -1,16 +0,0 @@
[MESSAGES CONTROL]
disable=
too-many-lines,
missing-module-docstring,
missing-function-docstring,
missing-class-docstring,
line-too-long,
too-many-arguments,
too-many-branches,
superfluous-parens,
invalid-name,
too-many-locals,
too-many-public-methods,
too-many-statements,
arguments-differ,
too-many-return-statements

View file

@ -1,20 +0,0 @@
name: Actions
on:
push:
branches:
- 'main'
pull_request:
jobs:
Lint Code (Pylint):
runs-on: docker
container: www.coastalcommits.com/cswimr/actions:galaxycogs
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
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

@ -16,8 +16,5 @@ A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context or screenshots about the feature request here.

View file

@ -7,9 +7,6 @@ assignees: ''
--- ---
**What cog is your feature request for?**
A cog in this repository.
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
@ -19,8 +16,5 @@ A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context or screenshots about the feature request here.

5
.gitignore vendored
View file

@ -1,2 +1,7 @@
galaxy/slashtag arguments.txt galaxy/slashtag arguments.txt
galaxy_server.yaml
global.yaml
galaxy_staff_server.yaml
combat_welder.yaml
OOUCogs
.venv .venv

View file

@ -1,9 +1,18 @@
# GalaxyCogs # GalaxyCogs
<p align="center">
<a href="https://discord.com/invite/robloxgalaxy">
<img src="https://discordapp.com/api/guilds/204965774618656769/widget.png?style=shield" alt="Galaxy Discord Server">
</a>
<a href="https://www.python.org/downloads/">
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/Red-Discordbot">
</a>
<a href="https://github.com/Rapptz/discord.py/">
<img src="https://img.shields.io/badge/discord-py-blue.svg" alt="discord.py">
</a>
</p>
Repository for Redbot cogs developed by the Galaxy Discord Management team. Repository for Redbot cogs developed by the Galaxy Discord Management team.
**This readme is heavily outdated.**
## ExportChannels **(WIP)**[^incomplete] ## ExportChannels **(WIP)**[^incomplete]
This cog allows you to easily export channels using Discord Chat Exporter. This cog allows you to easily export channels using Discord Chat Exporter.
@ -30,7 +39,18 @@ Currently supports:
* Users * Users
* Servers/Guilds * Servers/Guilds
* Roles * Roles[^dpy_notice]
[^dpy_notice]:
Due to Red's use of Discord.py 1.7.3, the ``[p]roleinfo`` command in the Info cog does not show all permissions. This is due to the outdated Discord.py version not supporting checking for all permissions.
## MusicDownloader
Allows users to download **just the audio files** from any YouTube video. Currently only supports YouTube and does not work for video.
Currently partially broken on **just old versions of Red**, if you're on an old version (such as `3.4.18`) please download [this database file](https://drive.google.com/file/d/11Ena7bPrF7eVhR9ZVmdXb0hHCg-0I76d/view?usp=sharing) and put it in your cog data directory for this cog, which should be
```
<where-ever you've installed red>/Red-DiscordBot/data/<instance name>/cogs/MusicDownloader
```
## Podcast **(WIP)**[^incomplete] ## Podcast **(WIP)**[^incomplete]
@ -40,7 +60,7 @@ Also features user blacklists, both configurable per-server and globally.
[^incomplete]: [^incomplete]:
This cog currently is non-functional. This notice will be removed once the Cog is completed. This cog currently is non-functional. This notice will be removed once the Cog is completed.
## Shortmute ## Shortmutes **(WIP)**[^incomplete]
Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature. Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.
@ -53,7 +73,7 @@ Features:
* Separate approved and denied channels * Separate approved and denied channels
* Custom emoji support * Custom emoji support
## SugonCredit[^incomplete] ## SugonCredit
Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community. Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community.
@ -62,11 +82,3 @@ Features:
* Add Credit to people. * Add Credit to people.
* Remove Credit from people. * Remove Credit from people.
* Supports custom currency names and bank names. * Supports custom currency names and bank names.
## YouTubeDownloader
Allows users to download any YouTube video.
`[p]download https://youtu.be/dQw4w9WgXcQ` - Downloads the audio from [this video](https://youtu.be/dQw4w9WgXcQ) in `.m4a` format.
`[p]download https://youtu.be/dQw4w9WgXcQ false` - Downloads [this video](https://youtu.be/dQw4w9WgXcQ) in `.mp4` format.

View file

@ -1,4 +1,4 @@
from .exportchannels import ExportChannels from .exportchannels import ExportChannels
async def setup(bot): def setup(bot):
await bot.add_cog(ExportChannels(bot)) bot.add_cog(ExportChannels(bot))

Binary file not shown.

Binary file not shown.

View file

@ -6,14 +6,14 @@
"compilationOptions": {}, "compilationOptions": {},
"targets": { "targets": {
".NETCoreApp,Version=v7.0": { ".NETCoreApp,Version=v7.0": {
"DiscordChatExporter.Cli/2.40.4": { "DiscordChatExporter.Cli/2.39.1": {
"dependencies": { "dependencies": {
"CliFx": "2.3.4", "CliFx": "2.3.1",
"Deorcify": "1.0.2", "DiscordChatExporter.Core": "2.39.1",
"DiscordChatExporter.Core": "2.40.4", "DotnetRuntimeBootstrapper": "2.4.0",
"DotnetRuntimeBootstrapper": "2.5.1", "FuckRussia": "1.0.1",
"Gress": "2.1.1", "Gress": "2.0.1",
"Spectre.Console": "0.47.0" "Spectre.Console": "0.46.0"
}, },
"runtime": { "runtime": {
"DiscordChatExporter.Cli.dll": {} "DiscordChatExporter.Cli.dll": {}
@ -27,40 +27,29 @@
} }
} }
}, },
"AngleSharp/1.0.4": { "AsyncKeyedLock/6.2.0": {
"dependencies": {
"System.Text.Encoding.CodePages": "7.0.0"
},
"runtime": {
"lib/net7.0/AngleSharp.dll": {
"assemblyVersion": "1.0.4.0",
"fileVersion": "1.0.4.0"
}
}
},
"AsyncKeyedLock/6.2.1": {
"runtime": { "runtime": {
"lib/net5.0/AsyncKeyedLock.dll": { "lib/net5.0/AsyncKeyedLock.dll": {
"assemblyVersion": "6.2.1.0", "assemblyVersion": "6.2.0.0",
"fileVersion": "6.2.1.0" "fileVersion": "6.2.0.0"
} }
} }
}, },
"CliFx/2.3.4": { "CliFx/2.3.1": {
"runtime": { "runtime": {
"lib/netstandard2.1/CliFx.dll": { "lib/netstandard2.1/CliFx.dll": {
"assemblyVersion": "2.3.4.0", "assemblyVersion": "2.3.1.0",
"fileVersion": "2.3.4.0" "fileVersion": "2.3.1.0"
} }
} }
}, },
"Deorcify/1.0.2": {}, "DotnetRuntimeBootstrapper/2.4.0": {},
"DotnetRuntimeBootstrapper/2.5.1": {}, "FuckRussia/1.0.1": {},
"Gress/2.1.1": { "Gress/2.0.1": {
"runtime": { "runtime": {
"lib/netstandard2.0/Gress.dll": { "lib/netstandard2.0/Gress.dll": {
"assemblyVersion": "2.1.1.0", "assemblyVersion": "2.0.1.0",
"fileVersion": "2.1.1.0" "fileVersion": "2.0.1.0"
} }
} }
}, },
@ -72,30 +61,30 @@
} }
} }
}, },
"Polly/7.2.4": { "Polly/7.2.3": {
"runtime": { "runtime": {
"lib/netstandard2.0/Polly.dll": { "lib/netstandard2.0/Polly.dll": {
"assemblyVersion": "7.0.0.0", "assemblyVersion": "7.0.0.0",
"fileVersion": "7.2.4.982" "fileVersion": "7.2.3.0"
} }
} }
}, },
"RazorBlade/0.4.3": { "RazorBlade/0.4.2": {
"runtime": { "runtime": {
"lib/net6.0/RazorBlade.dll": { "lib/net6.0/RazorBlade.dll": {
"assemblyVersion": "0.4.3.0", "assemblyVersion": "0.4.2.0",
"fileVersion": "0.4.3.0" "fileVersion": "0.4.2.0"
} }
} }
}, },
"Spectre.Console/0.47.0": { "Spectre.Console/0.46.0": {
"dependencies": { "dependencies": {
"System.Memory": "4.5.5" "System.Memory": "4.5.5"
}, },
"runtime": { "runtime": {
"lib/net7.0/Spectre.Console.dll": { "lib/net7.0/Spectre.Console.dll": {
"assemblyVersion": "0.0.0.0", "assemblyVersion": "0.0.0.0",
"fileVersion": "0.47.0.0" "fileVersion": "0.46.0.0"
} }
} }
}, },
@ -108,39 +97,26 @@
} }
}, },
"System.Memory/4.5.5": {}, "System.Memory/4.5.5": {},
"System.Text.Encoding.CodePages/7.0.0": {}, "WebMarkupMin.Core/2.13.8": {
"WebMarkupMin.Core/2.14.0": {
"dependencies": { "dependencies": {
"AdvancedStringBuilder": "0.1.0" "AdvancedStringBuilder": "0.1.0"
}, },
"runtime": { "runtime": {
"lib/netstandard2.1/WebMarkupMin.Core.dll": { "lib/netstandard2.1/WebMarkupMin.Core.dll": {
"assemblyVersion": "2.14.0.0", "assemblyVersion": "2.13.8.0",
"fileVersion": "2.14.0.0" "fileVersion": "2.13.8.0"
} }
} }
}, },
"YoutubeExplode/6.3.1": { "DiscordChatExporter.Core/2.39.1": {
"dependencies": { "dependencies": {
"AngleSharp": "1.0.4" "AsyncKeyedLock": "6.2.0",
}, "Gress": "2.0.1",
"runtime": {
"lib/net5.0/YoutubeExplode.dll": {
"assemblyVersion": "6.3.1.0",
"fileVersion": "6.3.1.0"
}
}
},
"DiscordChatExporter.Core/2.40.4": {
"dependencies": {
"AsyncKeyedLock": "6.2.1",
"Gress": "2.1.1",
"JsonExtensions": "1.2.0", "JsonExtensions": "1.2.0",
"Polly": "7.2.4", "Polly": "7.2.3",
"RazorBlade": "0.4.3", "RazorBlade": "0.4.2",
"Superpower": "3.0.0", "Superpower": "3.0.0",
"WebMarkupMin.Core": "2.14.0", "WebMarkupMin.Core": "2.13.8"
"YoutubeExplode": "6.3.1"
}, },
"runtime": { "runtime": {
"DiscordChatExporter.Core.dll": {} "DiscordChatExporter.Core.dll": {}
@ -149,7 +125,7 @@
} }
}, },
"libraries": { "libraries": {
"DiscordChatExporter.Cli/2.40.4": { "DiscordChatExporter.Cli/2.39.1": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
@ -161,47 +137,40 @@
"path": "advancedstringbuilder/0.1.0", "path": "advancedstringbuilder/0.1.0",
"hashPath": "advancedstringbuilder.0.1.0.nupkg.sha512" "hashPath": "advancedstringbuilder.0.1.0.nupkg.sha512"
}, },
"AngleSharp/1.0.4": { "AsyncKeyedLock/6.2.0": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-G8R4C2MEDFQvjUbYz1QIcGfibjsTJnzP0aWy8iQgWWk7eKacYydCNGD3JMhVL0Q5pZQ9RYlqpKNESEU5NpqsRw==", "sha512": "sha512-FJqEME0eyM1EHVymnG32vmqKEFZ9X7/LX0NOuHyAeQqpncJFt+ODvzfx0SAlmF6m5NtL6xyPfvt9uTckbSWLhQ==",
"path": "anglesharp/1.0.4", "path": "asynckeyedlock/6.2.0",
"hashPath": "anglesharp.1.0.4.nupkg.sha512" "hashPath": "asynckeyedlock.6.2.0.nupkg.sha512"
}, },
"AsyncKeyedLock/6.2.1": { "CliFx/2.3.1": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-WUvN3Q7aL3wARMcVi/KYTHjOC+0Ld+/ikKU6UGr4aOl7TuK1I2MNKeUkmfr7y4S3UNjQbpzNQCV2OcJq1S/yXg==", "sha512": "sha512-erOn8SLCHt3p+pj8nIKmzqDyXV6hwQr1wHmyVuFujnLJRKP1ovuswPBNKUNXv9Le7/HhNawln/Upc5v6Nhn3zA==",
"path": "asynckeyedlock/6.2.1", "path": "clifx/2.3.1",
"hashPath": "asynckeyedlock.6.2.1.nupkg.sha512" "hashPath": "clifx.2.3.1.nupkg.sha512"
}, },
"CliFx/2.3.4": { "DotnetRuntimeBootstrapper/2.4.0": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-CFD8yL9McQrKa2dnGdXx/e1ob3QXcVICHpE9XbPXnFw1yPdqAgij3pdS4Xfii3U0+D1HJRW1rOPtNnuR7lI1cQ==", "sha512": "sha512-CuNwd8O1trpAaPlhcP4ourZH4onf6FqBDWQzzmKqqgF3TZCKkFuL5xDZNvbWWdDQI2dx3z/XBRxwBQghVKSD7Q==",
"path": "clifx/2.3.4", "path": "dotnetruntimebootstrapper/2.4.0",
"hashPath": "clifx.2.3.4.nupkg.sha512" "hashPath": "dotnetruntimebootstrapper.2.4.0.nupkg.sha512"
}, },
"Deorcify/1.0.2": { "FuckRussia/1.0.1": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-khu8MahgfbXc8eQxfZgRX38Jgu1ZLg+6aK906XtIkUpzMovhaPs4ZOWcUiDLtPz0a9m7TcQwtGtoeRgghO8mVw==", "sha512": "sha512-mdo3UfOIrRfi05m6qN+rJt3yKkM5Bq9hC3b/NYe5c0orhF6TGsIoJBo+OqX6P2LLouSeFNE01UjR9bbvXbT9+A==",
"path": "deorcify/1.0.2", "path": "fuckrussia/1.0.1",
"hashPath": "deorcify.1.0.2.nupkg.sha512" "hashPath": "fuckrussia.1.0.1.nupkg.sha512"
}, },
"DotnetRuntimeBootstrapper/2.5.1": { "Gress/2.0.1": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-Ufbf8Qe9hwY/KojfimWX4Kqka3QFH4IoGiQj8LPRlyk68/ApHxsG+p6GeTwukY0sIgxA1VXUAFkbvnn9rbZyaw==", "sha512": "sha512-Ky68Wbb3WUkFw5g92tcTRNf5IVMV5AyLB8+uw4pCpXmfS2gkIzWV9BnTGNu+aTaklYqvSZlQL6BnH+9I7HG5tg==",
"path": "dotnetruntimebootstrapper/2.5.1", "path": "gress/2.0.1",
"hashPath": "dotnetruntimebootstrapper.2.5.1.nupkg.sha512" "hashPath": "gress.2.0.1.nupkg.sha512"
},
"Gress/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-n/YK1XYM2xUhoPFRRJpoTk5qtWBwTkSZlWtxqb4JNX3pDfwoMiu6G+zs65y8Z8SfIUxhOhU5yO/v58WkZynECg==",
"path": "gress/2.1.1",
"hashPath": "gress.2.1.1.nupkg.sha512"
}, },
"JsonExtensions/1.2.0": { "JsonExtensions/1.2.0": {
"type": "package", "type": "package",
@ -210,26 +179,26 @@
"path": "jsonextensions/1.2.0", "path": "jsonextensions/1.2.0",
"hashPath": "jsonextensions.1.2.0.nupkg.sha512" "hashPath": "jsonextensions.1.2.0.nupkg.sha512"
}, },
"Polly/7.2.4": { "Polly/7.2.3": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-bw00Ck5sh6ekduDE3mnCo1ohzuad946uslCDEENu3091+6UKnBuKLo4e+yaNcCzXxOZCXWY2gV4a35+K1d4LDA==", "sha512": "sha512-DeCY0OFbNdNxsjntr1gTXHJ5pKUwYzp04Er2LLeN3g6pWhffsGuKVfMBLe1lw7x76HrPkLxKEFxBlpRxS2nDEQ==",
"path": "polly/7.2.4", "path": "polly/7.2.3",
"hashPath": "polly.7.2.4.nupkg.sha512" "hashPath": "polly.7.2.3.nupkg.sha512"
}, },
"RazorBlade/0.4.3": { "RazorBlade/0.4.2": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-EwoDxG2aw5UuKsxKbg9W8PPlmkvPvp9y7yLjRhfIQvnDRZEfr3n+RCs46VHlIvALbCGDCX3S0B/zKMEmkFLHLQ==", "sha512": "sha512-uLNIoO35t+gOEWT7EdofyN0tFsbrntoHX5a19CgjwheDhrDSgGY3ubF9zsp4hQnkGhBLAQaQ4K2yyxDHbuC3GQ==",
"path": "razorblade/0.4.3", "path": "razorblade/0.4.2",
"hashPath": "razorblade.0.4.3.nupkg.sha512" "hashPath": "razorblade.0.4.2.nupkg.sha512"
}, },
"Spectre.Console/0.47.0": { "Spectre.Console/0.46.0": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-wz8mszcZr0cSOo8GyoG9e2DFW0SkMT8/n78Q/lIXX7EbCtHNXOoOKWpJ9Str+rCYtmQOGGyDutZzubrUHK/XkA==", "sha512": "sha512-qd2OMEGxfQW1KLuQj56KKHkIK6eB/IUF/AET5CRl/efROHGsvBehx9shfkL0HgJEHLCwRiR7foWi4/LkdPk18g==",
"path": "spectre.console/0.47.0", "path": "spectre.console/0.46.0",
"hashPath": "spectre.console.0.47.0.nupkg.sha512" "hashPath": "spectre.console.0.46.0.nupkg.sha512"
}, },
"Superpower/3.0.0": { "Superpower/3.0.0": {
"type": "package", "type": "package",
@ -245,28 +214,14 @@
"path": "system.memory/4.5.5", "path": "system.memory/4.5.5",
"hashPath": "system.memory.4.5.5.nupkg.sha512" "hashPath": "system.memory.4.5.5.nupkg.sha512"
}, },
"System.Text.Encoding.CodePages/7.0.0": { "WebMarkupMin.Core/2.13.8": {
"type": "package", "type": "package",
"serviceable": true, "serviceable": true,
"sha512": "sha512-LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==", "sha512": "sha512-dXAyg/mMmkOBGSzhjOv5dJIvT+u8mBhEPG+QGp+UW4et/lGucSas055tb1793UT+gV5BOAp+v0FqufLyC7Urjw==",
"path": "system.text.encoding.codepages/7.0.0", "path": "webmarkupmin.core/2.13.8",
"hashPath": "system.text.encoding.codepages.7.0.0.nupkg.sha512" "hashPath": "webmarkupmin.core.2.13.8.nupkg.sha512"
}, },
"WebMarkupMin.Core/2.14.0": { "DiscordChatExporter.Core/2.39.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fRydp8dkCWkC3RDL+7ABWLLTgAnFEb5k7flhWrTlyGatg0rpT4HArKnGmfgfQ27FVMa1lCOAzFE1ORF5y5Pzbw==",
"path": "webmarkupmin.core/2.14.0",
"hashPath": "webmarkupmin.core.2.14.0.nupkg.sha512"
},
"YoutubeExplode/6.3.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-+k/RYu3UCI2TwSo/QuIHnhSQ74GRy1gNSuthb/o2ZRNSLidzmbyWaNtBABOxqQJj6ohG/fVScin8cuFVg3Q5vA==",
"path": "youtubeexplode/6.3.1",
"hashPath": "youtubeexplode.6.3.1.nupkg.sha512"
},
"DiscordChatExporter.Core/2.40.4": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""

Binary file not shown.

Binary file not shown.

View file

@ -1,2 +1,2 @@
This bundled version of Discord Chat Exporter can be found here: https://github.com/Tyrrrz/DiscordChatExporter/releases/tag/2.40.4 This bundled version of Discord Chat Exporter can be found here: https://github.com/Tyrrrz/DiscordChatExporter/releases/tag/2.39.1
I DID NOT MAKE THIS PROGRAM. This program is developed by Tyrrrz, NOT me. I DID NOT MAKE THIS PROGRAM. This program is developed by Tyrrrz, NOT me.

Binary file not shown.

View file

@ -1,35 +1,24 @@
import asyncio
import os
import discord import discord
from redbot.core import Config, checks, commands, data_manager import subprocess
import os
from redbot.core import Config, checks, commands, bot, data_manager
class ExportChannels(commands.Cog): class ExportChannels(commands.Cog):
"""Custom cog to export channels to JSON and HTML formats using Discord Chat Exporter. """Custom cog to export channels to JSON and HTML formats using Discord Chat Exporter.
Developed by cswimr and yname.""" Developed by SeaswimmerTheFsh and yname."""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=48258471944753312) self.config = Config.get_conf(self, identifier=48258471944753312)
self.config.register_global( self.config.register_global(
bot_token = None bot_token = "0"
) )
def export(self, ctx, channel, token):
self.data_path = data_manager.cog_data_path(self) self.data_path = data_manager.cog_data_path(self)
self.bundled_data_path = data_manager.bundled_data_path(self)
class ConfigException(Exception): out = f'{self.data_path}/Exported Channels'
pass
async def export(self, channels: list):
token = await self.config.bot_token()
if token is None:
raise(self.ConfigException("Bot token not set!"))
data_path = data_manager.cog_data_path(self)
bundled_data_path = data_manager.bundled_data_path(self)
out = f'{data_path}{os.sep}Exported Channels'
channel = channels[0]
file_location_html = f'{os.sep}{out}{os.sep}%g{os.sep}%c{os.sep}Export.html'
file_location_json = f'{os.sep}{out}{os.sep}%g{os.sep}%c{os.sep}DCE-f.json'
try: try:
os.mkdir(out) os.mkdir(out)
except FileExistsError: except FileExistsError:
@ -39,47 +28,41 @@ class ExportChannels(commands.Cog):
'DiscordChatExporter.Cli.dll', 'DiscordChatExporter.Cli.dll',
'export', 'export',
'--format', 'HtmlDark', '--format', 'HtmlDark',
'--output', file_location_html, '--output', f'/{out}/%G (%g)/%C (%c)/Export.html',
'--token', token, '--token', f'{token}',
'--channel', channel, '--channel', {channel},
'--reuse-media',
'--media', '--media',
'--fuck_russia', 'true', '--fuck_russia', 'true',
] ]
os.chdir(bundled_data_path) if bot:
process_1 = await asyncio.create_subprocess_exec(*args) args += '--bot'
while True: os.chdir(self.bundled_data_path)
return_code_1 = process_1.poll() subprocess.call(args)
if return_code_1 is not None:
break
args = [ args = [
'dotnet', 'dotnet',
'DiscordChatExporter.Cli.dll', 'DiscordChatExporter.Cli.dll',
'export', 'export',
'--format', 'Json', '--format', 'Json',
'--output', file_location_json, '--output', f'/{out}/%G (%g)/%C (%c)/DCE-f.json',
'--token', token, '--token', f'{token}',
'--channel', channels, '--channel', {channel},
'--reuse_media', '--reuse_media',
'--media', '--media',
'--markdown', 'false',
'--fuck_russia', 'true', '--fuck_russia', 'true',
] ]
os.chdir(bundled_data_path) if bot:
process_2 = await asyncio.create_subprocess_exec(*args) args += '--bot'
while True: os.chdir(self.bundled_data_path)
return_code_2 = process_2.poll() subprocess.call(args)
if return_code_2 is not None:
break
@commands.group() @commands.group()
@checks.is_owner() @checks.is_owner()
async def exportset(self, ctx: commands.Context): async def exportset(self, ctx):
"""Configuration options for the ExportChannels cog.""" """Configuration options for the ExportChannels cog."""
@exportset.command() @exportset.command()
@checks.is_owner() @checks.is_owner()
async def token(self, ctx: commands.Context, token: str): async def token(self, ctx, token: str):
"""Sets the bot token used for Discord Chat Exporter.""" """Sets the bot token used for Discord Chat Exporter."""
await self.config.bot_token.set({token}) await self.config.bot_token.set({token})
await ctx.send(content="Token set!") await ctx.send(content="Token set!")
@ -87,18 +70,19 @@ class ExportChannels(commands.Cog):
@exportset.command() @exportset.command()
@checks.is_owner() @checks.is_owner()
async def checkoutputpath(self, ctx: commands.Context): async def checkoutputpath(self, ctx):
"""Checks what file path DCE is outputting to.""" """Checks what file path DCE is outputting to."""
self.data_path = data_manager.cog_data_path(self)
await ctx.send(content=f"{self.data_path}") await ctx.send(content=f"{self.data_path}")
@commands.command() @commands.command()
@commands.admin() @commands.admin()
async def exportchannel(self, ctx: commands.Context, channel: discord.TextChannel): async def exportchannel(self, ctx, channel: discord.Channel):
"""Exports a channel using Discord Chat Exporter.""" """Exports a channel using Discord Chat Exporter."""
token = await self.config.bot_token() token = await self.config.bot_token
if token is None: dce_install = data_manager.bundled_data_path(self)
await ctx.send(content=f"Please set your token with the ``{ctx.prefix}exportset token`` command!") if token == 0:
await ctx.send(content="Please set your token with the ``exportset token`` command!")
return return
id_list = [thread.id for thread in channel.threads] else:
id_list.insert(0, channel.id) await self.export(channel.id, token)
await self.export(id_list)

View file

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

View file

@ -1,208 +0,0 @@
import discord
from discord import ui
from redbot.core import Config, commands
class Forums(commands.Cog):
"""Custom cog intended for use on the Galaxy discord server.
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=2352711325)
self.config.register_guild(
request_roles=[],
forum_channel=None,
forum_tag=None
)
@commands.command()
@commands.guild_only()
async def resolved(self, ctx: commands.Context, *, reason: str = None):
"""Marks a thread as resolved."""
channel_id = await self.config.guild(ctx.guild).forum_channel()
tag = await self.config.guild(ctx.guild).forum_tag()
request_role_ids = await self.config.guild(ctx.guild).request_roles()
if channel_id is None or tag is None or request_role_ids is None:
await ctx.reply(f"Configuration not set properly! Please set configuration options with `{ctx.prefix}resolvedset`.")
return
if isinstance(ctx.channel, discord.Thread) and ctx.channel.parent_id == channel_id:
request_roles = [ctx.guild.get_role(role_id) for role_id in request_role_ids]
match = any(role in ctx.author.roles for role in request_roles)
passed_info = {
"ctx": ctx
}
if match and reason:
passed_info.update({"reason": reason})
if match or ctx.author.id == ctx.channel.owner.id:
msg = await ctx.send("Are you sure you'd like to mark this thread as resolved?")
passed_info.update({"msg": msg})
await msg.edit(view=self.ResolvedButtons(timeout=180, passed_info=passed_info))
else:
await ctx.message.add_reaction("")
await ctx.message.delete(delay=5)
class ResolvedButtons(ui.View):
def __init__(self, timeout, passed_info: dict):
super().__init__()
self.timeout = timeout
self.ctx: commands.Context = passed_info['ctx']
self.msg: discord.Message = passed_info['msg']
if 'reason' in passed_info:
self.reason: str = passed_info['reason']
else:
self.reason = False
self.config = Config.get_conf(None, cog_name='Forums', identifier=2352711325)
@ui.button(label="Yes", style=discord.ButtonStyle.success, emoji="")
async def resolved_button_yes(self, interaction: discord.Interaction, button: ui.Button): # pylint: disable=unused-argument
request_role_ids = await self.config.guild(interaction.guild).request_roles()
request_roles = [interaction.guild.get_role(role_id) for role_id in request_role_ids]
match = any(role in interaction.user.roles for role in request_roles)
if match or interaction.user.id == interaction.channel.owner.id:
await interaction.response.defer()
if self.reason:
response_reason = f"Thread closed by {interaction.user.mention} with reason: {self.reason}"
reason = f"Thread closed by {interaction.user.name} ({interaction.user.id}) with reason: {self.reason}"
else:
response_reason = f"Thread closed by {interaction.user.mention}"
reason = f"Thread closed by {interaction.user.name} ({interaction.user.id})"
await self.msg.edit(content=response_reason, view=None)
await self.ctx.message.delete()
tag = interaction.channel.parent.get_tag(await self.config.guild(interaction.guild).forum_tag())
if tag in interaction.channel.applied_tags:
await interaction.channel.edit(locked=True, archived=True, reason=reason)
else:
await interaction.channel.edit(locked=True, archived=True, applied_tags=interaction.channel.applied_tags + [tag], reason=reason)
else:
await interaction.response.send_message(content="You cannot close this thread!", ephemeral=True)
@ui.button(label="No", style=discord.ButtonStyle.danger, emoji="✖️")
async def resolved_button_no(self, interaction: discord.Interaction, button: ui.Button): # pylint: disable=unused-argument
request_role_ids = await self.config.guild(interaction.guild).request_roles()
request_roles = [interaction.guild.get_role(role_id) for role_id in request_role_ids]
match = any(role in interaction.user.roles for role in request_roles)
if match or interaction.user.id == interaction.channel.owner.id:
await interaction.response.defer()
await self.msg.delete()
await self.ctx.message.delete()
else:
await interaction.response.send_message(content="You cannot close this thread!", ephemeral=True)
@commands.group(name='resolvedset', autohelp=True, aliases=['resolvedconfig'])
@commands.guild_only()
@commands.admin()
async def resolvedset(self, ctx: commands.Context):
"""Manages the configuration for the [p]resolved command."""
@resolvedset.command(name='show', aliases=['settings'])
async def resolvedset_show(self, ctx: commands.Context):
"""Shows the current cog configuration."""
channel_id = await self.config.guild(ctx.guild).forum_channel()
tag = await self.config.guild(ctx.guild).forum_tag()
request_role_ids = await self.config.guild(ctx.guild).request_roles()
split_content = ctx.message.content.split()
command = ' '.join(split_content[:1])
already_in_list = []
for role_id in request_role_ids:
role_obj = ctx.guild.get_role(role_id)
if role_obj:
already_in_list.append(role_obj.mention)
if already_in_list:
roles_list = "**Allowed Roles**:\n" + "\n".join(already_in_list)
else:
roles_list = f"No roles are currently in the allowed roles list.\n- Use `{command} add` to add some."
tag_str = None
if channel_id is not None:
channel_obj = ctx.guild.get_channel(channel_id)
if channel_obj is None:
channel = f"**Channel**: {channel_id}\n- ⚠️ This channel cannot be found in this guild. Is this the correct ID?\n\n"
else:
channel = f"**Channel**: {channel_obj.mention}\n\n"
if tag is not None:
tag_obj = channel_obj.get_tag(tag)
if tag_obj is None:
tag_str = f"**Tag**: {tag}\n- ⚠️ This tag cannot be found in the set forums channel. Is this the correct ID?\n\n"
else:
tag_str = f"**Tag**: {tag_obj.emoji} {tag_obj.name}\n\n"
else:
channel = f"**Channel**: Not set!\n- Use `{command} channel` to set the forums channel.\n\n"
if tag_str is None:
tag_str = f"**Tag**: Not set!\n- Use `{command} tag` to set the tag.\n\n"
embed = discord.Embed(title="Cog Settings", color=await self.bot.get_embed_color(None), description=channel + tag_str + roles_list)
await ctx.reply(embed=embed)
@resolvedset.group(name='role', autohelp=True, aliases=['roles'])
async def resolvedset_role(self, ctx: commands.Context):
"""Manages the allowed roles list."""
@resolvedset_role.command(name='add')
async def resolvedset_role_add(self, ctx: commands.Context, role: discord.Role = None):
"""Adds roles to the allowed roles list."""
current_list = await self.config.guild(ctx.guild).request_roles()
if role:
if role.id in current_list:
await ctx.send("This role is already in the allowed roles list!")
else:
current_list.append(role.id)
await self.config.guild(ctx.guild).request_roles.set(current_list)
await ctx.send(f"{role.mention} has been added to the allowed roles list.", allowed_mentions = discord.AllowedMentions(roles=False))
else:
await ctx.send("Please provide a valid role.")
@resolvedset_role.command(name='remove')
async def resolvedset_role_remove(self, ctx: commands.Context, role: discord.Role = None):
"""Removes roles from the allowed roles list."""
current_list = await self.config.guild(ctx.guild).request_roles()
if role.id in current_list:
current_list.remove(role.id)
await self.config.guild(ctx.guild).request_roles.set(current_list)
await ctx.send(f"{role.mention} has been removed from the allowed roles list.", allowed_mentions = discord.AllowedMentions(roles=False))
else:
await ctx.send("Please provide a valid role that exists in the allowed roles list.")
def create_select_options(self, ctx: commands.Context, data):
options = []
for tag in data:
emoji = ctx.guild.get_emoji(tag.emoji.id) if tag.emoji.id else str(tag.emoji.name)
options.append(discord.SelectOption(label=tag.name, emoji=emoji, description="", value=tag.id))
return options
@resolvedset.command(name="channel")
async def resolvedset_channel(self, ctx: commands.Context, channel: discord.abc.GuildChannel):
"""Sets the channel used by the [p]resolved command."""
if isinstance(channel, discord.ForumChannel):
await self.config.guild(ctx.guild).forum_channel.set(channel.id)
await ctx.reply(f"Forum channel has been set to {channel.mention}.")
else:
await ctx.reply(f"{channel.mention} is not a forums channel!")
@resolvedset.command(name="tag")
async def resolvedset_tag(self, ctx: commands.Context):
"""Sets the tag used by the [p]resolved command."""
channel: discord.ForumChannel = ctx.guild.get_channel(await self.config.guild(ctx.guild).forum_channel())
if channel is not None:
options = self.create_select_options(ctx, channel.available_tags)
msg = await ctx.reply("Select a forum tag below.")
await msg.edit(view=SelectView(msg, options))
else:
await ctx.reply("Configuration error! Channel does not exist.")
class Select(ui.Select):
def __init__(self, message, options):
self.message = message
super().__init__(placeholder="Select an option", max_values=1, min_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
msg: discord.Message = self.message
config = Config.get_conf(None, cog_name='Forums', identifier=2352711325)
await config.guild(msg.guild).forum_tag.set(int(self.values[0]))
channel: discord.ForumChannel = msg.guild.get_channel(await config.guild(msg.guild).forum_channel())
tag = channel.get_tag(int(self.values[0]))
await msg.edit(content=f"Set resolved tag to {tag.emoji} {tag.name}", view=None)
await interaction.response.defer()
class SelectView(ui.View):
def __init__(self, message, options, *, timeout=180):
super().__init__(timeout=timeout)
self.add_item(Select(message, options))

View file

@ -1,10 +0,0 @@
{
"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.",
"end_user_data_statement" : "This cog does not store any End User Data.",
"hidden": false,
"disabled": false
}

View file

@ -1,5 +1,5 @@
from .galaxy import Galaxy from .galaxy import Galaxy
async def setup(bot): def setup(bot):
await bot.add_cog(Galaxy(bot)) bot.add_cog(Galaxy(bot))

View file

@ -1,155 +1,112 @@
import re from redbot.core import commands, checks, Config
from datetime import datetime
from random import randint
import discord import discord
from redbot.core import Config, app_commands, commands from datetime import datetime
from redbot.core.app_commands import Choice import re
class Galaxy(commands.Cog): class Galaxy(commands.Cog):
"""Custom cog intended for use on the Galaxy discord server. """Custom cog intended for use on the Galaxy discord server.
Developed by cswimr.""" Developed by SeaswimmerTheFsh."""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=6621962) self.config = Config.get_conf(self, identifier=6621962)
self.config.register_guild( self.config.register_guild(
autoreact_target = 0, cocotarget = 0,
autoreact_emoji = '💀' cocoemoji = 1028535684757209118
) )
@commands.command(aliases=["pxc", "pc", "polarisconvert", "tatsutopolaris", "ttp"])
@commands.guild_only()
async def polarisxpconvert(self, ctx, *, tatsu_studs: str):
"""Converts Tatsu Studs to Polaris XP."""
try:
tatsu_studs_int = int(f"{tatsu_studs}".replace(",", ""))
except ValueError:
await ctx.send(content="Please input a number!")
return
math = round((tatsu_studs_int/25)*10)
output_from = f'{tatsu_studs_int:,}'
output_to = f'{math:,}'
embed = discord.Embed(color=await self.bot.get_embed_color(None))
embed.add_field(name="Tatsu Studs", value=f"{output_from}", inline=False)
embed.add_field(name="Polaris XP", value=f"{output_to}", inline=False)
await ctx.send(embed=embed)
@commands.command() @commands.command()
async def carnagerefund(self, ctx: commands.Context, message_id: str): async def galaxyissues(self, ctx, target: discord.Member = None):
if ctx.me.id == 1070819799254438039:
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 [here](https://github.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.**")
else:
embed = discord.Embed(title="Issue Reporting & Suggestions", color=await self.bot.get_embed_color(None), description="Have a problem or a suggestion for GalaxyCogs? Read this!")
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 [here](https://github.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.**")
if target:
await ctx.send(embed=embed, content=f"{target.mention}")
else:
await ctx.send(embed=embed)
@commands.command()
async def carnagerefund(self, ctx, message_id: str):
"""This command generates a link to refund carnage of killed ships.""" """This command generates a link to refund carnage of killed ships."""
output = f"https://info.galaxy.casa/kills/{message_id}" output = f"https://info.galaxy.casa/kills/{message_id}"
await ctx.send(f"Output link: {output}") await ctx.send(f"Output link: {output}")
@commands.Cog.listener('on_message') @commands.Cog.listener('on_message')
async def gank_won_let_us_flee(self, message: discord.Message): async def cocoreact(self, message):
if message.author.id == 745790085789909033 and message.guild.id == 204965774618656769 and message.channel.id == 753714180900519937: emoji = self.bot.get_emoji(await self.config.guild(message.guild).cocoemoji())
gank_media_list = [ cocotarget = await self.config.guild(message.guild).cocotarget()
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174309711577138/gank_won_let_us_flee.gif", if cocotarget == 0:
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174310093262951/spin_back_I_dare_you.jpg",
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174310466572328/spin_back_mutt.gif",
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174310806290533/We_dont_run_1s_around_here.gif"
]
if len(message.embeds) == 1 and str(message.embeds[0].color) == '#57f287':
await message.reply(gank_media_list[randint(0,3)])
@commands.Cog.listener('on_message')
async def autoreact(self, message: discord.Message):
if message.guild is not None:
emoji_id = await self.config.guild(message.guild).autoreact_emoji()
if self.check_if_discord_unicode_emoji(emoji_id) is False:
emoji = self.bot.get_emoji(emoji_id)
elif self.check_if_discord_unicode_emoji(emoji_id) is True:
emoji = emoji_id
autoreact_target = await self.config.guild(message.guild).autoreact_target()
if autoreact_target == 0:
return return
if not message.author.id == autoreact_target: if not message.author.id == cocotarget:
return return
await message.add_reaction(emoji) await message.add_reaction(emoji)
@commands.command(name='autoreact') @commands.group(autohelp=False, invoke_without_command=True)
@commands.guild_only() @commands.guild_only()
async def autoreact_list(self, ctx: commands.Context): async def coco(self, ctx):
"""Checks Autoreact's settings.""" """Checks who Coco is currently set to."""
emoji_id = await self.config.guild(ctx.guild).autoreact_emoji() emoji = self.bot.get_emoji(await self.config.guild(ctx.guild).cocoemoji())
autoreact_target = await self.config.guild(ctx.guild).autoreact_target() cocotarget = await self.config.guild(ctx.guild).cocotarget()
if self.check_if_discord_unicode_emoji(emoji_id) is True: embed = discord.Embed(color=await self.bot.get_embed_color(None), description=f"Coco is currently set to <@{cocotarget}> ({cocotarget}).\nCoco's emoji is currently set to {emoji} ({await self.config.guild(ctx.guild).cocoemoji()}).")
emoji = emoji_id
embed = discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact is currently set to target <@{autoreact_target}> ({autoreact_target}).\nAutoreact's emoji is currently set to {emoji}.")
else:
emoji = self.bot.get_emoji(emoji_id)
embed = discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact is currently set to target <@{autoreact_target}> ({autoreact_target}).\nAutoreact's emoji is currently set to {emoji} ({await self.config.guild(ctx.guild).autoreact_emoji()}).")
await ctx.send(embed=embed) await ctx.send(embed=embed)
autoreact = app_commands.Group(name='autoreact', guild_only=True, description="This group handles the autoreact functionality.") @coco.command(name="emoji")
async def coco_emoji(self, ctx, emoji: discord.Emoji = None):
def check_if_discord_unicode_emoji(self, emoji: str): """Sets Coco's emoji."""
emoji_ranges = [
(0x1F600, 0x1F64F), # Emoticons
(0x1F300, 0x1F5FF), # Miscellaneous symbols and pictographs
(0x1F680, 0x1F6FF), # Transport and map symbols
(0x1F700, 0x1F77F), # Alchemical symbols
]
try:
for char in emoji:
code_point = ord(char)
for start, end in emoji_ranges:
if start <= code_point <= end:
return True
except TypeError:
return False
return False
def extract_id(self, input_string):
match = re.search(r'(?<=:)\d+(?=>)', input_string)
if match:
return int(match.group())
return input_string
@autoreact.command(name="emoji")
@app_commands.describe(emoji="Which emoji are you setting Autoreact to use?")
async def autoreact_emoji(self, interaction: discord.Interaction, emoji: str = None):
"""Sets Autoreact's emoji."""
if emoji: if emoji:
if self.check_if_discord_unicode_emoji(emoji) is True: await self.config.guild(ctx.guild).cocoemoji.set(emoji.id)
await self.config.guild(interaction.guild).autoreactemoji.set(emoji) embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Coco's emoji has been set to {emoji} ({emoji.id}).")
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact's emoji has been set to {emoji}.") await ctx.send(embed=embed)
await interaction.response.send_message(embed=embed)
else: else:
emoji_id = self.extract_id(input_string=emoji) await self.config.guild(ctx.guild).cocoemoji.set(1028535684757209118)
for guild in self.bot.guilds: emoji = self.bot.get_emoji(1028535684757209118)
emoji_to_find = discord.utils.get(guild.emojis, id=emoji_id) embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Coco's emoji has been set to {emoji} (1028535684757209118).")
if emoji_to_find: await ctx.send(embed=embed)
emoji_obj = emoji_to_find
break
else:
await interaction.response.send_message(content="You're trying to set the autoreact emoji to an emoji I don't have access to!", ephemeral=True)
return
await self.config.guild(interaction.guild).autoreact_emoji.set(emoji_obj.id)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact's emoji has been set to {emoji_obj} ({emoji_obj.id}).")
await interaction.response.send_message(embed=embed)
else:
await self.config.guild(interaction.guild).autoreact_emoji.set('💀')
embed=discord.Embed(color=await self.bot.get_embed_color(None), description="Autoreact's emoji has been set to 💀.")
await interaction.response.send_message(embed=embed)
@autoreact.command(name="set") @coco.command(name="set")
@app_commands.describe(member="Who are you targetting?") async def coco_set(self, ctx, member: discord.Member):
async def autoreact_set(self, interaction: discord.Interaction, member: discord.Member): """Sets Coco's target."""
"""Sets Autoreact's target."""
if member: if member:
await self.config.guild(interaction.guild).autoreact_target.set(member.id) await self.config.guild(ctx.guild).cocotarget.set(member.id)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact has been set to automatically react to {member.mention} ({member.id})'s messages.") embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Coco has been set to {member.mention} ({member.id}).")
await interaction.response.send_message(embed=embed) await ctx.send(embed=embed)
else: else:
await interaction.response.send_message(content="That is not a valid argument!", ephemeral=True) await ctx.send(content="That is not a valid argument!")
@autoreact.command(name="reset") @coco.command(name="reset")
async def autoreact_reset(self, interaction: discord.Interaction): async def coco_reset(self, ctx):
"""Resets Autoreact's target.""" """Resets Coco's target."""
await self.config.guild(interaction.guild).autoreact_target.set(0) await self.config.guild(ctx.guild).cocotarget.set(0)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description="Autoreact's target has been reset.") embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Coco has been reset.")
await interaction.response.send_message(embed=embed) await ctx.send(embed=embed)
@commands.command() @commands.command()
async def unix(self, ctx: commands.Context): async def unix(self, ctx):
"""Posts the current Unix timestamp.""" """Posts the current Unix timestamp."""
timestamp = int(datetime.timestamp(datetime.now())) timestamp = int(datetime.timestamp(datetime.now()))
embed=discord.Embed(title="Current Time", url="https://www.unixtimestamp.com/", color=await self.bot.get_embed_color(None)) embed=discord.Embed(title="Current Time", url="https://www.unixtimestamp.com/", color=await self.bot.get_embed_color(None), description=f"<t:{timestamp}>")
embed.add_field(name="Default", value=f"<t:{timestamp}>\n`<t:{timestamp}>`")
embed.add_field(name="Short Time", value=f"<t:{timestamp}:t>\n`<t:{timestamp}:t>`")
embed.add_field(name="Long Time", value=f"<t:{timestamp}:T>\n`<t:{timestamp}:T>`")
embed.add_field(name="Short Date/Time", value=f"<t:{timestamp}:f>\n`<t:{timestamp}:f>`")
embed.add_field(name="Short Date", value=f"<t:{timestamp}:d>\n`<t:{timestamp}:d>`")
embed.add_field(name="Long Date", value=f"<t:{timestamp}:D>\n`<t:{timestamp}:D>`")
embed.add_field(name="Long Date/Time", value=f"<t:{timestamp}:F>\n`<t:{timestamp}:F>`")
embed.add_field(name="Relative Time", value=f"<t:{timestamp}:R>\n`<t:{timestamp}:R>`")
embed.set_footer(text=f"{timestamp}") embed.set_footer(text=f"{timestamp}")
embed.set_image(url="https://cdn.discordapp.com/attachments/1047347377348030494/1080048421127335956/image.png")
await ctx.send(embed=embed) await ctx.send(embed=embed)
await ctx.message.delete() await ctx.message.delete()
@ -183,8 +140,8 @@ class Galaxy(commands.Cog):
} }
try: try:
insurance_dict[f'{ship_class}'] insurance_dict[f'{ship_class}']
except KeyError as error: except KeyError:
raise ValueError("Received value is not a valid ship class!") from error raise ValueError("Received value is not a valid ship class!")
if ship_class == "super_capital": if ship_class == "super_capital":
humanized_class = ship_class.replace("_", " ").title() humanized_class = ship_class.replace("_", " ").title()
else: else:
@ -271,27 +228,85 @@ class Galaxy(commands.Cog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
await ctx.message.delete() await ctx.message.delete()
@app_commands.command() @commands.group(autohelp=True)
@app_commands.describe(answer='Which answer are you trying to post?') async def faq(self, ctx):
@app_commands.choices(answer=[
Choice(name='Important Links', value='links'),
Choice(name='DPS Calculations', value='dps'),
Choice(name='Reward Roles', value='reward_roles'),
Choice(name='NPC Intervals', value='npc_intervals'),
Choice(name='Linked Role', value='linked_role'),
Choice(name='RoPro', value='ropro')
])
async def faq(self, interaction: discord.Interaction, answer: Choice[str], member: discord.Member = None):
"""Posts answers to frequently asked questions.""" """Posts answers to frequently asked questions."""
embed = None
embed_secondary = None @faq.command(name="test")
if answer.value == 'dps': @checks.admin()
async def faq_test(self, ctx, member: discord.Member = None):
"""Testing FAQ"""
embed=discord.Embed(title="Test Embed", color=await self.bot.get_embed_color(None), description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer in faucibus odio, at mollis metus.")
embed.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url_as(format="png", size=512))
if member:
await ctx.channel.send(embed=embed, content=member.mention)
else:
await ctx.channel.send(embed=embed)
await ctx.message.delete()
@faq.command(name="dps")
async def faq_dps(self, ctx, member: discord.Member = None):
"""DPS Calculations/Inaccuracy"""
embed=discord.Embed(title="DPS Calculations", color=await self.bot.get_embed_color(None), description="The ``/info`` command (and by extention ``/shipinfo`` from Odin) misreports DPS, due to it calculating DPS disregarding the turret's type (kinetic, laser), causing it to assume the target ship is both hulled and has shield simultaneously. It also ignores turret overrides, custom reloads, and custom damage values. If you'd like to check ship stats accurately, you can either use the ``/ship`` command in this channel or you can use the [Galaxy Info Website](https://info.galaxy.casa/ships). Alternatively, to check turret stats, you can use the [Galaxy Info Turrets Page](https://info.galaxy.casa/turrets).") embed=discord.Embed(title="DPS Calculations", color=await self.bot.get_embed_color(None), description="The ``/info`` command (and by extention ``/shipinfo`` from Odin) misreports DPS, due to it calculating DPS disregarding the turret's type (kinetic, laser), causing it to assume the target ship is both hulled and has shield simultaneously. It also ignores turret overrides, custom reloads, and custom damage values. If you'd like to check ship stats accurately, you can either use the ``/ship`` command in this channel or you can use the [Galaxy Info Website](https://info.galaxy.casa/ships). Alternatively, to check turret stats, you can use the [Galaxy Info Turrets Page](https://info.galaxy.casa/turrets).")
elif answer.value == 'links': if member:
await ctx.channel.send(embed=embed, content=member.mention)
else:
await ctx.channel.send(embed=embed)
await ctx.message.delete()
@faq.command(name="links")
async def faq_links(self, ctx, member: discord.Member = None):
"""Posts important links, primarily invite links."""
embed=discord.Embed(title="Important Links", color=await self.bot.get_embed_color(None)) embed=discord.Embed(title="Important Links", color=await self.bot.get_embed_color(None))
embed.add_field(name="Galaxy", value="[Galaxy Discord](https://discord.com/invite/robloxgalaxy)\n[Galaxy Support](https://discord.com/invite/ShWshkhYhZ)") embed.add_field(name="Galaxy", value="[Galaxy Discord](https://discord.com/invite/robloxgalaxy)\n[Galaxy Support](https://discord.com/invite/ShWshkhYhZ)")
embed.add_field(name="Galaxypedia", value="[Galaxypedia Website](https://robloxgalaxy.wiki/wiki/Main_Page)\n[Galaxypedia Discord](https://discord.robloxgalaxy.wiki/)") embed.add_field(name="Galaxypedia", value="[Galaxypedia Website](https://robloxgalaxy.wiki/wiki/Main_Page)\n[Galaxypedia Discord](https://discord.robloxgalaxy.wiki/)")
elif answer.value == 'npc_intervals': if member:
await ctx.channel.send(embed=embed, content=member.mention)
else:
await ctx.channel.send(embed=embed)
await ctx.message.delete()
@faq.command(name="ropro")
async def faq_ropro(self, ctx, member: discord.Member = None):
"""Posts a link to RoPro"""
embed=discord.Embed(title="RoPro", url="https://ropro.io", color=await self.bot.get_embed_color(None), description="""[RoPro](https://ropro.io) is a browser extension that tracks ROBLOX playtime, enhances your profile, and provides other useful utilities. **Please keep in mind that RoPro only tracks playtime from AFTER you install the extension.**""")
if member:
await ctx.channel.send(embed=embed, content=member.mention)
else:
await ctx.channel.send(embed=embed)
await ctx.message.delete()
@faq.command(name="polaris_ranks")
async def faq_polaris_ranks(self, ctx, member: discord.Member = None):
"""Lists required levels for certain roles."""
embed=discord.Embed(title="Polaris Ranks", color=await self.bot.get_embed_color(None))
embed.add_field(name="Picture Perms", value="Level 7", inline=False)
embed.add_field(name="Suggestions", value="Level 9", inline=False)
embed.add_field(name="DJ", value="Level 11", inline=False)
embed.add_field(name="Reaction Perms", value="Level 30", inline=False)
if member:
await ctx.channel.send(embed=embed, content=member.mention)
else:
await ctx.channel.send(embed=embed)
await ctx.message.delete()
@faq.command(name="polaris_switch")
@checks.admin()
async def faq_polaris_switch(self, ctx, member: discord.Member = None):
"""Posts an embed on the switch to the Polaris bot."""
embed=discord.Embed(title="Polaris FAQ", color=await self.bot.get_embed_color(None), description="As you probably know, we've decided to switch to the Polaris bot for leveling/xp, as opposed to Tatsu.\nThere are many reasons for this, which will be explained below.")
embed.add_field(name="Problems with Tatsu", value="1: Tatsu does not provide nearly as much configuration potential as Polaris does. An example of this is Polaris' customizable Level Curve.\n\n2: Tatsu does not have channel/role modifiers.\n\n3: Tatsu does not have actual levels, instead it has unconfigurable \"Global XP\", which gives \"Global Levels\". You cannot do anything with Global XP aside from blacklisting channels where people can gain it, like a bot-commands channel or something like that.\n\n4: Tatsu's leaderboard sucks, and only shows the top 10 on the web version.\n\n5: Tatsu has no XP management commands.\n\n6: Tatsu has TONS of bloat/useless commands, making the bot harder to configure.", inline=False)
embed.add_field(name="Polaris' Features", value="1: Polaris allows you to customize the level curve of your server, and provides presets to make the transition easier.\n\n2: Polaris has XP management commands.\n\n3: Polaris has way more configuration in terms of Reward Roles.\n\n4: Polaris allows you to customize the level-up message shown whenever people achieve the next level.\n\n5: Polaris has both role and channel modifiers.\n\n6: Polaris' leaderboard is excellent, showing the top 1,000 ranked users on the same webpage, and allowing you to see your own stats, progress towards your next reward role, and all 350 levels and your progress towards them.\n\n7: Polaris is **just** a leveling bot. You don't have to deal with any of the bloat of multi-purpose bots like Tatsu or MEE6, you only get what you actually need.", inline=False)
embed.add_field(name="Conclusion",value="With all of that said, you're probably wondering why we're putting so much effort into transferring peoples' data to the new bot.\n\nWell, Tatsu has been going since 2020, and I don't particularly favor the idea of clearing everyone's XP, especially when people have built up reward roles from Tatsu already, like Picture Perms, Suggestions access, and DJ.\n\nWith all this in mind, I hope this isn't too much of an inconvenience for you all, as I tried to make the process as seamless as possible without having to update all 10,000 people in the server.", inline=False)
if member:
await ctx.channel.send(embed=embed, content=member.mention)
else:
await ctx.channel.send(embed=embed)
await ctx.message.delete()
@faq.command(name="npc_intervals")
async def faq_npc_intervals(self, ctx, member: discord.Member = None):
"""Posts an embed containing NPC spawn intervals."""
embed=discord.Embed(title="NPC Spawn Intervals", color=await self.bot.get_embed_color(None), description="*Disclaimer: Spawn times may be different if EventID is active!*") embed=discord.Embed(title="NPC Spawn Intervals", color=await self.bot.get_embed_color(None), description="*Disclaimer: Spawn times may be different if EventID is active!*")
embed.add_field(name="Every 6.7 Minutes", value="[Dragoon](https://robloxgalaxy.wiki/wiki/Dragoon) *(80% Chance)*") embed.add_field(name="Every 6.7 Minutes", value="[Dragoon](https://robloxgalaxy.wiki/wiki/Dragoon) *(80% Chance)*")
embed.add_field(name="Every 8.3 Minutes", value="[Swarmer](https://robloxgalaxy.wiki/wiki/Swarmer) *(33% Chance)*") embed.add_field(name="Every 8.3 Minutes", value="[Swarmer](https://robloxgalaxy.wiki/wiki/Swarmer) *(33% Chance)*")
@ -302,47 +317,40 @@ class Galaxy(commands.Cog):
embed.add_field(name="Every 60 Minutes", value="[X-0](https://robloxgalaxy.wiki/wiki/X-0) *(45% Chance)*\n[Decimator](https://robloxgalaxy.wiki/wiki/Decimator)") embed.add_field(name="Every 60 Minutes", value="[X-0](https://robloxgalaxy.wiki/wiki/X-0) *(45% Chance)*\n[Decimator](https://robloxgalaxy.wiki/wiki/Decimator)")
embed.add_field(name="Every 70 Minutes", value="[Galleon](https://robloxgalaxy.wiki/wiki/Galleon)") embed.add_field(name="Every 70 Minutes", value="[Galleon](https://robloxgalaxy.wiki/wiki/Galleon)")
embed.add_field(name="Every 120 Minutes", value="[Kodiak](https://robloxgalaxy.wiki/wiki/Kodiak)") embed.add_field(name="Every 120 Minutes", value="[Kodiak](https://robloxgalaxy.wiki/wiki/Kodiak)")
elif answer.value == 'linked_role': if member:
embed = discord.Embed(title="Desktop / Web", color=await self.bot.get_embed_color(None), description="**Step 1:** Open the Server Dropdown menu in the top-left by clicking on the server's name.\n\n**Step 2:** Click the \"*Linked Roles*\" button.\n\n**Step 3:** Click on \"*Linked*.\"\n\n**Step 4:** Click \"*Finish*.\" You're done!\n*Note: You should already be Verified on Bloxlink. If you are not, go to the verification channel to verify.*") await ctx.channel.send(embed=embed, content=member.mention)
embed.set_thumbnail(url="https://cdn.discordapp.com/attachments/1070838419212738621/1079927564421836930/image.png")
embed_secondary = discord.Embed(title="Mobile", color=await self.bot.get_embed_color(None), description="**Step 1:** Open the Server menu on the top of the channel list by tapping the server's name.\n\n**Step 2:** Scroll down and tap the \"*Linked Roles*\" button.\n\n**Step 3:** Tap on \"*Linked*.\"\n\n**Step 4:** Tap \"*Finish*.\" You're done!\n*Note: You should already be Verified on Bloxlink. If you are not, go to the verification channel to verify.*")
embed_secondary.set_thumbnail(url="https://cdn.discordapp.com/attachments/1047347377348030494/1079930169562771576/Screenshot_20230227_195338_Discord.jpg")
elif answer.value == 'reward_roles':
embed = discord.Embed(title="Reward Roles", color=await self.bot.get_embed_color(None))
embed.add_field(name="Picture Perms", value="Level 6")
embed.add_field(name="Suggestions", value="Level 8")
embed.add_field(name="DJ", value="Level 10")
embed.add_field(name="Reaction Perms", value="Level 20")
embed.add_field(name="External Emoji Perms", value="Level 30")
embed.set_footer(text="Use `-profile` to get your current level.")
elif answer.value == 'ropro':
embed = discord.Embed(title="RoPro", url="https://ropro.io", color=await self.bot.get_embed_color(None), description="""[RoPro](https://ropro.io) is a browser extension that tracks ROBLOX playtime, enhances your profile, and provides other useful utilities. **Please keep in mind that RoPro only tracks playtime from AFTER you install the extension.**""")
content = member.mention if member else None
await interaction.response.send_message(content="> The rigid requirement for bots to compulsorily respond to interactions in Discord, such as slash commands or application commands, is an irksome limitation that curtails the flexibility and natural flow of interactions. This forced response paradigm undermines the very essence of automation and intelligent design that bots were intended to offer. There are instances where silence or lack of response is not only acceptable but also desired, aligning with the nuanced dynamics of human communication. Discord's insistence on a response, even when it serves no purpose, imposes unnecessary complexity and verbosity, creating an environment where superfluous replies dilute the efficiency and elegance of bot-driven interactions. This constraint highlights the importance of granting bot developers the autonomy to determine the most suitable course of action based on context, contributing to a more seamless and user-centric experience within the Discord ecosystem.\n - ChatGPT", ephemeral=True)
response: discord.InteractionMessage = await interaction.original_response()
await response.delete()
if embed_secondary:
await interaction.channel.send(content=content, embeds=[embed, embed_secondary])
else: else:
await interaction.channel.send(content=content, embed=embed) await ctx.channel.send(embed=embed)
await ctx.message.delete()
# @faq.command(name="polaris_switch") @faq.command(name="linked_role")
# @checks.admin() async def faq_linked_role(self, ctx, member: discord.Member = None):
# async def faq_polaris_switch(self, ctx, member: discord.Member = None): """Posts an embed containing FAQ about Linked Role."""
# """Posts an embed on the switch to the Polaris bot.""" color=await self.bot.get_embed_color(None)
# embed=discord.Embed(title="Polaris FAQ", color=await self.bot.get_embed_color(None), description="As you probably know, we've decided to switch to the Polaris bot for leveling/xp, as opposed to Tatsu.\nThere are many reasons for this, which will be explained below.") embed=discord.Embed(title="Linked Role", color=color, description="**Before reading this, please make sure your Discord client is updated! On Mobile, you can do this by going to your app store of choice and updating Discord manually. On the desktop app you can do this by clicking the green update button in the top right.**")
# embed.add_field(name="Problems with Tatsu", value="1: Tatsu does not provide nearly as much configuration potential as Polaris does. An example of this is Polaris' customizable Level Curve.\n\n2: Tatsu does not have channel/role modifiers.\n\n3: Tatsu does not have actual levels, instead it has unconfigurable \"Global XP\", which gives \"Global Levels\". You cannot do anything with Global XP aside from blacklisting channels where people can gain it, like a bot-commands channel or something like that.\n\n4: Tatsu's leaderboard sucks, and only shows the top 10 on the web version.\n\n5: Tatsu has no XP management commands.\n\n6: Tatsu has TONS of bloat/useless commands, making the bot harder to configure.", inline=False) embed_desktop=discord.Embed(title="Desktop / Web", color=color, description="**Step 1:** Open the Server Dropdown menu in the top-left by clicking on the server's name.\n\n**Step 2:** Click the \"*Linked Roles*\" button.\n\n**Step 3:** Click on \"*Linked*.\"\n\n**Step 4:** Click \"*Finish*.\" You're done!\n*Note: You should already be Verified on Bloxlink. If you are not, go to the verification channel to verify.*")
# embed.add_field(name="Polaris' Features", value="1: Polaris allows you to customize the level curve of your server, and provides presets to make the transition easier.\n\n2: Polaris has XP management commands.\n\n3: Polaris has way more configuration in terms of Reward Roles.\n\n4: Polaris allows you to customize the level-up message shown whenever people achieve the next level.\n\n5: Polaris has both role and channel modifiers.\n\n6: Polaris' leaderboard is excellent, showing the top 1,000 ranked users on the same webpage, and allowing you to see your own stats, progress towards your next reward role, and all 350 levels and your progress towards them.\n\n7: Polaris is **just** a leveling bot. You don't have to deal with any of the bloat of multi-purpose bots like Tatsu or MEE6, you only get what you actually need.", inline=False) embed_desktop.set_thumbnail(url="https://cdn.discordapp.com/attachments/1070838419212738621/1079927564421836930/image.png")
# embed.add_field(name="Conclusion",value="With all of that said, you're probably wondering why we're putting so much effort into transferring peoples' data to the new bot.\n\nWell, Tatsu has been going since 2020, and I don't particularly favor the idea of clearing everyone's XP, especially when people have built up reward roles from Tatsu already, like Picture Perms, Suggestions access, and DJ.\n\nWith all this in mind, I hope this isn't too much of an inconvenience for you all, as I tried to make the process as seamless as possible without having to update all 10,000 people in the server.", inline=False) embed_mobile=discord.Embed(title="Mobile", color=color, description="**Step 1:** Open the Server menu on the top of the channel list by tapping the server's name.\n\n**Step 2:** Scroll down and tap the \"*Linked Roles*\" button.\n\n**Step 3:** Tap on \"*Linked*.\"\n\n**Step 4:** Tap \"*Finish*.\" You're done!\n*Note: You should already be Verified on Bloxlink. If you are not, go to the verification channel to verify.*")
# if member: embed_mobile.set_thumbnail(url="https://cdn.discordapp.com/attachments/1047347377348030494/1079930169562771576/Screenshot_20230227_195338_Discord.jpg")
# await ctx.channel.send(embed=embed, content=member.mention) if member:
# else: await ctx.channel.send(embed=embed, content=member.mention)
# await ctx.channel.send(embed=embed) else:
# await ctx.message.delete() await ctx.channel.send(embed=embed)
await ctx.channel.send(embed=embed_desktop)
await ctx.channel.send(embed=embed_mobile)
await ctx.message.delete()
@warehouse.error @warehouse.error
@unix.error @unix.error
async def faq_handler(self, error): @faq_test.error
@faq_linked_role.error
@faq_npc_intervals.error
@faq_links.error
@faq_dps.error
@faq_ropro.error
@faq_polaris_ranks.error
@faq_polaris_switch.error
async def faq_handler(self, ctx, error):
"""Error Handler for Galaxy.""" """Error Handler for Galaxy."""
if isinstance(error, discord.NotFound): if isinstance(error, discord.NotFound):
return return

View file

@ -1,10 +1,8 @@
{ {
"author" : ["cswimr"], "author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Galaxy!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs", "install_msg" : "Thank you for installing Galaxy!\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs",
"name" : "Galaxy", "name" : "Galaxy",
"short" : "Custom cog intended for use on the Galaxy discord server.", "short" : "Custom cog intended for use on the Galaxy discord server.",
"description" : "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.", "end_user_data_statement" : "This cog does not store any End User Data."
"hidden": true,
"disabled": false
} }

0
galaxy/temp.py Normal file
View file

View file

@ -1,8 +1,8 @@
{ {
"author": [ "author": [
"cswimr, yname, meelyman" "SeaswimmerTheFsh, 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/cswimr/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://github.com/SeaswimmerTheFsh/GalaxyCogs/issues.",
"name": "Galaxy", "name": "Galaxy",
"short": "Cogs intended for use on the Galaxy discord server.", "short": "Cogs intended for use on the Galaxy discord server.",
"description": "Custom cogs/cog modifications intended for the Galaxy discord server." "description": "Custom cogs/cog modifications intended for the Galaxy discord server."

View file

@ -1,5 +1,5 @@
from .info import Info from .info import Info
async def setup(bot): def setup(bot):
await bot.add_cog(Info(bot)) bot.add_cog(Info(bot))

View file

@ -1,10 +1,9 @@
{ {
"author" : ["cswimr"], "author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Info!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs", "install_msg" : "Thank you for installing Info!\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs",
"name" : "Info", "name" : "Info",
"short" : "Provides information on Discord objects.", "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>.", "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>.",
"end_user_data_statement" : "This cog does not store any End User Data.", "end_user_data_statement" : "This cog does not store any End User Data."
"hidden": true,
"disabled": false
} }

View file

@ -1,13 +1,19 @@
import re
from datetime import datetime from datetime import datetime
import discord import discord
from redbot.core import Config, app_commands, commands from redbot.core import commands, checks, Config
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.i18n import Translator, cog_i18n from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import (bold, humanize_number, import re
humanize_timedelta) from redbot.core.utils.chat_formatting import (
bold,
humanize_number,
humanize_timedelta,
)
from redbot.core.utils.common_filters import ( from redbot.core.utils.common_filters import (
escape_spoilers_and_mass_mentions, filter_invites) filter_invites,
escape_spoilers_and_mass_mentions
)
_ = T_ = Translator("General", __file__) _ = T_ = Translator("General", __file__)
@ -28,14 +34,14 @@ class Info(commands.Cog):
self.config.register_user(**self.default_user_settings) self.config.register_user(**self.default_user_settings)
self.cache: dict = {} self.cache: dict = {}
async def red_delete_data_for_user(self): async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete.""" """Nothing to delete."""
return return
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(embed_links=True) @commands.bot_has_permissions(embed_links=True)
async def serverinfo(self, ctx: commands.Context, details: bool = False): async def serverinfo(self, ctx, details: bool = False):
""" """
Show server information. Show server information.
@ -73,8 +79,8 @@ class Info(commands.Cog):
) )
) )
if guild.icon: if guild.icon:
data.set_author(name=guild.name, icon_url=str(guild.icon.replace(format='png'))) data.set_author(name=guild.name, icon_url=str(guild.icon_url_as(format='png')))
data.set_thumbnail(url=str(guild.icon.replace(format='png'))) data.set_thumbnail(url=str(guild.icon_url_as(format='png')))
else: else:
data.set_author(name=guild.name) data.set_author(name=guild.name)
else: else:
@ -82,16 +88,16 @@ class Info(commands.Cog):
def _size(num: int): def _size(num: int):
for unit in ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]: for unit in ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]:
if abs(num) < 1024.0: if abs(num) < 1024.0:
return f"{num:.1f}{unit}" return "{0:.1f}{1}".format(num, unit)
num /= 1024.0 num /= 1024.0
return f"{num:.1f}YB" return "{0:.1f}{1}".format(num, "YB")
def _bitsize(num: int): def _bitsize(num: int):
for unit in ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]: for unit in ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]:
if abs(num) < 1000.0: if abs(num) < 1000.0:
return f"{num:.1f}{unit}" return "{0:.1f}{1}".format(num, unit)
num /= 1000.0 num /= 1000.0
return f"{num:.1f}YB" return "{0:.1f}{1}".format(num, "YB")
shard_info = ( shard_info = (
_("\nShard ID: **{shard_id}/{shard_count}**").format( _("\nShard ID: **{shard_id}/{shard_count}**").format(
@ -123,7 +129,7 @@ class Info(commands.Cog):
for emoji, value in online_stats.items(): for emoji, value in online_stats.items():
try: try:
num = len([m for m in guild.members if value(m)]) num = len([m for m in guild.members if value(m)])
except Exception as error: # pylint: disable=broad-exception-caught except Exception as error:
print(error) print(error)
continue continue
else: else:
@ -167,7 +173,7 @@ class Info(commands.Cog):
name=guild.name name=guild.name
) )
if guild.icon: if guild.icon:
data.set_thumbnail(url=str(guild.icon.url)) data.set_thumbnail(url=str(guild.icon_url))
data.add_field(name=_("Members:"), value=member_msg) data.add_field(name=_("Members:"), value=member_msg)
data.add_field( data.add_field(
name=_("Channels:"), name=_("Channels:"),
@ -254,7 +260,7 @@ class Info(commands.Cog):
) )
data.add_field(name=_("Nitro Boost:"), value=nitro_boost) data.add_field(name=_("Nitro Boost:"), value=nitro_boost)
if guild.splash: if guild.splash:
data.set_image(url=str(guild.splash.replace(format='png'))) data.set_image(url=str(guild.splash_url_as(format='png')))
data.set_footer(text=joined_on) data.set_footer(text=joined_on)
await ctx.send(embed=data) await ctx.send(embed=data)
@ -267,7 +273,7 @@ class Info(commands.Cog):
c_status = None c_status = None
if not a.name and not a.emoji: if not a.name and not a.emoji:
return None, discord.ActivityType.custom return None, discord.ActivityType.custom
if a.name and a.emoji: elif a.name and a.emoji:
c_status = _("Custom: {emoji} {name}").format(emoji=a.emoji, name=a.name) c_status = _("Custom: {emoji} {name}").format(emoji=a.emoji, name=a.name)
elif a.emoji: elif a.emoji:
c_status = _("Custom: {emoji}").format(emoji=a.emoji) c_status = _("Custom: {emoji}").format(emoji=a.emoji)
@ -341,7 +347,7 @@ class Info(commands.Cog):
self.handle_watching(user), self.handle_watching(user),
self.handle_competing(user), self.handle_competing(user),
]: ]:
status_string = a status_string, status_type = a
if status_string is None: if status_string is None:
continue continue
string += f"{status_string}\n" string += f"{status_string}\n"
@ -359,7 +365,7 @@ class Info(commands.Cog):
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@commands.bot_has_permissions(embed_links=True) @commands.bot_has_permissions(embed_links=True)
async def userinfo(self, ctx: commands.Context, *, member: discord.Member = None): async def userinfo(self, ctx, *, member: discord.Member = None):
"""Show information about a member. """Show information about a member.
This includes fields for status, discord join date, server This includes fields for status, discord join date, server
join date, voice state and previous names/nicknames. join date, voice state and previous names/nicknames.
@ -471,10 +477,10 @@ class Info(commands.Cog):
if voice_state and voice_state.channel: if voice_state and voice_state.channel:
data.add_field( data.add_field(
name=_("Current voice channel"), name=_("Current voice channel"),
value=f"{voice_state.channel.mention} ID: {voice_state.channel.id}", value="{0.mention} ID: {0.id}".format(voice_state.channel),
inline=False, inline=False,
) )
data.set_footer(text=_(f"Member #{member_number} | User ID: {member.id}")) data.set_footer(text=_("Member #{} | User ID: {}").format(member_number, member.id))
if member.discriminator == "0": if member.discriminator == "0":
name = str(member.name) name = str(member.name)
@ -483,42 +489,17 @@ class Info(commands.Cog):
name = " ~ ".join((name, member.nick)) if member.nick else name name = " ~ ".join((name, member.nick)) if member.nick else name
name = filter_invites(name) name = filter_invites(name)
avatar = member.avatar.replace(format='png') avatar = member.avatar_url_as(format='png')
data.set_author(name=f"{statusemoji} {name}", url=avatar) data.set_author(name=f"{statusemoji} {name}", url=avatar)
data.set_thumbnail(url=avatar) data.set_thumbnail(url=avatar)
await ctx.send(embed=data) await ctx.send(embed=data)
async def fetch_twemoji(self, unicode_emoji): @commands.command()
base_url = "https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/" @commands.guild_only()
emoji_codepoint = "-".join([hex(ord(char))[2:] for char in unicode_emoji]) async def roleinfo(self, ctx, role: discord.Role, list_permissions: bool = False):
segments = emoji_codepoint.split("-") """Gives information on a specific role.
valid_segments = [seg for seg in segments if len(seg) >= 4] `list_permissions` is ignored if the role you're checking has the `Administrator` permission."""
emoji_url = f"{base_url}{valid_segments[0]}.png"
return emoji_url
@app_commands.command()
@app_commands.guild_only()
async def roleinfo(self, interaction: discord.Interaction, role: discord.Role, list_permissions: bool = False):
"""Gives information on a given role.
Parameters
-----------
role: discord.Role
The role you're checking
list_permissions: bool
Whether or not to list permissions. Ignored if the role has Administrator.
"""
try:
icon = role.display_icon
if isinstance(icon, discord.Asset):
icon_url = icon.url
elif isinstance(icon, str):
icon_url = await self.fetch_twemoji(unicode_emoji=icon)
else:
icon_url = None
except: # pylint: disable=bare-except
icon_url = None
permissions = role.permissions permissions = role.permissions
if role.color.value == 0: if role.color.value == 0:
colorint = 10070709 colorint = 10070709
@ -528,22 +509,7 @@ class Info(commands.Cog):
color = re.sub('#',"",str(role.color)) color = re.sub('#',"",str(role.color))
colorcodelink = f"https://www.color-hex.com/color/{color}" colorcodelink = f"https://www.color-hex.com/color/{color}"
timestamp = int(datetime.timestamp(role.created_at)) timestamp = int(datetime.timestamp(role.created_at))
if role.managed is True: embed = discord.Embed(title=f"{role.name}", color=colorint, description=f"**ID:** {role.id}\n**Mention:** {role.mention}\n**Creation Date:** <t:{timestamp}>\n**Color:** [#{color}]({colorcodelink})\n**Hoisted:** {role.hoist}\n**Position:** {role.position}\n**Managed:** {role.managed}\n**Mentionable:** {role.mentionable}\n**Administrator:** {permissions.administrator}")
managed_status = "True" if permissions.administrator == False and list_permissions == True:
if role.is_premium_subscriber() is True: embed.add_field(name="Permissions", value=f"**Manage Server:** {permissions.manage_guild}\n**Manage Webhooks:** {permissions.manage_webhooks}\n**Manage Channels:** {permissions.manage_channels}\n**Manage Roles:** {permissions.manage_roles}\n**Manage Emojis:** {permissions.manage_emojis}\n**Manage Messages:** {permissions.manage_messages}\n**Manage Nicknames:** {permissions.manage_nicknames}\n**Mention @everyone**: {permissions.mention_everyone}\n**Ban Members:** {permissions.ban_members}\n**Kick Members:** {permissions.kick_members}")
managed_status += "\n**Management Type:** Nitro Booster" await ctx.send(embed=embed)
if role.is_bot_managed() is True:
managed_status += "\n**Management Type:** Managed Bot Role"
if role.tags.is_guild_connection() is True:
managed_status +="\n**Management Type:** Guild Connection"
if role.tags.is_available_for_purchase() is True:
managed_status +="\n**Management Type:** Subscription"
else:
managed_status = "False"
description = f"**ID:** {role.id}\n**Mention:** {role.mention}\n**Creation Date:** <t:{timestamp}>\n**Color:** [#{color}]({colorcodelink})\n**Hoisted:** {role.hoist}\n**Position:** {role.position}\n**Managed:** {managed_status}\n**Mentionable:** {role.mentionable}\n**Administrator:** {permissions.administrator}"
embed = discord.Embed(title=f"{role.name}", color=colorint, description=description)
if icon_url:
embed.set_thumbnail(url=icon_url)
if permissions.administrator is False and list_permissions is True:
embed.add_field(name="Permissions", value=f"**Manage Server:** {permissions.manage_guild}\n**Manage Webhooks:** {permissions.manage_webhooks}\n**Manage Channels:** {permissions.manage_channels}\n**Manage Roles:** {permissions.manage_roles}\n**Create Expressions:** {permissions.create_expressions}\n**Manage Events:** {permissions.manage_events}\n**Manage Messages:** {permissions.manage_messages}\n**Manage Nicknames:** {permissions.manage_nicknames}\n**Mention @everyone**: {permissions.mention_everyone}\n**Ban Members:** {permissions.ban_members}\n**Kick Members:** {permissions.kick_members}\n**Timeout Members:** {permissions.moderate_members}\n**View Audit Log:** {permissions.view_audit_log}")
await interaction.response.send_message(embed=embed, ephemeral=True)

View file

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

View file

@ -1,10 +0,0 @@
{
"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.",
"end_user_data_statement" : "This cog does not store any End User Data.",
"hidden": true,
"disabled": false
}

View file

@ -1,100 +0,0 @@
import discord
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 cswimr."""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=4285273314713, force_registration=True)
self.config.register_global(
request_channel = None,
gitea_root_url = None,
gitea_repository_owner = None,
gitea_repository = None,
gitea_token = None
)
@commands.command()
@checks.is_owner()
async def issuesconfig(self, ctx: commands.Context, channel: discord.TextChannel = None):
if channel:
await self.config.request_channel.set(channel.id)
await ctx.send(content=f"Channel set to {channel.mention}.\nRun this command again without a channel argument to configure the other settings.")
else:
await ctx.channel.send(content="Click the button below to configure the cog.", view=self.IssueConfigurationButton(self.config, ctx))
@app_commands.command()
async def issues(self, interaction: discord.Interaction):
"""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/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):
channel = self.bot.get_channel(await self.config.request_channel())
if channel is None:
await original_interaction.edit_original_response(content="Command cancelled.", view=None)
await interaction.response.send_message(content="The cog is misconfigured, please report this error.", ephemeral=True)
try:
message = await channel.send(content=".")
await message.edit(content="", embed=embed, view=self.IssueResponseButtons(channel, message.id, interaction.user))
await original_interaction.edit_original_response(content="Issue request sent!", embed=embed, view=None)
await interaction.response.defer()
except (discord.HTTPException, discord.Forbidden) as error:
await original_interaction.edit_original_response(content="Command cancelled.", view=None)
await interaction.response.send_message(content=f"The cog is misconfigured, please report this error.\n```{error}```", ephemeral=True)
class IssueButtons(discord.ui.View):
def __init__(self, color, cog_instance, original_interaction):
super().__init__()
self.color = color
self.cog_instance = cog_instance
self.original_interaction = original_interaction
@discord.ui.button(label="Bot Bug", style=discord.ButtonStyle.danger)
async def issue_button_bot_bug(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.BotBugModal(self.color, self.cog_instance, self.original_interaction))
@discord.ui.button(label="Cog Bug", style=discord.ButtonStyle.danger)
async def issue_button_cog_bug(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.CogBugModal(self.color, self.cog_instance, self.original_interaction))
@discord.ui.button(label="Bot Suggestion", style=discord.ButtonStyle.blurple)
async def issue_button_bot_suggestion(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.BotSuggestionModal(self.color, self.cog_instance, self.original_interaction))
@discord.ui.button(label="Cog Suggestion", style=discord.ButtonStyle.blurple)
async def issue_button_cog_suggestion(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.CogSuggestionModal(self.color, self.cog_instance, self.original_interaction))
class IssueConfigurationButton(discord.ui.View):
def __init__(self, config, ctx):
super().__init__()
self.config = config
self.ctx = ctx
@discord.ui.button(label="Change Configuration", style=discord.ButtonStyle.blurple, row=0)
async def issue_configuration_button(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.IssuesConfigurationModal(self.config, self.ctx))
class IssueResponseButtons(discord.ui.View):
def __init__(self, channel, message_id, user):
super().__init__()
self.channel = channel
self.message_id = message_id
self.user = user
@discord.ui.button(label="Approve", style=discord.ButtonStyle.green)
async def issue_response_button_approve(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.IssueResponseModal(self.channel, self.message_id, self.user, True))
@discord.ui.button(label="Deny", style=discord.ButtonStyle.danger)
async def issue_response_button_deny(self, interaction: discord.Interaction, button: discord.ui.Button): # pylint: disable=unused-argument
await interaction.response.send_modal(modals.IssueResponseModal(self.channel, self.message_id, self.user, False))

View file

@ -1,382 +0,0 @@
import aiohttp
import discord
from redbot.core import Config
#
# Misc. functions
#
def construct_embed(interaction: discord.Interaction, fields: list[discord.Embed.fields], color: str):
embed = discord.Embed(title="Issue Request", color=color)
for item in fields:
title = item.label
value = item.value
if value is not None:
if len(value) > 1024:
words = value.split()
split_value = []
current_part = ""
for word in words:
if len(current_part) + len(word) + 1 <= 1024:
current_part += word + " "
else:
split_value.append(current_part.strip())
current_part = word + " "
if current_part:
split_value.append(current_part.strip())
for i, part in enumerate(split_value):
embed.add_field(
name=title if i == 0 else "\u200b", value=part, inline=False
)
else:
embed.add_field(name=title, value=value, inline=False)
if interaction.user.discriminator == "0":
username = interaction.user.name
else:
username = f"{interaction.user.name}#{interaction.user.discriminator}"
embed.set_footer(
text=f"Submitted by {username} ({interaction.user.id})",
icon_url=interaction.user.display_avatar.url,
)
return embed
#
# Modals for the core '/issues' command.
#
class BotBugModal(discord.ui.Modal, title="Creating issue..."):
def __init__(self, color, cog_instance, original_interaction):
super().__init__()
self.color = color
self.cog_instance = cog_instance
self.original_interaction = original_interaction
bug_description = discord.ui.TextInput(
label="Describe the bug",
placeholder="A clear and concise description of what the bug is.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
reproduction_steps = discord.ui.TextInput(
label="To Reproduce",
placeholder="What caused the bug?",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
expected_behavior = discord.ui.TextInput(
label="Expected Behavior",
placeholder="A clear and concise description of what you expected to happen.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
additional_context = discord.ui.TextInput(
label="Additional Context",
placeholder="Add any other context about the problem here.",
style=discord.TextStyle.paragraph,
required=False,
max_length=2048
)
async def on_submit(self, interaction: discord.Interaction):
fields = [self.bug_description, self.reproduction_steps, self.expected_behavior, self.additional_context]
embed = construct_embed(interaction, fields, self.color)
embed.set_author(name="Bot Bug Report")
await self.cog_instance.submit_issue_request(interaction=interaction, original_interaction=self.original_interaction, embed=embed)
class CogBugModal(discord.ui.Modal, title="Creating issue..."):
def __init__(self, color, cog_instance, original_interaction):
super().__init__()
self.color = color
self.cog_instance = cog_instance
self.original_interaction = original_interaction
cog_name = discord.ui.TextInput(
label="What cog is causing this error?",
placeholder="If unsure, put \"GalaxyCogs\".",
style=discord.TextStyle.short,
required=True,
max_length=50
)
bug_description = discord.ui.TextInput(
label="Describe the bug",
placeholder="A clear and concise description of what the bug is.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
reproduction_steps = discord.ui.TextInput(
label="To Reproduce",
placeholder="What caused the bug?",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
expected_behavior = discord.ui.TextInput(
label="Expected Behavior",
placeholder="A clear and concise description of what you expected to happen.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
additional_context = discord.ui.TextInput(
label="Additional Context",
placeholder="Add any other context about the problem here.",
style=discord.TextStyle.paragraph,
required=False,
max_length=2048
)
async def on_submit(self, interaction: discord.Interaction):
fields = [self.cog_name, self.bug_description, self.reproduction_steps, self.expected_behavior, self.additional_context]
embed = construct_embed(interaction, fields, self.color)
embed.set_author(name="Cog Bug Report")
await self.cog_instance.submit_issue_request(interaction=interaction, original_interaction=self.original_interaction, embed=embed)
class BotSuggestionModal(discord.ui.Modal, title="Creating issue..."):
def __init__(self, color, cog_instance, original_interaction):
super().__init__()
self.color = color
self.cog_instance = cog_instance
self.original_interaction = original_interaction
suggestion_description = discord.ui.TextInput(
label="Describe your suggestion.",
placeholder="A clear and concise description of what your suggestion is.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
alternatives = discord.ui.TextInput(
label="Describe alternatives you've considered.",
placeholder="A clear and concise description of any alternative solutions or features you've cnosidered.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
additional_context = discord.ui.TextInput(
label="Additional Context",
placeholder="Add any other context about your suggestion here.",
style=discord.TextStyle.paragraph,
required=False,
max_length=2048
)
async def on_submit(self, interaction: discord.Interaction):
fields = [self.suggestion_description, self.alternatives, self.additional_context]
embed = construct_embed(interaction, fields, self.color)
embed.set_author(name="Cog Suggestion")
await self.cog_instance.submit_issue_request(interaction=interaction, original_interaction=self.original_interaction, embed=embed)
class CogSuggestionModal(discord.ui.Modal, title="Creating issue..."):
def __init__(self, color, cog_instance, original_interaction):
super().__init__()
self.color = color
self.cog_instance = cog_instance
self.original_interaction = original_interaction
cog_name = discord.ui.TextInput(
label="What cog is your suggestion for?",
placeholder="If unsure, put \"GalaxyCogs\".",
style=discord.TextStyle.short,
required=True,
max_length=50
)
suggestion_description = discord.ui.TextInput(
label="Describe your suggestion.",
placeholder="A clear and concise description of what your suggestion is.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
alternatives = discord.ui.TextInput(
label="Describe alternatives you've considered.",
placeholder="A clear and concise description of any alternative solutions or features you've cnosidered.",
style=discord.TextStyle.paragraph,
required=True,
max_length=2048
)
additional_context = discord.ui.TextInput(
label="Additional Context",
placeholder="Add any other context about your suggestion here.",
style=discord.TextStyle.paragraph,
required=False,
max_length=2048
)
async def on_submit(self, interaction: discord.Interaction):
fields = [self.cog_name, self.suggestion_description, self.alternatives, self.additional_context]
embed = construct_embed(interaction, fields, self.color)
embed.set_author(name="Cog Suggestion")
await self.cog_instance.submit_issue_request(interaction=interaction, original_interaction=self.original_interaction, embed=embed)
#
# Response modal
#
class IssueResponseModal(discord.ui.Modal, title="Sending response message..."):
def __init__(self, channel, message_id, user, approved):
super().__init__()
self.channel = channel
self.message_id = message_id
self.user = user
self.approved = approved
self.config = Config.get_conf(None, cog_name="Issues", identifier=4285273314713)
issue_title_input = discord.ui.TextInput(
label="Title",
placeholder="Only use this if you're accepting the issue request.",
style=discord.TextStyle.short,
required=False,
max_length=200,
)
response = discord.ui.TextInput(
label="Response",
placeholder="",
style=discord.TextStyle.paragraph,
required=False,
max_length=1024,
)
async def on_submit(self, interaction: discord.Interaction):
message: discord.Message = await self.channel.fetch_message(self.message_id)
embed = message.embeds[0]
field_values = []
field_names = []
if self.approved:
embed.color = 1226519
embed.title = "Issue Request Approved"
status = "accepted"
else:
embed.color = 15671552
embed.title = "Issue Request Denied"
status = "denied"
await interaction.response.send_message(content=f"Issue request {status}.", ephemeral=True)
if self.response.value != "":
embed.add_field(
name=f"Response from {interaction.user.name}",
value=self.response.value,
inline=False,
)
if self.approved:
for field in embed.fields:
field_names.append(f"**{field.name}**")
field_values.append(field.value)
if self.issue_title_input.value != "":
_issue_title = self.issue_title_input.value
else:
_issue_title = "Automatically generated issue"
if embed.author.name == "Bot Bug Report":
issue_title = f"[BOT BUG] {_issue_title}"
# desired_labels = ["bot", "bug"]
elif embed.author.name == "Bot Suggestion":
issue_title = f"[BOT SUGGESTION] {_issue_title}"
# desired_labels = ["bot", "enhancement"]
elif embed.author.name == "Cog Bug Report":
issue_title = f"[{field_values[0]}] {_issue_title}"
# desired_labels = ["cog", "bug"]
elif embed.author.name == "Cog Suggestion":
issue_title = f"[{field_values[0]}] {_issue_title}"
# desired_labels = ["cog", "enhancement"]
headers = {
"Authorization": f"Bearer {await self.config.gitea_token()}",
"Accept": "application/json",
}
url = f"{await self.config.gitea_root_url()}/api/v1/repos/{await self.config.gitea_repository_owner()}/{await self.config.gitea_repository()}/issues"
issue_body = "\n".join(
[f"{name}\n{value}" for name, value in zip(field_names, field_values)]
)
# async def fetch_labels():
# async with aiohttp.ClientSession(headers=headers) as session:
# async with session.post(url=f"{await self.config.gitea_root_url()}/api/v1/repos/{await self.config.gitea_repository_owner()}/{await self.config.gitea_repository()}/labels") as response:
# label_list = []
# for label in response.json():
# if label["name"] in desired_labels:
# label_list.append(label["id"])
# if label_list is None:
# print("Error! Labels are not properly configured on the target repository.")
# return await label_list
issue_labels = None
if issue_labels is None:
issue_data = {"title": issue_title, "body": issue_body}
else:
issue_data = {"title": issue_title, "body": issue_body, "labels": issue_labels}
async def create_issue():
async with aiohttp.ClientSession(headers=headers) as session:
async with session.post(url, json=issue_data) as response:
return await response.json(), response.status
response_json, status_code = await create_issue()
if status_code == 201:
await interaction.response.send_message(
content=f"Issue request {status}.\n[Issue successfully created.]({response_json.get('html_url')})",
ephemeral=True,
)
embed.url = response_json.get("html_url")
await message.edit(embed=embed, view=None)
await self.user.send(embed=embed)
#
# Configuration modal
#
class IssuesConfigurationModal(discord.ui.Modal, title="Modifying configuration..."):
def __init__(self, config, ctx):
super().__init__()
self.config = config
self.ctx = ctx
gitea_root_url = discord.ui.TextInput(
label="Gitea Root URL",
placeholder="https://try.gitea.io",
style=discord.TextStyle.short,
required=False,
max_length=200
)
gitea_repository_owner = discord.ui.TextInput(
label="Gitea Repository Owner",
placeholder="foo",
style=discord.TextStyle.short,
required=False,
max_length=200
)
gitea_repository = discord.ui.TextInput(
label="Gitea Repository Name",
placeholder="bar",
style=discord.TextStyle.short,
required=False,
max_length=200
)
gitea_token = discord.ui.TextInput(
label="Gitea User Access Token",
placeholder="Generate one from your user settings page.",
style=discord.TextStyle.short,
required=False,
max_length=200
)
async def on_submit(self, interaction: discord.Interaction):
if self.gitea_token.value != "":
await self.config.gitea_token.set(self.gitea_token.value)
if self.gitea_root_url.value != "":
await self.config.gitea_root_url.set(self.gitea_root_url.value)
if self.gitea_repository_owner.value != "":
await self.config.gitea_repository_owner.set(self.gitea_repository_owner.value)
if self.gitea_repository.value != "":
await self.config.gitea_repository.set(self.gitea_repository.value)
await interaction.response.send_message(content="Configuration changed!")

View file

@ -0,0 +1,5 @@
from .musicdownloader import MusicDownloader
def setup(bot):
bot.add_cog(MusicDownloader(bot))

View file

@ -0,0 +1,9 @@
{
"author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing MusicDownloader!\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs",
"name" : "MusicDownloader",
"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": "yt_dlp"
}

View file

@ -0,0 +1,237 @@
import asyncio
import re
import discord
import os
import sqlite3
import concurrent.futures
from yt_dlp import YoutubeDL, utils
from redbot.core import commands, checks, Config, data_manager
class MusicDownloader(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=475728338)
self.config.register_global(
save_directory = str(data_manager.cog_data_path()) + f"{os.sep}MusicDownloader"
)
class UserBlacklisted(Exception):
def __init__(self, message="The user is blacklisted from using this command."):
super().__init__(message)
def create_table(self):
data_path = str(data_manager.cog_data_path()) + f"{os.sep}MusicDownloader"
db_path = os.path.join(data_path, "database.db")
if not os.path.isfile(db_path):
con = sqlite3.connect(db_path)
cur = con.cursor()
cur.execute('''
CREATE TABLE [IF NOT EXISTS] "blacklist_log" (
"user_id" INTEGER NOT NULL UNIQUE,
"reason" TEXT DEFAULT NULL,
PRIMARY KEY("user_id")
);
''')
con.commit()
con.close()
def blacklist_checker(self, user_id):
data_path = str(data_manager.cog_data_path()) + f"{os.sep}MusicDownloader"
db_path = os.path.join(data_path, "database.db")
con = sqlite3.connect(db_path)
cur = con.cursor()
cur.execute(f"SELECT user_id, reason FROM blacklist_log WHERE user_id = ?;", (user_id,))
result = cur.fetchone()
con.close()
if result:
user_id, reason = result
raise self.UserBlacklisted(reason)
async def cog_load(self):
self.create_table()
@commands.command()
@checks.is_owner()
async def change_data_path(self, ctx: commands.Context, *, data_path: str = None):
"""This command changes the data path the `[p]download` command outputs to."""
old_path = await self.config.save_directory()
if not data_path:
await ctx.send(f"The current data path is `{old_path}`.")
return
if os.path.isdir(data_path):
await self.config.save_directory.set(data_path)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"The save directory has been set to `{data_path}`.\n It was previously set to `{old_path}`.")
await ctx.send(embed=embed)
elif os.path.isfile(data_path):
await ctx.send("The path you've provided leads to a file, not a directory!")
elif os.path.exists(data_path) is False:
await ctx.send("The path you've provided doesn't exist!")
def youtube_download(self, url: str, path: str):
"""This method does the actual downloading of the YouTube Video."""
class Logger:
def debug(self, msg):
if msg.startswith('[debug] '):
pass
else:
self.info(msg)
def info(self, msg):
pass
def warning(self, msg):
pass
def error(self, msg):
print(msg)
ydl_opts = {
'logger': Logger(),
'format': 'm4a/bestaudio/best',
'postprocessors': [{'key': 'FFmpegExtractAudio', 'preferredcodec': 'm4a',}],
'paths': {'home': path},
'verbose': True
}
with YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url=url, download=False)
title = info['title']
id = info['id']
filename = title + f' [{id}].m4a'
full_filename = os.path.join(path, filename)
if os.path.isfile(full_filename):
previously_existed = True
else:
with YoutubeDL(ydl_opts) as ydl:
error_code = ydl.download(url)
previously_existed = False
return filename, previously_existed
async def download_file(self, url: str, path: str):
with concurrent.futures.ThreadPoolExecutor() as executor:
result = await self.bot.loop.run_in_executor(executor, self.youtube_download, url, path)
return result
@commands.command(aliases=["dl"])
async def download(self, ctx: commands.Context, url: str, delete: bool = False, subfolder: str = None):
"""This command downloads a YouTube Video as an `m4a` 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.
**Arguments**
- The `url` argument is just the url of the YouTube Video you're downloading.
- The `delete` argument will automatically delete the audio file after uploading it to Discord. If set to False, it will only save the file if you are a bot owner.
- 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)
except self.UserBlacklisted as e:
await ctx.send(f"You are blacklisted from running this command!\nReason: `{e}`")
return
data_path = await self.config.save_directory()
if subfolder and await self.bot.is_owner(ctx.author):
data_path = os.path.join(data_path, subfolder)
illegal_chars = r'<>:"/\|?*'
if any(char in illegal_chars for char in subfolder):
pattern = "[" + re.escape(illegal_chars) + "]"
modified_subfolder = re.sub(pattern, r'__**\g<0>**__', subfolder)
await ctx.send(f"Your subfolder contains illegal characters: `{modified_subfolder}`")
return
elif os.path.isfile(data_path):
await ctx.send("Your 'subfolder' is a file, not a directory!")
return
elif os.path.exists(data_path) is False:
message = await ctx.send("Your subfolder does not exist yet, would you like to continue? It will be automatically created.")
def check(message):
return message.author == ctx.author and message.content.lower() in ['yes', 'ye', 'y']
try:
await self.bot.wait_for('message', check=check, timeout=60) # Timeout after 60 seconds
except asyncio.TimeoutError:
await message.edit("You took too long to respond.")
else:
await message.edit("Confirmed!")
try:
os.makedirs(data_path)
except OSError as e:
await message.edit(f"Encountered an error attempting to create the subfolder!\n`{e}`")
msg = message.edit
else:
msg = ctx.send
message = await msg("YouTube Downloader started!")
try:
ytdlp_output = await self.download_file(url, data_path)
except utils.DownloadError or utils.ExtractorError:
await message.edit(content="Please provide a link to YouTube and not another site.\nThe site you've linked to is known for using DRM protection, so MusicDownloader cannot download from it.")
return
full_filename = os.path.join(data_path, ytdlp_output[0])
while not os.path.isfile(full_filename):
await asyncio.sleep(0.2)
if os.path.isfile(full_filename):
with open(full_filename, 'rb') as file:
try:
complete_message = await ctx.send(content="YouTube Downloader completed!\nDownloaded file:", file=discord.File(file, ytdlp_output[0]))
except ValueError:
complete_message = await ctx.send(content="YouTube Downloader completed, but the audio file was too large to upload.")
file.close()
if delete is True or await self.bot.is_owner(ctx.author) is False:
if ytdlp_output[1] is False:
os.remove(full_filename)
await complete_message.edit(content="YouTube Downloader completed!\nFile has been deleted.\nDownloaded file:")
if ytdlp_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:")
@commands.group(name="dl-blacklist", invoke_without_command=True)
async def blacklist(self, ctx: commands.Context, user: discord.User = None):
"""Group command for managing the blacklist."""
if not user:
user = ctx.author
data_path = str(data_manager.cog_data_path()) + f"{os.sep}MusicDownloader"
db_path = os.path.join(data_path, "database.db")
if user is None:
await ctx.send("Please provide a user to check in the blacklist.")
return
con = sqlite3.connect(db_path)
cur = con.cursor()
cur.execute("SELECT user_id, reason FROM blacklist_log WHERE user_id = ?;", (user.id,))
result = cur.fetchone()
if result:
user_id, reason = result
await ctx.send(f"{user.mention} is in the blacklist for the following reason: `{reason}`", allowed_mentions = discord.AllowedMentions(users=False))
else:
await ctx.send(f"{user.mention} is not in the blacklist.", allowed_mentions = discord.AllowedMentions(users=False))
con.close()
@blacklist.command(name='add')
@checks.is_owner()
async def blacklist_add(self, ctx: commands.Context, user: discord.User, *, reason: str = None):
if not reason:
reason = 'No reason provided.'
data_path = str(data_manager.cog_data_path()) + f"{os.sep}MusicDownloader"
db_path = os.path.join(data_path, "database.db")
con = sqlite3.connect(db_path)
cur = con.cursor()
cur.execute("SELECT user_id FROM blacklist_log WHERE user_id = ?;", (user.id,))
result = cur.fetchone()
if result:
await ctx.send("User is already in the blacklist.")
con.close()
return
cur.execute("INSERT INTO blacklist_log (user_id, reason) VALUES (?, ?);", (user.id, reason))
con.commit()
con.close()
await ctx.send(f"{user.mention} has been added to the blacklist with the reason: `{reason}`")
@blacklist.command(name='remove')
@checks.is_owner()
async def blacklist_remove(self, ctx: commands.Context, user: discord.User):
data_path = str(data_manager.cog_data_path()) + f"{os.sep}MusicDownloader"
db_path = os.path.join(data_path, "database.db")
con = sqlite3.connect(db_path)
cur = con.cursor()
cur.execute("SELECT user_id FROM blacklist_log WHERE user_id = ?;", (user.id,))
result = cur.fetchone()
if not result:
await ctx.send("User is not in the blacklist.")
con.close()
return
cur.execute("DELETE FROM blacklist_log WHERE user_id = ?;", (user.id,))
con.commit()
con.close()
await ctx.send(f"{user.mention} has been removed from the blacklist.")

View file

@ -1,5 +1,5 @@
from .podcast import Podcast from .podcast import Podcast
async def setup(bot): def setup(bot):
await bot.add_cog(Podcast(bot)) bot.add_cog(Podcast(bot))

View file

@ -1,10 +1,9 @@
{ {
"author" : ["cswimr"], "author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Podcast!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs", "install_msg" : "Thank you for installing Podcast!\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs",
"name" : "Podcast", "name" : "Podcast",
"short" : "Provides a questions submission system.", "short" : "Provides a questions submission system.",
"description" : "Provies a questions submission system.", "description" : "Provies a questions submission system.",
"end_user_data_statement" : "This cog does not store any End User Data.", "end_user_data_statement" : "This cog does not store any End User Data."
"hidden": true,
"disabled": true
} }

View file

@ -1,9 +1,11 @@
from redbot.core import Config, checks, commands import discord
from datetime import datetime
from redbot.core.bot import Red
from redbot.core import commands, checks, Config, bot
class Podcast(commands.Cog): class Podcast(commands.Cog):
"""Provides a questions submission system for podcasts. """Provides a questions submission system for podcasts.
Developed by cswimr.""" Developed by SeaswimmerTheFsh."""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@ -19,19 +21,20 @@ class Podcast(commands.Cog):
) )
@commands.command() @commands.command()
async def podcast(self, ctx: commands.Context, question: str): async def podcast(self, ctx, question: str):
"""Submits a question to the Podcast.""" """Submits a question to the Podcast."""
prefix = await self.bot.get_valid_prefixes() prefix = await self.bot.get_valid_prefixes()
if await self.config.guild(ctx.guild).global_mode(True): if await self.config.guild(ctx.guild).global_mode(True):
submission_channel = ctx.bot.get_channel(await self.config.global_submission_channel()) submission_channel = bot.get_channel(await self.config.global_submission_channel())
blacklisted_users = await self.config.global_blacklisted_users() blacklisted_users = await self.config.global_blacklisted_users()
elif await self.config.guild(ctx.guild).global_mode(False): elif await self.config.guild(ctx.guild).global_mode(False):
submission_channel = ctx.bot.get_channel(await self.config.guild(ctx.guild).submission_channel()) submission_channel = bot.get_channel(await self.config.guild(ctx.guild).submission_channel())
blacklisted_users = await self.config.guild(ctx.guild).blacklisted_users() blacklisted_users = await self.config.guild(ctx.guild).blacklisted_users()
else: else:
return return
if ctx.author.id in blacklisted_users: if ctx.author.id in blacklisted_users:
await ctx.author.send(content=f"You are blacklisted from ``{prefix}podcast``!") await ctx.author.send(content=f"You are blacklisted from ``{prefix}podcast``!")
return
else: else:
await submission_channel.send(content=f"{question}") await submission_channel.send(content=f"{question}")
await ctx.send(content="Question submitted!") await ctx.send(content="Question submitted!")
@ -45,10 +48,10 @@ class Podcast(commands.Cog):
@podcastset.command(name="global") @podcastset.command(name="global")
async def set_global_mode(self, ctx, enabled: bool): async def set_global_mode(self, ctx, enabled: bool):
"""Enables or disables global mode.""" """Enables or disables global mode."""
if enabled is True: if enabled == True:
await self.config.guild(ctx.guild).global_mode.set(True) await self.config.guild(ctx.guild).global_mode.set(True)
await ctx.send(content="``global_mode`` has been enabled.") await ctx.send(content="``global_mode`` has been enabled.")
elif enabled is False: elif enabled == False:
await self.config.guild(ctx.guild).global_mode.set(False) await self.config.guild(ctx.guild).global_mode.set(False)
await ctx.send(content="``global_mode`` has been disabled.") await ctx.send(content="``global_mode`` has been disabled.")
else: else:

2023
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,28 +0,0 @@
[tool.poetry]
name = "galaxycogs"
version = "0.1.0"
description = "Custom cogs/cog modifications intended for the Galaxy discord server."
authors = ["Galaxy Discord Management Team"]
license = "MPL 2"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = ">=3.9,<3.12"
Red-DiscordBot = "^3.5.5"
pytimeparse2 = "^1.7.1"
prisma = "^0.10.0"
mysql-connector-python = "^8.1.0"
humanize = "^4.8.0"
pytube = "^15.0.0"
ruff = "^0.3.6"
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
pylint = "^2.17.5"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View file

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

View file

@ -1,10 +0,0 @@
{
"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!.",
"end_user_data_statement" : "This cog does not store any End User Data.",
"hidden": true,
"disabled": false
}

View file

@ -1,66 +0,0 @@
from typing import Union
import discord
from redbot.core import commands, app_commands
class Send(commands.Cog):
"""Allows you to send messages as the bot account."""
def __init__(self, bot):
self.bot = bot
async def send_to_target(self, target: Union[discord.Member, discord.TextChannel], interaction: discord.Interaction, message: str, secondary_message: str = None):
if isinstance(target, discord.Member):
target_type = "member"
elif isinstance(target, discord.TextChannel):
target_type = "textchannel"
try:
await target.send(message)
if secondary_message is not None:
await target.send(secondary_message)
await interaction.response.send_message(content=f"Message sent to {target.mention}!\nMessage contents:\n```{message}```\n```{secondary_message}```", ephemeral=True)
else:
await interaction.response.send_message(content=f"Message sent to {target.mention}!\nMessage contents:\n```{message}```", ephemeral=True)
except (discord.HTTPException, discord.Forbidden):
if target_type == "member":
await interaction.response.send_message(content="That user has their direct messages closed!", ephemeral=True)
elif target_type == "textchannel":
await interaction.response.send_message(content="I cannot access that channel!", ephemeral=True)
class MessageModal(discord.ui.Modal, title="Sending message..."):
def __init__(self, target):
super().__init__()
self.target = target
message = discord.ui.TextInput(
label="Message Content",
placeholder="I'm contacting you about your cars extended warranty...",
style=discord.TextStyle.paragraph,
max_length=1750
)
secondary_message = discord.ui.TextInput(
label="Secondary Message Content",
placeholder="Typically used for images/image links.",
style=discord.TextStyle.short,
required=False,
max_length=200
)
async def on_submit(self, interaction: discord.Interaction):
await Send.send_to_target(self, self.target, interaction, self.message, self.secondary_message)
send = app_commands.Group(name="send", description="Send a message as the bot user!")
@send.command(name="user", description="Sends a direct message to a user.")
async def user(self, interaction: discord.Interaction, member: discord.Member, message: str = None):
"""Sends a direct message to a user."""
if message:
await Send.send_to_target(self, member, interaction, message)
else:
await interaction.response.send_modal(Send.MessageModal(member))
@send.command(name="channel", description="Sends a message to a channel.")
async def channel(self, interaction: discord.Interaction, channel: discord.TextChannel, message: str = None):
"""Sends a message to a channel."""
if message:
await Send.send_to_target(self, channel, interaction, message)
else:
await interaction.response.send_modal(Send.MessageModal(channel))

View file

@ -1,5 +1,5 @@
from .shortmute import Shortmute from .shortmute import Shortmute
async def setup(bot): def setup(bot):
await bot.add_cog(Shortmute(bot)) bot.add_cog(Shortmute(bot))

View file

@ -1,12 +1,9 @@
{ {
"author" : ["cswimr"], "author" : ["SeaswimmerTheFsh"],
"install_msg" : "Thank you for installing Shortmute!\nYou can find the source code of this cog here: https://coastalcommits.com/cswimr/GalaxyCogs", "install_msg" : "Thank you for installing Shortmute!\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs\nYou can find the original source code from sravan-cogs here: <https://github.com/sravan1946/sravan-cogs/tree/master/timeout>",
"name" : "Shortmute", "name" : "Shortmute",
"short" : "Allows staff members to shortmute individuals for up to 30 minutes.", "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.", "description" : "Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.",
"tags" : ["moderation, mutes, timeouts"], "tags" : ["moderation, mutes, timeouts"],
"end_user_data_statement": "This cog stores no end user data.", "end_user_data_statement": "This cog stores no end user data."
"requirements": ["pytimeparse2"],
"hidden": true,
"disabled": false
} }

View file

@ -1,346 +1,237 @@
import contextlib
import datetime
from typing import List, Literal, Optional, Union
import discord import discord
from discord import ui import humanize
from discord.ext.commands import Bot from discord.http import Route
from pytimeparse2 import disable_dateutil, parse from redbot.core import Config, commands, modlog
from redbot.core import Config, app_commands, commands from redbot.core.bot import Red
from redbot.core.commands.converter import TimedeltaConverter
RequestType = Literal["discord_deleted_user", "owner", "user", "user_strict"]
class Shortmute(commands.Cog): class Shortmute(commands.Cog):
"""Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.""" """Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.
Maintained by SeaswimmerTheFsh.
Original source code by sravan1946."""
def __init__(self, bot) -> None: def __init__(self, bot: Red) -> None:
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=25781647388294, force_registration=True) self.config = Config.get_conf(self, identifier=2354731, force_registration=True)
self.config.register_guild( self.config.register_guild(
dm = True, dm = True
confirm = True,
logging_channels = [],
immune_roles = [],
blacklisted_users = []
) )
@app_commands.command() def format_help_for_context(self, ctx: commands.Context) -> str:
@app_commands.rename(target='member')
async def shortmute(self, interaction: discord.Interaction, target: discord.Member, duration: int, reason: str, evidence_link: str = None, evidence_image: discord.Attachment = None, skip_confirmation: bool = None):
"""Shortmute someone for up to 30m.
Parameters
-----------
target: discord.Member
The member to shortmute
duration: int
The duration of the shortmute
reason: str
The reason for the shortmute
evidence_link: str = None
An image link to evidence for the shortmute, do not use with evidence_image
evidence_image: discord.Attachment = None
An image file used as evidence for the shortmute, do not use with evidence_link
skip_confirmation: bool = None
This allows you skip the confirmation prompt and immediately shortmute the user.
""" """
disable_dateutil() Thanks Sinbad!
timedelta = parse(f'{duration} minutes', as_timedelta=True) """
if evidence_image and evidence_link: pre_processed = super().format_help_for_context(ctx)
await interaction.response.send_message(content="You've provided both the `evidence_image` and the `evidence_link` arguments! Please only use one or the other.", ephemeral=True) return f"{pre_processed}\n\nAuthors: {', '.join(self.__author__)}\nCog Version: {self.__version__}"
return
if evidence_link: async def red_delete_data_for_user(
evidence = evidence_link self, *, requester: RequestType, user_id: int
elif evidence_image: ) -> None:
evidence = str(evidence_image) # TODO: Replace this with the proper end user data removal handling.
else: super().red_delete_data_for_user(requester=requester, user_id=user_id)
evidence = None
if skip_confirmation is None: async def is_user_timed_out(self, member: discord.Member) -> bool:
skip_confirmation = not bool(await self.config.guild(interaction.guild).confirm()) r = Route(
passed_info = { "GET",
"target": target, "/guilds/{guild_id}/members/{user_id}",
"timedelta": timedelta, guild_id=member.guild.id,
"reason": reason, user_id=member.id,
"interaction": interaction, )
"color": await self.bot.get_embed_color(interaction.guild), try:
"evidence": evidence data = await self.bot.http.request(r)
except discord.NotFound:
return False
return data["communication_disabled_until"] is not None
async def pre_load(self):
with contextlib.suppress(RuntimeError):
await modlog.register_casetype(
name="timeout",
default_setting=True,
image=":mute:",
case_str="Timeout",
)
await modlog.register_casetype(
name="untimeout",
default_setting=True,
image=":sound:",
case_str="Untimeout",
)
async def timeout_user(
self,
ctx: commands.Context,
member: discord.Member,
time: Optional[datetime.timedelta],
reason: Optional[str] = None,
) -> None:
r = Route(
"PATCH",
"/guilds/{guild_id}/members/{user_id}",
guild_id=ctx.guild.id,
user_id=member.id,
)
payload = {
"communication_disabled_until": str(
datetime.datetime.now(datetime.timezone.utc) + time
)
if time
else None
} }
blacklisted_users_list = await self.config.guild(interaction.guild).blacklisted_users()
for user_id in blacklisted_users_list: await ctx.bot.http.request(r, json=payload, reason=reason)
if user_id == interaction.user.id: await modlog.create_case(
await interaction.response.send_message(content="You are blacklisted from `/shortmute`!", ephemeral=True) bot=ctx.bot,
return guild=ctx.guild,
if target.bot is True: created_at=datetime.datetime.now(datetime.timezone.utc),
await interaction.response.send_message(content="You cannot shortmute bots!", ephemeral=True) action_type="timeout" if time else "untimeout",
return user=member,
if interaction.user.guild_permissions.administrator is False: moderator=ctx.author,
immune_roles_list = await self.config.guild(interaction.guild).immune_roles() reason=reason,
for role_id in immune_roles_list: until=(datetime.datetime.now(datetime.timezone.utc) + time)
role = interaction.guild.get_role(role_id) if time
if role in target.roles: else None,
await interaction.response.send_message(content="You're trying to shortmute someone who is immune from shortmuting.", ephemeral=True) channel=ctx.channel,
return )
if target.guild_permissions.administrator is True: if await self.config.guild(member.guild).dm():
await interaction.response.send_message(content="You cannot shortmute people with the Administrator permission!", ephemeral=True) with contextlib.suppress(discord.HTTPException):
return message = "You have been"
if duration in (1, -1): message += (
readable_duration = f"{duration} minute" f" timed out for {humanize.naturaldelta(time)}"
else: if time
readable_duration = f"{duration} minutes" else " untimedout"
passed_info.update({ )
"readable_duration": readable_duration message += f" in {ctx.guild.name}"
}) message += f" for reason: {reason}" if reason else ""
if duration > 30: await member.send(message)
await interaction.response.send_message(content=f"{readable_duration} is longer than the 30 minutes you are allowed to shortmute users for.", ephemeral=True)
return async def timeout_role(
if duration < 1: self,
await interaction.response.send_message(content=f"Please shortmute the user for longer than {readable_duration}! The maximum duration is 30 minutes.", ephemeral=True) ctx: commands.Context,
return role: discord.Role,
if skip_confirmation is False: time: datetime.timedelta,
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)) reason: Optional[str] = None,
embed.set_footer(text="/shortmute") ) -> List[discord.Member]:
if evidence: failed = []
embed.set_image(url=evidence) members = list(role.members)
await interaction.response.send_message(embed=embed, view=self.ShortmuteButtons(timeout=180, passed_info=passed_info), ephemeral=True) for member in members:
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(interaction.guild))
edit_embed.set_footer(text="/shortmute")
if evidence:
edit_embed.set_image(url=evidence)
message = await interaction.response.send_message(embed=edit_embed, ephemeral=True)
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(interaction.guild))
dm_embed.set_footer(text="/shortmute")
if evidence:
dm_embed.set_image(url=evidence)
try: try:
await target.send(embed=dm_embed) await self.timeout_user(ctx, member, time, reason)
except discord.HTTPException: except discord.HTTPException:
await message.edit(content="Could not message the target, user most likely has Direct Messages disabled.") failed.append(member)
logging_channels_list = await self.config.guild(interaction.guild).logging_channels() return failed
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(interaction.guild))
logging_embed.set_footer(text="/shortmute")
if evidence:
logging_embed.set_image(url=evidence)
for channel_id in logging_channels_list:
channel_obj = interaction.guild.get_channel(channel_id)
await channel_obj.send(embed=logging_embed)
class ShortmuteButtons(ui.View): @commands.command(aliases=["tt"])
def __init__(self, timeout, passed_info: dict):
super().__init__()
self.timeout = timeout
self.passed_info = passed_info
self.config = Config.get_conf(None, cog_name='Shortmute', identifier=25781647388294)
@ui.button(label="Yes", style=discord.ButtonStyle.success)
async def shortmute_button_yes(self, button: ui.Button, interaction: discord.Interaction): # pylint: disable=unused-argument
disable_dateutil()
target: discord.Member = self.passed_info['target']
readable_duration = self.passed_info['readable_duration']
reason: str = self.passed_info['reason']
old_interaction: discord.Interaction = self.passed_info['interaction']
color = self.passed_info['color']
timedelta = self.passed_info['timedelta']
evidence = self.passed_info['evidence']
edit_embed = discord.Embed(title="Shortmute confirmed!", description=f"**Moderator:** {old_interaction.user.mention}\n**Target:** {target.mention}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=color)
if evidence:
edit_embed.set_image(url=evidence)
old_message = await old_interaction.edit_original_response(embed=edit_embed, view=None)
await target.timeout(timedelta, reason=f"User shortmuted for {readable_duration} by {old_interaction.user.name} ({old_interaction.user.id}) for: {reason}")
await old_interaction.channel.send(content=f"{target.mention} was shortmuted for {readable_duration} by {old_interaction.user.mention} for: `{reason}`")
if await self.config.guild(old_interaction.guild).dm() is True:
dm_embed = discord.Embed(title=f"You've been shortmuted in {old_interaction.guild.name}!", description=f"**Moderator:** {old_interaction.user.mention}\n**Target:** {target.mention}\n**Duration:** {readable_duration}\n**Reason:** `{reason}`", color=color)
dm_embed.set_footer(text="/shortmute")
if evidence:
dm_embed.set_image(url=evidence)
try:
await target.send(embed=dm_embed)
except discord.HTTPException:
await old_message.edit(content="Could not message the target, user most likely has Direct Messages disabled.")
logging_channels_list = await self.config.guild(old_interaction.guild).logging_channels()
if logging_channels_list:
logging_embed = discord.Embed(title="User Shortmuted", description=f"**Moderator:** {old_interaction.user.mention} ({old_interaction.user.id})\n**Target:** {target.mention} ({target.id})\n**Duration:** {readable_duration}\n**Reason:** `{reason}`\n**Confirmation Skipped:** False", color=color)
logging_embed.set_footer(text="/shortmute")
if evidence:
logging_embed.set_image(url=evidence)
for channel_id in logging_channels_list:
channel_obj = old_interaction.guild.get_channel(channel_id)
await channel_obj.send(embed=logging_embed)
@ui.button(label="No", style=discord.ButtonStyle.danger)
async def shortmute_button_no(self, button: ui.Button, interaction: discord.Interaction): # pylint: disable=unused-argument
message: discord.Message = await self.passed_info['interaction'].edit_original_response(content="Command cancelled.", view=None, embed=None)
await message.delete(delay=3)
@commands.group(name='shortmuteset', autohelp=True)
@commands.guild_only() @commands.guild_only()
@commands.admin() @commands.cooldown(1, 1, commands.BucketType.user)
async def shortmute_config(self, ctx: commands.Context): @commands.mod_or_permissions(administrator=True)
"""Used to configure the /shortmute command.""" async def timeout(
self,
ctx: commands.Context,
member_or_role: Union[discord.Member, discord.Role],
time: TimedeltaConverter(
minimum=datetime.timedelta(minutes=1),
maximum=datetime.timedelta(days=28),
default_unit="minutes",
allowed_units=["minutes", "seconds", "hours", "days"],
) = None,
*,
reason: Optional[str] = None,
):
"""
Timeout users.
`<member_or_role>` is the username/rolename, ID or mention. If provided a role,
everyone with that role will be timedout.
`[time]` is the time to mute for. Time is any valid time length such as `45 minutes`
or `3 days`. If nothing is provided the timeout will be 60 seconds default.
`[reason]` is the reason for the timeout. Defaults to `None` if nothing is provided.
Examples:
`[p]timeout @member 5m talks too much`
`[p]timeout @member 10m`
"""
if not time:
time = datetime.timedelta(seconds=60)
timestamp = datetime.datetime.now(datetime.timezone.utc) + time
timestamp = int(datetime.datetime.timestamp(timestamp))
if isinstance(member_or_role, discord.Member):
check = await is_allowed_by_hierarchy(ctx.bot, ctx.author, member_or_role)
if not check:
return await ctx.send("You cannot timeout this user due to hierarchy.")
if member_or_role.permissions_in(ctx.channel).administrator:
return await ctx.send("You can't timeout an administrator.")
await self.timeout_user(ctx, member_or_role, time, reason)
return await ctx.send(
f"{member_or_role.mention} has been timed out till <t:{timestamp}:f>."
)
if isinstance(member_or_role, discord.Role):
await ctx.send(
f"Timeing out {len(member_or_role.members)} members till <t:{timestamp}:f>."
)
failed = await self.timeout_role(ctx, member_or_role, time, reason)
return await ctx.send(f"Failed to timeout {len(failed)} members.")
@shortmute_config.group(name='channel', invoke_without_command=True, aliases=['channels']) @commands.command(aliases=["utt"])
@commands.guild_only() @commands.guild_only()
@commands.admin() @commands.cooldown(1, 1, commands.BucketType.user)
async def shortmute_config_channel(self, ctx: commands.Context): @commands.mod_or_permissions(administrator=True)
"""Manages /shortmute logging.""" async def untimeout(
current_list = await self.config.guild(ctx.guild).logging_channels() self,
already_in_list = [] ctx: commands.Context,
for channel_id in current_list: member_or_role: Union[discord.Member, discord.Role],
channel_obj = ctx.guild.get_channel(channel_id) *,
if channel_obj: reason: Optional[str] = None,
already_in_list.append(channel_obj.mention) ):
if already_in_list: """
await ctx.send("Channels already in the list:\n" + "\n".join(already_in_list)) Untimeout users.
else: `<member_or_role>` is the username/rolename, ID or mention. If
await ctx.send("No channels are currently in the logging list.") provided a role, everyone with that role will be untimed.
`[reason]` is the reason for the untimeout. Defaults to `None`
if nothing is provided.
"""
if isinstance(member_or_role, discord.Member):
is_timedout = await self.is_user_timed_out(member_or_role)
if not is_timedout:
return await ctx.send("This user is not timed out.")
await self.timeout_user(ctx, member_or_role, None, reason)
return await ctx.send(f"Removed timeout from {member_or_role.mention}")
if isinstance(member_or_role, discord.Role):
await ctx.send(
f"Removing timeout from {len(member_or_role.members)} members."
)
members = list(member_or_role.members)
for member in members:
if await self.is_user_timed_out(member):
await self.timeout_user(ctx, member, None, reason)
return await ctx.send(f"Removed timeout from {len(members)} members.")
@shortmute_config_channel.command(name='add') @commands.group()
@commands.guild_only() @commands.guild_only()
@commands.admin() @commands.admin_or_permissions(manage_guild=True)
async def shortmute_config_channel_add(self, ctx: commands.Context, channel: discord.TextChannel = None): async def timeoutset(self, ctx: commands.Context):
"""Adds a channel to the logging channels list.""" """Manage timeout settings."""
current_list: list = await self.config.guild(ctx.guild).logging_channels()
if channel:
if channel.id in current_list:
await ctx.send("This channel is already in the logging channel list!")
else:
current_list.append(channel.id)
await self.config.guild(ctx.guild).logging_channels.set(current_list)
await ctx.send(f"{channel.mention} has been added to the logging channels list.")
else:
await ctx.send("Please provide a valid channel!")
@shortmute_config_channel.command(name='remove') @timeoutset.command(name="dm")
@commands.guild_only() async def timeoutset_dm(self, ctx: commands.Context):
@commands.admin() """Change whether to DM the user when they are timed out."""
async def shortmute_config_channel_remove(self, ctx: commands.Context, channel: discord.TextChannel = None): current = await self.config.guild(ctx.guild).dm()
"""Removes a channel from the logging channels list.""" await self.config.guild(ctx.guild).dm.set(not current)
current_list = await self.config.guild(ctx.guild).logging_channels() w = "Will not" if current else "Will"
if channel.id in current_list: await ctx.send(f"I {w} DM the user when they are timed out.")
current_list.remove(channel.id)
await self.config.guild(ctx.guild).logging_channels.set(current_list)
await ctx.send(f"{channel.mention} has been removed from the logging channels list.")
else:
await ctx.send("Please provide a valid channel that exists in the logging channels list.")
@shortmute_config.group(name='role', invoke_without_command=True, aliases=['roles'])
@commands.guild_only()
@commands.admin()
async def shortmute_config_role(self, ctx: commands.Context):
"""Manages the immune roles list."""
current_list = await self.config.guild(ctx.guild).immune_roles()
already_in_list = []
for role_id in current_list:
role_obj = ctx.guild.get_role(role_id)
if role_obj:
already_in_list.append(role_obj.mention)
if already_in_list:
await ctx.send("Roles already in the immune roles list:\n" + "\n".join(already_in_list), allowed_mentions = discord.AllowedMentions(roles=False))
else:
await ctx.send("No roles are currently in the immune roles list.")
@shortmute_config_role.command(name='add') # https://github.com/phenom4n4n/phen-cogs/blob/8727d6ee74b40709c7eb9300713dc22b88a17915/roleutils/utils.py#L34
@commands.guild_only() async def is_allowed_by_hierarchy(
@commands.admin() bot: Red, user: discord.Member, member: discord.Member
async def shortmute_config_role_add(self, ctx: commands.Context, role: discord.Role = None): ) -> bool:
"""Adds roles to the immune roles list.""" return (
current_list = await self.config.guild(ctx.guild).immune_roles() user.guild.owner_id == user.id
if role: or user.top_role > member.top_role
if role.id in current_list: or await bot.is_owner(user)
await ctx.send("This role is already in the immune roles list!") )
else:
current_list.append(role.id)
await self.config.guild(ctx.guild).immune_roles.set(current_list)
await ctx.send(f"{role.mention} has been added to the immune roles list.", allowed_mentions = discord.AllowedMentions(roles=False))
else:
await ctx.send("Please provide a valid role.")
@shortmute_config_role.command(name='remove')
@commands.guild_only()
@commands.admin()
async def shortmute_config_role_remove(self, ctx: commands.Context, role: discord.Role = None):
"""Removes roles from the immune roles list."""
current_list = await self.config.guild(ctx.guild).immune_roles()
if role.id in current_list:
current_list.remove(role.id)
await self.config.guild(ctx.guild).immune_roles.set(current_list)
await ctx.send(f"{role.mention} has been removed from the immune roles list.", allowed_mentions = discord.AllowedMentions(roles=False))
else:
await ctx.send("Please provide a valid role that exists in the immune roles list.")
@shortmute_config.group(name='blacklist', invoke_without_command=True)
@commands.guild_only()
@commands.admin()
async def shortmute_config_blacklist(self, ctx: commands.Context):
"""Manages the blacklist."""
current_list = await self.config.guild(ctx.guild).blacklisted_users()
already_in_list = []
for user_id in current_list:
user_obj = await Bot.fetch_user(user_id) # pylint: disable=no-value-for-parameter
if user_obj:
already_in_list.append(user_obj.mention)
if already_in_list:
await ctx.send("Users already blacklisted:\n" + "\n".join(already_in_list), allowed_mentions = discord.AllowedMentions(users=False))
else:
await ctx.send("No users are currently blacklisted.")
@shortmute_config_blacklist.command(name='add')
@commands.guild_only()
@commands.admin()
async def shortmute_config_blacklist_add(self, ctx: commands.Context, user: discord.User = None):
"""Adds users to the /shortmute blacklist."""
current_list = await self.config.guild(ctx.guild).blacklisted_users()
if user:
if user.id in current_list:
await ctx.send("That user is already in the blacklisted users list!")
else:
current_list.append(user.id)
await self.config.guild(ctx.guild).blacklisted_users.set(current_list)
await ctx.send(f"{user.mention} has been blacklisted from using `/shortmute`.", allowed_mentions = discord.AllowedMentions(users=False))
else:
await ctx.send("Please provide a valid user.")
@shortmute_config_blacklist.command(name='remove')
@commands.guild_only()
@commands.admin()
async def shortmute_config_blacklist_remove(self, ctx: commands.Context, user: discord.User = None):
"""Removes users from the /shortmute blacklist."""
current_list = await self.config.guild(ctx.guild).blacklisted_users()
if user.id in current_list:
current_list.remove(user.id)
await self.config.guild(ctx.guild).blacklisted_users.set(current_list)
await ctx.send(f"{user.mention} has been removed from the `/shortmute` blacklist.", allowed_mentions = discord.AllowedMentions(users=False))
else:
await ctx.send("Please provide a valid user who is blacklisted from `/shortmute`.")
@shortmute_config.command(name='message')
@commands.guild_only()
@commands.admin()
async def shortmute_config_message(self, ctx: commands.Context, enabled: bool = None):
"""Manages if /shortmute Direct Messages its target.
Parameters
------------
enabled: bool (optional)
This parameter, if set, will toggle this setting to either True or False."""
old_value = await self.config.guild(ctx.guild).dm()
if enabled is None:
await ctx.send(content=f"Shortmute Direct Messages are currently {'enabled' if old_value else 'disabled'}!")
else:
await self.config.guild(ctx.guild).dm.set(enabled)
await ctx.send(content=f"Shortmute Direct Message setting changed!\nOld value: `{old_value}`\nNew value: `{enabled}`")
@shortmute_config.command(name='confirmation', aliases=['confirm'])
@commands.guild_only()
@commands.admin()
async def shortmute_config_confirmation(self, ctx: commands.Context, enabled: bool = None):
"""Manages if /shortmute has a confirmation prompt by default.
Parameters
------------
enabled: bool (optional)
This parameter, if set, will toggle this setting to either True or False."""
old_value = await self.config.guild(ctx.guild).confirm()
if enabled is None:
await ctx.send(content=f"Shortmute Confirmations are currently {'enabled' if old_value else 'disabled'} by default!")
else:
await self.config.guild(ctx.guild).confirm.set(enabled)
await ctx.send(content=f"Shortmute Confirmation setting changed!\nOld value: `{old_value}`\nNew value: `{enabled}`")

View file

@ -1,12 +1,5 @@
from .suggestions import Suggestions, approve_context, deny_context from .suggestions import Suggestions
async def setup(bot): def setup(bot):
await bot.add_cog(Suggestions(bot)) bot.add_cog(Suggestions(bot))
bot.tree.add_command(approve_context)
bot.tree.add_command(deny_context)
async def teardown(bot):
# We're removing the commands here to ensure they get unloaded properly when the cog is unloaded.
bot.tree.remove_command("approve_context", type=discord.AppCommandType.message) # pylint: disable=undefined-variable
bot.tree.remove_command("deny_context", type=discord.AppCommandType.message) # pylint: disable=undefined-variable

View file

@ -1,11 +1,9 @@
{ {
"author" : ["cswimr, meelyman"], "author" : ["SeaswimmerTheFsh, 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>", "install_msg" : "Thank you for installing Suggestions!\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs\nYou can find the original source code from SauriCogs here: <https://github.com/elijabesu/SauriCogs>",
"name" : "Suggestions", "name" : "Suggestions",
"short" : "Simple suggestions system.", "short" : "Simple suggestions system.",
"description" : "Per guild suggestions system.", "description" : "Per guild suggestions system.",
"tags" : ["suggestions, suggestion, voting"], "tags" : ["suggestions, suggestion, voting"],
"end_user_data_statement": "This cog stores user names, discriminators, and IDs upon sending a suggestion. This data is used solely to display user information in suggestions.", "end_user_data_statement": "This cog stores user names, discriminators, and IDs upon sending a suggestion. This data is used solely to display user information in suggestions."
"hidden": true,
"disabled": false
} }

View file

@ -1,7 +1,10 @@
import re
import typing
import discord import discord
from redbot.core import Config, app_commands, checks, commands import datetime
import typing
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import humanize_list
from redbot.core.bot import Red from redbot.core.bot import Red
@ -44,9 +47,10 @@ class Suggestions(commands.Cog):
def check_discrim(self, user: discord.User): def check_discrim(self, user: discord.User):
if user.discriminator == "0": if user.discriminator == "0":
return user.name return user.name
else:
return f"{user.name}#{user.discriminator}" return f"{user.name}#{user.discriminator}"
async def red_delete_data_for_user(self, *, requester, user_id: discord.User.id): async def red_delete_data_for_user(self, *, requester, user_id):
# per guild suggestions # per guild suggestions
for guild in self.bot.guilds: for guild in self.bot.guilds:
for suggestion_id in range(1, await self.config.guild(guild).next_id()): for suggestion_id in range(1, await self.config.guild(guild).next_id()):
@ -62,13 +66,11 @@ class Suggestions(commands.Cog):
context = super().format_help_for_context(ctx) context = super().format_help_for_context(ctx)
return f"{context}" return f"{context}"
@commands.command(name='suggest') @commands.command()
@commands.guild_only() @commands.guild_only()
@checks.bot_has_permissions(add_reactions=True) @checks.bot_has_permissions(add_reactions=True)
async def suggest(self, ctx: commands.Context, *, suggestion: str): async def suggest(self, ctx: commands.Context, *, suggestion: str):
"""Suggest something.""" """Suggest something."""
if ctx.interaction is True:
await ctx.defer()
suggest_id = await self.config.guild(ctx.guild).suggest_id() suggest_id = await self.config.guild(ctx.guild).suggest_id()
if not suggest_id: if not suggest_id:
if not await self.config.toggle(): if not await self.config.toggle():
@ -82,9 +84,9 @@ class Suggestions(commands.Cog):
"Uh oh, looks like the Admins haven't added the required channel." "Uh oh, looks like the Admins haven't added the required channel."
) )
embed = discord.Embed(color=await ctx.embed_colour(), description=suggestion) embed = discord.Embed(color=await ctx.embed_colour(), description=suggestion)
footer = [f"Suggested by {self.check_discrim(ctx.author)} ({ctx.author.id})", footer = [f"Suggested by {self.check_discrim(ctx.author)} ({ctx.author.id})",
ctx.author.display_avatar.url] ctx.author.avatar_url]
author = [f"{ctx.author.display_name}", ctx.author.display_avatar.url] author = [f"{ctx.author.display_name}", ctx.author.avatar_url]
embed.set_footer( embed.set_footer(
text=footer[0], text=footer[0],
icon_url=footer[1] icon_url=footer[1]
@ -135,7 +137,7 @@ class Suggestions(commands.Cog):
reason: typing.Optional[str], reason: typing.Optional[str],
): ):
"""Approve a suggestion.""" """Approve a suggestion."""
await self._finish_suggestion(ctx, suggestion_id, True, reason) await self._finish_suggestion(ctx, suggestion_id, True, reason, ctx.author)
@checks.admin() @checks.admin()
@commands.command() @commands.command()
@ -149,7 +151,7 @@ class Suggestions(commands.Cog):
reason: typing.Optional[str], reason: typing.Optional[str],
): ):
"""Deny a suggestion. Reason is optional.""" """Deny a suggestion. Reason is optional."""
await self._finish_suggestion(ctx, suggestion_id, False, reason) await self._finish_suggestion(ctx, suggestion_id, False, reason, ctx.author)
@checks.admin() @checks.admin()
@commands.command() @commands.command()
@ -187,8 +189,8 @@ class Suggestions(commands.Cog):
content = old_msg.content content = old_msg.content
approved = "Approved" if approve else "Denied" approved = "Approved" if approve else "Denied"
embed.title = f"Suggestion {approved} (#{suggestion_id})" embed.title = f"Suggestion {approved} (#{suggestion_id})"
footer = [f"{approved} by {self.check_discrim(author)} ({author.id})", footer = [f"{approved} by {self.check_discrim(author)} ({author.id}",
author.display_avatar.replace(format="png", size=512)] author.avatar_url_as(format="png", size=512)]
embed.set_footer( embed.set_footer(
text=footer[0], text=footer[0],
icon_url=footer[1] icon_url=footer[1]
@ -370,7 +372,7 @@ class Suggestions(commands.Cog):
embed = discord.Embed( embed = discord.Embed(
colour=await ctx.embed_colour() colour=await ctx.embed_colour()
) )
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url) embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url)
embed.title = "**Suggestion settings:**" embed.title = "**Suggestion settings:**"
embed.add_field(name="Suggestions channel:", value=suggest_channel) embed.add_field(name="Suggestions channel:", value=suggest_channel)
@ -393,7 +395,7 @@ class Suggestions(commands.Cog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Message): async def on_reaction_add(self, reaction, user):
message = reaction.message message = reaction.message
if user.id == self.bot.user.id: if user.id == self.bot.user.id:
return return
@ -408,7 +410,7 @@ class Suggestions(commands.Cog):
): ):
await message_reaction.remove(user) await message_reaction.remove(user)
async def _get_results(self, ctx: commands.Context, message: discord.Message): async def _get_results(self, ctx, message):
up_emoji, down_emoji = await self._get_emojis(ctx) up_emoji, down_emoji = await self._get_emojis(ctx)
up_count = 0 up_count = 0
down_count = 0 down_count = 0
@ -421,7 +423,7 @@ class Suggestions(commands.Cog):
return f"{up_count}x {up_emoji}\n{down_count}x {down_emoji}" return f"{up_count}x {up_emoji}\n{down_count}x {down_emoji}"
async def _get_emojis(self, ctx: typing.Union[commands.Context, discord.Interaction]): async def _get_emojis(self, ctx):
up_emoji = self.bot.get_emoji(await self.config.guild(ctx.guild).up_emoji()) up_emoji = self.bot.get_emoji(await self.config.guild(ctx.guild).up_emoji())
if not up_emoji: if not up_emoji:
up_emoji = "✅" up_emoji = "✅"
@ -430,9 +432,8 @@ class Suggestions(commands.Cog):
down_emoji = "❎" down_emoji = "❎"
return up_emoji, down_emoji return up_emoji, down_emoji
async def _finish_suggestion(self, ctx: commands.Context, suggestion_id: int, approve: bool, reason: str): async def _finish_suggestion(self, ctx, suggestion_id, approve, reason, author):
server = ctx.guild.id server = ctx.guild.id
author = ctx.author
old_channel = ctx.guild.get_channel( old_channel = ctx.guild.get_channel(
await self.config.guild(ctx.guild).suggest_id() await self.config.guild(ctx.guild).suggest_id()
) )
@ -462,8 +463,8 @@ class Suggestions(commands.Cog):
approved = "Approved" if approve else "Denied" approved = "Approved" if approve else "Denied"
embed.title = f"Suggestion {approved} (#{suggestion_id})" embed.title = f"Suggestion {approved} (#{suggestion_id})"
footer = [f"{approved} by {self.check_discrim(author)} ({author.id})", footer = [f"{approved} by {self.check_discrim(author)} ({author.id}",
author.display_avatar.replace(format="png", size=512)] author.avatar_url_as(format="png", size=512)]
embed.set_footer( embed.set_footer(
text=footer[0], text=footer[0],
icon_url=footer[1] icon_url=footer[1]
@ -509,162 +510,3 @@ class Suggestions(commands.Cog):
True True
) )
await ctx.tick() await ctx.tick()
async def _interaction_get_results(self, interaction: discord.Interaction, message: discord.Message):
up_emoji, down_emoji = await self._get_emojis(interaction)
up_count = 0
down_count = 0
for reaction in message.reactions:
if reaction.emoji == up_emoji:
up_count = reaction.count - 1 # minus the bot
if reaction.emoji == down_emoji:
down_count = reaction.count - 1 # minus the bot
return f"{up_count}x {up_emoji}\n{down_count}x {down_emoji}"
async def _interaction_finish_suggestion(self, interaction: discord.Interaction, message: discord.Message, approve: bool, reason: str = None):
embed = message.embeds
title = embed[0].title
if not title.startswith("Suggestion #"):
await interaction.response.send_message(content="This message is not a suggestion!", ephemeral=True)
return
numbers = re.findall(r'\d+', title)
suggestion_id = ''.join(numbers)
author = interaction.user
server = interaction.guild.id
old_channel = interaction.guild.get_channel(
await self.config.guild(interaction.guild).suggest_id()
)
if approve:
channel = interaction.guild.get_channel(
await self.config.guild(interaction.guild).approve_id()
)
else:
channel = interaction.guild.get_channel(
await self.config.guild(interaction.guild).denied_id()
)
msg_id = await self.config.custom("SUGGESTION", server, suggestion_id).msg_id()
if (
msg_id != 0
and await self.config.custom("SUGGESTION", server, suggestion_id).finished()
):
return await interaction.response.send_message(content="This suggestion has been finished already.", ephemeral=True)
try:
old_msg = await old_channel.fetch_message(msg_id)
except discord.NotFound:
return await interaction.response.send_message(content="Uh oh, message with this ID doesn't exist.", ephemeral=True)
if not old_msg:
return await interaction.response.send_message(content="Uh oh, message with this ID doesn't exist.", ephemeral=True)
embed = old_msg.embeds[0]
content = old_msg.content
approved = "Approved" if approve else "Denied"
embed.title = f"Suggestion {approved} (#{suggestion_id})"
footer = [f"{approved} by {self.check_discrim(author)} • ({author.id})",
author.display_avatar.replace(format="png", size=512)]
embed.set_footer(
text=footer[0],
icon_url=footer[1]
)
embed.add_field(
name="Results", value=await self._interaction_get_results(interaction, old_msg), inline=False
)
if reason:
embed.add_field(name="Reason", value=reason, inline=False)
await self.config.custom("SUGGESTION", server, suggestion_id).reason.set(
True
)
await self.config.custom("SUGGESTION", server, suggestion_id).rtext.set(
reason
)
if channel:
if not await self.config.guild(interaction.guild).same():
if await self.config.guild(interaction.guild).delete_suggestion():
await old_msg.delete()
nmsg = await channel.send(content=content, embed=embed)
await self.config.custom(
"SUGGESTION", server, suggestion_id
).msg_id.set(nmsg.id)
else:
await old_msg.edit(content=content, embed=embed)
else:
if not await self.config.guild(interaction.guild).same():
if await self.config.guild(interaction.guild).delete_suggestion():
await old_msg.delete()
await self.config.custom(
"SUGGESTION", server, suggestion_id
).msg_id.set(1)
else:
await old_msg.edit(content=content, embed=embed)
await self.config.custom("SUGGESTION", server, suggestion_id).finished.set(True)
if approve:
await self.config.custom("SUGGESTION", server, suggestion_id).approved.set(
True
)
else:
await self.config.custom("SUGGESTION", server, suggestion_id).denied.set(
True
)
return nmsg
# pylint: disable=protected-access
class SuggestionApproveModal(discord.ui.Modal, title="Approving suggestion..."):
def __init__(self, message):
super().__init__()
self.message: discord.Message = message
reason = discord.ui.TextInput(
label="Approval Reason",
placeholder="Why are you approving this suggestion?",
style=discord.TextStyle.paragraph,
required=False,
max_length=1024
)
async def on_submit(self, interaction: discord.Interaction):
cog = interaction.client.get_cog('Suggestions')
if self.reason.value != "":
nmsg = await Suggestions._interaction_finish_suggestion(cog, interaction, self.message, True, self.reason.value)
else:
nmsg = await Suggestions._interaction_finish_suggestion(cog, interaction, self.message, True, None)
msg = await interaction.response.send_message(content=f"Suggestion approved!\nJump Link: {nmsg.jump_url}", ephemeral=True)
await msg.delete(10)
class SuggestionDenyModal(discord.ui.Modal, title="Denying suggestion..."):
def __init__(self, message):
super().__init__()
self.message: discord.Message = message
reason = discord.ui.TextInput(
label="Denial Reason",
placeholder="Why are you denying this suggestion?",
style=discord.TextStyle.paragraph,
required=False,
max_length=1024
)
async def on_submit(self, interaction: discord.Interaction):
cog = interaction.client.get_cog('Suggestions')
if self.reason.value != "":
nmsg = await Suggestions._interaction_finish_suggestion(cog, interaction, self.message, False, self.reason.value)
else:
nmsg = await Suggestions._interaction_finish_suggestion(cog, interaction, self.message, False, None)
msg: discord.Message = await interaction.response.send_message(content=f"Suggestion denied!\nJump Link: {nmsg.jump_url}", ephemeral=True)
await msg.delete(10)
@app_commands.context_menu(name="Approve Suggestion")
async def approve_context(interaction: discord.Interaction, message: discord.Message):
if message.author.id == interaction.client.user.id and len(message.embeds) == 1 and message.embeds[0].title.startswith('Suggestion #'):
await interaction.response.send_modal(SuggestionApproveModal(message))
else:
await interaction.response.send_message(content="This is not a suggestion!", ephemeral=True)
@app_commands.context_menu(name="Deny Suggestion")
async def deny_context(interaction: discord.Interaction, message: discord.Message):
if message.author.id == interaction.client.user.id and len(message.embeds) == 1 and message.embeds[0].title.startswith('Suggestion #'):
await interaction.response.send_modal(SuggestionDenyModal(message))
else:
await interaction.response.send_message(content="This is not a suggestion!", ephemeral=True)

View file

@ -1,5 +1,5 @@
from .sugoncredit import SugonCredit from .sugoncredit import SugonCredit
async def setup(bot): def setup(bot):
await bot.add_cog(SugonCredit(bot)) bot.add_cog(SugonCredit(bot))

View file

@ -1,11 +1,9 @@
{ {
"author" : ["cswimr"], "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/cswimr/GalaxyCogs.", "install_msg" : "Thank you for installing SugonCredit!\n**Please load the Bank cog before loading this cog.**\nYou can find the source code of this cog here: https://github.com/SeaswimmerTheFsh/GalaxyCogs.",
"name" : "SugonCredit", "name" : "SugonCredit",
"short" : "Simple points system.", "short" : "Simple points system.",
"description" : "Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community.", "description" : "Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community.",
"tags" : ["economy"], "tags" : ["bank"],
"end_user_data_statement": "This cog stores no end user data.", "end_user_data_statement": "This cog stores no end user data."
"hidden": true,
"disabled": false
} }

View file

@ -1,37 +1,39 @@
import discord import discord
from redbot.core import commands, bank, data_manager from redbot.core import commands, bank, checks, data_manager
class SugonCredit(commands.Cog): class SugonCredit(commands.Cog):
"""Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community.""" """Implements a way for moderators to give out social-credit like points, dubbed 'sugoncredits' by the community."""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@commands.hybrid_group(autohelp=True, aliases=["sugoncredit"]) @commands.group(autohelp=True, aliases=["sugoncredit"])
@commands.guild_only() @commands.guild_only()
async def credit(self, ctx: commands.Context): async def credit(self, ctx):
"""Simple points system.""" """Simple points system."""
@credit.command() @credit.command()
async def balance(self, ctx: commands.Context, user: discord.Member = None): @commands.guild_only()
async def balance(self, ctx, user: discord.Member = None):
"""Checks an account's balance.""" """Checks an account's balance."""
bank_name = await bank.get_bank_name(ctx.guild) bank_name = await bank.get_bank_name(ctx.guild)
currency_name = await bank.get_currency_name(ctx.guild) currency_name = await bank.get_currency_name(ctx.guild)
if user is None: if user == None:
bal = await bank.get_balance(ctx.author) bal = await bank.get_balance(ctx.author)
target = ctx.author target = ctx.author
else: else:
bal = await bank.get_balance(user) bal = await bank.get_balance(user)
target = user target = user
output_bal = (f'{bal:,}') output_bal = (f'{bal:,}')
if bal in (1, -1): if bal == 1 or bal == -1:
embed=discord.Embed(title=f"{bank_name} - Balance", color=await self.bot.get_embed_color(None), description=f"{target.mention} has {output_bal} {currency_name}.") embed=discord.Embed(title=f"{bank_name} - Balance", color=await self.bot.get_embed_color(None), description=f"{target.mention} has {output_bal} {currency_name}.")
else: else:
embed=discord.Embed(title=f"{bank_name} - Balance", color=await self.bot.get_embed_color(None), description=f"{target.mention} has {output_bal} {currency_name}s.") embed=discord.Embed(title=f"{bank_name} - Balance", color=await self.bot.get_embed_color(None), description=f"{target.mention} has {output_bal} {currency_name}s.")
await ctx.send(embed=embed) await ctx.send(embed=embed)
@credit.command() @credit.command()
@commands.guild_only()
@commands.mod() @commands.mod()
async def add(self, ctx: commands.Context, target: discord.Member, amount: int): async def add(self, ctx, target: discord.Member, amount: int):
"""Adds credits to an account.""" """Adds credits to an account."""
try: try:
val = int(amount) val = int(amount)
@ -49,13 +51,15 @@ class SugonCredit(commands.Cog):
output_max_bal = (f'{max_bal:,}') output_max_bal = (f'{max_bal:,}')
if new_bal > max_bal: if new_bal > max_bal:
await ctx.send(content=f"You are attempting to set {target.mention}'s balance to above {output_max_bal}. Please try again!") await ctx.send(content=f"You are attempting to set {target.mention}'s balance to above {output_max_bal}. Please try again!")
return
elif new_bal < 0: elif new_bal < 0:
await ctx.send(content=f"You are attempting to set {target.mention}'s balance to below 0. Please try again!") await ctx.send(content=f"You are attempting to set {target.mention}'s balance to below 0. Please try again!")
return
elif ctx.guild.id == 204965774618656769: elif ctx.guild.id == 204965774618656769:
logging_channel: discord.TextChannel = self.bot.get_channel(1082495815878189076) logging_channel = self.bot.get_channel(1082495815878189076)
await bank.deposit_credits(target, amount=amount) await bank.deposit_credits(target, amount=amount)
await ctx.send(content=f"{target.mention} now has {output_amount} more SugonCredit, with a total of {output_new_bal}!") await ctx.send(content=f"{target.mention} now has {output_amount} more SugonCredit, with a total of {output_new_bal}!")
if amount in (1, -1): if amount == 1 or amount == -1:
await target.send(content=f"You gained {output_amount} SugonCredit! Good work community member! You now have {output_new_bal} SugonCredits.", file=image) await target.send(content=f"You gained {output_amount} SugonCredit! Good work community member! You now have {output_new_bal} SugonCredits.", file=image)
else: else:
await target.send(content=f"You gained {output_amount} SugonCredits! Good work community member! You now have {output_new_bal} SugonCredits.", file=image) await target.send(content=f"You gained {output_amount} SugonCredits! Good work community member! You now have {output_new_bal} SugonCredits.", file=image)
@ -67,8 +71,9 @@ class SugonCredit(commands.Cog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
@credit.command() @credit.command()
@commands.guild_only()
@commands.mod() @commands.mod()
async def remove(self, ctx: commands.Context, target: discord.Member, amount: int): async def remove(self, ctx, target: discord.Member, amount: int):
"""Removes credits from an account.""" """Removes credits from an account."""
try: try:
val = int(amount) val = int(amount)
@ -84,11 +89,12 @@ class SugonCredit(commands.Cog):
output_new_bal = (f'{new_bal:,}') output_new_bal = (f'{new_bal:,}')
if new_bal < 0: if new_bal < 0:
await ctx.send(content=f"You are attempting to set {target.mention}'s balance to below 0. Please try again!") await ctx.send(content=f"You are attempting to set {target.mention}'s balance to below 0. Please try again!")
return
elif ctx.guild.id == 204965774618656769: elif ctx.guild.id == 204965774618656769:
await bank.withdraw_credits(target, amount=amount) await bank.withdraw_credits(target, amount=amount)
logging_channel: discord.TextChannel = self.bot.get_channel(1082495815878189076) logging_channel = self.bot.get_channel(1082495815878189076)
await ctx.send(content=f"{target.mention} now has {output_amount} less SugonCredit, with a total of {output_new_bal}!\nIf this is a punishment, do better Galaxy Player! Re-education mods will be sent to your DM's if your SugonCredit drops to a substantially low amount!") await ctx.send(content=f"{target.mention} now has {output_amount} less SugonCredit, with a total of {output_new_bal}!\nIf this is a punishment, do better Galaxy Player! Re-education mods will be sent to your DM's if your SugonCredit drops to a substantially low amount!")
if amount in (1, -1): if amount == 1 or amount == -1:
await target.send(content=f"__MESSAGE FROM THE MINISTRY OF THE MEGA BASE__\n\n(我们的) {output_amount} SugonCredit has been taken from your account. Citizen, do not continue to preform bad actions! Glory to the Galaxy Communist Party!", file=image) await target.send(content=f"__MESSAGE FROM THE MINISTRY OF THE MEGA BASE__\n\n(我们的) {output_amount} SugonCredit has been taken from your account. Citizen, do not continue to preform bad actions! Glory to the Galaxy Communist Party!", file=image)
else: else:
await target.send(content=f"__MESSAGE FROM THE MINISTRY OF THE MEGA BASE__\n\n(我们的) {output_amount} SugonCredits have been taken from your account. Citizen, do not continue to preform bad actions! Glory to the Galaxy Communist Party!", file=image) await target.send(content=f"__MESSAGE FROM THE MINISTRY OF THE MEGA BASE__\n\n(我们的) {output_amount} SugonCredits have been taken from your account. Citizen, do not continue to preform bad actions! Glory to the Galaxy Communist Party!", file=image)

View file

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

View file

@ -1,10 +0,0 @@
{
"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!",
"end_user_data_statement" : "This cog does not store any End User Data.",
"hidden": true,
"disabled": false
}

View file

@ -1,37 +0,0 @@
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 cswimr."""
def __init__(self, bot):
self.bot = bot
@commands.group(name="worldzero", invoke_without_command=True, aliases=['wz'])
async def worldzero(self, ctx: commands.Context):
"""Tells the user that this command doesn't do anything currently."""
await ctx.send("This command doesn't do anything currently, have you tried a subcommand?\nCurrent subcommands:\n- `-worldzero upgrade` - Checks what the Attack Power/Health of an item will be after upgrading it.\n - See `-help worldzero upgrade` for more information.")
@worldzero.command(name="upgrade")
async def worldzero_upgrade(self, ctx: commands.Context, power_amount: str, upgrade_amount: str):
"""Checks what the Attack Power/Health of an item will be after upgrading it.
**Arguments**
- The `power_amount` argument is the Attack Power/Health of the item you're looking to upgrade.
- The `upgrade_amount` argument is the number of times your item can be upgraded."""
try:
stat_int = int(f"{power_amount}".replace(",", ""))
upgrade_int = int(f"{upgrade_amount}".replace(",", ""))
except ValueError:
await ctx.send(content="Please input a number!")
return
math = round(stat_int + ((stat_int/15)*upgrade_int))
output_from = f'{stat_int:,}'
output_to = f'{math:,}'
embed = discord.Embed(color=await self.bot.get_embed_color(None))
embed.add_field(name="Default Power", value=f"{output_from}", inline=False)
embed.add_field(name="Upgraded Power", value=f"{output_to}", inline=False)
await ctx.send(embed=embed)

View file

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

View file

@ -1,11 +0,0 @@
{
"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.",
"end_user_data_statement" : "This cog does not store any End User Data.",
"requirements": ["pytube"],
"hidden": false,
"disabled": false
}

View file

@ -1,160 +0,0 @@
import asyncio
import os
import re
import discord
from redbot.core import Config, checks, commands, data_manager
from pytube import YouTube, exceptions
class YouTubeDownloader(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=475728338)
self.config.register_global(
save_directory = str(data_manager.cog_data_path()) + f"{os.sep}YouTubeDownloader",
blacklisted_users = []
)
class UserBlacklisted(Exception):
def __init__(self, message="The user is blacklisted from using this command."):
super().__init__(message)
async def blacklist_checker(self, user_id):
blacklisted_users = await self.config.blacklisted_users()
for blacklisted_user_id in blacklisted_users:
if blacklisted_user_id == user_id:
raise self.UserBlacklisted
@commands.command()
@checks.is_owner()
async def change_data_path(self, ctx: commands.Context, *, data_path: str = None):
"""This command changes the data path the `[p]download` command outputs to."""
old_path = await self.config.save_directory()
if not data_path:
await ctx.send(f"The current data path is `{old_path}`.")
return
if os.path.isdir(data_path):
await self.config.save_directory.set(data_path)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"The save directory has been set to `{data_path}`.\n It was previously set to `{old_path}`.")
await ctx.send(embed=embed)
elif os.path.isfile(data_path):
await ctx.send("The path you've provided leads to a file, not a directory!")
elif os.path.exists(data_path) is False:
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 = 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.
**Arguments**
- The `url` argument is just the url of the YouTube Video you're downloading.
- The `audio_only` argument determines if the video will be an `m4a` file or an `mp4` file.
- The `delete` argument will automatically delete the file after uploading it to Discord. If set to False, it will only save the file if you are a bot owner.
- 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:
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)
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')
filename_format = filename + ".m4a"
else:
stream = yt.streams.filter(progressive=True, mime_type='video/mp4')
stream = stream.order_by('resolution')
filename_format = filename + ".mp4"
previously_existed = bool(os.path.isfile(path + f"{os.sep}{filename_format}"))
return stream.desc().first().download(filename=filename, output_path=path), previously_existed, filename_format
data_path = await self.config.save_directory()
if subfolder and await self.bot.is_owner(ctx.author):
data_path = os.path.join(data_path, subfolder)
illegal_chars = r'<>:"/\|?*'
if any(char in illegal_chars for char in subfolder):
pattern = "[" + re.escape(illegal_chars) + "]"
modified_subfolder = re.sub(pattern, r'__**\g<0>**__', subfolder)
await ctx.send(f"Your subfolder contains illegal characters: `{modified_subfolder}`")
elif os.path.isfile(data_path):
await ctx.send("Your 'subfolder' is a file, not a directory!")
elif os.path.exists(data_path) is False:
message = await ctx.send("Your subfolder does not exist yet, would you like to continue? It will be automatically created.")
def check(message):
return message.author == ctx.author and message.content.lower() in ['yes', 'ye', 'y', '1']
try:
await self.bot.wait_for('message', check=check, timeout=60)
except asyncio.TimeoutError:
await message.edit("You took too long to respond.")
else:
await message.edit("Confirmed!")
try:
os.makedirs(data_path)
except OSError as e:
await message.edit(f"Encountered an error attempting to create the subfolder!\n`{e}`")
msg = message.edit
else:
msg = ctx.send
message = await msg("YouTube Downloader started!")
try:
output = youtube_download(url, data_path)
except exceptions.RegexMatchError:
await message.edit(content="Please provide a link to YouTube and not another site.")
return
while not os.path.isfile(output[0]):
await asyncio.sleep(0.2)
if os.path.isfile(output[0]):
with open(output[0], 'rb') as file:
try:
complete_message = await ctx.send(content="YouTube Downloader completed!\nDownloaded file:", file=discord.File(file, output[2]))
except ValueError:
complete_message = await ctx.send(content="YouTube Downloader completed, but the file was too large to upload.")
file.close()
if delete is True or await self.bot.is_owner(ctx.author) is False:
if output[1] is False:
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:")
@commands.group(name="dl-blacklist", invoke_without_command=True)
async def blacklist(self, ctx: commands.Context, user: discord.User):
"""Group command for managing the blacklist."""
for user_id in await self.config.blacklisted_users():
if user_id == user.id:
await ctx.send(f"{user.mention} is in the blacklist.", allowed_mentions = discord.AllowedMentions(users=False))
return
await ctx.send(f"{user.mention} is not in the blacklist.", allowed_mentions = discord.AllowedMentions(users=False))
@blacklist.command(name='add')
@checks.is_owner()
async def blacklist_add(self, ctx: commands.Context, user: discord.User):
blacklisted_users: list = await self.config.blacklisted_users()
for user_id in blacklisted_users:
if user_id == user.id:
await ctx.send(f"{user.mention} is already in the blacklist.")
return
blacklisted_users.append(user.id)
await self.config.blacklisted_users.set(blacklisted_users)
await ctx.send(f"{user.mention} has been added to the blacklist.")
@blacklist.command(name='remove')
@checks.is_owner()
async def blacklist_remove(self, ctx: commands.Context, user: discord.User):
blacklisted_users: list = await self.config.blacklisted_users()
for user_id in blacklisted_users:
if user_id == user.id:
blacklisted_users.remove(user_id)
await self.config.blacklisted_users.set(blacklisted_users)
await ctx.send(f"{user.mention} has been removed from the blacklist.")
return
await ctx.send(f"{user.mention} is not in the blacklist.")