Compare commits
11 commits
main
...
aurora-hyb
Author | SHA1 | Date | |
---|---|---|---|
416d67bf65 | |||
e2d2b7bdc1 | |||
246140da9b | |||
918c058f5a | |||
16a37691b5 | |||
354b505e6e | |||
9290ac3d66 | |||
c99b6efafb | |||
b4ffd213dd | |||
cfa95118ae | |||
67d7e04956 |
66 changed files with 3129 additions and 9474 deletions
|
@ -80,23 +80,9 @@ Manage the immunity whitelist.
|
||||||
- Usage: `[p]aurora timedelta <duration>`
|
- Usage: `[p]aurora timedelta <duration>`
|
||||||
- Aliases: `tdc, td, and timedeltaconvert`
|
- Aliases: `tdc, td, and timedeltaconvert`
|
||||||
|
|
||||||
This command converts a duration to a [`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) Python object.You cannot convert years or months as they are not fixed units. Use `[p]aurora relativedelta` for that.
|
This command converts a duration to a [`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) Python object.
|
||||||
|
|
||||||
**Example usage**
|
**Example usage**
|
||||||
`[p]aurora timedelta 1 day 15hr 82 minutes 52s`
|
`[p]timedelta 1 day 15hr 82 minutes 52s`
|
||||||
|
|
||||||
**Output**
|
**Output**
|
||||||
`1 day, 16:22:52`
|
`1 day, 16:22:52`
|
||||||
|
|
||||||
### aurora relativedelta
|
|
||||||
|
|
||||||
- Usage: `[p]aurora relativedelta <duration>`
|
|
||||||
- Aliases: `rdc, rd, and relativedeltaconvert`
|
|
||||||
|
|
||||||
This command converts a duration to a [`relativedelta`](https://dateutil.readthedocs.io/en/stable/relativedelta.html) Python object.
|
|
||||||
|
|
||||||
**Example usage**
|
|
||||||
`[p]aurora relativedelta 3 years 1 day 15hr 82 minutes 52s`
|
|
||||||
|
|
||||||
**Output**
|
|
||||||
`relativedelta(years=+3, days=+1, hours=+15, minutes=+82, seconds=+52)`
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ Aurora is a fully-featured moderation system. It is heavily inspired by Galactic
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
[p]cog install seacogs aurora
|
[p]cog install seacogs aurora
|
||||||
[p]cog load aurora
|
[p]cog load aurora
|
||||||
```
|
```
|
||||||
|
|
|
@ -23,27 +23,19 @@ Add a note to a user.
|
||||||
|
|
||||||
Warn a user.
|
Warn a user.
|
||||||
|
|
||||||
## addrole
|
## blacklist
|
||||||
|
|
||||||
- Usage: `/addrole <target> <role> <reason> [duration] [silent]`
|
/// admonition | This command is unfinished, and doesn't currently work.
|
||||||
- `target:` (Required) Who are you adding a role to?
|
type: warning
|
||||||
- `role:` (Required) What role are you adding to the target?
|
///
|
||||||
- `reason:` (Required) Why are you adding a role to this user?
|
|
||||||
- `duration:` (Optional) How long are you adding this role for?
|
- Usage: `/blacklist <target> <role> <reason> [silent]`
|
||||||
|
- `target:` (Required) Who are you blacklisting?
|
||||||
|
- `role:` (Required) What blacklist type are you applying to the target?
|
||||||
|
- `reason:` (Required) Why are you blacklisting this user?
|
||||||
- `silent:` (Optional) Should the user be messaged?
|
- `silent:` (Optional) Should the user be messaged?
|
||||||
|
|
||||||
Add a role to a user.
|
Add a blacklist role to a user.
|
||||||
|
|
||||||
## removerole
|
|
||||||
|
|
||||||
- Usage: `/removerole <target> <role> <reason> [duration] [silent]`
|
|
||||||
- `target:` (Required) Who are you removing a role from?
|
|
||||||
- `role:` (Required) What role are you removing from the target?
|
|
||||||
- `reason:` (Required) Why are you removing a role from this user?
|
|
||||||
- `duration:` (Optional) How long are you removing this role for?
|
|
||||||
- `silent:` (Optional) Should the user be messaged?
|
|
||||||
|
|
||||||
Remove a role from a user.
|
|
||||||
|
|
||||||
## mute
|
## mute
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
# Backup
|
# Backup
|
||||||
|
|
||||||
Backup allows you to export a JSON list of all of your installed repositories and cogs, then reimport them and automatically reinstall the cogs.
|
Backup allows you to export a JSON list of all of your installed repositories and cogs, then reimport them and automatically reinstall/reload the cogs.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
[p]cog install seacogs backup
|
[p]cog install seacogs backup
|
||||||
[p]cog load backup
|
[p]cog load backup
|
||||||
```
|
```
|
||||||
|
|
||||||
## Version Compatibility
|
|
||||||
|
|
||||||
As of commit [1edb08a](https://www.coastalcommits.com/cswimr/SeaCogs/commit/1edb08a1271f12098ca0bed11a735f7162cedd14), the Backup cog no longer supports Red versions older than 3.5.6. If you want to use the cog on an earlier version (3.5.0 - 3.5.5), install the cog pinned to this commit: `43464db6a7c51bc69282b1ae3dc507a4aae851de`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
[p]cog installversion sea-cogs 43464db6a7c51bc69282b1ae3dc507a4aae851de backup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### backup export
|
### backup export
|
||||||
|
|
|
@ -6,7 +6,7 @@ This cog does require an api key to work.
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
[p]cog install seacogs bible
|
[p]cog install seacogs bible
|
||||||
[p]cog load bible
|
[p]cog load bible
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# EmojiInfo
|
|
||||||
|
|
||||||
EmojiInfo allows you to retrieve information about an emoji.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
|
||||||
[p]cog install seacogs emojiinfo
|
|
||||||
[p]cog load emojiinfo
|
|
||||||
```
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### emoji
|
|
||||||
|
|
||||||
- Usage: `[p]emoji <emoji> [ephemeral]`
|
|
||||||
|
|
||||||
Retrieve information about the provided emoji. If `ephemeral` is provided and the command is used as a slash command, the response will be sent as an ephemeral message.
|
|
|
@ -5,7 +5,7 @@ Nerdify allows you to nerdify other people's text.
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
[p]cog install seacogs nerdify
|
[p]cog install seacogs nerdify
|
||||||
[p]cog load nerdify
|
[p]cog load nerdify
|
||||||
```
|
```
|
||||||
|
|
|
@ -39,9 +39,7 @@ Default value:
|
||||||
tellraw @a ["",{"text":".$N ","color":".$C","insertion":"<@.$I>","hoverEvent":{"action":"show_text","contents":"Shift click to mention this user inside Discord"}},{"text":"(DISCORD):","color":"blue","clickEvent":{"action":"open_url","value":".$V"},"hoverEvent":{"action":"show_text","contents":"Click to join the Discord Server"}},{"text":" .$M","color":"white"}]
|
tellraw @a ["",{"text":".$N ","color":".$C","insertion":"<@.$I>","hoverEvent":{"action":"show_text","contents":"Shift click to mention this user inside Discord"}},{"text":"(DISCORD):","color":"blue","clickEvent":{"action":"open_url","value":".$V"},"hoverEvent":{"action":"show_text","contents":"Click to join the Discord Server"}},{"text":" .$M","color":"white"}]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `console`
|
## `consolechannel`
|
||||||
|
|
||||||
### `channel`
|
|
||||||
|
|
||||||
/// admonition | Only give access to the console channel to people you trust!
|
/// admonition | Only give access to the console channel to people you trust!
|
||||||
type: danger
|
type: danger
|
||||||
|
@ -65,21 +63,6 @@ This is to prevent the console channel from flooding and getting backed up by Di
|
||||||
|
|
||||||
Default value: `None`
|
Default value: `None`
|
||||||
|
|
||||||
### `commands`
|
|
||||||
|
|
||||||
/// admonition | This has no effect on the `[p]pterodactyl command` text command, or the matching slash command.
|
|
||||||
type: danger
|
|
||||||
If you want to disable the ability to execute commands on the server through Discord, use the following commands:
|
|
||||||
`[p]pterodactyl config console commands False` - this command
|
|
||||||
`[p]command disable pterodactyl command` - disables the text command that lets you execute commands on the server
|
|
||||||
`[p]slash disable pterodactyl` - due to how slash commands are laid out, this is the only way to disable the ability to execute commands on the server
|
|
||||||
`[p]slash sync` - apply above slash command change
|
|
||||||
///
|
|
||||||
|
|
||||||
This option determines if commands sent to the console channel will be sent to the Pterodactyl console.
|
|
||||||
|
|
||||||
Default value: `False`
|
|
||||||
|
|
||||||
## `invite`
|
## `invite`
|
||||||
|
|
||||||
This option determines what url the chat command will substitute in for the Discord invite placeholder.
|
This option determines what url the chat command will substitute in for the Discord invite placeholder.
|
||||||
|
@ -153,41 +136,6 @@ This option determines which server's websocket to connect to. See [Getting Star
|
||||||
|
|
||||||
Default value: `None`
|
Default value: `None`
|
||||||
|
|
||||||
## `topic`
|
|
||||||
|
|
||||||
### `host`
|
|
||||||
|
|
||||||
This option determines the hostname of your server that will be used to retrieve server information.
|
|
||||||
|
|
||||||
### `port`
|
|
||||||
|
|
||||||
This option determines the port of your server that will be used to retrieve server information.
|
|
||||||
|
|
||||||
Default value: `25565`
|
|
||||||
|
|
||||||
### `text`
|
|
||||||
|
|
||||||
This option determines what the channel topic will be set to.
|
|
||||||
|
|
||||||
Available placeholders:
|
|
||||||
|
|
||||||
- `.$H` - replaced with the server's hostname
|
|
||||||
- `.$O` - replaced with the server's port
|
|
||||||
|
|
||||||
Available with a Minecraft server:
|
|
||||||
|
|
||||||
- `.$I` - replaced with the server's ip address
|
|
||||||
- `.$M` - replaced with maximum player count
|
|
||||||
- `.$P` - replaced with current online player count
|
|
||||||
- `.$V` - replaced with the server's current version
|
|
||||||
- `.$D` - replaced with the server's description / message of the day
|
|
||||||
|
|
||||||
Default value:
|
|
||||||
|
|
||||||
```
|
|
||||||
Server IP: .$H\nServer Players: .$P/.$M
|
|
||||||
```
|
|
||||||
|
|
||||||
## `url`
|
## `url`
|
||||||
|
|
||||||
This option determines what panel the cog will send requests to. See [Getting Started](getting-started.md#getting-server-information) for more information on this.
|
This option determines what panel the cog will send requests to. See [Getting Started](getting-started.md#getting-server-information) for more information on this.
|
||||||
|
|
|
@ -28,7 +28,7 @@ The Downloader cog allows you to add Git repositories to your bot in order to do
|
||||||
Now, use Downloader to add my repository to your bot:
|
Now, use Downloader to add my repository to your bot:
|
||||||
|
|
||||||
```
|
```
|
||||||
[p]repo add sea-cogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add sea-cogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, install the Pterodactyl cog:
|
Now, install the Pterodactyl cog:
|
||||||
|
|
|
@ -10,7 +10,7 @@ Pterodactyl allows for connecting to a Pterodactyl server through websockets. It
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
[p]cog install seacogs pterodactyl
|
[p]cog install seacogs pterodactyl
|
||||||
[p]cog load aurora
|
[p]cog load aurora
|
||||||
```
|
```
|
||||||
|
|
|
@ -10,7 +10,7 @@ There are a few caveats to running an instance of Red on Pterodactyl.
|
||||||
|
|
||||||
- You will not receive any support from the Red developers.
|
- You will not receive any support from the Red developers.
|
||||||
- The built-in Audio cog will not work.
|
- The built-in Audio cog will not work.
|
||||||
- Depending on your host, you might have to request a [`tmpfs` size increase](https://github.com/pelican-eggs/eggs/tree/master/bots/discord/redbot#additional-requirements).
|
- Depending on your host, you might have to request a [`tmpfs` size increase](https://github.com/ign-gg/Pterodactyl-Eggs/tree/master/bots/discord/redbot#additional-requirements).
|
||||||
|
|
||||||
If these are unacceptable to you, you should [install Red normally](https://docs.discord.red/en/stable/install_guides/index.html).
|
If these are unacceptable to you, you should [install Red normally](https://docs.discord.red/en/stable/install_guides/index.html).
|
||||||
///
|
///
|
||||||
|
@ -64,7 +64,7 @@ Red is quite a large bot, so I'll focus on the specifics of getting the bot work
|
||||||
```
|
```
|
||||||
2. Add my repository to the bot
|
2. Add my repository to the bot
|
||||||
```bash
|
```bash
|
||||||
[p]repo add sea-cogs https://www.coastalcommits.com/cswimr/SeaCogs
|
[p]repo add sea-cogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
```
|
```
|
||||||
3. Install and load the Pterodactyl cog
|
3. Install and load the Pterodactyl cog
|
||||||
```bash
|
```bash
|
||||||
|
|
10
.envrc
10
.envrc
|
@ -1,10 +0,0 @@
|
||||||
if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
|
|
||||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
|
|
||||||
fi
|
|
||||||
|
|
||||||
watch_file flake.nix
|
|
||||||
watch_file flake.lock
|
|
||||||
if ! use flake . --no-pure-eval
|
|
||||||
then
|
|
||||||
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
|
|
||||||
fi
|
|
23
.forgejo/ISSUE_TEMPLATE/bug-report.md
Normal file
23
.forgejo/ISSUE_TEMPLATE/bug-report.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Got an issue with a cog from SeaCogs? Use this.
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
What caused the error?
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
|
@ -1,53 +0,0 @@
|
||||||
name: Bug Report
|
|
||||||
about: File a bug report
|
|
||||||
labels: [bug]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thank you for taking the time to fill out this bug report!
|
|
||||||
- type: checkboxes
|
|
||||||
id: confirmation
|
|
||||||
attributes:
|
|
||||||
label: Please confirm that;
|
|
||||||
options:
|
|
||||||
- label: I have checked that this bug does not already have an opened/closed [issue](https://www.coastalcommits.com/cswimr/SeaCogs/issues) or [pull request](https://www.coastalcommits.com/cswimr/SeaCogs/pulls) associated with it.
|
|
||||||
required: true
|
|
||||||
- label: I have checked that I am on the latest version of [Red-DiscordBot](https://github.com/CogCreators/Red-DiscordBot), and SeaCogs.
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: hosting
|
|
||||||
attributes:
|
|
||||||
label: Hosting
|
|
||||||
description: How is your bot hosted?
|
|
||||||
placeholder: Local, Docker, etc.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: what-happened
|
|
||||||
attributes:
|
|
||||||
label: What happened?
|
|
||||||
description: Also, what did you expect to happen?
|
|
||||||
placeholder: A bug happened!
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: reproduction
|
|
||||||
attributes:
|
|
||||||
label: Reproduction steps
|
|
||||||
description: Please provide detailed steps to reproduce the bug.
|
|
||||||
placeholder: |
|
|
||||||
1. Do this
|
|
||||||
2. Do that
|
|
||||||
3. Do this other thing
|
|
||||||
4. Bug happens!
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Relevant traceback or logs
|
|
||||||
description: Please copy and paste any relevant tracebacks or log output. This will be automatically formatted into code, so no need for backticks.
|
|
||||||
render: py
|
|
||||||
validations:
|
|
||||||
required: false
|
|
|
@ -1,5 +0,0 @@
|
||||||
blank_issues_enabled: true
|
|
||||||
contact_links:
|
|
||||||
- name: Other Concerns?
|
|
||||||
url: https://discord.gg/eMUMe77Yb8
|
|
||||||
about: Ask in our Discord server!
|
|
26
.forgejo/ISSUE_TEMPLATE/suggestion.md
Normal file
26
.forgejo/ISSUE_TEMPLATE/suggestion.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
name: Suggestion
|
||||||
|
about: Trying to suggest something for SeaCogs? Use this.
|
||||||
|
title: "[SUGGESTION]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**What cog is your feature request for?**
|
||||||
|
A cog in this repository.
|
||||||
|
|
||||||
|
**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 [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives 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**
|
||||||
|
Add any other context about the problem here.
|
|
@ -1,53 +0,0 @@
|
||||||
name: Suggestion
|
|
||||||
about: Trying to suggest something for SeaCogs? Use this.
|
|
||||||
labels: [enhancement]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thank you for taking the time to suggest an enhancement!
|
|
||||||
- type: input
|
|
||||||
id: cog
|
|
||||||
attributes:
|
|
||||||
label: What cog is your feature request for?
|
|
||||||
description: Specify the cog within the repository.
|
|
||||||
placeholder: E.g., Pterodactyl
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: problem-description
|
|
||||||
attributes:
|
|
||||||
label: Is your feature request related to a problem? Please describe.
|
|
||||||
placeholder: A clear and concise description of what the problem is.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: solution-description
|
|
||||||
attributes:
|
|
||||||
label: Describe the solution you'd like
|
|
||||||
placeholder: A clear and concise description of what you want to happen.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: alternatives
|
|
||||||
attributes:
|
|
||||||
label: Describe alternatives you've considered
|
|
||||||
placeholder: A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: screenshots
|
|
||||||
attributes:
|
|
||||||
label: Screenshots
|
|
||||||
description: If applicable, add screenshots to help explain your problem.
|
|
||||||
placeholder: Paste image links here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: additional-context
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add any other context about the problem here.
|
|
||||||
placeholder: Any extra information you think might be useful.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
|
@ -1,6 +0,0 @@
|
||||||
# Describe what your pull request does and which issue you're targeting
|
|
||||||
|
|
||||||
<!-- Create a new issue, if it doesn't exist yet -->
|
|
||||||
|
|
||||||
- [ ] By submitting this pull request, I permit cswimr to license my work under
|
|
||||||
the [Mozilla Public License Version 2.0](https://www.coastalcommits.com/cswimr/SeaCogs/src/branch/main/LICENSE).
|
|
|
@ -17,6 +17,4 @@
|
||||||
import-outside-toplevel,
|
import-outside-toplevel,
|
||||||
import-self,
|
import-self,
|
||||||
relative-beyond-top-level,
|
relative-beyond-top-level,
|
||||||
too-many-instance-attributes,
|
too-many-instance-attributes
|
||||||
duplicate-code,
|
|
||||||
too-many-nested-blocks
|
|
||||||
|
|
|
@ -1,46 +1,39 @@
|
||||||
name: Actions
|
name: Actions
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
Lint Code (Ruff & Pylint):
|
||||||
name: Lint Code (Ruff & Pylint)
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container: www.coastalcommits.com/cswimr/actions:uv
|
container: www.coastalcommits.com/seaswimmerthefsh/actionscontainers-seacogs:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install python
|
|
||||||
run: uv python install 3.11
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync
|
run: poetry install --with dev --no-root
|
||||||
|
|
||||||
- name: Analysing code with Ruff
|
- name: Analysing code with Ruff
|
||||||
run: uv run ruff check $(git ls-files '*.py')
|
run: ruff check $(git ls-files '*.py')
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Analysing code with Pylint
|
- name: Analysing code with Pylint
|
||||||
run: uv run pylint --rcfile=.forgejo/workflows/config/.pylintrc $(git ls-files '*.py')
|
run: pylint --rcfile=.forgejo/workflows/config/.pylintrc $(git ls-files '*.py')
|
||||||
|
|
||||||
docs:
|
Build Documentation (MkDocs):
|
||||||
name: Build Documentation (MkDocs)
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container: www.coastalcommits.com/cswimr/actions:docs
|
container: www.coastalcommits.com/seaswimmerthefsh/actionscontainers-seacogs:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install python
|
|
||||||
run: uv python install 3.11
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --no-dev --extra=documentation
|
run: poetry install --with docs --no-root
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
uses: actions/env@v2
|
uses: actions/env@v2
|
||||||
|
@ -49,7 +42,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
export SITE_URL="https://$CI_ACTION_REF_NAME_SLUG.seacogs.coastalcommits.com"
|
export SITE_URL="https://$CI_ACTION_REF_NAME_SLUG.seacogs.coastalcommits.com"
|
||||||
export EDIT_URI="src/branch/$CI_ACTION_REF_NAME/.docs"
|
export EDIT_URI="src/branch/$CI_ACTION_REF_NAME/.docs"
|
||||||
uv run mkdocs build -v
|
mkdocs build -v
|
||||||
|
|
||||||
- name: Deploy documentation
|
- name: Deploy documentation
|
||||||
run: |
|
run: |
|
||||||
|
@ -65,7 +58,7 @@ jobs:
|
||||||
npx -p "@getmeli/cli" meli upload ./site \
|
npx -p "@getmeli/cli" meli upload ./site \
|
||||||
--url "https://pages.coastalcommits.com" \
|
--url "https://pages.coastalcommits.com" \
|
||||||
--site "${{ vars.MELI_SITE_ID }}" \
|
--site "${{ vars.MELI_SITE_ID }}" \
|
||||||
--token "${{ secrets.MELI_TOKEN }}" \
|
--token "${{ secrets.MELI_SITE_SECRET }}" \
|
||||||
--release "$CI_ACTION_REF_NAME_SLUG/${{ env.GITHUB_SHA }}" \
|
--release "$CI_ACTION_REF_NAME_SLUG/${{ env.GITHUB_SHA }}" \
|
||||||
--branch "$CI_ACTION_REF_NAME_SLUG"
|
--branch "$CI_ACTION_REF_NAME_SLUG"
|
||||||
|
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,7 +1,3 @@
|
||||||
.cache
|
.cache
|
||||||
.vscode
|
.vscode
|
||||||
site
|
site
|
||||||
.venv
|
|
||||||
__pycache__
|
|
||||||
.direnv
|
|
||||||
.devenv
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ My assorted cogs for Red-DiscordBot.
|
||||||
To get started with a development environment, first clone this repository.
|
To get started with a development environment, first clone this repository.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://coastalcommits.com/cswimr/SeaCogs.git
|
git clone https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, install Poetry.
|
Then, install Poetry.
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from .antipolls import AntiPolls
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
|
||||||
await bot.add_cog(AntiPolls(bot))
|
|
|
@ -1,181 +0,0 @@
|
||||||
# _____ _
|
|
||||||
# / ____| (_)
|
|
||||||
# | (___ ___ __ _ _____ ___ _ __ ___ _ __ ___ ___ _ __
|
|
||||||
# \___ \ / _ \/ _` / __\ \ /\ / / | '_ ` _ \| '_ ` _ \ / _ \ '__|
|
|
||||||
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
|
|
||||||
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from red_commons.logging import getLogger
|
|
||||||
from redbot.core import commands
|
|
||||||
from redbot.core.bot import Config, Red
|
|
||||||
from redbot.core.utils.chat_formatting import bold, humanize_list
|
|
||||||
|
|
||||||
|
|
||||||
class AntiPolls(commands.Cog):
|
|
||||||
"""AntiPolls deletes messages that contain polls, with a configurable per-guild role and channel whitelist and support for default Discord permissions (Manage Messages)."""
|
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
|
||||||
__version__ = "1.0.1"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/antipolls/"
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
|
||||||
super().__init__()
|
|
||||||
self.bot = bot
|
|
||||||
self.logger = getLogger("red.SeaCogs.AntiPolls")
|
|
||||||
self.config = Config.get_conf(self, identifier=23517395243, force_registration=True)
|
|
||||||
self.config.register_guild(
|
|
||||||
role_whitelist=[],
|
|
||||||
channel_whitelist=[],
|
|
||||||
manage_messages=True,
|
|
||||||
)
|
|
||||||
if not self.bot.intents.message_content:
|
|
||||||
self.logger.error("Message Content intent is not enabled, cog will not load.")
|
|
||||||
raise RuntimeError("This cog requires the Message Content intent to function. To prevent potentially destructive behavior, the cog will not load without the intent enabled.")
|
|
||||||
|
|
||||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
|
||||||
pre_processed = super().format_help_for_context(ctx) or ""
|
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
|
||||||
text = [
|
|
||||||
f"{pre_processed}{n}",
|
|
||||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
|
||||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
|
||||||
f"{bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
async def red_delete_data_for_user(self, **kwargs): # pylint: disable=unused-argument
|
|
||||||
"""Nothing to delete."""
|
|
||||||
return
|
|
||||||
|
|
||||||
@commands.Cog.listener('on_message')
|
|
||||||
async def polls_listener(self, message: discord.Message) -> None:
|
|
||||||
if message.guild is None:
|
|
||||||
return self.logger.verbose("Message in direct messages ignored")
|
|
||||||
|
|
||||||
if message.author.bot:
|
|
||||||
return self.logger.verbose("Message from bot ignored")
|
|
||||||
|
|
||||||
if await self.bot.cog_disabled_in_guild(self, message.guild):
|
|
||||||
return self.logger.verbose("Message ignored, cog is disabled in guild %s", message.guild.id)
|
|
||||||
|
|
||||||
guild_config = await self.config.guild(message.guild).all()
|
|
||||||
|
|
||||||
if guild_config['manage_messages'] is True and message.author.guild_permissions.manage_messages:
|
|
||||||
return self.logger.verbose("Message from user with Manage Messages permission ignored")
|
|
||||||
|
|
||||||
if message.channel.id in guild_config['channel_whitelist']:
|
|
||||||
return self.logger.verbose("Message in whitelisted channel %s ignored", message.channel.id)
|
|
||||||
|
|
||||||
if any(role.id in guild_config['role_whitelist'] for role in message.author.roles):
|
|
||||||
return self.logger.verbose("Message from whitelisted role %s ignored", message.author.roles)
|
|
||||||
|
|
||||||
if not message.content and not message.embeds and not message.attachments and not message.stickers:
|
|
||||||
self.logger.trace("Message %s is a poll, attempting to delete", message.id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await message.delete()
|
|
||||||
except discord.HTTPException as e:
|
|
||||||
return self.logger.error("Failed to delete message: %s", e)
|
|
||||||
|
|
||||||
return self.logger.trace("Deleted poll message %s", message.id)
|
|
||||||
self.logger.verbose("Message %s is not a poll, ignoring", message.id)
|
|
||||||
|
|
||||||
@commands.group(name="antipolls", aliases=["ap"])
|
|
||||||
@commands.guild_only()
|
|
||||||
@commands.admin_or_permissions(manage_guild=True)
|
|
||||||
async def antipolls(self, ctx: commands.Context) -> None:
|
|
||||||
"""Manage AntiPolls settings."""
|
|
||||||
|
|
||||||
@antipolls.group(name="roles")
|
|
||||||
async def antipolls_roles(self, ctx: commands.Context) -> None:
|
|
||||||
"""Manage role whitelist."""
|
|
||||||
|
|
||||||
@antipolls_roles.command(name="add")
|
|
||||||
async def antipolls_roles_add(self, ctx: commands.Context, *roles: discord.Role) -> None:
|
|
||||||
"""Add roles to the whitelist."""
|
|
||||||
async with self.config.guild(ctx.guild).role_whitelist() as role_whitelist:
|
|
||||||
role_whitelist: list
|
|
||||||
failed: list[discord.Role] = []
|
|
||||||
for role in roles:
|
|
||||||
if role.id in role_whitelist:
|
|
||||||
failed.append(role)
|
|
||||||
continue
|
|
||||||
role_whitelist.append(role.id)
|
|
||||||
await ctx.tick()
|
|
||||||
if failed:
|
|
||||||
await ctx.send(f"The following roles were already in the whitelist: {humanize_list([role.mention for role in failed])}", delete_after=10)
|
|
||||||
|
|
||||||
@antipolls_roles.command(name="remove")
|
|
||||||
async def antipolls_roles_remove(self, ctx: commands.Context, *roles: discord.Role) -> None:
|
|
||||||
"""Remove roles from the whitelist."""
|
|
||||||
async with self.config.guild(ctx.guild).role_whitelist() as role_whitelist:
|
|
||||||
role_whitelist: list
|
|
||||||
failed: list[discord.Role] = []
|
|
||||||
for role in roles:
|
|
||||||
if role.id not in role_whitelist:
|
|
||||||
failed.append(role)
|
|
||||||
continue
|
|
||||||
role_whitelist.remove(role.id)
|
|
||||||
await ctx.tick()
|
|
||||||
if failed:
|
|
||||||
await ctx.send(f"The following roles were not in the whitelist: {humanize_list([role.mention for role in failed])}", delete_after=10)
|
|
||||||
|
|
||||||
@antipolls_roles.command(name="list")
|
|
||||||
async def antipolls_roles_list(self, ctx: commands.Context) -> None:
|
|
||||||
"""List roles in the whitelist."""
|
|
||||||
role_whitelist = await self.config.guild(ctx.guild).role_whitelist()
|
|
||||||
if not role_whitelist:
|
|
||||||
return await ctx.send("No roles in the whitelist.")
|
|
||||||
roles = [ctx.guild.get_role(role) for role in role_whitelist]
|
|
||||||
await ctx.send(humanize_list([role.mention for role in roles]))
|
|
||||||
|
|
||||||
@antipolls.group(name="channels")
|
|
||||||
async def antipolls_channels(self, ctx: commands.Context) -> None:
|
|
||||||
"""Manage channel whitelist."""
|
|
||||||
|
|
||||||
@antipolls_channels.command(name="add")
|
|
||||||
async def antipolls_channels_add(self, ctx: commands.Context, *channels: discord.TextChannel) -> None:
|
|
||||||
"""Add channels to the whitelist."""
|
|
||||||
async with self.config.guild(ctx.guild).channel_whitelist() as channel_whitelist:
|
|
||||||
channel_whitelist: list
|
|
||||||
failed: list[discord.TextChannel] = []
|
|
||||||
for channel in channels:
|
|
||||||
if channel.id in channel_whitelist:
|
|
||||||
failed.append(channel)
|
|
||||||
continue
|
|
||||||
channel_whitelist.append(channel.id)
|
|
||||||
await ctx.tick()
|
|
||||||
if failed:
|
|
||||||
await ctx.send(f"The following channels were already in the whitelist: {humanize_list([channel.mention for channel in failed])}", delete_after=10)
|
|
||||||
|
|
||||||
@antipolls_channels.command(name="remove")
|
|
||||||
async def antipolls_channels_remove(self, ctx: commands.Context, *channels: discord.TextChannel) -> None:
|
|
||||||
"""Remove channels from the whitelist."""
|
|
||||||
async with self.config.guild(ctx.guild).channel_whitelist() as channel_whitelist:
|
|
||||||
channel_whitelist: list
|
|
||||||
failed: list[discord.TextChannel] = []
|
|
||||||
for channel in channels:
|
|
||||||
if channel.id not in channel_whitelist:
|
|
||||||
failed.append(channel)
|
|
||||||
continue
|
|
||||||
channel_whitelist.remove(channel.id)
|
|
||||||
await ctx.tick()
|
|
||||||
if failed:
|
|
||||||
await ctx.send(f"The following channels were not in the whitelist: {humanize_list([channel.mention for channel in failed])}", delete_after=10)
|
|
||||||
|
|
||||||
@antipolls_channels.command(name="list")
|
|
||||||
async def antipolls_channels_list(self, ctx: commands.Context) -> None:
|
|
||||||
"""List channels in the whitelist."""
|
|
||||||
channel_whitelist = await self.config.guild(ctx.guild).channel_whitelist()
|
|
||||||
if not channel_whitelist:
|
|
||||||
return await ctx.send("No channels in the whitelist.")
|
|
||||||
channels = [ctx.guild.get_channel(channel) for channel in channel_whitelist]
|
|
||||||
await ctx.send(humanize_list([channel.mention for channel in channels]))
|
|
||||||
|
|
||||||
@antipolls.command(name="managemessages")
|
|
||||||
async def antipolls_managemessages(self, ctx: commands.Context, enabled: bool) -> None:
|
|
||||||
"""Toggle Manage Messages permission check."""
|
|
||||||
await self.config.guild(ctx.guild).manage_messages.set(enabled)
|
|
||||||
await ctx.tick()
|
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"author" : ["cswimr"],
|
|
||||||
"install_msg" : "Thank you for installing AntiPolls!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
|
||||||
"name" : "AntiPolls",
|
|
||||||
"short" : "AntiPolls deletes messages that contain polls.",
|
|
||||||
"description" : "AntiPolls deletes messages that contain polls, with a configurable per-guild role and channel whitelist and support for default Discord permissions (Manage Messages).",
|
|
||||||
"end_user_data_statement" : "This cog does not store any user data.",
|
|
||||||
"hidden": true,
|
|
||||||
"disabled": false,
|
|
||||||
"min_bot_version": "3.5.0",
|
|
||||||
"min_python_version": [3, 10, 0],
|
|
||||||
"tags": [
|
|
||||||
"automod",
|
|
||||||
"automoderation",
|
|
||||||
"polls"
|
|
||||||
]
|
|
||||||
}
|
|
596
aurora/aurora.py
596
aurora/aurora.py
|
@ -13,13 +13,14 @@ from datetime import datetime, timedelta, timezone
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord import Object
|
import humanize
|
||||||
from discord.ext import tasks
|
from discord.ext import tasks
|
||||||
|
from pytimeparse2 import disable_dateutil, parse
|
||||||
from redbot.core import app_commands, commands, data_manager
|
from redbot.core import app_commands, commands, data_manager
|
||||||
from redbot.core.app_commands import Choice
|
from redbot.core.app_commands import Choice
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.commands.converter import parse_relativedelta, parse_timedelta
|
from redbot.core.utils.chat_formatting import (box, error, humanize_list,
|
||||||
from redbot.core.utils.chat_formatting import box, error, humanize_list, humanize_timedelta, warning
|
warning)
|
||||||
|
|
||||||
from aurora.importers.aurora import ImportAuroraView
|
from aurora.importers.aurora import ImportAuroraView
|
||||||
from aurora.importers.galacticbot import ImportGalacticBotView
|
from aurora.importers.galacticbot import ImportGalacticBotView
|
||||||
|
@ -28,10 +29,17 @@ from aurora.menus.guild import Guild
|
||||||
from aurora.menus.immune import Immune
|
from aurora.menus.immune import Immune
|
||||||
from aurora.menus.overrides import Overrides
|
from aurora.menus.overrides import Overrides
|
||||||
from aurora.utilities.config import config, register_config
|
from aurora.utilities.config import config, register_config
|
||||||
from aurora.utilities.database import connect, create_guild_table, fetch_case, mysql_log
|
from aurora.utilities.database import (connect, create_guild_table, fetch_case,
|
||||||
from aurora.utilities.factory import addrole_embed, case_factory, changes_factory, evidenceformat_factory, guild_embed, immune_embed, message_factory, overrides_embed
|
mysql_log)
|
||||||
|
from aurora.utilities.factory import (addrole_embed, case_factory,
|
||||||
|
changes_factory, evidenceformat_factory,
|
||||||
|
guild_embed, immune_embed,
|
||||||
|
message_factory, overrides_embed)
|
||||||
from aurora.utilities.logger import logger
|
from aurora.utilities.logger import logger
|
||||||
from aurora.utilities.utils import check_moddable, check_permissions, convert_timedelta_to_str, fetch_channel_dict, fetch_user_dict, generate_dict, get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta
|
from aurora.utilities.utils import (check_moddable, check_permissions,
|
||||||
|
convert_timedelta_to_str,
|
||||||
|
fetch_channel_dict, fetch_user_dict,
|
||||||
|
generate_dict, log, send_evidenceformat)
|
||||||
|
|
||||||
|
|
||||||
class Aurora(commands.Cog):
|
class Aurora(commands.Cog):
|
||||||
|
@ -39,9 +47,8 @@ class Aurora(commands.Cog):
|
||||||
It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs.
|
It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs.
|
||||||
This cog stores all of its data in an SQLite database."""
|
This cog stores all of its data in an SQLite database."""
|
||||||
|
|
||||||
__author__ = ["cswimr"]
|
__author__ = ["SeaswimmerTheFsh"]
|
||||||
__version__ = "2.1.3"
|
__version__ = "2.1.6"
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/aurora/"
|
|
||||||
|
|
||||||
async def red_delete_data_for_user(self, *, requester, user_id: int):
|
async def red_delete_data_for_user(self, *, requester, user_id: int):
|
||||||
if requester == "discord_deleted_user":
|
if requester == "discord_deleted_user":
|
||||||
|
@ -77,6 +84,7 @@ class Aurora(commands.Cog):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
register_config(config)
|
register_config(config)
|
||||||
|
disable_dateutil()
|
||||||
self.handle_expiry.start()
|
self.handle_expiry.start()
|
||||||
|
|
||||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||||
|
@ -86,7 +94,6 @@ class Aurora(commands.Cog):
|
||||||
f"{pre_processed}{n}",
|
f"{pre_processed}{n}",
|
||||||
f"Cog Version: **{self.__version__}**",
|
f"Cog Version: **{self.__version__}**",
|
||||||
f"Author: {humanize_list(self.__author__)}",
|
f"Author: {humanize_list(self.__author__)}",
|
||||||
f"Documentation: {self.__documentation__}",
|
|
||||||
]
|
]
|
||||||
return "\n".join(text)
|
return "\n".join(text)
|
||||||
|
|
||||||
|
@ -114,20 +121,6 @@ class Aurora(commands.Cog):
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
return
|
return
|
||||||
|
|
||||||
@commands.Cog.listener("on_member_join")
|
|
||||||
async def addrole_on_member_join(self, member: discord.Member):
|
|
||||||
"""This method automatically adds roles to users when they join the server."""
|
|
||||||
if not await self.bot.cog_disabled_in_guild(self, member.guild):
|
|
||||||
query = f"""SELECT moderation_id, role_id, reason FROM moderation_{member.guild.id} WHERE target_id = ? AND moderation_type = 'ADDROLE' AND expired = 0 AND resolved = 0;"""
|
|
||||||
database = connect()
|
|
||||||
cursor = database.cursor()
|
|
||||||
cursor.execute(query, (member.id,))
|
|
||||||
results = cursor.fetchall()
|
|
||||||
for result in results:
|
|
||||||
role = member.guild.get_role(result[1])
|
|
||||||
reason = result[2]
|
|
||||||
await member.add_roles(role, reason=f"Role automatically added on member rejoin for: {reason} (Case #{result[0]:,})")
|
|
||||||
|
|
||||||
@commands.Cog.listener("on_audit_log_entry_create")
|
@commands.Cog.listener("on_audit_log_entry_create")
|
||||||
async def autologger(self, entry: discord.AuditLogEntry):
|
async def autologger(self, entry: discord.AuditLogEntry):
|
||||||
"""This method automatically logs moderations done by users manually ("right clicks")."""
|
"""This method automatically logs moderations done by users manually ("right clicks")."""
|
||||||
|
@ -187,50 +180,47 @@ class Aurora(commands.Cog):
|
||||||
### COMMANDS
|
### COMMANDS
|
||||||
#######################################################################################################################
|
#######################################################################################################################
|
||||||
|
|
||||||
@app_commands.command(name="note")
|
@commands.hybrid_command(name="note")
|
||||||
|
@commands.mod_or_permissions(moderate_members=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
target="Who are you noting?",
|
||||||
|
reason="Why are you noting this user?",
|
||||||
|
silent="Should the user be messaged?",
|
||||||
|
)
|
||||||
async def note(
|
async def note(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
ctx: commands.Context,
|
||||||
target: discord.User,
|
target: discord.User,
|
||||||
reason: str,
|
reason: str,
|
||||||
silent: bool = None,
|
silent: bool = None,
|
||||||
):
|
):
|
||||||
"""Add a note to a user.
|
"""Add a note to a user."""
|
||||||
|
if not await check_moddable(target, ctx, ["moderate_members"]):
|
||||||
Parameters
|
|
||||||
-----------
|
|
||||||
target: discord.User
|
|
||||||
Who are you noting?
|
|
||||||
reason: str
|
|
||||||
Why are you noting this user?
|
|
||||||
silent: bool
|
|
||||||
Should the user be messaged?"""
|
|
||||||
if not await check_moddable(target, interaction, ["moderate_members"]):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await interaction.response.send_message(
|
message = await ctx.send(
|
||||||
content=f"{target.mention} has recieved a note!\n**Reason** - `{reason}`"
|
content=f"{target.mention} has recieved a note!\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
if silent is None:
|
if silent is None:
|
||||||
silent = not await config.guild(interaction.guild).dm_users()
|
silent = not await config.guild(ctx.guild).dm_users()
|
||||||
if silent is False:
|
if silent is False:
|
||||||
try:
|
try:
|
||||||
embed = await message_factory(
|
embed = await message_factory(
|
||||||
await self.bot.get_embed_color(interaction.channel),
|
await self.bot.get_embed_color(ctx.channel),
|
||||||
guild=interaction.guild,
|
guild=ctx.guild,
|
||||||
moderator=interaction.user,
|
moderator=ctx.author,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
moderation_type="note",
|
moderation_type="note",
|
||||||
response=await interaction.original_response(),
|
response=message,
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation_id = await mysql_log(
|
moderation_id = await mysql_log(
|
||||||
interaction.guild.id,
|
ctx.guild.id,
|
||||||
interaction.user.id,
|
ctx.author.id,
|
||||||
"NOTE",
|
"NOTE",
|
||||||
"USER",
|
"USER",
|
||||||
target.id,
|
target.id,
|
||||||
|
@ -238,58 +228,55 @@ class Aurora(commands.Cog):
|
||||||
"NULL",
|
"NULL",
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
await interaction.edit_original_response(
|
await message.edit(
|
||||||
content=f"{target.mention} has received a note! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
content=f"{target.mention} has received a note! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
await log(interaction, moderation_id)
|
await log(ctx, moderation_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
case = await fetch_case(moderation_id, ctx.guild.id)
|
||||||
await send_evidenceformat(interaction, case)
|
await send_evidenceformat(ctx, case)
|
||||||
|
|
||||||
@app_commands.command(name="warn")
|
@commands.hybrid_command(name="warn")
|
||||||
|
@commands.mod_or_permissions(moderate_members=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
target="Who are you warning?",
|
||||||
|
reason="Why are you warning this user?",
|
||||||
|
silent="Should the user be messaged?",
|
||||||
|
)
|
||||||
async def warn(
|
async def warn(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
ctx: commands.Context,
|
||||||
target: discord.Member,
|
target: discord.Member,
|
||||||
reason: str,
|
reason: str,
|
||||||
silent: bool = None,
|
silent: bool = None,
|
||||||
):
|
):
|
||||||
"""Warn a user.
|
"""Warn a user."""
|
||||||
|
if not await check_moddable(target, ctx, ["moderate_members"]):
|
||||||
Parameters
|
|
||||||
-----------
|
|
||||||
target: discord.Member
|
|
||||||
Who are you warning?
|
|
||||||
reason: str
|
|
||||||
Why are you warning this user?
|
|
||||||
silent: bool
|
|
||||||
Should the user be messaged?"""
|
|
||||||
if not await check_moddable(target, interaction, ["moderate_members"]):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await interaction.response.send_message(
|
message = await ctx.send(
|
||||||
content=f"{target.mention} has been warned!\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been warned!\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
if silent is None:
|
if silent is None:
|
||||||
silent = not await config.guild(interaction.guild).dm_users()
|
silent = not await config.guild(ctx.guild).dm_users()
|
||||||
if silent is False:
|
if silent is False:
|
||||||
try:
|
try:
|
||||||
embed = await message_factory(
|
embed = await message_factory(
|
||||||
await self.bot.get_embed_color(interaction.channel),
|
await self.bot.get_embed_color(ctx.channel),
|
||||||
guild=interaction.guild,
|
guild=ctx.guild,
|
||||||
moderator=interaction.user,
|
moderator=ctx.author,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
moderation_type="warned",
|
moderation_type="warned",
|
||||||
response=await interaction.original_response(),
|
response=message,
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation_id = await mysql_log(
|
moderation_id = await mysql_log(
|
||||||
interaction.guild.id,
|
ctx.guild.id,
|
||||||
interaction.user.id,
|
ctx.author.id,
|
||||||
"WARN",
|
"WARN",
|
||||||
"USER",
|
"USER",
|
||||||
target.id,
|
target.id,
|
||||||
|
@ -297,13 +284,13 @@ class Aurora(commands.Cog):
|
||||||
"NULL",
|
"NULL",
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
await interaction.edit_original_response(
|
await message.edit(
|
||||||
content=f"{target.mention} has been warned! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been warned! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
await log(interaction, moderation_id)
|
await log(ctx, moderation_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
case = await fetch_case(moderation_id, ctx.guild.id)
|
||||||
await send_evidenceformat(interaction, case)
|
await send_evidenceformat(ctx, case)
|
||||||
|
|
||||||
@app_commands.command(name="addrole")
|
@app_commands.command(name="addrole")
|
||||||
async def addrole(
|
async def addrole(
|
||||||
|
@ -339,10 +326,13 @@ class Aurora(commands.Cog):
|
||||||
return
|
return
|
||||||
|
|
||||||
if duration is not None:
|
if duration is not None:
|
||||||
parsed_time = parse_timedelta(duration)
|
try:
|
||||||
if parsed_time is None:
|
parsed_time = parse(
|
||||||
|
sval=duration, as_timedelta=True, raise_exception=True
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content=error("Please provide a valid duration!"), ephemeral=True
|
error("Please provide a valid duration!"), ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -381,16 +371,16 @@ class Aurora(commands.Cog):
|
||||||
duration=parsed_time,
|
duration=parsed_time,
|
||||||
role=role,
|
role=role,
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await target.add_roles(
|
await target.add_roles(
|
||||||
role,
|
role,
|
||||||
reason=f"Role added by {interaction.user.id}{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''} for: {reason}",
|
reason=f"Role added by {interaction.user.id}{(' for ' + {humanize.precisedelta(parsed_time)} if parsed_time != 'NULL' else '')} for: {reason}",
|
||||||
)
|
)
|
||||||
response: discord.WebhookMessage = await interaction.followup.send(
|
response: discord.WebhookMessage = await interaction.followup.send(
|
||||||
content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been given the {role.mention} role{(' for ' + {humanize.precisedelta(parsed_time)} if parsed_time != 'NULL' else '')}!\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
moderation_id = await mysql_log(
|
moderation_id = await mysql_log(
|
||||||
|
@ -404,145 +394,35 @@ class Aurora(commands.Cog):
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
await response.edit(
|
await response.edit(
|
||||||
content=f"{target.mention} has been given the {role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`",
|
content=f"{target.mention} has been given the {role.mention} role{(' for ' + {humanize.precisedelta(parsed_time)} if parsed_time != 'NULL' else '')}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`",
|
||||||
)
|
)
|
||||||
await log(interaction, moderation_id)
|
await log(interaction, moderation_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
case = await fetch_case(moderation_id, interaction.guild.id)
|
||||||
await send_evidenceformat(interaction, case)
|
await send_evidenceformat(interaction, case)
|
||||||
|
|
||||||
@app_commands.command(name="removerole")
|
@commands.hybrid_command(name="mute")
|
||||||
async def removerole(
|
@commands.mod_or_permissions(moderate_members=True)
|
||||||
self,
|
@app_commands.describe(
|
||||||
interaction: discord.Interaction,
|
target="Who are you muting?",
|
||||||
target: discord.Member,
|
duration="How long are you muting this user for?",
|
||||||
role: discord.Role,
|
reason="Why are you muting this user?",
|
||||||
reason: str,
|
silent="Should the user be messaged?",
|
||||||
duration: str = None,
|
)
|
||||||
silent: bool = None,
|
|
||||||
):
|
|
||||||
"""Remove a role from a user.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
-----------
|
|
||||||
target: discord.Member
|
|
||||||
Who are you removing a role from?
|
|
||||||
role: discord.Role
|
|
||||||
What role are you removing from the target?
|
|
||||||
reason: str
|
|
||||||
Why are you removing a role from this user?
|
|
||||||
duration: str
|
|
||||||
How long are you removing this role for?
|
|
||||||
silent: bool
|
|
||||||
Should the user be messaged?"""
|
|
||||||
addrole_whitelist = await config.guild(interaction.guild).addrole_whitelist()
|
|
||||||
|
|
||||||
if not addrole_whitelist:
|
|
||||||
await interaction.response.send_message(
|
|
||||||
content=error("There are no whitelisted roles set for this server!"),
|
|
||||||
ephemeral=True,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if duration is not None:
|
|
||||||
parsed_time = parse_timedelta(duration)
|
|
||||||
if parsed_time is None:
|
|
||||||
await interaction.response.send_message(
|
|
||||||
content=error("Please provide a valid duration!"), ephemeral=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
parsed_time = "NULL"
|
|
||||||
|
|
||||||
if role.id not in addrole_whitelist:
|
|
||||||
await interaction.response.send_message(
|
|
||||||
content=error("That role isn't whitelisted!"), ephemeral=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not await check_moddable(
|
|
||||||
target, interaction, ["moderate_members", "manage_roles"]
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
if role.id not in [user_role.id for user_role in target.roles]:
|
|
||||||
await interaction.response.send_message(
|
|
||||||
content=error(f"{target.mention} does not have this role!"),
|
|
||||||
ephemeral=True,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
await interaction.response.defer()
|
|
||||||
if silent is None:
|
|
||||||
silent = not await config.guild(interaction.guild).dm_users()
|
|
||||||
if silent is False:
|
|
||||||
try:
|
|
||||||
embed = await message_factory(
|
|
||||||
await self.bot.get_embed_color(interaction.channel),
|
|
||||||
guild=interaction.guild,
|
|
||||||
moderator=interaction.user,
|
|
||||||
reason=reason,
|
|
||||||
moderation_type="removerole",
|
|
||||||
response=await interaction.original_response(),
|
|
||||||
duration=parsed_time,
|
|
||||||
role=role,
|
|
||||||
)
|
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
|
||||||
except discord.errors.HTTPException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
await target.remove_roles(
|
|
||||||
role,
|
|
||||||
reason=f"Role removed by {interaction.user.id}{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''} for: {reason}",
|
|
||||||
)
|
|
||||||
response: discord.WebhookMessage = await interaction.followup.send(
|
|
||||||
content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`"
|
|
||||||
)
|
|
||||||
|
|
||||||
moderation_id = await mysql_log(
|
|
||||||
interaction.guild.id,
|
|
||||||
interaction.user.id,
|
|
||||||
"REMOVEROLE",
|
|
||||||
"USER",
|
|
||||||
target.id,
|
|
||||||
role.id,
|
|
||||||
parsed_time,
|
|
||||||
reason,
|
|
||||||
)
|
|
||||||
await response.edit(
|
|
||||||
content=f"{target.mention} has had the {role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`",
|
|
||||||
)
|
|
||||||
await log(interaction, moderation_id)
|
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
|
||||||
await send_evidenceformat(interaction, case)
|
|
||||||
|
|
||||||
@app_commands.command(name="mute")
|
|
||||||
async def mute(
|
async def mute(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
ctx: commands.Context,
|
||||||
target: discord.Member,
|
target: discord.Member,
|
||||||
duration: str,
|
duration: str,
|
||||||
reason: str,
|
reason: str,
|
||||||
silent: bool = None,
|
silent: bool = None,
|
||||||
):
|
):
|
||||||
"""Mute a user.
|
"""Mute a user."""
|
||||||
|
if not await check_moddable(target, ctx, ["moderate_members"]):
|
||||||
Parameters
|
|
||||||
-----------
|
|
||||||
target: discord.Member
|
|
||||||
Who are you unbanning?
|
|
||||||
duration: str
|
|
||||||
How long are you muting this user for?
|
|
||||||
reason: str
|
|
||||||
Why are you unbanning this user?
|
|
||||||
silent: bool
|
|
||||||
Should the user be messaged?"""
|
|
||||||
if not await check_moddable(target, interaction, ["moderate_members"]):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if target.is_timed_out() is True:
|
if target.is_timed_out() is True:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
error(f"{target.mention} is already muted!"),
|
error(f"{target.mention} is already muted!"),
|
||||||
allowed_mentions=discord.AllowedMentions(users=False),
|
allowed_mentions=discord.AllowedMentions(users=False),
|
||||||
ephemeral=True,
|
ephemeral=True,
|
||||||
|
@ -550,46 +430,47 @@ class Aurora(commands.Cog):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed_time = parse_timedelta(duration, maximum=timedelta(days=28))
|
parsed_time = parse(sval=duration, as_timedelta=True, raise_exception=True)
|
||||||
if parsed_time is None:
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
error("Please provide a valid duration!"), ephemeral=True
|
error("Please provide a valid duration!"), ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except commands.BadArgument:
|
|
||||||
await interaction.response.send_message(
|
if parsed_time.total_seconds() / 1000 > 2419200000:
|
||||||
error("Please provide a duration that is less than 28 days."), ephemeral=True
|
await ctx.send(
|
||||||
|
error("Please provide a duration that is less than 28 days.")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await target.timeout(
|
await target.timeout(
|
||||||
parsed_time, reason=f"Muted by {interaction.user.id} for: {reason}"
|
parsed_time, reason=f"Muted by {ctx.author.id} for: {reason}"
|
||||||
)
|
)
|
||||||
|
|
||||||
await interaction.response.send_message(
|
message = await ctx.send(
|
||||||
content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}!\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been muted for {humanize.precisedelta(parsed_time)}!\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
if silent is None:
|
if silent is None:
|
||||||
silent = not await config.guild(interaction.guild).dm_users()
|
silent = not await config.guild(ctx.guild).dm_users()
|
||||||
if silent is False:
|
if silent is False:
|
||||||
try:
|
try:
|
||||||
embed = await message_factory(
|
embed = await message_factory(
|
||||||
await self.bot.get_embed_color(interaction.channel),
|
await ctx.embed_color(),
|
||||||
guild=interaction.guild,
|
guild=ctx.guild,
|
||||||
moderator=interaction.user,
|
moderator=ctx.author,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
moderation_type="muted",
|
moderation_type="muted",
|
||||||
response=await interaction.original_response(),
|
response=message,
|
||||||
duration=parsed_time,
|
duration=parsed_time,
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation_id = await mysql_log(
|
moderation_id = await mysql_log(
|
||||||
interaction.guild.id,
|
ctx.guild.id,
|
||||||
interaction.user.id,
|
ctx.author.id,
|
||||||
"MUTE",
|
"MUTE",
|
||||||
"USER",
|
"USER",
|
||||||
target.id,
|
target.id,
|
||||||
|
@ -597,37 +478,34 @@ class Aurora(commands.Cog):
|
||||||
parsed_time,
|
parsed_time,
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
await interaction.edit_original_response(
|
await message.edit(
|
||||||
content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been muted for {humanize.precisedelta(parsed_time)}! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
await log(interaction, moderation_id)
|
await log(ctx, moderation_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
case = await fetch_case(moderation_id, ctx.guild.id)
|
||||||
await send_evidenceformat(interaction, case)
|
await send_evidenceformat(ctx, case)
|
||||||
|
|
||||||
@app_commands.command(name="unmute")
|
@commands.hybrid_command(name="unmute")
|
||||||
|
@commands.mod_or_permissions(moderate_members=True)
|
||||||
|
@app_commands.describe(
|
||||||
|
target="Who are you unmuting?",
|
||||||
|
reason="Why are you unmuting this user?",
|
||||||
|
silent="Should the user be messaged?",
|
||||||
|
)
|
||||||
async def unmute(
|
async def unmute(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
ctx: commands.Context,
|
||||||
target: discord.Member,
|
target: discord.Member,
|
||||||
reason: str = None,
|
reason: str = None,
|
||||||
silent: bool = None,
|
silent: bool = None,
|
||||||
):
|
):
|
||||||
"""Unmute a user.
|
"""Unmute a user."""
|
||||||
|
if not await check_moddable(target, ctx, ["moderate_members"]):
|
||||||
Parameters
|
|
||||||
-----------
|
|
||||||
target: discord.user
|
|
||||||
Who are you unmuting?
|
|
||||||
reason: str
|
|
||||||
Why are you unmuting this user?
|
|
||||||
silent: bool
|
|
||||||
Should the user be messaged?"""
|
|
||||||
if not await check_moddable(target, interaction, ["moderate_members"]):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if target.is_timed_out() is False:
|
if target.is_timed_out() is False:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
error(f"{target.mention} is not muted!"),
|
error(f"{target.mention} is not muted!"),
|
||||||
allowed_mentions=discord.AllowedMentions(users=False),
|
allowed_mentions=discord.AllowedMentions(users=False),
|
||||||
ephemeral=True,
|
ephemeral=True,
|
||||||
|
@ -636,35 +514,35 @@ class Aurora(commands.Cog):
|
||||||
|
|
||||||
if reason:
|
if reason:
|
||||||
await target.timeout(
|
await target.timeout(
|
||||||
None, reason=f"Unmuted by {interaction.user.id} for: {reason}"
|
None, reason=f"Unmuted by {ctx.author.id} for: {reason}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await target.timeout(None, reason=f"Unbanned by {interaction.user.id}")
|
await target.timeout(None, reason=f"Unmuted by {ctx.author.id}")
|
||||||
reason = "No reason given."
|
reason = "No reason given."
|
||||||
|
|
||||||
await interaction.response.send_message(
|
message = await ctx.send(
|
||||||
content=f"{target.mention} has been unmuted!\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been unmuted!\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
if silent is None:
|
if silent is None:
|
||||||
silent = not await config.guild(interaction.guild).dm_users()
|
silent = not await config.guild(ctx.guild).dm_users()
|
||||||
if silent is False:
|
if silent is False:
|
||||||
try:
|
try:
|
||||||
embed = await message_factory(
|
embed = await message_factory(
|
||||||
await self.bot.get_embed_color(interaction.channel),
|
await ctx.embed_color(),
|
||||||
guild=interaction.guild,
|
guild=ctx.guild,
|
||||||
moderator=interaction.user,
|
moderator=ctx.author,
|
||||||
reason=reason,
|
reason=reason,
|
||||||
moderation_type="unmuted",
|
moderation_type="unmuted",
|
||||||
response=await interaction.original_response(),
|
response=message,
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
moderation_id = await mysql_log(
|
moderation_id = await mysql_log(
|
||||||
interaction.guild.id,
|
ctx.guild.id,
|
||||||
interaction.user.id,
|
ctx.author.id,
|
||||||
"UNMUTE",
|
"UNMUTE",
|
||||||
"USER",
|
"USER",
|
||||||
target.id,
|
target.id,
|
||||||
|
@ -672,13 +550,13 @@ class Aurora(commands.Cog):
|
||||||
"NULL",
|
"NULL",
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
await interaction.edit_original_response(
|
await message.edit(
|
||||||
content=f"{target.mention} has been unmuted! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been unmuted! (Case `#{moderation_id:,}`)\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
await log(interaction, moderation_id)
|
await log(ctx, moderation_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
case = await fetch_case(moderation_id, ctx.guild.id)
|
||||||
await send_evidenceformat(interaction, case)
|
await send_evidenceformat(ctx, case)
|
||||||
|
|
||||||
@app_commands.command(name="kick")
|
@app_commands.command(name="kick")
|
||||||
async def kick(
|
async def kick(
|
||||||
|
@ -717,7 +595,7 @@ class Aurora(commands.Cog):
|
||||||
moderation_type="kicked",
|
moderation_type="kicked",
|
||||||
response=await interaction.original_response(),
|
response=await interaction.original_response(),
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -793,22 +671,18 @@ class Aurora(commands.Cog):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if duration:
|
if duration:
|
||||||
parsed_time = parse_relativedelta(duration)
|
|
||||||
if parsed_time is None:
|
|
||||||
await interaction.response.send_message(
|
|
||||||
content=error("Please provide a valid duration!"), ephemeral=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
parsed_time = timedelta_from_relativedelta(parsed_time)
|
parsed_time = parse(
|
||||||
|
sval=duration, as_timedelta=True, raise_exception=True
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content=error("Please provide a valid duration!"), ephemeral=True
|
error("Please provide a valid duration!"), ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content=f"{target.mention} has been banned for {humanize_timedelta(timedelta=parsed_time)}!\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been banned for {humanize.precisedelta(parsed_time)}!\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -821,7 +695,7 @@ class Aurora(commands.Cog):
|
||||||
response=await interaction.original_response(),
|
response=await interaction.original_response(),
|
||||||
duration=parsed_time,
|
duration=parsed_time,
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -842,7 +716,7 @@ class Aurora(commands.Cog):
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
await interaction.edit_original_response(
|
await interaction.edit_original_response(
|
||||||
content=f"{target.mention} has been banned for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation_id}`)\n**Reason** - `{reason}`"
|
content=f"{target.mention} has been banned for {humanize.precisedelta(parsed_time)}! (Case `#{moderation_id}`)\n**Reason** - `{reason}`"
|
||||||
)
|
)
|
||||||
await log(interaction, moderation_id)
|
await log(interaction, moderation_id)
|
||||||
|
|
||||||
|
@ -865,7 +739,7 @@ class Aurora(commands.Cog):
|
||||||
moderation_type="banned",
|
moderation_type="banned",
|
||||||
response=await interaction.original_response(),
|
response=await interaction.original_response(),
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -948,7 +822,7 @@ class Aurora(commands.Cog):
|
||||||
moderation_type="unbanned",
|
moderation_type="unbanned",
|
||||||
response=await interaction.original_response(),
|
response=await interaction.original_response(),
|
||||||
)
|
)
|
||||||
await target.send(embed=embed, file=get_footer_image(self))
|
await target.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1182,9 +1056,9 @@ class Aurora(commands.Cog):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
duration_embed = (
|
duration_embed = (
|
||||||
f"{humanize_timedelta(timedelta=td)} | <t:{case['end_timestamp']}:R>"
|
f"{humanize.precisedelta(td)} | <t:{case['end_timestamp']}:R>"
|
||||||
if bool(case["expired"]) is False
|
if bool(case["expired"]) is False
|
||||||
else f"{humanize_timedelta(timedelta=td)} | Expired"
|
else f"{humanize.precisedelta(td)} | Expired"
|
||||||
)
|
)
|
||||||
field_value += f"\n**Duration:** {duration_embed}"
|
field_value += f"\n**Duration:** {duration_embed}"
|
||||||
|
|
||||||
|
@ -1427,7 +1301,7 @@ class Aurora(commands.Cog):
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
return
|
return
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content=box(json.dumps(case_dict, indent=2), 'json'),
|
content=box({json.dumps(case_dict, indent=2)}),
|
||||||
ephemeral=ephemeral,
|
ephemeral=ephemeral,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -1493,8 +1367,11 @@ class Aurora(commands.Cog):
|
||||||
case_dict = await fetch_case(case, interaction.guild.id)
|
case_dict = await fetch_case(case, interaction.guild.id)
|
||||||
if case_dict:
|
if case_dict:
|
||||||
if duration:
|
if duration:
|
||||||
parsed_time = parse_timedelta(duration)
|
try:
|
||||||
if parsed_time is None:
|
parsed_time = parse(
|
||||||
|
sval=duration, as_timedelta=True, raise_exception=True
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
error("Please provide a valid duration!"), ephemeral=True
|
error("Please provide a valid duration!"), ephemeral=True
|
||||||
)
|
)
|
||||||
|
@ -1607,13 +1484,10 @@ class Aurora(commands.Cog):
|
||||||
|
|
||||||
@tasks.loop(minutes=1)
|
@tasks.loop(minutes=1)
|
||||||
async def handle_expiry(self):
|
async def handle_expiry(self):
|
||||||
await self.bot.wait_until_red_ready()
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
database = connect()
|
database = connect()
|
||||||
cursor = database.cursor()
|
cursor = database.cursor()
|
||||||
global_unban_num = 0
|
global_num = 0
|
||||||
global_addrole_num = 0
|
|
||||||
global_removerole_num = 0
|
|
||||||
|
|
||||||
guilds: list[discord.Guild] = self.bot.guilds
|
guilds: list[discord.Guild] = self.bot.guilds
|
||||||
for guild in guilds:
|
for guild in guilds:
|
||||||
|
@ -1631,7 +1505,7 @@ class Aurora(commands.Cog):
|
||||||
target_ids = [row[0] for row in result]
|
target_ids = [row[0] for row in result]
|
||||||
moderation_ids = [row[1] for row in result]
|
moderation_ids = [row[1] for row in result]
|
||||||
|
|
||||||
unban_num = 0
|
num = 0
|
||||||
for target_id, moderation_id in zip(target_ids, moderation_ids):
|
for target_id, moderation_id in zip(target_ids, moderation_ids):
|
||||||
user: discord.User = await self.bot.fetch_user(target_id)
|
user: discord.User = await self.bot.fetch_user(target_id)
|
||||||
name = (
|
name = (
|
||||||
|
@ -1652,7 +1526,7 @@ class Aurora(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await user.send(embed=embed, file=get_footer_image(self))
|
await user.send(embed=embed)
|
||||||
except discord.errors.HTTPException:
|
except discord.errors.HTTPException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1663,7 +1537,7 @@ class Aurora(commands.Cog):
|
||||||
guild.name,
|
guild.name,
|
||||||
guild.id,
|
guild.id,
|
||||||
)
|
)
|
||||||
unban_num = unban_num + 1
|
num = num + 1
|
||||||
except (
|
except (
|
||||||
discord.errors.NotFound,
|
discord.errors.NotFound,
|
||||||
discord.errors.Forbidden,
|
discord.errors.Forbidden,
|
||||||
|
@ -1678,87 +1552,44 @@ class Aurora(commands.Cog):
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
|
|
||||||
removerole_num = 0
|
expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= ? AND expired = 0 AND moderation_type != 'BLACKLIST') OR (expired = 0 AND resolved = 1 AND moderation_type != 'BLACKLIST')"
|
||||||
addrole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'ADDROLE' AND expired = 0"
|
|
||||||
try:
|
|
||||||
cursor.execute(addrole_query, (time.time(),))
|
|
||||||
result = cursor.fetchall()
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
continue
|
|
||||||
target_ids = [row[0] for row in result]
|
|
||||||
moderation_ids = [row[1] for row in result]
|
|
||||||
role_ids = [row[2] for row in result]
|
|
||||||
|
|
||||||
for target_id, moderation_id, role_id in zip(
|
|
||||||
target_ids, moderation_ids, role_ids
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
member = await guild.fetch_member(target_id)
|
|
||||||
|
|
||||||
await member.remove_roles(
|
|
||||||
Object(role_id), reason=f"Automatic role removal from case #{moderation_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
removerole_num = removerole_num + 1
|
|
||||||
except (
|
|
||||||
discord.errors.NotFound,
|
|
||||||
discord.errors.Forbidden,
|
|
||||||
discord.errors.HTTPException,
|
|
||||||
) as e:
|
|
||||||
logger.error(
|
|
||||||
"Removing the role %s from user %s failed due to: \n%s",
|
|
||||||
role_id,
|
|
||||||
target_id,
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
addrole_num = 0
|
|
||||||
removerole_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'REMOVEROLE' AND expired = 0"
|
|
||||||
try:
|
|
||||||
cursor.execute(removerole_query, (time.time(),))
|
|
||||||
result = cursor.fetchall()
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
continue
|
|
||||||
target_ids = [row[0] for row in result]
|
|
||||||
moderation_ids = [row[1] for row in result]
|
|
||||||
role_ids = [row[2] for row in result]
|
|
||||||
|
|
||||||
for target_id, moderation_id, role_id in zip(
|
|
||||||
target_ids, moderation_ids, role_ids
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
member = await guild.fetch_member(target_id)
|
|
||||||
|
|
||||||
await member.add_roles(
|
|
||||||
Object(role_id), reason=f"Automatic role addition from case #{moderation_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
addrole_num = addrole_num + 1
|
|
||||||
except (
|
|
||||||
discord.errors.NotFound,
|
|
||||||
discord.errors.Forbidden,
|
|
||||||
discord.errors.HTTPException,
|
|
||||||
) as e:
|
|
||||||
logger.error("Adding the role %s to user %s failed due to: \n%s", role_id, target_id, e)
|
|
||||||
continue
|
|
||||||
|
|
||||||
expiry_query = f"UPDATE `moderation_{guild.id}` SET expired = 1 WHERE (end_timestamp != 0 AND end_timestamp <= ? AND expired = 0) OR (expired = 0 AND resolved = 1);"
|
|
||||||
cursor.execute(expiry_query, (time.time(),))
|
cursor.execute(expiry_query, (time.time(),))
|
||||||
|
|
||||||
|
blacklist_query = f"SELECT target_id, moderation_id, role_id FROM moderation_{guild.id} WHERE end_timestamp != 0 AND end_timestamp <= ? AND moderation_type = 'BLACKLIST' AND expired = 0"
|
||||||
|
try:
|
||||||
|
cursor.execute(blacklist_query, (time.time(),))
|
||||||
|
result = cursor.fetchall()
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
continue
|
||||||
|
target_ids = [row[0] for row in result]
|
||||||
|
moderation_ids = [row[1] for row in result]
|
||||||
|
role_ids = [row[2] for row in result]
|
||||||
|
|
||||||
|
for target_id, moderation_id, role_id in zip(
|
||||||
|
target_ids, moderation_ids, role_ids
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
# member: discord.Member = await guild.fetch_member(target_id)
|
||||||
|
|
||||||
|
role: discord.Role = guild.get_role(role_id)
|
||||||
|
if role is None:
|
||||||
|
raise discord.errors.NotFound
|
||||||
|
except (
|
||||||
|
discord.errors.NotFound,
|
||||||
|
discord.errors.Forbidden,
|
||||||
|
discord.errors.HTTPException,
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
per_guild_completion_time = (time.time() - time_per_guild) * 1000
|
per_guild_completion_time = (time.time() - time_per_guild) * 1000
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Completed expiry loop for %s (%s) in %sms with %s users unbanned, %s roles added, and %s roles removed",
|
"Completed expiry loop for %s (%s) in %sms with %s users unbanned",
|
||||||
guild.name,
|
guild.name,
|
||||||
guild.id,
|
guild.id,
|
||||||
f"{per_guild_completion_time:.6f}",
|
f"{per_guild_completion_time:.6f}",
|
||||||
unban_num,
|
num,
|
||||||
addrole_num,
|
|
||||||
removerole_num,
|
|
||||||
)
|
)
|
||||||
global_unban_num = global_unban_num + unban_num
|
global_num = global_num + num
|
||||||
global_addrole_num = global_addrole_num + addrole_num
|
|
||||||
global_removerole_num = global_removerole_num + removerole_num
|
|
||||||
|
|
||||||
database.commit()
|
database.commit()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
@ -1766,11 +1597,9 @@ class Aurora(commands.Cog):
|
||||||
|
|
||||||
completion_time = (time.time() - current_time) * 1000
|
completion_time = (time.time() - current_time) * 1000
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Completed expiry loop in %sms with %s users unbanned, %s roles added, and %s roles removed",
|
"Completed expiry loop in %sms with %s users unbanned",
|
||||||
f"{completion_time:.6f}",
|
f"{completion_time:.6f}",
|
||||||
global_unban_num,
|
global_num,
|
||||||
global_addrole_num,
|
|
||||||
global_removerole_num,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
@ -1788,16 +1617,14 @@ class Aurora(commands.Cog):
|
||||||
@aurora_settings.command(name="overrides", aliases=["override", "user"])
|
@aurora_settings.command(name="overrides", aliases=["override", "user"])
|
||||||
async def aurora_settings_overrides(self, ctx: commands.Context):
|
async def aurora_settings_overrides(self, ctx: commands.Context):
|
||||||
"""Manage Aurora's user overriddable settings."""
|
"""Manage Aurora's user overriddable settings."""
|
||||||
msg = await ctx.send(embed=await overrides_embed(ctx))
|
await ctx.send(embed=await overrides_embed(ctx), view=Overrides(ctx))
|
||||||
await msg.edit(view=Overrides(ctx, msg, 60))
|
|
||||||
|
|
||||||
@aurora_settings.command(name="guild", aliases=["server"])
|
@aurora_settings.command(name="guild", aliases=["server"])
|
||||||
@commands.admin_or_permissions(manage_guild=True)
|
@commands.admin_or_permissions(manage_guild=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def aurora_settings_guild(self, ctx: commands.Context):
|
async def aurora_settings_guild(self, ctx: commands.Context):
|
||||||
"""Manage Aurora's guild settings."""
|
"""Manage Aurora's guild settings."""
|
||||||
msg = await ctx.send(embed=await guild_embed(ctx))
|
await ctx.send(embed=await guild_embed(ctx), view=Guild(ctx))
|
||||||
await msg.edit(view=Guild(ctx, msg, 60))
|
|
||||||
|
|
||||||
@aurora_settings.command(name="addrole", aliases=["removerole"])
|
@aurora_settings.command(name="addrole", aliases=["removerole"])
|
||||||
@commands.admin_or_permissions(manage_guild=True)
|
@commands.admin_or_permissions(manage_guild=True)
|
||||||
|
@ -1806,16 +1633,14 @@ class Aurora(commands.Cog):
|
||||||
"""Manage the addrole whitelist.
|
"""Manage the addrole whitelist.
|
||||||
|
|
||||||
Roles added to this list are also applied to `/removerole`."""
|
Roles added to this list are also applied to `/removerole`."""
|
||||||
msg = await ctx.send(embed=await addrole_embed(ctx))
|
await ctx.send(embed=await addrole_embed(ctx), view=Addrole(ctx))
|
||||||
await msg.edit(view=Addrole(ctx, msg, 60))
|
|
||||||
|
|
||||||
@aurora_settings.command(name="immunity")
|
@aurora_settings.command(name="immunity")
|
||||||
@commands.admin_or_permissions(manage_guild=True)
|
@commands.admin_or_permissions(manage_guild=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def aurora_settings_immunity(self, ctx: commands.Context):
|
async def aurora_settings_immunity(self, ctx: commands.Context):
|
||||||
"""Manage the immunity whitelist."""
|
"""Manage the immunity whitelist."""
|
||||||
msg = await ctx.send(embed=await immune_embed(ctx))
|
await ctx.send(embed=await immune_embed(ctx), view=Immune(ctx))
|
||||||
await msg.edit(view=Immune(ctx, msg, 60))
|
|
||||||
|
|
||||||
@aurora.group(autohelp=True, name="import")
|
@aurora.group(autohelp=True, name="import")
|
||||||
@commands.admin()
|
@commands.admin()
|
||||||
|
@ -1862,34 +1687,15 @@ class Aurora(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
@aurora.command(aliases=["tdc", "td", "timedeltaconvert"])
|
@aurora.command(aliases=["tdc", "td", "timedeltaconvert"])
|
||||||
async def timedelta(self, ctx: commands.Context, *, duration: str) -> None:
|
async def timedelta(self, ctx: commands.Context, *, duration: str):
|
||||||
"""Convert a string to a timedelta.
|
"""This command converts a duration to a [`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) Python object.
|
||||||
|
|
||||||
This command converts a duration to a [`timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) Python object.
|
|
||||||
You cannot convert years or months as they are not fixed units. Use `[p]aurora relativedelta` for that.
|
|
||||||
|
|
||||||
**Example usage**
|
**Example usage**
|
||||||
`[p]aurora timedelta 1 day 15hr 82 minutes 52s`
|
`[p]aurora timedelta 1 day 15hr 82 minutes 52s`
|
||||||
**Output**
|
**Output**
|
||||||
`1 day, 16:22:52`"""
|
`1 day, 16:22:52`"""
|
||||||
parsed_time = parse_timedelta(duration)
|
try:
|
||||||
if parsed_time is None:
|
parsed_time = parse(duration, as_timedelta=True, raise_exception=True)
|
||||||
|
await ctx.send(f"`{str(parsed_time)}`")
|
||||||
|
except ValueError:
|
||||||
await ctx.send(error("Please provide a convertible value!"))
|
await ctx.send(error("Please provide a convertible value!"))
|
||||||
return
|
|
||||||
await ctx.send(f"`{parsed_time}`")
|
|
||||||
|
|
||||||
@aurora.command(aliases=["rdc", "rd", "relativedeltaconvert"])
|
|
||||||
async def relativedelta(self, ctx: commands.Context, *, duration: str) -> None:
|
|
||||||
"""Convert a string to a relativedelta.
|
|
||||||
|
|
||||||
This command converts a duration to a [`relativedelta`](https://dateutil.readthedocs.io/en/stable/relativedelta.html) Python object.
|
|
||||||
|
|
||||||
**Example usage**
|
|
||||||
`[p]aurora relativedelta 3 years 1 day 15hr 82 minutes 52s`
|
|
||||||
**Output**
|
|
||||||
`relativedelta(years=+3, days=+1, hours=+15, minutes=+82, seconds=+52)`"""
|
|
||||||
parsed_time = parse_relativedelta(duration)
|
|
||||||
if parsed_time is None:
|
|
||||||
await ctx.send(error("Please provide a convertible value!"))
|
|
||||||
return
|
|
||||||
await ctx.send(f"`{parsed_time}`")
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.2 KiB |
|
@ -43,7 +43,7 @@ class ImportAuroraView(ui.View):
|
||||||
file = await self.ctx.message.attachments[0].read()
|
file = await self.ctx.message.attachments[0].read()
|
||||||
data: list[dict] = sorted(json.loads(file), key=lambda x: x["moderation_id"])
|
data: list[dict] = sorted(json.loads(file), key=lambda x: x["moderation_id"])
|
||||||
|
|
||||||
user_mod_types = ["NOTE", "WARN", "ADDROLE", "REMOVEROLE", "MUTE", "UNMUTE", "KICK", "TEMPBAN", "BAN", "UNBAN"]
|
user_mod_types = ["NOTE", "WARN", "MUTE", "UNMUTE", "KICK", "BAN", "UNBAN"]
|
||||||
|
|
||||||
channel_mod_types = ["SLOWMODE", "LOCKDOWN"]
|
channel_mod_types = ["SLOWMODE", "LOCKDOWN"]
|
||||||
|
|
||||||
|
@ -58,8 +58,6 @@ class ImportAuroraView(ui.View):
|
||||||
case["target_type"] = "USER"
|
case["target_type"] = "USER"
|
||||||
elif case["moderation_type"] in channel_mod_types:
|
elif case["moderation_type"] in channel_mod_types:
|
||||||
case["target_type"] = "CHANNEL"
|
case["target_type"] = "CHANNEL"
|
||||||
else:
|
|
||||||
case["target_type"] = "USER"
|
|
||||||
|
|
||||||
if "role_id" not in case or not case["role_id"]:
|
if "role_id" not in case or not case["role_id"]:
|
||||||
case["role_id"] = 0
|
case["role_id"] = 0
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
{
|
{
|
||||||
"author" : ["cswimr"],
|
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||||
"install_msg" : "Thank you for installing Aurora!\nMost of this cog's functionality requires enabling slash commands.\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
"install_msg" : "Thank you for installing Aurora!\nMost of this cog's functionality requires enabling slash commands.\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||||
"name" : "Aurora",
|
"name" : "Aurora",
|
||||||
"short" : "A full replacement for Red's core Mod cogs.",
|
"short" : "A full replacement for Red's core Mod cogs.",
|
||||||
"description" : "Aurora is a fully-featured moderation system. It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs. This cog stores all of its data in an SQLite database.",
|
"description" : "Aurora is a fully-featured moderation system. It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs. This cog stores all of its data in an SQLite database.",
|
||||||
"end_user_data_statement" : "This cog stores the following information:\n- User IDs of accounts who moderate users or are moderated\n- Guild IDs of guilds with the cog enabled\n- Timestamps of moderations\n- Other information relating to moderations",
|
"end_user_data_statement" : "This cog stores the following information:\n- User IDs of accounts who moderate users or are moderated\n- Guild IDs of guilds with the cog enabled\n- Timestamps of moderations\n- Other information relating to moderations",
|
||||||
|
"requirements": ["humanize", "pytimeparse2"],
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"min_bot_version": "3.5.0",
|
"min_bot_version": "3.5.0",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import error
|
from redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
|
@ -7,14 +7,9 @@ from aurora.utilities.factory import addrole_embed
|
||||||
|
|
||||||
|
|
||||||
class Addrole(ui.View):
|
class Addrole(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
async def on_timeout(self):
|
|
||||||
await self.message.edit(view=None)
|
|
||||||
|
|
||||||
@ui.select(cls=ui.RoleSelect, placeholder="Select a role", min_values=0, max_values=25)
|
@ui.select(cls=ui.RoleSelect, placeholder="Select a role", min_values=0, max_values=25)
|
||||||
async def addrole_select(self, interaction: Interaction, select: ui.RoleSelect):
|
async def addrole_select(self, interaction: Interaction, select: ui.RoleSelect):
|
||||||
|
@ -22,13 +17,12 @@ class Addrole(ui.View):
|
||||||
await interaction.response.send_message(error("You must have the manage guild permission to add roles to the addrole whitelist."), ephemeral=True)
|
await interaction.response.send_message(error("You must have the manage guild permission to add roles to the addrole whitelist."), ephemeral=True)
|
||||||
return
|
return
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
async with config.guild(self.ctx.guild).addrole_whitelist() as addrole_whitelist:
|
addrole_whitelist: list = await config.guild(self.ctx.guild).addrole_whitelist()
|
||||||
addrole_whitelist: list # type hint
|
if select.values[0].id in addrole_whitelist:
|
||||||
for value in select.values:
|
addrole_whitelist.remove(select.values[0].id)
|
||||||
if value.id in addrole_whitelist:
|
else:
|
||||||
addrole_whitelist.remove(value.id)
|
addrole_whitelist.append(select.values[0].id)
|
||||||
else:
|
await config.guild(self.ctx.guild).addrole_whitelist.set(addrole_whitelist)
|
||||||
addrole_whitelist.append(value.id)
|
|
||||||
await interaction.message.edit(embed=await addrole_embed(self.ctx))
|
await interaction.message.edit(embed=await addrole_embed(self.ctx))
|
||||||
|
|
||||||
@ui.button(label="Clear", style=ButtonStyle.red, row=1)
|
@ui.button(label="Clear", style=ButtonStyle.red, row=1)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from aurora.utilities.config import config
|
||||||
|
@ -7,14 +7,9 @@ from aurora.utilities.utils import create_pagesize_options
|
||||||
|
|
||||||
|
|
||||||
class Guild(ui.View):
|
class Guild(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
async def on_timeout(self):
|
|
||||||
await self.message.edit(view=None)
|
|
||||||
|
|
||||||
@ui.button(label="Show Moderator", style=ButtonStyle.green, row=0)
|
@ui.button(label="Show Moderator", style=ButtonStyle.green, row=0)
|
||||||
async def show_moderator(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
async def show_moderator(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
||||||
|
@ -22,7 +17,7 @@ class Guild(ui.View):
|
||||||
await interaction.response.send_message("You must have the manage guild permission to change this setting.", ephemeral=True)
|
await interaction.response.send_message("You must have the manage guild permission to change this setting.", ephemeral=True)
|
||||||
return
|
return
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
current_setting = await config.guild(interaction.guild).show_moderator()
|
current_setting = await config.guild(interaction.guild).show_moderator
|
||||||
await config.guild(interaction.guild).show_moderator.set(not current_setting)
|
await config.guild(interaction.guild).show_moderator.set(not current_setting)
|
||||||
await interaction.message.edit(embed=await guild_embed(self.ctx))
|
await interaction.message.edit(embed=await guild_embed(self.ctx))
|
||||||
|
|
||||||
|
@ -36,16 +31,6 @@ class Guild(ui.View):
|
||||||
await config.guild(interaction.guild).use_discord_permissions.set(not current_setting)
|
await config.guild(interaction.guild).use_discord_permissions.set(not current_setting)
|
||||||
await interaction.message.edit(embed=await guild_embed(self.ctx))
|
await interaction.message.edit(embed=await guild_embed(self.ctx))
|
||||||
|
|
||||||
@ui.button(label="Respect Hierarchy", style=ButtonStyle.green, row=0)
|
|
||||||
async def respect_heirarchy(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
|
||||||
if not interaction.user.guild_permissions.manage_guild and not interaction.user.guild_permissions.administrator:
|
|
||||||
await interaction.response.send_message("You must have the manage guild permission to change this setting.", ephemeral=True)
|
|
||||||
return
|
|
||||||
await interaction.response.defer()
|
|
||||||
current_setting = await config.guild(interaction.guild).respect_hierarchy()
|
|
||||||
await config.guild(interaction.guild).respect_hierarchy.set(not current_setting)
|
|
||||||
await interaction.message.edit(embed=await guild_embed(self.ctx))
|
|
||||||
|
|
||||||
@ui.button(label="Ignore Modlog", style=ButtonStyle.green, row=0)
|
@ui.button(label="Ignore Modlog", style=ButtonStyle.green, row=0)
|
||||||
async def ignore_modlog(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
async def ignore_modlog(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
||||||
if not interaction.user.guild_permissions.manage_guild and not interaction.user.guild_permissions.administrator:
|
if not interaction.user.guild_permissions.manage_guild and not interaction.user.guild_permissions.administrator:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import error
|
from redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
|
@ -7,14 +7,9 @@ from aurora.utilities.factory import immune_embed
|
||||||
|
|
||||||
|
|
||||||
class Immune(ui.View):
|
class Immune(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
async def on_timeout(self):
|
|
||||||
await self.message.edit(view=None)
|
|
||||||
|
|
||||||
@ui.select(cls=ui.RoleSelect, placeholder="Select a role", min_values=0, max_values=25)
|
@ui.select(cls=ui.RoleSelect, placeholder="Select a role", min_values=0, max_values=25)
|
||||||
async def immune_select(self, interaction: Interaction, select: ui.RoleSelect):
|
async def immune_select(self, interaction: Interaction, select: ui.RoleSelect):
|
||||||
|
@ -22,13 +17,13 @@ class Immune(ui.View):
|
||||||
await interaction.response.send_message(error("You must have the manage guild permission to add immune roles."), ephemeral=True)
|
await interaction.response.send_message(error("You must have the manage guild permission to add immune roles."), ephemeral=True)
|
||||||
return
|
return
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
async with config.guild(self.ctx.guild).immune_roles() as immune_roles:
|
immune_roles: list = await config.guild(self.ctx.guild).immune_roles()
|
||||||
immune_roles: list # type hint
|
for role in select.values:
|
||||||
for value in select.values:
|
if role.id in immune_roles:
|
||||||
if value.id in immune_roles:
|
immune_roles.remove(role.id)
|
||||||
immune_roles.remove(value.id)
|
else:
|
||||||
else:
|
immune_roles.append(role.id)
|
||||||
immune_roles.append(value.id)
|
await config.guild(self.ctx.guild).immune_roles.set(immune_roles)
|
||||||
await interaction.message.edit(embed=await immune_embed(self.ctx))
|
await interaction.message.edit(embed=await immune_embed(self.ctx))
|
||||||
|
|
||||||
@ui.button(label="Clear", style=ButtonStyle.red, row=1)
|
@ui.button(label="Clear", style=ButtonStyle.red, row=1)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from aurora.utilities.config import config
|
||||||
|
@ -7,14 +7,9 @@ from aurora.utilities.utils import create_pagesize_options
|
||||||
|
|
||||||
|
|
||||||
class Overrides(ui.View):
|
class Overrides(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
async def on_timeout(self):
|
|
||||||
await self.message.edit(view=None)
|
|
||||||
|
|
||||||
@ui.button(label="Auto Evidence Format", style=ButtonStyle.green, row=0)
|
@ui.button(label="Auto Evidence Format", style=ButtonStyle.green, row=0)
|
||||||
async def auto_evidenceformat(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
async def auto_evidenceformat(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
|
||||||
|
|
|
@ -7,7 +7,6 @@ def register_config(config_obj: Config):
|
||||||
config_obj.register_guild(
|
config_obj.register_guild(
|
||||||
show_moderator=True,
|
show_moderator=True,
|
||||||
use_discord_permissions=True,
|
use_discord_permissions=True,
|
||||||
respect_hierarchy=True,
|
|
||||||
ignore_modlog=True,
|
ignore_modlog=True,
|
||||||
ignore_other_bots=True,
|
ignore_other_bots=True,
|
||||||
dm_users=True,
|
dm_users=True,
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from discord import Color, Embed, Guild, Interaction, InteractionMessage, Member, Role, User
|
import humanize
|
||||||
|
from discord import Color, Embed, Guild, Member, Message, Role, User
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning
|
from redbot.core.utils.chat_formatting import bold, box, error, warning
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from aurora.utilities.config import config
|
||||||
from aurora.utilities.utils import fetch_channel_dict, fetch_user_dict, get_bool_emoji, get_next_case_number, get_pagesize_str
|
from aurora.utilities.utils import (fetch_channel_dict, fetch_user_dict,
|
||||||
|
get_bool_emoji, get_next_case_number,
|
||||||
|
get_pagesize_str)
|
||||||
|
|
||||||
|
|
||||||
async def message_factory(
|
async def message_factory(
|
||||||
|
@ -17,7 +20,7 @@ async def message_factory(
|
||||||
moderation_type: str,
|
moderation_type: str,
|
||||||
moderator: Union[Member, User] = None,
|
moderator: Union[Member, User] = None,
|
||||||
duration: timedelta = None,
|
duration: timedelta = None,
|
||||||
response: InteractionMessage = None,
|
response: Message = None,
|
||||||
role: Role = None,
|
role: Role = None,
|
||||||
) -> Embed:
|
) -> Embed:
|
||||||
"""This function creates a message from set parameters, meant for contacting the moderated user.
|
"""This function creates a message from set parameters, meant for contacting the moderated user.
|
||||||
|
@ -29,10 +32,9 @@ async def message_factory(
|
||||||
moderation_type (str): The type of moderation.
|
moderation_type (str): The type of moderation.
|
||||||
moderator (Union[Member, User], optional): The moderator who performed the moderation. Defaults to None.
|
moderator (Union[Member, User], optional): The moderator who performed the moderation. Defaults to None.
|
||||||
duration (timedelta, optional): The duration of the moderation. Defaults to None.
|
duration (timedelta, optional): The duration of the moderation. Defaults to None.
|
||||||
response (InteractionMessage, optional): The response message. Defaults to None.
|
response (Message, optional): The response message. Defaults to None.
|
||||||
role (Role, optional): The role that was added or removed. Defaults to None.
|
role (Role, optional): The role that was added or removed. Defaults to None.
|
||||||
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
embed: The message embed.
|
embed: The message embed.
|
||||||
"""
|
"""
|
||||||
|
@ -46,10 +48,8 @@ async def message_factory(
|
||||||
else:
|
else:
|
||||||
guild_name = guild.name
|
guild_name = guild.name
|
||||||
|
|
||||||
title = moderation_type
|
|
||||||
|
|
||||||
if moderation_type in ["tempbanned", "muted"] and duration:
|
if moderation_type in ["tempbanned", "muted"] and duration:
|
||||||
embed_duration = f" for {humanize_timedelta(timedelta=duration)}"
|
embed_duration = f" for {humanize.precisedelta(duration)}"
|
||||||
else:
|
else:
|
||||||
embed_duration = ""
|
embed_duration = ""
|
||||||
|
|
||||||
|
@ -57,17 +57,13 @@ async def message_factory(
|
||||||
embed_desc = "received a"
|
embed_desc = "received a"
|
||||||
elif moderation_type == "addrole":
|
elif moderation_type == "addrole":
|
||||||
embed_desc = f"received the {role.name} role"
|
embed_desc = f"received the {role.name} role"
|
||||||
title = "Role Added"
|
|
||||||
moderation_type = ""
|
|
||||||
elif moderation_type == "removerole":
|
elif moderation_type == "removerole":
|
||||||
embed_desc = f"lost the {role.name} role"
|
embed_desc = f"lost the {role.name} role"
|
||||||
title = "Role Removed"
|
|
||||||
moderation_type = ""
|
|
||||||
else:
|
else:
|
||||||
embed_desc = "been"
|
embed_desc = "been"
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=str.title(title),
|
title=str.title(moderation_type),
|
||||||
description=f"You have {embed_desc} {moderation_type}{embed_duration} in {guild_name}.",
|
description=f"You have {embed_desc} {moderation_type}{embed_duration} in {guild_name}.",
|
||||||
color=color,
|
color=color,
|
||||||
timestamp=datetime.now(),
|
timestamp=datetime.now(),
|
||||||
|
@ -87,14 +83,14 @@ async def message_factory(
|
||||||
|
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=f"Case #{await get_next_case_number(guild.id):,}",
|
text=f"Case #{await get_next_case_number(guild.id):,}",
|
||||||
icon_url="attachment://arrow.png",
|
icon_url="https://cdn.discordapp.com/attachments/1070822161389994054/1159469476773904414/arrow-right-circle-icon-512x512-2p1e2aaw.png?ex=65312319&is=651eae19&hm=3cebdd28e805c13a79ec48ef87c32ca532ffa6b9ede2e48d0cf8e5e81f3a6818&",
|
||||||
)
|
)
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def log_factory(
|
async def log_factory(
|
||||||
interaction: Interaction, case_dict: dict, resolved: bool = False
|
ctx: commands.Context, case_dict: dict, resolved: bool = False
|
||||||
) -> Embed:
|
) -> Embed:
|
||||||
"""This function creates a log embed from set parameters, meant for moderation logging.
|
"""This function creates a log embed from set parameters, meant for moderation logging.
|
||||||
|
|
||||||
|
@ -105,20 +101,20 @@ async def log_factory(
|
||||||
"""
|
"""
|
||||||
if resolved:
|
if resolved:
|
||||||
if case_dict["target_type"] == "USER":
|
if case_dict["target_type"] == "USER":
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
target_user = await fetch_user_dict(ctx.bot, case_dict["target_id"])
|
||||||
target_name = (
|
target_name = (
|
||||||
f"`{target_user['name']}`"
|
f"`{target_user['name']}`"
|
||||||
if target_user["discriminator"] == "0"
|
if target_user["discriminator"] == "0"
|
||||||
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
||||||
)
|
)
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
elif case_dict["target_type"] == "CHANNEL":
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
target_user = await fetch_channel_dict(ctx.guild, case_dict["target_id"])
|
||||||
if target_user["mention"]:
|
if target_user["mention"]:
|
||||||
target_name = f"{target_user['mention']}"
|
target_name = f"{target_user['mention']}"
|
||||||
else:
|
else:
|
||||||
target_name = f"`{target_user['name']}`"
|
target_name = f"`{target_user['name']}`"
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
moderator_user = await fetch_user_dict(ctx.bot, case_dict["moderator_id"])
|
||||||
moderator_name = (
|
moderator_name = (
|
||||||
f"`{moderator_user['name']}`"
|
f"`{moderator_user['name']}`"
|
||||||
if moderator_user["discriminator"] == "0"
|
if moderator_user["discriminator"] == "0"
|
||||||
|
@ -127,7 +123,7 @@ async def log_factory(
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,} Resolved",
|
title=f"📕 Case #{case_dict['moderation_id']:,} Resolved",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await ctx.client.get_embed_color(ctx.channel),
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
||||||
|
@ -143,9 +139,9 @@ async def log_factory(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
duration_embed = (
|
duration_embed = (
|
||||||
f"{humanize_timedelta(timedelta=td)} | <t:{case_dict['end_timestamp']}:R>"
|
f"{humanize.precisedelta(td)} | <t:{case_dict['end_timestamp']}:R>"
|
||||||
if case_dict["expired"] == "0"
|
if case_dict["expired"] == "0"
|
||||||
else str(humanize_timedelta(timedelta=td))
|
else str(humanize.precisedelta(td))
|
||||||
)
|
)
|
||||||
embed.description = (
|
embed.description = (
|
||||||
embed.description
|
embed.description
|
||||||
|
@ -154,7 +150,7 @@ async def log_factory(
|
||||||
|
|
||||||
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
||||||
|
|
||||||
resolved_user = await fetch_user_dict(interaction.client, case_dict["resolved_by"])
|
resolved_user = await fetch_user_dict(ctx.bot, case_dict["resolved_by"])
|
||||||
resolved_name = (
|
resolved_name = (
|
||||||
resolved_user["name"]
|
resolved_user["name"]
|
||||||
if resolved_user["discriminator"] == "0"
|
if resolved_user["discriminator"] == "0"
|
||||||
|
@ -168,20 +164,20 @@ async def log_factory(
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if case_dict["target_type"] == "USER":
|
if case_dict["target_type"] == "USER":
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
target_user = await fetch_user_dict(ctx.bot, case_dict["target_id"])
|
||||||
target_name = (
|
target_name = (
|
||||||
f"`{target_user['name']}`"
|
f"`{target_user['name']}`"
|
||||||
if target_user["discriminator"] == "0"
|
if target_user["discriminator"] == "0"
|
||||||
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
||||||
)
|
)
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
elif case_dict["target_type"] == "CHANNEL":
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
target_user = await fetch_channel_dict(ctx.guild, case_dict["target_id"])
|
||||||
if target_user["mention"]:
|
if target_user["mention"]:
|
||||||
target_name = target_user["mention"]
|
target_name = target_user["mention"]
|
||||||
else:
|
else:
|
||||||
target_name = f"`{target_user['name']}`"
|
target_name = f"`{target_user['name']}`"
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
moderator_user = await fetch_user_dict(ctx.bot, case_dict["moderator_id"])
|
||||||
moderator_name = (
|
moderator_name = (
|
||||||
f"`{moderator_user['name']}`"
|
f"`{moderator_user['name']}`"
|
||||||
if moderator_user["discriminator"] == "0"
|
if moderator_user["discriminator"] == "0"
|
||||||
|
@ -190,7 +186,7 @@ async def log_factory(
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,}",
|
title=f"📕 Case #{case_dict['moderation_id']:,}",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await ctx.bot.get_embed_color(ctx.channel),
|
||||||
)
|
)
|
||||||
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
||||||
|
|
||||||
|
@ -206,35 +202,35 @@ async def log_factory(
|
||||||
)
|
)
|
||||||
embed.description = (
|
embed.description = (
|
||||||
embed.description
|
embed.description
|
||||||
+ f"\n**Duration:** {humanize_timedelta(timedelta=td)} | <t:{case_dict['end_timestamp']}:R>"
|
+ f"\n**Duration:** {humanize.precisedelta(td)} | <t:{case_dict['end_timestamp']}:R>"
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
async def case_factory(ctx: commands.Context, case_dict: dict) -> Embed:
|
||||||
"""This function creates a case embed from set parameters.
|
"""This function creates a case embed from set parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (Interaction): The interaction object.
|
ctx (commands.Context): The context object.
|
||||||
case_dict (dict): The case dictionary.
|
case_dict (dict): The case dictionary.
|
||||||
"""
|
"""
|
||||||
if case_dict["target_type"] == "USER":
|
if case_dict["target_type"] == "USER":
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
target_user = await fetch_user_dict(ctx.bot, case_dict["target_id"])
|
||||||
target_name = (
|
target_name = (
|
||||||
f"`{target_user['name']}`"
|
f"`{target_user['name']}`"
|
||||||
if target_user["discriminator"] == "0"
|
if target_user["discriminator"] == "0"
|
||||||
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
||||||
)
|
)
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
elif case_dict["target_type"] == "CHANNEL":
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
target_user = await fetch_channel_dict(ctx.guild, case_dict["target_id"])
|
||||||
if target_user["mention"]:
|
if target_user["mention"]:
|
||||||
target_name = f"{target_user['mention']}"
|
target_name = f"{target_user['mention']}"
|
||||||
else:
|
else:
|
||||||
target_name = f"`{target_user['name']}`"
|
target_name = f"`{target_user['name']}`"
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
moderator_user = await fetch_user_dict(ctx.bot, case_dict["moderator_id"])
|
||||||
moderator_name = (
|
moderator_name = (
|
||||||
f"`{moderator_user['name']}`"
|
f"`{moderator_user['name']}`"
|
||||||
if moderator_user["discriminator"] == "0"
|
if moderator_user["discriminator"] == "0"
|
||||||
|
@ -243,7 +239,7 @@ async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,}",
|
title=f"📕 Case #{case_dict['moderation_id']:,}",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await ctx.bot.get_embed_color(ctx.channel),
|
||||||
)
|
)
|
||||||
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Resolved:** {bool(case_dict['resolved'])}\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Resolved:** {bool(case_dict['resolved'])}\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
||||||
|
|
||||||
|
@ -257,9 +253,9 @@ async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
duration_embed = (
|
duration_embed = (
|
||||||
f"{humanize_timedelta(timedelta=td)} | <t:{case_dict['end_timestamp']}:R>"
|
f"{humanize.precisedelta(td)} | <t:{case_dict['end_timestamp']}:R>"
|
||||||
if bool(case_dict["expired"]) is False
|
if bool(case_dict["expired"]) is False
|
||||||
else str(humanize_timedelta(timedelta=td))
|
else str(humanize.precisedelta(td))
|
||||||
)
|
)
|
||||||
embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}"
|
embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}"
|
||||||
|
|
||||||
|
@ -269,9 +265,6 @@ async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
else "\n**Changes:** 0"
|
else "\n**Changes:** 0"
|
||||||
)
|
)
|
||||||
|
|
||||||
if case_dict["role_id"]:
|
|
||||||
embed.description += f"\n**Role:** <@&{case_dict['role_id']}>"
|
|
||||||
|
|
||||||
if case_dict["metadata"]:
|
if case_dict["metadata"]:
|
||||||
if case_dict["metadata"]["imported_from"]:
|
if case_dict["metadata"]["imported_from"]:
|
||||||
embed.description += (
|
embed.description += (
|
||||||
|
@ -281,7 +274,7 @@ async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
||||||
|
|
||||||
if case_dict["resolved"] == 1:
|
if case_dict["resolved"] == 1:
|
||||||
resolved_user = await fetch_user_dict(interaction.client, case_dict["resolved_by"])
|
resolved_user = await fetch_user_dict(ctx.bot, case_dict["resolved_by"])
|
||||||
resolved_name = (
|
resolved_name = (
|
||||||
f"`{resolved_user['name']}`"
|
f"`{resolved_user['name']}`"
|
||||||
if resolved_user["discriminator"] == "0"
|
if resolved_user["discriminator"] == "0"
|
||||||
|
@ -296,16 +289,16 @@ async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
async def changes_factory(ctx: commands.Context, case_dict: dict) -> Embed:
|
||||||
"""This function creates a changes embed from set parameters.
|
"""This function creates a changes embed from set parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (Interaction): The interaction object.
|
ctx (commands.Context): The context object.
|
||||||
case_dict (dict): The case dictionary.
|
case_dict (dict): The case dictionary.
|
||||||
"""
|
"""
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,} Changes",
|
title=f"📕 Case #{case_dict['moderation_id']:,} Changes",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await ctx.bot.get_embed_color(ctx.channel),
|
||||||
)
|
)
|
||||||
|
|
||||||
memory_dict = {}
|
memory_dict = {}
|
||||||
|
@ -314,7 +307,7 @@ async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
for change in case_dict["changes"]:
|
for change in case_dict["changes"]:
|
||||||
if change["user_id"] not in memory_dict:
|
if change["user_id"] not in memory_dict:
|
||||||
memory_dict[str(change["user_id"])] = await fetch_user_dict(
|
memory_dict[str(change["user_id"])] = await fetch_user_dict(
|
||||||
interaction.client, change["user_id"]
|
ctx.bot, change["user_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
user = memory_dict[str(change["user_id"])]
|
user = memory_dict[str(change["user_id"])]
|
||||||
|
@ -353,7 +346,7 @@ async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> str:
|
async def evidenceformat_factory(ctx: commands.Context, case_dict: dict) -> str:
|
||||||
"""This function creates a codeblock in evidence format from set parameters.
|
"""This function creates a codeblock in evidence format from set parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -361,7 +354,7 @@ async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> s
|
||||||
case_dict (dict): The case dictionary.
|
case_dict (dict): The case dictionary.
|
||||||
"""
|
"""
|
||||||
if case_dict["target_type"] == "USER":
|
if case_dict["target_type"] == "USER":
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
target_user = await fetch_user_dict(ctx.bot, case_dict["target_id"])
|
||||||
target_name = (
|
target_name = (
|
||||||
target_user["name"]
|
target_user["name"]
|
||||||
if target_user["discriminator"] == "0"
|
if target_user["discriminator"] == "0"
|
||||||
|
@ -369,10 +362,10 @@ async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> s
|
||||||
)
|
)
|
||||||
|
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
elif case_dict["target_type"] == "CHANNEL":
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
target_user = await fetch_channel_dict(ctx.guild, case_dict["target_id"])
|
||||||
target_name = target_user["name"]
|
target_name = target_user["name"]
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
moderator_user = await fetch_user_dict(ctx.bot, case_dict["moderator_id"])
|
||||||
moderator_name = (
|
moderator_name = (
|
||||||
moderator_user["name"]
|
moderator_user["name"]
|
||||||
if moderator_user["discriminator"] == "0"
|
if moderator_user["discriminator"] == "0"
|
||||||
|
@ -381,14 +374,10 @@ async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> s
|
||||||
|
|
||||||
content = f"Case: {case_dict['moderation_id']:,} ({str.title(case_dict['moderation_type'])})\nTarget: {target_name} ({target_user['id']})\nModerator: {moderator_name} ({moderator_user['id']})"
|
content = f"Case: {case_dict['moderation_id']:,} ({str.title(case_dict['moderation_type'])})\nTarget: {target_name} ({target_user['id']})\nModerator: {moderator_name} ({moderator_user['id']})"
|
||||||
|
|
||||||
if case_dict["role_id"] != "0":
|
|
||||||
role = interaction.guild.get_role(int(case_dict["role_id"]))
|
|
||||||
content += "\nRole: " + (role.name if role is not None else case_dict["role_id"])
|
|
||||||
|
|
||||||
if case_dict["duration"] != "NULL":
|
if case_dict["duration"] != "NULL":
|
||||||
hours, minutes, seconds = map(int, case_dict["duration"].split(":"))
|
hours, minutes, seconds = map(int, case_dict["duration"].split(":"))
|
||||||
td = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
td = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
||||||
content += f"\nDuration: {humanize_timedelta(timedelta=td)}"
|
content += f"\nDuration: {humanize.precisedelta(td)}"
|
||||||
|
|
||||||
content += f"\nReason: {case_dict['reason']}"
|
content += f"\nReason: {case_dict['reason']}"
|
||||||
|
|
||||||
|
@ -464,7 +453,6 @@ async def guild_embed(ctx: commands.Context) -> Embed:
|
||||||
ctx.guild
|
ctx.guild
|
||||||
).history_inline_pagesize(),
|
).history_inline_pagesize(),
|
||||||
"auto_evidenceformat": await config.guild(ctx.guild).auto_evidenceformat(),
|
"auto_evidenceformat": await config.guild(ctx.guild).auto_evidenceformat(),
|
||||||
"respect_hierarchy": await config.guild(ctx.guild).respect_hierarchy(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channel = ctx.guild.get_channel(guild_settings["log_channel"])
|
channel = ctx.guild.get_channel(guild_settings["log_channel"])
|
||||||
|
@ -481,9 +469,6 @@ async def guild_embed(ctx: commands.Context) -> Embed:
|
||||||
+ bold("Use Discord Permissions: ")
|
+ bold("Use Discord Permissions: ")
|
||||||
+ get_bool_emoji(guild_settings["use_discord_permissions"]),
|
+ get_bool_emoji(guild_settings["use_discord_permissions"]),
|
||||||
"- "
|
"- "
|
||||||
+ bold("Respect Hierarchy: ")
|
|
||||||
+ get_bool_emoji(guild_settings["respect_hierarchy"]),
|
|
||||||
"- "
|
|
||||||
+ bold("Ignore Modlog: ")
|
+ bold("Ignore Modlog: ")
|
||||||
+ get_bool_emoji(guild_settings["ignore_modlog"]),
|
+ get_bool_emoji(guild_settings["ignore_modlog"]),
|
||||||
"- "
|
"- "
|
||||||
|
@ -523,29 +508,15 @@ async def guild_embed(ctx: commands.Context) -> Embed:
|
||||||
async def addrole_embed(ctx: commands.Context) -> Embed:
|
async def addrole_embed(ctx: commands.Context) -> Embed:
|
||||||
"""Generates a configuration menu field value for a guild's addrole whitelist."""
|
"""Generates a configuration menu field value for a guild's addrole whitelist."""
|
||||||
|
|
||||||
roles = []
|
whitelist = await config.guild(ctx.guild).addrole_whitelist()
|
||||||
async with config.guild(ctx.guild).addrole_whitelist() as whitelist:
|
if whitelist:
|
||||||
for role in whitelist:
|
whitelist = [
|
||||||
evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)")
|
ctx.guild.get_role(role).mention or error(f"`{role}` (Not Found)")
|
||||||
if isinstance(evalulated_role, Role):
|
for role in whitelist
|
||||||
roles.append({
|
]
|
||||||
"id": evalulated_role.id,
|
whitelist = "\n".join(whitelist)
|
||||||
"mention": evalulated_role.mention,
|
|
||||||
"position": evalulated_role.position
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
roles.append({
|
|
||||||
"id": role,
|
|
||||||
"mention": error(f"`{role}` (Not Found)"),
|
|
||||||
"position": 0
|
|
||||||
})
|
|
||||||
|
|
||||||
if roles:
|
|
||||||
roles = sorted(roles, key=lambda x: x["position"], reverse=True)
|
|
||||||
roles = [role["mention"] for role in roles]
|
|
||||||
whitelist_str = "\n".join(roles)
|
|
||||||
else:
|
else:
|
||||||
whitelist_str = warning("No roles are on the addrole whitelist!")
|
whitelist = warning("No roles are on the addrole whitelist!")
|
||||||
|
|
||||||
e = await _config(ctx)
|
e = await _config(ctx)
|
||||||
e.title += ": Addrole Whitelist"
|
e.title += ": Addrole Whitelist"
|
||||||
|
@ -553,8 +524,8 @@ async def addrole_embed(ctx: commands.Context) -> Embed:
|
||||||
"Use the select menu below to manage this guild's addrole whitelist."
|
"Use the select menu below to manage this guild's addrole whitelist."
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(whitelist_str) > 4000 and len(whitelist_str) < 5000:
|
if len(whitelist) > 4000 and len(whitelist) < 5000:
|
||||||
lines = whitelist_str.split("\n")
|
lines = whitelist.split("\n")
|
||||||
chunks = []
|
chunks = []
|
||||||
chunk = ""
|
chunk = ""
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
@ -568,35 +539,21 @@ async def addrole_embed(ctx: commands.Context) -> Embed:
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
e.add_field(name="", value=chunk)
|
e.add_field(name="", value=chunk)
|
||||||
else:
|
else:
|
||||||
e.description += "\n\n" + whitelist_str
|
e.description += "\n\n" + whitelist
|
||||||
|
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
async def immune_embed(ctx: commands.Context) -> Embed:
|
async def immune_embed(ctx: commands.Context) -> Embed:
|
||||||
"""Generates a configuration menu embed for a guild's immune roles."""
|
"""Generates a configuration menu field value for a guild's immune roles."""
|
||||||
|
|
||||||
roles = []
|
immune_roles = await config.guild(ctx.guild).immune_roles()
|
||||||
async with config.guild(ctx.guild).immune_roles() as immune_roles:
|
if immune_roles:
|
||||||
for role in immune_roles:
|
immune_str = [
|
||||||
evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)")
|
ctx.guild.get_role(role).mention or error(f"`{role}` (Not Found)")
|
||||||
if isinstance(evalulated_role, Role):
|
for role in immune_roles
|
||||||
roles.append({
|
]
|
||||||
"id": evalulated_role.id,
|
immune_str = "\n".join(immune_str)
|
||||||
"mention": evalulated_role.mention,
|
|
||||||
"position": evalulated_role.position
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
roles.append({
|
|
||||||
"id": role,
|
|
||||||
"mention": error(f"`{role}` (Not Found)"),
|
|
||||||
"position": 0
|
|
||||||
})
|
|
||||||
|
|
||||||
if roles:
|
|
||||||
roles = sorted(roles, key=lambda x: x["position"], reverse=True)
|
|
||||||
roles = [role["mention"] for role in roles]
|
|
||||||
immune_str = "\n".join(roles)
|
|
||||||
else:
|
else:
|
||||||
immune_str = warning("No roles are set as immune roles!")
|
immune_str = warning("No roles are set as immune roles!")
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
logger = getLogger("red.SeaCogs.Aurora")
|
logger = getLogger("red.seacogs.aurora")
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
from datetime import timedelta as td
|
from datetime import timedelta as td
|
||||||
from typing import Optional, Union
|
from typing import Union
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta as rd
|
from discord import Guild, Interaction, Member, SelectOption, User
|
||||||
from discord import File, Guild, Interaction, Member, SelectOption, User
|
|
||||||
from discord.errors import Forbidden, NotFound
|
from discord.errors import Forbidden, NotFound
|
||||||
from redbot.core import commands, data_manager
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import error
|
from redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
from .config import config
|
from .config import config
|
||||||
|
@ -42,11 +40,11 @@ def check_permissions(
|
||||||
|
|
||||||
|
|
||||||
async def check_moddable(
|
async def check_moddable(
|
||||||
target: Union[User, Member], interaction: Interaction, permissions: list
|
target: Union[User, Member], ctx: Union[commands.Context, Interaction], permissions: list
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Checks if a moderator can moderate a target."""
|
"""Checks if a moderator can moderate a target."""
|
||||||
if check_permissions(interaction.client.user, permissions, guild=interaction.guild):
|
if check_permissions(ctx.bot.user, permissions, guild=ctx.guild):
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
error(
|
error(
|
||||||
f"I do not have the `{permissions}` permission, required for this action."
|
f"I do not have the `{permissions}` permission, required for this action."
|
||||||
),
|
),
|
||||||
|
@ -54,9 +52,9 @@ async def check_moddable(
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if await config.guild(interaction.guild).use_discord_permissions() is True:
|
if await config.guild(ctx.guild).use_discord_permissions() is True:
|
||||||
if check_permissions(interaction.user, permissions, guild=interaction.guild):
|
if check_permissions(ctx.author, permissions, guild=ctx.guild):
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
error(
|
error(
|
||||||
f"You do not have the `{permissions}` permission, required for this action."
|
f"You do not have the `{permissions}` permission, required for this action."
|
||||||
),
|
),
|
||||||
|
@ -64,21 +62,21 @@ async def check_moddable(
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if interaction.user.id == target.id:
|
if ctx.author.id == target.id:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
content="You cannot moderate yourself!", ephemeral=True
|
content="You cannot moderate yourself!", ephemeral=True
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if target.bot:
|
if target.bot:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
content="You cannot moderate bots!", ephemeral=True
|
content="You cannot moderate bots!", ephemeral=True
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if isinstance(target, Member):
|
if isinstance(target, Member):
|
||||||
if interaction.user.top_role <= target.top_role and await config.guild(interaction.guild).respect_hierarchy() is True:
|
if ctx.author.top_role <= target.top_role:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
content=error(
|
content=error(
|
||||||
"You cannot moderate members with a higher role than you!"
|
"You cannot moderate members with a higher role than you!"
|
||||||
),
|
),
|
||||||
|
@ -87,10 +85,10 @@ async def check_moddable(
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if (
|
if (
|
||||||
interaction.guild.get_member(interaction.client.user.id).top_role
|
ctx.guild.get_member(ctx.bot.user.id).top_role
|
||||||
<= target.top_role
|
<= target.top_role
|
||||||
):
|
):
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
content=error(
|
content=error(
|
||||||
"You cannot moderate members with a role higher than the bot!"
|
"You cannot moderate members with a role higher than the bot!"
|
||||||
),
|
),
|
||||||
|
@ -102,7 +100,7 @@ async def check_moddable(
|
||||||
|
|
||||||
for role in target.roles:
|
for role in target.roles:
|
||||||
if role.id in immune_roles:
|
if role.id in immune_roles:
|
||||||
await interaction.response.send_message(
|
await ctx.send(
|
||||||
content=error("You cannot moderate members with an immune role!"),
|
content=error("You cannot moderate members with an immune role!"),
|
||||||
ephemeral=True,
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
|
@ -205,19 +203,19 @@ async def fetch_role_dict(guild: Guild, role_id: int) -> dict:
|
||||||
return role_dict
|
return role_dict
|
||||||
|
|
||||||
|
|
||||||
async def log(interaction: Interaction, moderation_id: int, resolved: bool = False) -> None:
|
async def log(ctx: Union[commands.Context, Interaction], moderation_id: int, resolved: bool = False) -> None:
|
||||||
"""This function sends a message to the guild's configured logging channel when an infraction takes place."""
|
"""This function sends a message to the guild's configured logging channel when an infraction takes place."""
|
||||||
from .database import fetch_case
|
from .database import fetch_case
|
||||||
from .factory import log_factory
|
from .factory import log_factory
|
||||||
|
|
||||||
logging_channel_id = await config.guild(interaction.guild).log_channel()
|
logging_channel_id = await config.guild(ctx.guild).log_channel()
|
||||||
if logging_channel_id != " ":
|
if logging_channel_id != " ":
|
||||||
logging_channel = interaction.guild.get_channel(logging_channel_id)
|
logging_channel = ctx.guild.get_channel(logging_channel_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
case = await fetch_case(moderation_id, ctx.guild.id)
|
||||||
if case:
|
if case:
|
||||||
embed = await log_factory(
|
embed = await log_factory(
|
||||||
interaction=interaction, case_dict=case, resolved=resolved
|
ctx=ctx, case_dict=case, resolved=resolved
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await logging_channel.send(embed=embed)
|
await logging_channel.send(embed=embed)
|
||||||
|
@ -225,20 +223,20 @@ async def log(interaction: Interaction, moderation_id: int, resolved: bool = Fal
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
async def send_evidenceformat(interaction: Interaction, case_dict: dict) -> None:
|
async def send_evidenceformat(ctx: commands.Context, case_dict: dict) -> None:
|
||||||
"""This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel."""
|
"""This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel."""
|
||||||
from .factory import evidenceformat_factory
|
from .factory import evidenceformat_factory
|
||||||
|
|
||||||
send_evidence_bool = (
|
send_evidence_bool = (
|
||||||
await config.user(interaction.user).auto_evidenceformat()
|
await config.user(ctx.author).auto_evidenceformat()
|
||||||
or await config.guild(interaction.guild).auto_evidenceformat()
|
or await config.guild(ctx.guild).auto_evidenceformat()
|
||||||
or False
|
or False
|
||||||
)
|
)
|
||||||
if send_evidence_bool is False:
|
if send_evidence_bool is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
content = await evidenceformat_factory(interaction=interaction, case_dict=case_dict)
|
content = await evidenceformat_factory(ctx=ctx, case_dict=case_dict)
|
||||||
await interaction.followup.send(content=content, ephemeral=True)
|
await ctx.send(content=content, ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
def convert_timedelta_to_str(timedelta: td) -> str:
|
def convert_timedelta_to_str(timedelta: td) -> str:
|
||||||
|
@ -250,7 +248,7 @@ def convert_timedelta_to_str(timedelta: td) -> str:
|
||||||
return f"{hours}:{minutes}:{seconds}"
|
return f"{hours}:{minutes}:{seconds}"
|
||||||
|
|
||||||
|
|
||||||
def get_bool_emoji(value: Optional[bool]) -> str:
|
def get_bool_emoji(value: bool) -> str:
|
||||||
"""Returns a unicode emoji based on a boolean value."""
|
"""Returns a unicode emoji based on a boolean value."""
|
||||||
if value is True:
|
if value is True:
|
||||||
return "\N{WHITE HEAVY CHECK MARK}"
|
return "\N{WHITE HEAVY CHECK MARK}"
|
||||||
|
@ -285,14 +283,3 @@ def create_pagesize_options() -> list[SelectOption]:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def timedelta_from_relativedelta(relativedelta: rd) -> td:
|
|
||||||
"""Converts a relativedelta object to a timedelta object."""
|
|
||||||
now = datetime.now()
|
|
||||||
then = now - relativedelta
|
|
||||||
return now - then
|
|
||||||
|
|
||||||
def get_footer_image(coginstance: commands.Cog) -> File:
|
|
||||||
"""Returns the footer image for the embeds."""
|
|
||||||
image_path = data_manager.bundled_data_path(coginstance) / "arrow.png"
|
|
||||||
return File(image_path, filename="arrow.png", description="arrow")
|
|
||||||
|
|
|
@ -14,31 +14,29 @@ from redbot.cogs.downloader import errors
|
||||||
from redbot.cogs.downloader.converters import InstalledCog
|
from redbot.cogs.downloader.converters import InstalledCog
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.utils.chat_formatting import bold, error, humanize_list, text_to_file
|
from redbot.core.utils.chat_formatting import (error, humanize_list,
|
||||||
|
text_to_file)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
class Backup(commands.Cog):
|
class Backup(commands.Cog):
|
||||||
"""A utility to make reinstalling repositories and cogs after migrating the bot far easier."""
|
"""A utility to make reinstalling repositories and cogs after migrating the bot far easier."""
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
__author__ = ["SeaswimmerTheFsh"]
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
__version__ = "1.0.1"
|
||||||
__version__ = "1.1.1"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/backup/"
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.logger = getLogger("red.SeaCogs.Backup")
|
self.logger = getLogger("red.seacogs.backup")
|
||||||
|
|
||||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||||
pre_processed = super().format_help_for_context(ctx) or ""
|
pre_processed = super().format_help_for_context(ctx) or ""
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
n = "\n" if "\n\n" not in pre_processed else ""
|
||||||
text = [
|
text = [
|
||||||
f"{pre_processed}{n}",
|
f"{pre_processed}{n}",
|
||||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
f"Cog Version: **{self.__version__}**",
|
||||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
f"Author: {humanize_list(self.__author__)}",
|
||||||
f"{bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
]
|
||||||
return "\n".join(text)
|
return "\n".join(text)
|
||||||
|
|
||||||
|
@ -101,7 +99,7 @@ class Backup(commands.Cog):
|
||||||
except (json.JSONDecodeError, IndexError):
|
except (json.JSONDecodeError, IndexError):
|
||||||
try:
|
try:
|
||||||
export = json.loads(await ctx.message.reference.resolved.attachments[0].read())
|
export = json.loads(await ctx.message.reference.resolved.attachments[0].read())
|
||||||
except (json.JSONDecodeError, IndexError, AttributeError):
|
except (json.JSONDecodeError, IndexError):
|
||||||
await ctx.send(error("Please provide a valid JSON export file."))
|
await ctx.send(error("Please provide a valid JSON export file."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -161,15 +159,17 @@ class Backup(commands.Cog):
|
||||||
)
|
)
|
||||||
self.logger.debug("Repository %s already exists", name)
|
self.logger.debug("Repository %s already exists", name)
|
||||||
|
|
||||||
except errors.AuthenticationError as err:
|
# This is commented out because errors.AuthenticationError is not yet implemented in Red 3.5.5's Downloader cog.
|
||||||
repo_e.append(f"Authentication error while adding repository {name}. See logs for more information.")
|
# Rather, it is only in the development version and will be added in version 3.5.6 (or whatever the next version is).
|
||||||
self.logger.exception(
|
# except errors.AuthenticationError as err:
|
||||||
"Something went wrong whilst cloning %s (to revision %s)",
|
# repo_e.append(f"Authentication error while adding repository {name}. See logs for more information.")
|
||||||
url,
|
# self.logger.exception(
|
||||||
branch,
|
# "Something went wrong whilst cloning %s (to revision %s)",
|
||||||
exc_info=err,
|
# url,
|
||||||
)
|
# branch,
|
||||||
continue
|
# exc_info=err,
|
||||||
|
# )
|
||||||
|
# continue
|
||||||
|
|
||||||
except errors.CloningError as err:
|
except errors.CloningError as err:
|
||||||
repo_e.append(
|
repo_e.append(
|
||||||
|
@ -197,7 +197,7 @@ class Backup(commands.Cog):
|
||||||
cog_modules = []
|
cog_modules = []
|
||||||
for cog in cogs:
|
for cog in cogs:
|
||||||
# If you're forking this cog, make sure to change these strings!
|
# If you're forking this cog, make sure to change these strings!
|
||||||
if cog["name"] == "backup" and "cswimr/SeaCogs" in url:
|
if cog["name"] == "backup" and "SeaswimmerTheFsh/SeaCogs" in url:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
cog_module = await InstalledCog.convert(ctx, cog["name"])
|
cog_module = await InstalledCog.convert(ctx, cog["name"])
|
||||||
|
@ -233,7 +233,7 @@ class Backup(commands.Cog):
|
||||||
commit = None
|
commit = None
|
||||||
|
|
||||||
# If you're forking this cog, make sure to change these strings!
|
# If you're forking this cog, make sure to change these strings!
|
||||||
if cog_name == "backup" and "cswimr/SeaCogs" in url:
|
if cog_name == "backup" and "SeaswimmerTheFsh/SeaCogs" in url:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
async with repository.checkout(
|
async with repository.checkout(
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"author" : ["cswimr"],
|
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||||
"install_msg" : "Thank you for installing Backup!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
"install_msg" : "Thank you for installing Backup!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||||
"name" : "Backup",
|
"name" : "Backup",
|
||||||
"short" : "A utility to make reinstalling repositories and cogs after migrating the bot far easier.",
|
"short" : "A utility to make reinstalling repositories and cogs after migrating the bot far easier.",
|
||||||
"description" : "A utility to make reinstalling repositories and cogs after migrating the bot far easier.",
|
"description" : "A utility to make reinstalling repositories and cogs after migrating the bot far easier.",
|
||||||
"end_user_data_statement" : "This cog does not store end user data.",
|
"end_user_data_statement" : "This cog does not store end user data.",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"min_bot_version": "3.5.6",
|
"min_bot_version": "3.5.0",
|
||||||
"max_bot_version": "3.5.13",
|
"max_bot_version": "3.5.5",
|
||||||
"min_python_version": [3, 9, 0],
|
"min_python_version": [3, 9, 0],
|
||||||
"tags": [
|
"tags": [
|
||||||
"utility",
|
"utility",
|
||||||
|
|
|
@ -6,16 +6,13 @@
|
||||||
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
|
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import numpy as np
|
from discord import Embed
|
||||||
from discord import Colour, Embed, File
|
|
||||||
from PIL import Image
|
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
from redbot.core import Config, commands, data_manager
|
from redbot.core import Config, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.utils.chat_formatting import bold, error, humanize_list
|
from redbot.core.utils.chat_formatting import error, humanize_list
|
||||||
|
|
||||||
import bible.errors
|
import bible.errors
|
||||||
from bible.models import Version
|
from bible.models import Version
|
||||||
|
@ -24,10 +21,8 @@ from bible.models import Version
|
||||||
class Bible(commands.Cog):
|
class Bible(commands.Cog):
|
||||||
"""Retrieve Bible verses from the API.bible API."""
|
"""Retrieve Bible verses from the API.bible API."""
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
__author__ = ["SeaswimmerTheFsh"]
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
__version__ = "1.0.1"
|
||||||
__version__ = "1.1.1"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/pterodactyl/"
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -36,7 +31,7 @@ class Bible(commands.Cog):
|
||||||
self.config = Config.get_conf(
|
self.config = Config.get_conf(
|
||||||
self, identifier=481923957134912, force_registration=True
|
self, identifier=481923957134912, force_registration=True
|
||||||
)
|
)
|
||||||
self.logger = getLogger("red.SeaCogs.Bible")
|
self.logger = getLogger("red.seacogs.bible")
|
||||||
self.config.register_global(bible="de4e12af7f28f599-02")
|
self.config.register_global(bible="de4e12af7f28f599-02")
|
||||||
self.config.register_user(bible=None)
|
self.config.register_user(bible=None)
|
||||||
|
|
||||||
|
@ -45,29 +40,11 @@ class Bible(commands.Cog):
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
n = "\n" if "\n\n" not in pre_processed else ""
|
||||||
text = [
|
text = [
|
||||||
f"{pre_processed}{n}",
|
f"{pre_processed}{n}",
|
||||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
f"Cog Version: **{self.__version__}**",
|
||||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
f"Author: {humanize_list(self.__author__)}",
|
||||||
f"{bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
]
|
||||||
return "\n".join(text)
|
return "\n".join(text)
|
||||||
|
|
||||||
|
|
||||||
def get_icon(self, color: Colour) -> File:
|
|
||||||
"""Get the docs.api.bible favicon with a given color."""
|
|
||||||
image_path = data_manager.bundled_data_path(self) / "api.bible-logo.png"
|
|
||||||
image = Image.open(image_path)
|
|
||||||
image = image.convert("RGBA")
|
|
||||||
data = np.array(image)
|
|
||||||
red, green, blue, alpha = data.T # pylint: disable=unused-variable
|
|
||||||
white_areas = (red == 255) & (blue == 255) & (green == 255)
|
|
||||||
data[..., :-1][white_areas.T] = color.to_rgb()
|
|
||||||
image = Image.fromarray(data)
|
|
||||||
|
|
||||||
with BytesIO() as image_binary:
|
|
||||||
image.save(image_binary, "PNG")
|
|
||||||
image_binary.seek(0)
|
|
||||||
return File(image_binary, filename="icon.png", description="API.Bible Icon")
|
|
||||||
|
|
||||||
async def translate_book_name(self, bible_id: str, book_name: str) -> str:
|
async def translate_book_name(self, bible_id: str, book_name: str) -> str:
|
||||||
"""Translate a book name to a book ID."""
|
"""Translate a book name to a book ID."""
|
||||||
book_name_list = [
|
book_name_list = [
|
||||||
|
@ -269,17 +246,15 @@ class Bible(commands.Cog):
|
||||||
return
|
return
|
||||||
|
|
||||||
if await ctx.embed_requested():
|
if await ctx.embed_requested():
|
||||||
icon = self.get_icon(await ctx.embed_color())
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"{passage['reference']}",
|
title=f"{passage['reference']}",
|
||||||
description=passage["content"].replace("¶ ", ""),
|
description=passage["content"].replace("¶ ", ""),
|
||||||
color=await ctx.embed_color(),
|
color=await self.bot.get_embed_color(ctx.channel),
|
||||||
)
|
)
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=f"{ctx.prefix}bible passage - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})",
|
text=f"{ctx.prefix}bible passage - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})"
|
||||||
icon_url="attachment://icon.png"
|
|
||||||
)
|
)
|
||||||
await ctx.send(embed=embed, file=icon)
|
await ctx.send(embed=embed)
|
||||||
else:
|
else:
|
||||||
await ctx.send(f"## {passage['reference']}\n{passage['content']}")
|
await ctx.send(f"## {passage['reference']}\n{passage['content']}")
|
||||||
|
|
||||||
|
@ -311,16 +286,14 @@ class Bible(commands.Cog):
|
||||||
return
|
return
|
||||||
|
|
||||||
if await ctx.embed_requested():
|
if await ctx.embed_requested():
|
||||||
icon = self.get_icon(await ctx.embed_color())
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"{passage['reference']}",
|
title=f"{passage['reference']}",
|
||||||
description=passage["content"].replace("¶ ", ""),
|
description=passage["content"].replace("¶ ", ""),
|
||||||
color=await ctx.embed_color(),
|
color=await self.bot.get_embed_color(ctx.channel),
|
||||||
)
|
)
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=f"{ctx.prefix}bible random - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})",
|
text=f"{ctx.prefix}bible random - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})"
|
||||||
icon_url="attachment://icon.png"
|
|
||||||
)
|
)
|
||||||
await ctx.send(embed=embed, file=icon)
|
await ctx.send(embed=embed)
|
||||||
else:
|
else:
|
||||||
await ctx.send(f"## {passage['reference']}\n{passage['content']}")
|
await ctx.send(f"## {passage['reference']}\n{passage['content']}")
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"author" : ["cswimr"],
|
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||||
"install_msg" : "Thank you for installing Bible!\nThis cog requires setting an API key for API.Bible. Please read the [documentation](https://seacogs.coastalcommits.com/bible/#setup) for more information.\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
"install_msg" : "Thank you for installing Bible!\nThis cog requires setting an API key for API.Bible. Please read the [documentation](https://seacogs.coastalcommits.com/bible/#setup) for more information.\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||||
"name" : "Bible",
|
"name" : "Bible",
|
||||||
"short" : "Retrieve Bible verses from API.Bible.",
|
"short" : "Retrieve Bible verses from API.Bible.",
|
||||||
"description" : "Retrieve Bible verses from the API.Bible API. This cog requires an API.Bible api key.",
|
"description" : "Retrieve Bible verses from the API.Bible API. This cog requires an API.Bible api key.",
|
||||||
|
@ -9,7 +9,6 @@
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"min_bot_version": "3.5.0",
|
"min_bot_version": "3.5.0",
|
||||||
"min_python_version": [3, 10, 0],
|
"min_python_version": [3, 10, 0],
|
||||||
"requirements": ["numpy", "pillow"],
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"fun",
|
"fun",
|
||||||
"utility",
|
"utility",
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from .emojiinfo import EmojiInfo
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
|
||||||
await bot.add_cog(EmojiInfo(bot))
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,130 +0,0 @@
|
||||||
import io
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import discord
|
|
||||||
from colorthief import ColorThief
|
|
||||||
from red_commons.logging import RedTraceLogger, getLogger
|
|
||||||
from redbot.core import app_commands, commands
|
|
||||||
from redbot.core.bot import Red
|
|
||||||
from redbot.core.utils.chat_formatting import bold, humanize_list
|
|
||||||
|
|
||||||
from .model import PartialEmoji
|
|
||||||
|
|
||||||
|
|
||||||
class EmojiInfo(commands.Cog):
|
|
||||||
"""Retrieve information about emojis."""
|
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
|
||||||
__version__ = "1.0.1"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/emojiinfo/"
|
|
||||||
|
|
||||||
def __init__(self, bot: Red) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.bot: Red = bot
|
|
||||||
self.logger: RedTraceLogger = getLogger(name="red.SeaCogs.Emoji")
|
|
||||||
|
|
||||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
|
||||||
pre_processed = super().format_help_for_context(ctx) or ""
|
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
|
||||||
text = [
|
|
||||||
f"{pre_processed}{n}",
|
|
||||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
|
||||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
|
||||||
f"{bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_twemoji(self, unicode_emoji) -> str:
|
|
||||||
base_url = "https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/"
|
|
||||||
emoji_codepoint = "-".join([hex(ord(char))[2:] for char in unicode_emoji])
|
|
||||||
segments = emoji_codepoint.split("-")
|
|
||||||
valid_segments = [seg for seg in segments if len(seg) >= 4]
|
|
||||||
emoji_url = f"{base_url}{valid_segments[0]}.png"
|
|
||||||
return emoji_url
|
|
||||||
|
|
||||||
async def fetch_primary_color(self, emoji_url: str) -> discord.Color | None:
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(emoji_url) as response:
|
|
||||||
if response.status != 200:
|
|
||||||
return None
|
|
||||||
image = await response.read()
|
|
||||||
dominant_color = ColorThief(io.BytesIO(image)).get_color(quality=1)
|
|
||||||
color = discord.Color.from_rgb(*dominant_color)
|
|
||||||
return color
|
|
||||||
|
|
||||||
async def get_emoji_info(self, emoji: PartialEmoji) -> tuple[str, str]:
|
|
||||||
if emoji.is_unicode_emoji():
|
|
||||||
try:
|
|
||||||
emoji_url = await self.fetch_twemoji(unicode_emoji=emoji.name)
|
|
||||||
except IndexError as e:
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
emoji_url = emoji.url
|
|
||||||
|
|
||||||
if emoji.id is not None:
|
|
||||||
emoji_id = f"{bold('ID:')} `{emoji.id}`\n"
|
|
||||||
markdown = f"`<{'a' if emoji.animated else ''}:{emoji.name}:{emoji.id}>`"
|
|
||||||
name = f"{bold('Name:')} {emoji.name}\n"
|
|
||||||
aliases = ""
|
|
||||||
group = ""
|
|
||||||
else:
|
|
||||||
emoji_id = ""
|
|
||||||
markdown = f"`{emoji}`"
|
|
||||||
name = f"{bold('Name:')} {emoji.aliases.pop(0)}\n"
|
|
||||||
aliases = f"{bold('Aliases:')} {', '.join(emoji.aliases)}\n" if emoji.aliases else ""
|
|
||||||
group = f"{bold('Group:')} {emoji.group}\n"
|
|
||||||
|
|
||||||
return (
|
|
||||||
f"{name}"
|
|
||||||
f"{emoji_id}"
|
|
||||||
f"{bold('Native:')} {emoji.is_unicode_emoji()}\n"
|
|
||||||
f"{group}"
|
|
||||||
f"{aliases}"
|
|
||||||
f"{bold('Animated:')} {emoji.animated}\n"
|
|
||||||
f"{bold('Markdown:')} {markdown}\n"
|
|
||||||
f"{bold('URL:')} [Click Here]({emoji_url})"
|
|
||||||
), emoji_url
|
|
||||||
|
|
||||||
@app_commands.command(name="emoji")
|
|
||||||
@app_commands.describe(
|
|
||||||
emoji="What emoji would you like to get information on?",
|
|
||||||
ephemeral="Would you like the response to be hidden?"
|
|
||||||
)
|
|
||||||
async def emoji_slash(self, interaction: discord.Interaction, emoji: str, ephemeral: bool = True) -> None:
|
|
||||||
"""Retrieve information about an emoji."""
|
|
||||||
await interaction.response.defer(ephemeral=ephemeral)
|
|
||||||
|
|
||||||
try:
|
|
||||||
emoji: PartialEmoji = PartialEmoji.from_str(self, value=emoji)
|
|
||||||
string, emoji_url, = await self.get_emoji_info(emoji)
|
|
||||||
self.logger.verbose(f"Emoji:\n{string}")
|
|
||||||
except (IndexError, UnboundLocalError):
|
|
||||||
return await interaction.followup.send("Please provide a valid emoji!")
|
|
||||||
|
|
||||||
if await self.bot.embed_requested(channel=interaction.channel):
|
|
||||||
embed = embed = discord.Embed(title="Emoji Information", description=string, color = await self.fetch_primary_color(emoji_url) or await self.bot.get_embed_color(interaction.channel))
|
|
||||||
embed.set_thumbnail(url=emoji_url)
|
|
||||||
|
|
||||||
await interaction.followup.send(embed=embed)
|
|
||||||
else:
|
|
||||||
await interaction.followup.send(content=string)
|
|
||||||
|
|
||||||
@commands.command(name="emoji")
|
|
||||||
async def emoji(self, ctx: commands.Context, *, emoji: str) -> None:
|
|
||||||
"""Retrieve information about an emoji."""
|
|
||||||
try:
|
|
||||||
emoji: PartialEmoji = PartialEmoji.from_str(self, value=emoji)
|
|
||||||
string, emoji_url, = await self.get_emoji_info(emoji)
|
|
||||||
self.logger.verbose(f"Emoji:\n{string}")
|
|
||||||
except (IndexError, UnboundLocalError):
|
|
||||||
return await ctx.send("Please provide a valid emoji!")
|
|
||||||
|
|
||||||
if await ctx.embed_requested():
|
|
||||||
embed = embed = discord.Embed(title="Emoji Information", description=string, color = await self.fetch_primary_color(emoji_url) or await ctx.embed_color)
|
|
||||||
embed.set_thumbnail(url=emoji_url)
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
else:
|
|
||||||
await ctx.send(content=string)
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"author" : ["cswimr"],
|
|
||||||
"install_msg" : "Thank you for installing Emoji!",
|
|
||||||
"name" : "Emoji",
|
|
||||||
"short" : "Retrieve information about emojis.",
|
|
||||||
"description" : "Retrieve information about emojis.",
|
|
||||||
"end_user_data_statement" : "This cog does not store end user data.",
|
|
||||||
"hidden": false,
|
|
||||||
"disabled": false,
|
|
||||||
"min_bot_version": "3.5.0",
|
|
||||||
"min_python_version": [3, 10, 0],
|
|
||||||
"requirements": ["colorthief"],
|
|
||||||
"tags": [
|
|
||||||
"utility"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from redbot.core import commands, data_manager
|
|
||||||
|
|
||||||
|
|
||||||
class PartialEmoji(discord.PartialEmoji):
|
|
||||||
"""Represents a "partial" emoji. Subclasses `discord.PartialEmoji`
|
|
||||||
|
|
||||||
.. container:: operations
|
|
||||||
|
|
||||||
.. describe:: x == y
|
|
||||||
|
|
||||||
Checks if two emoji are the same.
|
|
||||||
|
|
||||||
.. describe:: x != y
|
|
||||||
|
|
||||||
Checks if two emoji are not the same.
|
|
||||||
|
|
||||||
.. describe:: hash(x)
|
|
||||||
|
|
||||||
Return the emoji's hash.
|
|
||||||
|
|
||||||
.. describe:: str(x)
|
|
||||||
|
|
||||||
Returns the emoji rendered for discord.
|
|
||||||
|
|
||||||
Attributes
|
|
||||||
-----------
|
|
||||||
name: Optional[:class:`str`]
|
|
||||||
The custom emoji name, if applicable, or the unicode codepoint
|
|
||||||
of the non-custom emoji. This can be ``None`` if the emoji
|
|
||||||
got deleted (e.g. removing a reaction with a deleted emoji).
|
|
||||||
animated: :class:`bool`
|
|
||||||
Whether the emoji is animated or not.
|
|
||||||
id: Optional[:class:`int`]
|
|
||||||
The ID of the custom emoji, if applicable.
|
|
||||||
group: Optional[:class:`str`]
|
|
||||||
The group name of the emoji if it is a native emoji.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *, name: str, animated: bool = False, id: int | None = None, group: str | None = None, aliases: list | None = None) -> None: # pylint: disable=redefined-builtin
|
|
||||||
super().__init__(name=name, animated=animated, id=id)
|
|
||||||
self.group = group
|
|
||||||
self.aliases = aliases
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_str(cls, coginstance: commands.Cog, value: str) -> "PartialEmoji":
|
|
||||||
"""Converts a Discord string representation of an emoji to a :class:`PartialEmoji`.
|
|
||||||
|
|
||||||
The formats accepted are:
|
|
||||||
|
|
||||||
- ``a:name:id``
|
|
||||||
- ``<a:name:id>``
|
|
||||||
- ``name:id``
|
|
||||||
- ``<:name:id>``
|
|
||||||
|
|
||||||
If the format does not match then it is assumed to be a unicode emoji.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
------------
|
|
||||||
value: :class:`str`
|
|
||||||
The string representation of an emoji.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
--------
|
|
||||||
:class:`PartialEmoji`
|
|
||||||
The partial emoji from this string.
|
|
||||||
"""
|
|
||||||
match = cls._CUSTOM_EMOJI_RE.match(value)
|
|
||||||
if match is not None:
|
|
||||||
groups = match.groupdict()
|
|
||||||
animated = bool(groups['animated'])
|
|
||||||
emoji_id = int(groups['id'])
|
|
||||||
name = groups['name']
|
|
||||||
return cls(name=name, animated=animated, id=emoji_id)
|
|
||||||
|
|
||||||
path: data_manager.Path = data_manager.bundled_data_path(coginstance) / "emojis.json"
|
|
||||||
with open(path, "r", encoding="UTF-8") as file:
|
|
||||||
emojis: dict = json.load(file)
|
|
||||||
emoji_aliases = []
|
|
||||||
emoji_group = None
|
|
||||||
for dict_name, group in emojis.items():
|
|
||||||
for k, v in group.items():
|
|
||||||
if v == value:
|
|
||||||
emoji_group = dict_name
|
|
||||||
if k not in emoji_aliases:
|
|
||||||
emoji_aliases.append(k)
|
|
||||||
return cls(name=value, animated=False, id=None, group=emoji_group, aliases=emoji_aliases)
|
|
303
flake.lock
303
flake.lock
|
@ -1,303 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"cachix": {
|
|
||||||
"inputs": {
|
|
||||||
"devenv": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"flake-compat": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"git-hooks": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1728672398,
|
|
||||||
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "cachix",
|
|
||||||
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"ref": "latest",
|
|
||||||
"repo": "cachix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devenv": {
|
|
||||||
"inputs": {
|
|
||||||
"cachix": "cachix",
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"git-hooks": "git-hooks",
|
|
||||||
"nix": "nix",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1732121232,
|
|
||||||
"narHash": "sha256-CmJt7aeSCJnJYGtYpyslRI+pC28RPVD43PD/7kkIVuM=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"rev": "6ff1e5f92c0d74bbb12f7454a239ca2f02e05ea1",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1696426674,
|
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat_2": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1696426674,
|
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": [
|
|
||||||
"devenv",
|
|
||||||
"nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1712014858,
|
|
||||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"git-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": [
|
|
||||||
"devenv",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs-stable": [
|
|
||||||
"devenv"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1730302582,
|
|
||||||
"narHash": "sha256-W1MIJpADXQCgosJZT8qBYLRuZls2KSiKdpnTVdKBuvU=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"rev": "af8a16fe5c264f5e9e18bcee2859b40a656876cf",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"devenv",
|
|
||||||
"git-hooks",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1709087332,
|
|
||||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"libgit2": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1697646580,
|
|
||||||
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
|
||||||
"owner": "libgit2",
|
|
||||||
"repo": "libgit2",
|
|
||||||
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "libgit2",
|
|
||||||
"repo": "libgit2",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"libgit2": "libgit2",
|
|
||||||
"nixpkgs": "nixpkgs_2",
|
|
||||||
"nixpkgs-23-11": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"nixpkgs-regression": [
|
|
||||||
"devenv"
|
|
||||||
],
|
|
||||||
"pre-commit-hooks": [
|
|
||||||
"devenv"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1727438425,
|
|
||||||
"narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
|
|
||||||
"owner": "domenkozar",
|
|
||||||
"repo": "nix",
|
|
||||||
"rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "domenkozar",
|
|
||||||
"ref": "devenv-2.24",
|
|
||||||
"repo": "nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1730531603,
|
|
||||||
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-python": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat_2",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1730716553,
|
|
||||||
"narHash": "sha256-n4cibCp/ggDlSacCTnP8dVnywclQKYcHy6PRfe35Hk0=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "nixpkgs-python",
|
|
||||||
"rev": "8fcdb8ec34a1c2bae3f5326873a41b310e948ccc",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "nixpkgs-python",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1717432640,
|
|
||||||
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "release-24.05",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731676054,
|
|
||||||
"narHash": "sha256-OZiZ3m8SCMfh3B6bfGC/Bm4x3qc1m2SVEAlkV6iY7Yg=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "5e4fbfb6b3de1aa2872b76d49fafc942626e2add",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"devenv": "devenv",
|
|
||||||
"nixpkgs": "nixpkgs_3",
|
|
||||||
"nixpkgs-python": "nixpkgs-python",
|
|
||||||
"systems": "systems"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
46
flake.nix
46
flake.nix
|
@ -1,46 +0,0 @@
|
||||||
{
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
nixpkgs-python.url = "github:cachix/nixpkgs-python";
|
|
||||||
nixpkgs-python.inputs = { nixpkgs.follows = "nixpkgs"; };
|
|
||||||
systems.url = "github:nix-systems/default";
|
|
||||||
devenv.url = "github:cachix/devenv";
|
|
||||||
devenv.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
nixConfig = {
|
|
||||||
extra-trusted-public-keys =
|
|
||||||
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
|
|
||||||
extra-substituters = "https://devenv.cachix.org";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, devenv, systems, ... }@inputs:
|
|
||||||
let forEachSystem = nixpkgs.lib.genAttrs (import systems);
|
|
||||||
in {
|
|
||||||
packages = forEachSystem (system: {
|
|
||||||
devenv-up = self.devShells.${system}.default.config.procfileScript;
|
|
||||||
devenv-test = self.devShells.${system}.default.config.test;
|
|
||||||
});
|
|
||||||
|
|
||||||
devShells = forEachSystem (system:
|
|
||||||
let pkgs = nixpkgs.legacyPackages.${system};
|
|
||||||
in {
|
|
||||||
default = devenv.lib.mkShell {
|
|
||||||
inherit inputs pkgs;
|
|
||||||
modules = [{
|
|
||||||
languages.python = {
|
|
||||||
enable = true;
|
|
||||||
version = "3.11";
|
|
||||||
uv = {
|
|
||||||
enable = true;
|
|
||||||
sync = {
|
|
||||||
enable = true;
|
|
||||||
allExtras = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
10
info.json
10
info.json
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"author": [
|
"author": [
|
||||||
"cswimr"
|
"SeaswimmerTheFsh (seasw.)"
|
||||||
],
|
],
|
||||||
"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/SeaCogs/issues) or join my [Discord Server](https://discord.gg/eMUMe77Yb8 ).",
|
"install_msg": "Thanks for installing my repo!\n\nIf you have any issues with any of the cogs, please create an issue [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs/issues) or join my [Discord Server](https://discord.gg/eMUMe77Yb8 ).",
|
||||||
"index_name": "sea-cogs",
|
"name": "SeaCogs",
|
||||||
"short": "Various cogs for Red, by cswimr",
|
"short": "Various cogs for Red, by SeaswimmerTheFsh (seasw.)",
|
||||||
"description": "Various cogs for Red, by cswimr"
|
"description": "Various cogs for Red, by SeaswimmerTheFsh (seasw.)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
site_name: SeaCogs Documentation
|
site_name: SeaCogs Documentation
|
||||||
site_url: !ENV [SITE_URL, 'https://seacogs.coastalcommits.com']
|
site_url: !ENV [SITE_URL, 'https://seacogs.coastalcommits.com']
|
||||||
repo_name: CoastalCommits
|
repo_name: CoastalCommits
|
||||||
repo_url: https://coastalcommits.com/cswimr/SeaCogs
|
repo_url: https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||||
edit_uri: !ENV [EDIT_URI, 'src/branch/main/.docs']
|
edit_uri: !ENV [EDIT_URI, 'src/branch/main/.docs']
|
||||||
copyright: Copyright © 2023-2024, cswimr
|
copyright: Copyright © 2023-2024, SeaswimmerTheFsh
|
||||||
docs_dir: .docs
|
docs_dir: .docs
|
||||||
|
|
||||||
site_author: cswimr
|
site_author: SeaswimmerTheFsh
|
||||||
site_description: Documentation for my Red-DiscordBot Cogs.
|
site_description: Documentation for my Red-DiscordBot Cogs.
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
|
@ -18,7 +18,6 @@ nav:
|
||||||
- Configuration: aurora/configuration.md
|
- Configuration: aurora/configuration.md
|
||||||
- Bible: bible.md
|
- Bible: bible.md
|
||||||
- Backup: backup.md
|
- Backup: backup.md
|
||||||
- EmojiInfo: emojiinfo.md
|
|
||||||
- Nerdify: nerdify.md
|
- Nerdify: nerdify.md
|
||||||
- Pterodactyl:
|
- Pterodactyl:
|
||||||
- pterodactyl/index.md
|
- pterodactyl/index.md
|
||||||
|
@ -113,5 +112,3 @@ watch:
|
||||||
- ./bible
|
- ./bible
|
||||||
- ./nerdify
|
- ./nerdify
|
||||||
- ./pterodactyl
|
- ./pterodactyl
|
||||||
- ./emojiinfo
|
|
||||||
- ./antipolls
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"author" : ["cswimr"],
|
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||||
"install_msg" : "Thank you for installing Nerdify!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs). Based off of PhasecoreX's [UwU](<https://github.com/PhasecoreX/PCXCogs/tree/master/uwu>) cog.",
|
"install_msg" : "Thank you for installing Nerdify!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs). Based off of PhasecoreX's [UwU](<https://github.com/PhasecoreX/PCXCogs/tree/master/uwu>) cog.",
|
||||||
"name" : "Nerdify",
|
"name" : "Nerdify",
|
||||||
"short" : "Nerdify your text!",
|
"short" : "Nerdify your text!",
|
||||||
"description" : "Nerdify your text!",
|
"description" : "Nerdify your text!",
|
||||||
|
|
|
@ -12,16 +12,13 @@ from typing import Any, Optional, Union
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils import chat_formatting, common_filters
|
from redbot.core.utils import chat_formatting, common_filters
|
||||||
from redbot.core.utils.chat_formatting import bold, humanize_list
|
|
||||||
|
|
||||||
|
|
||||||
class Nerdify(commands.Cog):
|
class Nerdify(commands.Cog):
|
||||||
"""Nerdify your text."""
|
"""Nerdify your text."""
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
__author__ = ["SeaswimmerTheFsh"]
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
__version__ = "1.3.3"
|
||||||
__version__ = "1.3.5"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/nerdify/"
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -31,13 +28,11 @@ class Nerdify(commands.Cog):
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
n = "\n" if "\n\n" not in pre_processed else ""
|
||||||
text = [
|
text = [
|
||||||
f"{pre_processed}{n}",
|
f"{pre_processed}{n}",
|
||||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
f"Cog Version: **{self.__version__}**",
|
||||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
f"Author: {chat_formatting.humanize_list(self.__author__)}",
|
||||||
f"{bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
]
|
||||||
return "\n".join(text)
|
return "\n".join(text)
|
||||||
|
|
||||||
|
|
||||||
@commands.command(aliases=["nerd"])
|
@commands.command(aliases=["nerd"])
|
||||||
async def nerdify(
|
async def nerdify(
|
||||||
self, ctx: commands.Context, *, text: Optional[str] = None
|
self, ctx: commands.Context, *, text: Optional[str] = None
|
||||||
|
|
2539
poetry.lock
generated
Normal file
2539
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,6 @@ def register_config(config_obj: Config) -> None:
|
||||||
base_url=None,
|
base_url=None,
|
||||||
server_id=None,
|
server_id=None,
|
||||||
console_channel=None,
|
console_channel=None,
|
||||||
console_commands_enabled=False,
|
|
||||||
current_status='',
|
current_status='',
|
||||||
chat_regex=r"^\[\d{2}:\d{2}:\d{2}\sINFO\]: (?!\[(?:Server|Rcon)\])(?:<|\[)(\w+)(?:>|\]) (.*)",
|
chat_regex=r"^\[\d{2}:\d{2}:\d{2}\sINFO\]: (?!\[(?:Server|Rcon)\])(?:<|\[)(\w+)(?:>|\]) (.*)",
|
||||||
server_regex=r"^\[\d{2}:\d{2}:\d{2} INFO\]:(?: \[Not Secure\])? \[(?:Server|Rcon)\] (.*)",
|
server_regex=r"^\[\d{2}:\d{2}:\d{2} INFO\]:(?: \[Not Secure\])? \[(?:Server|Rcon)\] (.*)",
|
||||||
|
@ -15,9 +14,6 @@ def register_config(config_obj: Config) -> None:
|
||||||
leave_regex=r"^\[\d{2}:\d{2}:\d{2} INFO\]: ([^<\n]+) left the game$",
|
leave_regex=r"^\[\d{2}:\d{2}:\d{2} INFO\]: ([^<\n]+) left the game$",
|
||||||
achievement_regex=r"^\[\d{2}:\d{2}:\d{2} INFO\]: (.*) has (made the advancement|completed the challenge) \[(.*)\]$",
|
achievement_regex=r"^\[\d{2}:\d{2}:\d{2} INFO\]: (.*) has (made the advancement|completed the challenge) \[(.*)\]$",
|
||||||
chat_command='tellraw @a ["",{"text":".$N ","color":".$C","insertion":"<@.$I>","hoverEvent":{"action":"show_text","contents":"Shift click to mention this user inside Discord"}},{"text":"(DISCORD):","color":"blue","clickEvent":{"action":"open_url","value":".$V"},"hoverEvent":{"action":"show_text","contents":"Click to join the Discord Server"}},{"text":" .$M","color":"white"}]', # noqa: E501
|
chat_command='tellraw @a ["",{"text":".$N ","color":".$C","insertion":"<@.$I>","hoverEvent":{"action":"show_text","contents":"Shift click to mention this user inside Discord"}},{"text":"(DISCORD):","color":"blue","clickEvent":{"action":"open_url","value":".$V"},"hoverEvent":{"action":"show_text","contents":"Click to join the Discord Server"}},{"text":" .$M","color":"white"}]', # noqa: E501
|
||||||
topic='Server IP: .$H\nServer Players: .$P/.$M',
|
|
||||||
topic_hostname=None,
|
|
||||||
topic_port=25565,
|
|
||||||
api_endpoint="minecraft",
|
api_endpoint="minecraft",
|
||||||
chat_channel=None,
|
chat_channel=None,
|
||||||
startup_msg='Server started!',
|
startup_msg='Server started!',
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 65 KiB |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"author" : ["cswimr"],
|
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||||
"install_msg" : "Thank you for installing Pterodactyl!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).\nDocumentation can be found [here](https://seacogs.coastalcommits.com/pterodactyl ).",
|
"install_msg" : "Thank you for installing Pterodactyl!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).\nDocumentation can be found [here](https://seacogs.coastalcommits.com/pterodactyl ).",
|
||||||
"name" : "Pterodactyl",
|
"name" : "Pterodactyl",
|
||||||
"short" : "Interface with Pterodactyl through websockets.",
|
"short" : "Interface with Pterodactyl through websockets.",
|
||||||
"description" : "Interface with Pterodactyl through websockets.",
|
"description" : "Interface with Pterodactyl through websockets.",
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"min_bot_version": "3.5.0",
|
"min_bot_version": "3.5.0",
|
||||||
"min_python_version": [3, 8, 0],
|
"min_python_version": [3, 8, 0],
|
||||||
"requirements": ["git+https://github.com/cswimr/pydactyl", "websockets"],
|
"requirements": ["git+https://github.com/SeaswimmerTheFsh/pydactyl", "websockets"],
|
||||||
"tags": [
|
"tags": [
|
||||||
"pterodactyl",
|
"pterodactyl",
|
||||||
"minecraft",
|
"minecraft",
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
from red_commons import logging
|
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
logger = getLogger('red.SeaCogs.Pterodactyl')
|
logger = getLogger('red.seacogs.pterodactyl')
|
||||||
websocket_logger = getLogger('red.SeaCogs.Pterodactyl.websocket')
|
websocket_logger = getLogger('red.seacogs.pterodactyl.websocket')
|
||||||
if logger.level >= logging.VERBOSE:
|
|
||||||
websocket_logger.setLevel(logging.logging.INFO)
|
|
||||||
elif logger.level < logging.VERBOSE:
|
|
||||||
websocket_logger.setLevel(logging.logging.DEBUG)
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
|
|
||||||
async def get_status(host: str, port: int = 25565) -> tuple[bool, dict]:
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(f'https://api.mcsrvstat.us/2/{host}:{port}') as response:
|
|
||||||
response = await response.json()
|
|
||||||
if response['online']:
|
|
||||||
return (True, response)
|
|
||||||
return (False, response)
|
|
|
@ -1,18 +1,16 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Mapping, Optional, Tuple, Union
|
from typing import Mapping, Optional, Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import websockets
|
import websockets
|
||||||
from discord.ext import tasks
|
|
||||||
from pydactyl import PterodactylClient
|
from pydactyl import PterodactylClient
|
||||||
from redbot.core import app_commands, commands
|
from redbot.core import app_commands, commands
|
||||||
from redbot.core.app_commands import Choice
|
from redbot.core.app_commands import Choice
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.utils.chat_formatting import bold, box, error, humanize_list
|
from redbot.core.utils.chat_formatting import box, error
|
||||||
from redbot.core.utils.views import ConfirmView
|
from redbot.core.utils.views import ConfirmView
|
||||||
|
|
||||||
from pterodactyl import mcsrvstatus
|
|
||||||
from pterodactyl.config import config, register_config
|
from pterodactyl.config import config, register_config
|
||||||
from pterodactyl.logger import logger
|
from pterodactyl.logger import logger
|
||||||
|
|
||||||
|
@ -20,11 +18,6 @@ from pterodactyl.logger import logger
|
||||||
class Pterodactyl(commands.Cog):
|
class Pterodactyl(commands.Cog):
|
||||||
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
|
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
|
||||||
__version__ = "2.0.4"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/pterodactyl/"
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.client: Optional[PterodactylClient] = None
|
self.client: Optional[PterodactylClient] = None
|
||||||
|
@ -32,39 +25,12 @@ class Pterodactyl(commands.Cog):
|
||||||
self.websocket: Optional[websockets.WebSocketClientProtocol] = None
|
self.websocket: Optional[websockets.WebSocketClientProtocol] = None
|
||||||
self.retry_counter: int = 0
|
self.retry_counter: int = 0
|
||||||
register_config(config)
|
register_config(config)
|
||||||
self.task = self.get_task()
|
|
||||||
self.update_topic.start()
|
|
||||||
|
|
||||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
|
||||||
pre_processed = super().format_help_for_context(ctx) or ""
|
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
|
||||||
text = [
|
|
||||||
f"{pre_processed}{n}",
|
|
||||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
|
||||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
|
||||||
f"{bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
async def cog_load(self) -> None:
|
async def cog_load(self) -> None:
|
||||||
pterodactyl_keys = await self.bot.get_shared_api_tokens("pterodactyl")
|
self.retry_counter = 0
|
||||||
api_key = pterodactyl_keys.get("api_key")
|
self.task = self.get_task()
|
||||||
if api_key is None:
|
|
||||||
self.task.cancel()
|
|
||||||
raise ValueError("Pterodactyl API key not set. Please set it using `[p]set api`.")
|
|
||||||
base_url = await config.base_url()
|
|
||||||
if base_url is None:
|
|
||||||
self.task.cancel()
|
|
||||||
raise ValueError("Pterodactyl base URL not set. Please set it using `[p]pterodactyl config url`.")
|
|
||||||
server_id = await config.server_id()
|
|
||||||
if server_id is None:
|
|
||||||
self.task.cancel()
|
|
||||||
raise ValueError("Pterodactyl server ID not set. Please set it using `[p]pterodactyl config serverid`.")
|
|
||||||
|
|
||||||
self.client = PterodactylClient(base_url, api_key).client
|
|
||||||
|
|
||||||
async def cog_unload(self) -> None:
|
async def cog_unload(self) -> None:
|
||||||
self.update_topic.cancel()
|
|
||||||
self.task.cancel()
|
self.task.cancel()
|
||||||
self.retry_counter = 0
|
self.retry_counter = 0
|
||||||
await self.client._session.close() # pylint: disable=protected-access
|
await self.client._session.close() # pylint: disable=protected-access
|
||||||
|
@ -90,26 +56,11 @@ class Pterodactyl(commands.Cog):
|
||||||
else:
|
else:
|
||||||
logger.info("Retry limit reached. Stopping task.")
|
logger.info("Retry limit reached. Stopping task.")
|
||||||
|
|
||||||
@tasks.loop(minutes=6)
|
|
||||||
async def update_topic(self):
|
|
||||||
await self.bot.wait_until_red_ready()
|
|
||||||
topic = await self.get_topic()
|
|
||||||
console = self.bot.get_channel(await config.console_channel())
|
|
||||||
chat = self.bot.get_channel(await config.chat_channel())
|
|
||||||
if console:
|
|
||||||
await console.edit(topic=topic)
|
|
||||||
if chat:
|
|
||||||
await chat.edit(topic=topic)
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_message_without_command(self, message: discord.Message) -> None:
|
async def on_message_without_command(self, message: discord.Message) -> None:
|
||||||
if message.channel.id == await config.console_channel() and message.author.bot is False:
|
if message.channel.id == await config.console_channel() and message.author.bot is False:
|
||||||
if await config.console_commands_enabled() is False:
|
|
||||||
await message.channel.send("Console commands are disabled.")
|
|
||||||
logger.debug("Received console command from %s, but console commands are disabled: %s", message.author.id, message.content)
|
|
||||||
return
|
|
||||||
logger.debug("Received console command from %s: %s", message.author.id, message.content)
|
logger.debug("Received console command from %s: %s", message.author.id, message.content)
|
||||||
await message.channel.send(f"Received console command from {message.author.id}: {message.content[:1900]}", allowed_mentions=discord.AllowedMentions.none())
|
await message.channel.send(f"Received console command from {message.author.id}: {message.content[:1900]}")
|
||||||
try:
|
try:
|
||||||
await self.websocket.send(json.dumps({"event": "send command", "args": [message.content]}))
|
await self.websocket.send(json.dumps({"event": "send command", "args": [message.content]}))
|
||||||
except websockets.exceptions.ConnectionClosed as e:
|
except websockets.exceptions.ConnectionClosed as e:
|
||||||
|
@ -121,7 +72,7 @@ class Pterodactyl(commands.Cog):
|
||||||
logger.debug("Received chat message from %s: %s", message.author.id, message.content)
|
logger.debug("Received chat message from %s: %s", message.author.id, message.content)
|
||||||
channel = self.bot.get_channel(await config.console_channel())
|
channel = self.bot.get_channel(await config.console_channel())
|
||||||
if channel:
|
if channel:
|
||||||
await channel.send(f"Received chat message from {message.author.id}: {message.content[:1900]}", allowed_mentions=discord.AllowedMentions.none())
|
await channel.send(f"Received chat message from {message.author.id}: {message.content[:1900]}")
|
||||||
msg = json.dumps({"event": "send command", "args": [await self.get_chat_command(message)]})
|
msg = json.dumps({"event": "send command", "args": [await self.get_chat_command(message)]})
|
||||||
logger.debug("Sending chat message to server:\n%s", msg)
|
logger.debug("Sending chat message to server:\n%s", msg)
|
||||||
try:
|
try:
|
||||||
|
@ -132,41 +83,13 @@ class Pterodactyl(commands.Cog):
|
||||||
self.retry_counter = 0
|
self.retry_counter = 0
|
||||||
self.task = self.get_task()
|
self.task = self.get_task()
|
||||||
|
|
||||||
async def get_topic(self) -> str:
|
|
||||||
topic: str = await config.topic()
|
|
||||||
placeholders = {
|
|
||||||
"H": await config.topic_hostname() or "unset",
|
|
||||||
"O": str(await config.topic_port()),
|
|
||||||
}
|
|
||||||
if await config.api_endpoint() == "minecraft":
|
|
||||||
status, response = await mcsrvstatus.get_status(await config.topic_hostname(), await config.topic_port())
|
|
||||||
if status:
|
|
||||||
placeholders.update({
|
|
||||||
"I": response['ip'],
|
|
||||||
"M": str(response['players']['max']),
|
|
||||||
"P": str(response['players']['online']),
|
|
||||||
"V": response['version'],
|
|
||||||
"D": response['motd']['clean'][0] if response['motd']['clean'] else "unset",
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
placeholders.update({
|
|
||||||
"I": response['ip'],
|
|
||||||
"M": "0",
|
|
||||||
"P": "0",
|
|
||||||
"V": "Server Offline",
|
|
||||||
"D": "Server Offline",
|
|
||||||
})
|
|
||||||
for key, value in placeholders.items():
|
|
||||||
topic = topic.replace('.$' + key, value)
|
|
||||||
return topic
|
|
||||||
|
|
||||||
async def get_chat_command(self, message: discord.Message) -> str:
|
async def get_chat_command(self, message: discord.Message) -> str:
|
||||||
command: str = await config.chat_command()
|
command: str = await config.chat_command()
|
||||||
placeholders = {
|
placeholders = {
|
||||||
"C": str(message.author.color),
|
"C": str(message.author.color),
|
||||||
"D": message.author.discriminator,
|
"D": message.author.discriminator,
|
||||||
"I": str(message.author.id),
|
"I": str(message.author.id),
|
||||||
"M": message.content.replace('"','').replace("\n", " "),
|
"M": message.content.replace('"',''),
|
||||||
"N": message.author.display_name,
|
"N": message.author.display_name,
|
||||||
"U": message.author.name,
|
"U": message.author.name,
|
||||||
"V": await config.invite() or "use [p]pterodactyl config invite to change me",
|
"V": await config.invite() or "use [p]pterodactyl config invite to change me",
|
||||||
|
@ -175,65 +98,78 @@ class Pterodactyl(commands.Cog):
|
||||||
command = command.replace('.$' + key, value)
|
command = command.replace('.$' + key, value)
|
||||||
return command
|
return command
|
||||||
|
|
||||||
async def get_player_list(self) -> Optional[Tuple[str, list]]:
|
|
||||||
if await config.api_endpoint() == "minecraft":
|
|
||||||
status, response = await mcsrvstatus.get_status(await config.topic_hostname(), await config.topic_port())
|
|
||||||
if status and 'list' in response['players']:
|
|
||||||
output_str = '\n'.join(response['players']['list'])
|
|
||||||
return output_str, response['players']['list']
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_player_list_embed(self, ctx: Union[commands.Context, discord.Interaction]) -> Optional[discord.Embed]:
|
|
||||||
player_list = await self.get_player_list()
|
|
||||||
if player_list:
|
|
||||||
embed = discord.Embed(color=await self.bot.get_embed_color(ctx.channel), title="Players Online")
|
|
||||||
embed.description = player_list[0]
|
|
||||||
return embed
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def power(self, ctx: Union[discord.Interaction, commands.Context], action: str, action_ing: str, warning: str = '') -> None:
|
async def power(self, ctx: Union[discord.Interaction, commands.Context], action: str, action_ing: str, warning: str = '') -> None:
|
||||||
if isinstance(ctx, discord.Interaction):
|
if isinstance(ctx, discord.Interaction):
|
||||||
ctx = await self.bot.get_context(ctx)
|
author = ctx.user
|
||||||
|
else:
|
||||||
|
author = ctx.author
|
||||||
|
|
||||||
current_status = await config.current_status()
|
current_status = await config.current_status()
|
||||||
|
|
||||||
if current_status == action_ing:
|
if current_status == action_ing:
|
||||||
return await ctx.send(f"Server is already {action_ing}.", ephemeral=True)
|
if isinstance(ctx, discord.Interaction):
|
||||||
|
return await ctx.response.send_message(f"Server is already {action_ing}.", ephemeral=True)
|
||||||
|
return await ctx.send(f"Server is already {action_ing}.")
|
||||||
|
|
||||||
if current_status in ["starting", "stopping"] and action != "kill":
|
if current_status in ["starting", "stopping"]:
|
||||||
return await ctx.send("Another power action is already in progress.", ephemeral=True)
|
if isinstance(ctx, discord.Interaction):
|
||||||
|
return await ctx.response.send_message("Another power action is already in progress.", ephemeral=True)
|
||||||
|
return await ctx.send("Another power action is already in progress.")
|
||||||
|
|
||||||
view = ConfirmView(ctx.author, disable_buttons=True)
|
view = ConfirmView(author, disable_buttons=True)
|
||||||
|
|
||||||
message = await ctx.send(f"{warning}Are you sure you want to {action} the server?", view=view)
|
if isinstance(ctx, discord.Interaction):
|
||||||
|
await ctx.response.send_message(f"{warning}Are you sure you want to {action} the server?", view=view)
|
||||||
|
else:
|
||||||
|
message = await ctx.send(f"{warning}Are you sure you want to {action} the server?", view=view)
|
||||||
|
|
||||||
await view.wait()
|
await view.wait()
|
||||||
|
|
||||||
if view.result is True:
|
if view.result is True:
|
||||||
await message.edit(content=f"Sending websocket command to {action} server...", view=None)
|
if isinstance(ctx, discord.Interaction):
|
||||||
|
await ctx.edit_original_response(content=f"Sending websocket command to {action} server...", view=None)
|
||||||
|
else:
|
||||||
|
await message.edit(content=f"Sending websocket command to {action} server...", view=None)
|
||||||
|
|
||||||
await self.websocket.send(json.dumps({"event": "set state", "args": [action]}))
|
await self.websocket.send(json.dumps({"event": "set state", "args": [action]}))
|
||||||
|
|
||||||
await message.edit(content=f"Server {action_ing}", view=None)
|
if isinstance(ctx, discord.Interaction):
|
||||||
|
await ctx.edit_original_response(content=f"Server {action_ing}", view=None)
|
||||||
|
else:
|
||||||
|
await message.edit(content=f"Server {action_ing}", view=None)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await message.edit(content="Cancelled.", view=None)
|
if isinstance(ctx, discord.Interaction):
|
||||||
|
await ctx.edit_original_response(content="Cancelled.", view=None)
|
||||||
|
else:
|
||||||
|
await message.edit(content="Cancelled.", view=None)
|
||||||
|
|
||||||
async def send_command(self, ctx: Union[discord.Interaction, commands.Context], command: str):
|
async def send_command(self, ctx: Union[discord.Interaction, commands.Context], command: str):
|
||||||
channel = self.bot.get_channel(await config.console_channel())
|
channel = self.bot.get_channel(await config.console_channel())
|
||||||
if isinstance(ctx, discord.Interaction):
|
if isinstance(ctx, discord.Interaction):
|
||||||
ctx = await self.bot.get_context(ctx)
|
if channel:
|
||||||
if channel:
|
await channel.send(f"Received console command from {ctx.user.id}: {command[:1900]}")
|
||||||
await channel.send(f"Received console command from {ctx.author.id}: {command[:1900]}", allowed_mentions=discord.AllowedMentions.none())
|
try:
|
||||||
try:
|
await self.websocket.send(json.dumps({"event": "send command", "args": [command]}))
|
||||||
await self.websocket.send(json.dumps({"event": "send command", "args": [command]}))
|
await ctx.response.send_message(f"Command sent to server. {box(command, 'json')}", ephemeral=True)
|
||||||
await ctx.send(f"Command sent to server. {box(command, 'json')}")
|
except websockets.exceptions.ConnectionClosed as e:
|
||||||
except websockets.exceptions.ConnectionClosed as e:
|
logger.error("WebSocket connection closed: %s", e)
|
||||||
logger.error("WebSocket connection closed: %s", e)
|
await ctx.response.send_message(error("WebSocket connection closed."))
|
||||||
await ctx.send(error("WebSocket connection closed."))
|
self.task.cancel()
|
||||||
self.task.cancel()
|
self.retry_counter = 0
|
||||||
self.retry_counter = 0
|
self.task = self.get_task()
|
||||||
self.task = self.get_task()
|
else:
|
||||||
|
if channel:
|
||||||
|
await channel.send(f"Received console command from {ctx.author.id}: {command[:1900]}")
|
||||||
|
try:
|
||||||
|
await self.websocket.send(json.dumps({"event": "send command", "args": [command]}))
|
||||||
|
await ctx.send(f"Command sent to server. {box(command, 'json')}")
|
||||||
|
except websockets.exceptions.ConnectionClosed as e:
|
||||||
|
logger.error("WebSocket connection closed: %s", e)
|
||||||
|
await ctx.send(error("WebSocket connection closed."))
|
||||||
|
self.task.cancel()
|
||||||
|
self.retry_counter = 0
|
||||||
|
self.task = self.get_task()
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_red_api_tokens_update(self, service_name: str, api_tokens: Mapping[str,str]): # pylint: disable=unused-argument
|
async def on_red_api_tokens_update(self, service_name: str, api_tokens: Mapping[str,str]): # pylint: disable=unused-argument
|
||||||
|
@ -255,15 +191,6 @@ class Pterodactyl(commands.Cog):
|
||||||
The command to send to the server."""
|
The command to send to the server."""
|
||||||
return await self.send_command(interaction, command)
|
return await self.send_command(interaction, command)
|
||||||
|
|
||||||
@slash_pterodactyl.command(name = "players", description = "Retrieve a list of players on the server.")
|
|
||||||
async def slash_pterodactyl_players(self, interaction: discord.Interaction) -> None:
|
|
||||||
"""Retrieve a list of players on the server."""
|
|
||||||
e = await self.get_player_list_embed(interaction)
|
|
||||||
if e:
|
|
||||||
await interaction.response.send_message(embed=e, ephemeral=True)
|
|
||||||
else:
|
|
||||||
await interaction.response.send_message("No players online.", ephemeral=True)
|
|
||||||
|
|
||||||
@slash_pterodactyl.command(name = "power", description = "Send power actions to the server.")
|
@slash_pterodactyl.command(name = "power", description = "Send power actions to the server.")
|
||||||
@app_commands.choices(action=[
|
@app_commands.choices(action=[
|
||||||
Choice(name="Start", value="start"),
|
Choice(name="Start", value="start"),
|
||||||
|
@ -280,23 +207,12 @@ class Pterodactyl(commands.Cog):
|
||||||
The action to perform on the server."""
|
The action to perform on the server."""
|
||||||
if action.value == "kill":
|
if action.value == "kill":
|
||||||
return await self.power(interaction, action.value, "stopping... (forcefully killed)", warning="**⚠️ Forcefully killing the server process can corrupt data in some cases. ⚠️**\n")
|
return await self.power(interaction, action.value, "stopping... (forcefully killed)", warning="**⚠️ Forcefully killing the server process can corrupt data in some cases. ⚠️**\n")
|
||||||
if action.value == "stop":
|
|
||||||
return await self.power(interaction, action.value, "stopping...")
|
|
||||||
return await self.power(interaction, action.value, f"{action.value}ing...")
|
return await self.power(interaction, action.value, f"{action.value}ing...")
|
||||||
|
|
||||||
@commands.group(autohelp = True, name = "pterodactyl", aliases = ["ptero"])
|
@commands.group(autohelp = True, name = "pterodactyl", aliases = ["ptero"])
|
||||||
async def pterodactyl(self, ctx: commands.Context) -> None:
|
async def pterodactyl(self, ctx: commands.Context) -> None:
|
||||||
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
|
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
|
||||||
|
|
||||||
@pterodactyl.command(name = "players", aliases=["list", "online", "playerlist", "who"])
|
|
||||||
async def pterodactyl_players(self, ctx: commands.Context) -> None:
|
|
||||||
"""Retrieve a list of players on the server."""
|
|
||||||
e = await self.get_player_list_embed(ctx)
|
|
||||||
if e:
|
|
||||||
await ctx.send(embed=e)
|
|
||||||
else:
|
|
||||||
await ctx.send("No players online.")
|
|
||||||
|
|
||||||
@pterodactyl.command(name = "command", aliases = ["cmd", "execute", "exec"])
|
@pterodactyl.command(name = "command", aliases = ["cmd", "execute", "exec"])
|
||||||
@commands.admin()
|
@commands.admin()
|
||||||
async def pterodactyl_command(self, ctx: commands.Context, *, command: str) -> None:
|
async def pterodactyl_command(self, ctx: commands.Context, *, command: str) -> None:
|
||||||
|
@ -356,60 +272,18 @@ class Pterodactyl(commands.Cog):
|
||||||
self.retry_counter = 0
|
self.retry_counter = 0
|
||||||
self.task = self.get_task()
|
self.task = self.get_task()
|
||||||
|
|
||||||
@pterodactyl_config.group(name = "console")
|
@pterodactyl_config.command(name = "consolechannel")
|
||||||
async def pterodactyl_config_console(self, ctx: commands.Context):
|
|
||||||
"""Configure console settings."""
|
|
||||||
|
|
||||||
@pterodactyl_config_console.command(name = "channel")
|
|
||||||
async def pterodactyl_config_console_channel(self, ctx: commands.Context, channel: discord.TextChannel) -> None:
|
async def pterodactyl_config_console_channel(self, ctx: commands.Context, channel: discord.TextChannel) -> None:
|
||||||
"""Set the channel to send console output to."""
|
"""Set the channel to send console output to."""
|
||||||
await config.console_channel.set(channel.id)
|
await config.console_channel.set(channel.id)
|
||||||
await ctx.send(f"Console channel set to {channel.mention}")
|
await ctx.send(f"Console channel set to {channel.mention}")
|
||||||
|
|
||||||
@pterodactyl_config_console.command(name = "commands")
|
|
||||||
async def pterodactyl_config_console_commands(self, ctx: commands.Context, enabled: bool) -> None:
|
|
||||||
"""Enable or disable console commands."""
|
|
||||||
await config.console_commands_enabled.set(enabled)
|
|
||||||
await ctx.send(f"Console commands set to {enabled}")
|
|
||||||
|
|
||||||
@pterodactyl_config.command(name = "invite")
|
@pterodactyl_config.command(name = "invite")
|
||||||
async def pterodactyl_config_invite(self, ctx: commands.Context, invite: str) -> None:
|
async def pterodactyl_config_invite(self, ctx: commands.Context, invite: str) -> None:
|
||||||
"""Set the invite link for your server."""
|
"""Set the invite link for your server."""
|
||||||
await config.invite.set(invite)
|
await config.invite.set(invite)
|
||||||
await ctx.send(f"Invite link set to {invite}")
|
await ctx.send(f"Invite link set to {invite}")
|
||||||
|
|
||||||
@pterodactyl_config.group(name = "topic")
|
|
||||||
async def pterodactyl_config_topic(self, ctx: commands.Context):
|
|
||||||
"""Set the topic for the console and chat channels."""
|
|
||||||
|
|
||||||
@pterodactyl_config_topic.command(name = "host", aliases = ["hostname", "ip"])
|
|
||||||
async def pterodactyl_config_topic_host(self, ctx: commands.Context, host: str) -> None:
|
|
||||||
"""Set the hostname or IP address of your server."""
|
|
||||||
await config.topic_hostname.set(host)
|
|
||||||
await ctx.send(f"Hostname/IP set to `{host}`")
|
|
||||||
|
|
||||||
@pterodactyl_config_topic.command(name = "port")
|
|
||||||
async def pterodactyl_config_topic_port(self, ctx: commands.Context, port: int) -> None:
|
|
||||||
"""Set the port of your server."""
|
|
||||||
await config.topic_port.set(port)
|
|
||||||
await ctx.send(f"Port set to `{port}`")
|
|
||||||
|
|
||||||
@pterodactyl_config_topic.command(name = "text")
|
|
||||||
async def pterodactyl_config_topic_text(self, ctx: commands.Context, *, text: str) -> None:
|
|
||||||
"""Set the text for the console and chat channels.
|
|
||||||
|
|
||||||
Available placeholders:
|
|
||||||
- `.$H` (hostname)
|
|
||||||
- `.$O` (port)
|
|
||||||
Available for Minecraft servers:
|
|
||||||
- `.$I` (ip)
|
|
||||||
- `.$M` (max players)
|
|
||||||
- `.$P` (players online)
|
|
||||||
- `.$V` (version)
|
|
||||||
- `.$D` (description / Message of the Day)"""
|
|
||||||
await config.topic.set(text)
|
|
||||||
await ctx.send(f"Topic set to:\n{box(text, 'yaml')}")
|
|
||||||
|
|
||||||
@pterodactyl_config.group(name = "chat")
|
@pterodactyl_config.group(name = "chat")
|
||||||
async def pterodactyl_config_chat(self, ctx: commands.Context):
|
async def pterodactyl_config_chat(self, ctx: commands.Context):
|
||||||
"""Configure chat settings."""
|
"""Configure chat settings."""
|
||||||
|
@ -534,7 +408,7 @@ class Pterodactyl(commands.Cog):
|
||||||
await view.wait()
|
await view.wait()
|
||||||
if view.result is True:
|
if view.result is True:
|
||||||
blacklist.update({name: regex})
|
blacklist.update({name: regex})
|
||||||
await msg.edit(content=f"Updated `{name}` in the regex blacklist.\n{box(regex, 're')}")
|
await msg.edit(f"Updated `{name}` in the regex blacklist.\n{box(regex, 're')}")
|
||||||
else:
|
else:
|
||||||
await msg.edit(content="Cancelled.")
|
await msg.edit(content="Cancelled.")
|
||||||
|
|
||||||
|
@ -549,7 +423,7 @@ class Pterodactyl(commands.Cog):
|
||||||
await view.wait()
|
await view.wait()
|
||||||
if view.result is True:
|
if view.result is True:
|
||||||
del blacklist[name]
|
del blacklist[name]
|
||||||
await msg.edit(content=f"Removed `{name}` from the regex blacklist.")
|
await msg.edit(content="Removed `{name}` from the regex blacklist.")
|
||||||
else:
|
else:
|
||||||
await msg.edit(content="Cancelled.")
|
await msg.edit(content="Cancelled.")
|
||||||
else:
|
else:
|
||||||
|
@ -561,7 +435,6 @@ class Pterodactyl(commands.Cog):
|
||||||
base_url = await config.base_url()
|
base_url = await config.base_url()
|
||||||
server_id = await config.server_id()
|
server_id = await config.server_id()
|
||||||
console_channel = await config.console_channel()
|
console_channel = await config.console_channel()
|
||||||
console_commands_enabled = await config.console_commands_enabled()
|
|
||||||
chat_channel = await config.chat_channel()
|
chat_channel = await config.chat_channel()
|
||||||
chat_command = await config.chat_command()
|
chat_command = await config.chat_command()
|
||||||
chat_regex = await config.chat_regex()
|
chat_regex = await config.chat_regex()
|
||||||
|
@ -577,14 +450,10 @@ class Pterodactyl(commands.Cog):
|
||||||
api_endpoint = await config.api_endpoint()
|
api_endpoint = await config.api_endpoint()
|
||||||
invite = await config.invite()
|
invite = await config.invite()
|
||||||
regex_blacklist: dict = await config.regex_blacklist()
|
regex_blacklist: dict = await config.regex_blacklist()
|
||||||
topic_text = await config.topic()
|
|
||||||
topic_hostname = await config.topic_hostname()
|
|
||||||
topic_port = await config.topic_port()
|
|
||||||
embed = discord.Embed(color = await ctx.embed_color(), title="Pterodactyl Configuration")
|
embed = discord.Embed(color = await ctx.embed_color(), title="Pterodactyl Configuration")
|
||||||
embed.description = f"""**Base URL:** {base_url}
|
embed.description = f"""**Base URL:** {base_url}
|
||||||
**Server ID:** `{server_id}`
|
**Server ID:** `{server_id}`
|
||||||
**Console Channel:** <#{console_channel}>
|
**Console Channel:** <#{console_channel}>
|
||||||
**Console Commands:** {self.get_bool_str(console_commands_enabled)}
|
|
||||||
**Chat Channel:** <#{chat_channel}>
|
**Chat Channel:** <#{chat_channel}>
|
||||||
**Startup Message:** {startup_msg}
|
**Startup Message:** {startup_msg}
|
||||||
**Shutdown Message:** {shutdown_msg}
|
**Shutdown Message:** {shutdown_msg}
|
||||||
|
@ -594,10 +463,6 @@ class Pterodactyl(commands.Cog):
|
||||||
**API Endpoint:** `{api_endpoint}`
|
**API Endpoint:** `{api_endpoint}`
|
||||||
**Invite:** {invite}
|
**Invite:** {invite}
|
||||||
|
|
||||||
**Topic Hostname:** `{topic_hostname}`
|
|
||||||
**Topic Port:** `{topic_port}`
|
|
||||||
**Topic Text:** {box(topic_text, 'yaml')}
|
|
||||||
|
|
||||||
**Chat Command:** {box(chat_command, 'json')}
|
**Chat Command:** {box(chat_command, 'json')}
|
||||||
**Chat Regex:** {box(chat_regex, 're')}
|
**Chat Regex:** {box(chat_regex, 're')}
|
||||||
**Server Regex:** {box(server_regex, 're')}
|
**Server Regex:** {box(server_regex, 're')}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from typing import Optional, Union
|
||||||
from typing import Optional, Tuple, Union
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
import websockets
|
import websockets
|
||||||
from pydactyl import PterodactylClient
|
from pydactyl import PterodactylClient
|
||||||
from redbot.core.data_manager import bundled_data_path
|
|
||||||
from redbot.core.utils.chat_formatting import bold, pagify
|
from redbot.core.utils.chat_formatting import bold, pagify
|
||||||
|
|
||||||
from pterodactyl.config import config
|
from pterodactyl.config import config
|
||||||
|
@ -17,7 +15,6 @@ from pterodactyl.pterodactyl import Pterodactyl
|
||||||
|
|
||||||
|
|
||||||
async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
||||||
await coginstance.bot.wait_until_red_ready()
|
|
||||||
base_url = await config.base_url()
|
base_url = await config.base_url()
|
||||||
base_url = base_url[:-1] if base_url.endswith('/') else base_url
|
base_url = base_url[:-1] if base_url.endswith('/') else base_url
|
||||||
|
|
||||||
|
@ -55,18 +52,18 @@ async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
||||||
if await config.mask_ip() is True:
|
if await config.mask_ip() is True:
|
||||||
content = mask_ip(content)
|
content = mask_ip(content)
|
||||||
|
|
||||||
console_channel = coginstance.bot.get_channel(await config.console_channel())
|
channel = coginstance.bot.get_channel(await config.console_channel())
|
||||||
chat_channel = coginstance.bot.get_channel(await config.chat_channel())
|
if channel is not None:
|
||||||
if console_channel is not None:
|
|
||||||
if content.startswith('['):
|
if content.startswith('['):
|
||||||
pagified_content = pagify(content, delims=[" ", "\n"])
|
pagified_content = pagify(content, delims=[" ", "\n"])
|
||||||
for page in pagified_content:
|
for page in pagified_content:
|
||||||
await console_channel.send(content=page, allowed_mentions=discord.AllowedMentions.none())
|
await channel.send(content=page, allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
|
||||||
server_message = await check_if_server_message(content)
|
server_message = await check_if_server_message(content)
|
||||||
if server_message:
|
if server_message:
|
||||||
if chat_channel is not None:
|
channel = coginstance.bot.get_channel(await config.chat_channel())
|
||||||
await chat_channel.send(server_message if len(server_message) < 2000 else server_message[:1997] + '...', allowed_mentions=discord.AllowedMentions.none())
|
if channel is not None:
|
||||||
|
await channel.send(server_message if len(server_message) < 2000 else server_message[:1997] + '...', allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
|
||||||
chat_message = await check_if_chat_message(content)
|
chat_message = await check_if_chat_message(content)
|
||||||
if chat_message:
|
if chat_message:
|
||||||
|
@ -78,37 +75,30 @@ async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
||||||
|
|
||||||
join_message = await check_if_join_message(content)
|
join_message = await check_if_join_message(content)
|
||||||
if join_message:
|
if join_message:
|
||||||
if chat_channel is not None:
|
channel = coginstance.bot.get_channel(await config.chat_channel())
|
||||||
if coginstance.bot.embed_requested(chat_channel):
|
if channel is not None:
|
||||||
embed, img = await generate_join_leave_embed(coginstance=coginstance, username=join_message,join=True)
|
if coginstance.bot.embed_requested(channel):
|
||||||
if img:
|
await channel.send(embed=await generate_join_leave_embed(join_message, True))
|
||||||
with open(img, 'rb') as file:
|
|
||||||
await chat_channel.send(embed=embed, file=file)
|
|
||||||
else:
|
|
||||||
await chat_channel.send(embed=embed)
|
|
||||||
else:
|
else:
|
||||||
await chat_channel.send(f"{join_message} joined the game", allowed_mentions=discord.AllowedMentions.none())
|
await channel.send(f"{join_message} joined the game", allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
|
||||||
leave_message = await check_if_leave_message(content)
|
leave_message = await check_if_leave_message(content)
|
||||||
if leave_message:
|
if leave_message:
|
||||||
if chat_channel is not None:
|
channel = coginstance.bot.get_channel(await config.chat_channel())
|
||||||
if coginstance.bot.embed_requested(chat_channel):
|
if channel is not None:
|
||||||
embed, img = await generate_join_leave_embed(coginstance=coginstance, username=leave_message,join=False)
|
if coginstance.bot.embed_requested(channel):
|
||||||
if img:
|
await channel.send(embed=await generate_join_leave_embed(leave_message, False))
|
||||||
with open(img, 'rb') as file:
|
|
||||||
await chat_channel.send(embed=embed, file=file)
|
|
||||||
else:
|
|
||||||
await chat_channel.send(embed=embed)
|
|
||||||
else:
|
else:
|
||||||
await chat_channel.send(f"{leave_message} left the game", allowed_mentions=discord.AllowedMentions.none())
|
await channel.send(f"{leave_message} left the game", allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
|
||||||
achievement_message = await check_if_achievement_message(content)
|
achievement_message = await check_if_achievement_message(content)
|
||||||
if achievement_message:
|
if achievement_message:
|
||||||
if chat_channel is not None:
|
channel = coginstance.bot.get_channel(await config.chat_channel())
|
||||||
if coginstance.bot.embed_requested(chat_channel):
|
if channel is not None:
|
||||||
await chat_channel.send(embed=await generate_achievement_embed(coginstance, achievement_message['username'], achievement_message['achievement'], achievement_message['challenge']))
|
if coginstance.bot.embed_requested(channel):
|
||||||
|
await channel.send(embed=await generate_achievement_embed(achievement_message['username'], achievement_message['achievement'], achievement_message['challenge']))
|
||||||
else:
|
else:
|
||||||
await chat_channel.send(f"{achievement_message['username']} has {'completed the challenge' if achievement_message['challenge'] else 'made the advancement'} {achievement_message['achievement']}")
|
await channel.send(f"{achievement_message['username']} has {'completed the challenge' if achievement_message['challenge'] else 'made the advancement'} {achievement_message['achievement']}")
|
||||||
|
|
||||||
if message['event'] == 'status':
|
if message['event'] == 'status':
|
||||||
old_status = await config.current_status()
|
old_status = await config.current_status()
|
||||||
|
@ -165,7 +155,7 @@ async def check_if_server_message(text: str) -> Union[bool, str]:
|
||||||
regex = await config.server_regex()
|
regex = await config.server_regex()
|
||||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||||
if match:
|
if match:
|
||||||
logger.trace("Message is a server message")
|
logger.debug("Message is a server message")
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -174,7 +164,7 @@ async def check_if_chat_message(text: str) -> Union[bool, dict]:
|
||||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||||
if match:
|
if match:
|
||||||
groups = {"username": match.group(1), "message": match.group(2)}
|
groups = {"username": match.group(1), "message": match.group(2)}
|
||||||
logger.trace("Message is a chat message\n%s", json.dumps(groups))
|
logger.debug("Message is a chat message\n%s", json.dumps(groups))
|
||||||
return groups
|
return groups
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -182,7 +172,7 @@ async def check_if_join_message(text: str) -> Union[bool, str]:
|
||||||
regex = await config.join_regex()
|
regex = await config.join_regex()
|
||||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||||
if match:
|
if match:
|
||||||
logger.trace("Message is a join message")
|
logger.debug("Message is a join message")
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -190,7 +180,7 @@ async def check_if_leave_message(text: str) -> Union[bool, str]:
|
||||||
regex = await config.leave_regex()
|
regex = await config.leave_regex()
|
||||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||||
if match:
|
if match:
|
||||||
logger.trace("Message is a leave message")
|
logger.debug("Message is a leave message")
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -203,23 +193,23 @@ async def check_if_achievement_message(text: str) -> Union[bool, dict]:
|
||||||
groups["challenge"] = True
|
groups["challenge"] = True
|
||||||
else:
|
else:
|
||||||
groups["challenge"] = False
|
groups["challenge"] = False
|
||||||
logger.trace("Message is an achievement message")
|
logger.debug("Message is an achievement message")
|
||||||
return groups
|
return groups
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_info(username: str) -> Optional[dict]:
|
async def get_info(username: str) -> Optional[dict]:
|
||||||
logger.verbose("Retrieving player info for %s", username)
|
logger.debug("Retrieving player info for %s", username)
|
||||||
endpoint = await config.api_endpoint()
|
endpoint = await config.api_endpoint()
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(f"https://playerdb.co/api/player/{endpoint}/{username}") as response:
|
async with session.get(f"https://playerdb.co/api/player/{endpoint}/{username}") as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
logger.verbose("Player info retrieved for %s", username)
|
logger.debug("Player info retrieved for %s", username)
|
||||||
return await response.json()
|
return await response.json()
|
||||||
logger.warning("Failed to retrieve player info for %s: %s", username, response.status)
|
logger.error("Failed to retrieve player info for %s: %s", username, response.status)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def send_chat_discord(coginstance: Pterodactyl, username: str, message: str, avatar_url: str) -> None:
|
async def send_chat_discord(coginstance: Pterodactyl, username: str, message: str, avatar_url: str) -> None:
|
||||||
logger.trace("Sending chat message to Discord")
|
logger.debug("Sending chat message to Discord")
|
||||||
channel = coginstance.bot.get_channel(await config.chat_channel())
|
channel = coginstance.bot.get_channel(await config.chat_channel())
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
webhooks = await channel.webhooks()
|
webhooks = await channel.webhooks()
|
||||||
|
@ -227,37 +217,33 @@ async def send_chat_discord(coginstance: Pterodactyl, username: str, message: st
|
||||||
if webhook is None:
|
if webhook is None:
|
||||||
webhook = await channel.create_webhook(name="Pterodactyl Chat")
|
webhook = await channel.create_webhook(name="Pterodactyl Chat")
|
||||||
await webhook.send(content=message, username=username, avatar_url=avatar_url, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=True))
|
await webhook.send(content=message, username=username, avatar_url=avatar_url, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=True))
|
||||||
logger.trace("Chat message sent to Discord")
|
logger.debug("Chat message sent to Discord")
|
||||||
else:
|
else:
|
||||||
logger.warning("Chat channel not set. Skipping sending chat message to Discord")
|
logger.warning("Chat channel not set. Skipping sending chat message to Discord")
|
||||||
|
|
||||||
async def generate_join_leave_embed(coginstance: Pterodactyl, username: str, join: bool) -> Tuple[discord.Embed, Optional[Union[str, Path]]]:
|
async def generate_join_leave_embed(username: str, join: bool) -> discord.Embed:
|
||||||
embed = discord.Embed()
|
embed = discord.Embed()
|
||||||
embed.color = discord.Color.green() if join else discord.Color.red()
|
embed.color = discord.Color.green() if join else discord.Color.red()
|
||||||
embed.description = await config.join_msg() if join else await config.leave_msg()
|
embed.description = await config.join_msg() if join else await config.leave_msg()
|
||||||
info = await get_info(username)
|
info = await get_info(username)
|
||||||
if info:
|
if info:
|
||||||
img = None
|
|
||||||
embed.set_author(name=username, icon_url=info['data']['player']['avatar'])
|
embed.set_author(name=username, icon_url=info['data']['player']['avatar'])
|
||||||
else:
|
else:
|
||||||
img = bundled_data_path(coginstance) / "unknown.png"
|
embed.set_author(name=username, icon_url='https://seafsh.cc/u/j3AzqQ.png')
|
||||||
embed.set_author(name=username, icon_url='attachment://unknown.png')
|
|
||||||
embed.timestamp = discord.utils.utcnow()
|
embed.timestamp = discord.utils.utcnow()
|
||||||
return embed, img
|
return embed
|
||||||
|
|
||||||
async def generate_achievement_embed(coginstance: Pterodactyl, username: str, achievement: str, challenge: bool) -> Tuple[discord.Embed, Optional[Union[str, Path]]]:
|
async def generate_achievement_embed(username: str, achievement: str, challenge: bool) -> discord.Embed:
|
||||||
embed = discord.Embed()
|
embed = discord.Embed()
|
||||||
embed.color = discord.Color.from_str('#a800a7') if challenge else discord.Color.from_str('#54fb54')
|
embed.color = discord.Color.from_str('#a800a7') if challenge else discord.Color.from_str('#54fb54')
|
||||||
embed.description = f"{bold(username)} has {'completed the challenge' if challenge else 'made the advancement'} {bold(achievement)}"
|
embed.description = f"{bold(username)} has {'completed the challenge' if challenge else 'made the advancement'} {bold(achievement)}"
|
||||||
info = await get_info(username)
|
info = await get_info(username)
|
||||||
if info:
|
if info:
|
||||||
img = None
|
|
||||||
embed.set_author(name=username, icon_url=info['data']['player']['avatar'])
|
embed.set_author(name=username, icon_url=info['data']['player']['avatar'])
|
||||||
else:
|
else:
|
||||||
img = bundled_data_path(coginstance) / "unknown.png"
|
embed.set_author(name=username, icon_url='https://seafsh.cc/u/j3AzqQ.png')
|
||||||
embed.set_author(name=username, icon_url='attachment://unknown.png')
|
|
||||||
embed.timestamp = discord.utils.utcnow()
|
embed.timestamp = discord.utils.utcnow()
|
||||||
return embed, img
|
return embed
|
||||||
|
|
||||||
def mask_ip(string: str) -> str:
|
def mask_ip(string: str) -> str:
|
||||||
def check(match: re.Match[str]):
|
def check(match: re.Match[str]):
|
||||||
|
|
|
@ -1,44 +1,40 @@
|
||||||
[project]
|
[tool.poetry]
|
||||||
name = "seacogs"
|
name = "seacogs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "My assorted cogs for Red-DiscordBot."
|
description = "My assorted cogs for Red-DiscordBot."
|
||||||
authors = [{name = "cswimr", email = "seaswimmerthefsh@gmail.com"}]
|
authors = ["SeaswimmerTheFsh"]
|
||||||
license = {file="LICENSE"}
|
license = "MPL 2"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
|
||||||
dependencies = [
|
|
||||||
"aiosqlite>=0.20.0",
|
|
||||||
"beautifulsoup4>=4.12.3",
|
|
||||||
"colorthief>=0.2.1",
|
|
||||||
"markdownify>=0.13.1",
|
|
||||||
"numpy>=2.1.2",
|
|
||||||
"phx-class-registry>=5.0.0",
|
|
||||||
"pillow>=10.4.0",
|
|
||||||
"py-dactyl",
|
|
||||||
"pydantic>=2.9.2",
|
|
||||||
"red-discordbot>=3.5.13",
|
|
||||||
"websockets>=13.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[tool.poetry.dependencies]
|
||||||
documentation = [
|
python = ">=3.11,<3.12"
|
||||||
"mkdocs>=1.6.1",
|
Red-DiscordBot = "^3.5.5"
|
||||||
"mkdocs-git-authors-plugin>=0.9.0",
|
pytimeparse2 = "^1.7.1"
|
||||||
"mkdocs-git-revision-date-localized-plugin>=1.2.9",
|
humanize = "^4.8.0"
|
||||||
"mkdocs-material[imaging]>=9.5.40",
|
py-dactyl = "^2.0.4"
|
||||||
"mkdocstrings[python]>=0.26.1",
|
websockets = "^12.0"
|
||||||
"mkdocs-redirects>=1.2.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.uv]
|
[tool.poetry.group.dev]
|
||||||
dev-dependencies = [
|
optional = true
|
||||||
"pylint>=3.3.1",
|
|
||||||
"ruff>=0.6.9",
|
|
||||||
"sqlite-web>=0.6.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.poetry.group.dev.dependencies]
|
||||||
py-dactyl = { git = "https://github.com/cswimr/pydactyl" }
|
ruff = "^0.2.1"
|
||||||
|
pylint = "^3.1.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.docs]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[tool.poetry.group.docs.dependencies]
|
||||||
|
mkdocs = "1.5.3"
|
||||||
|
mkdocstrings = {extras = ["python"], version = "0.24.0"}
|
||||||
|
mkdocs-git-authors-plugin = "0.7.2"
|
||||||
|
mkdocs-git-revision-date-localized-plugin = "1.2.2"
|
||||||
|
mkdocs-material = {extras = ["imaging"], version = "^9.5.2"}
|
||||||
|
mkdocs-redirects = "^1.2.1"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
# Exclude a variety of commonly ignored directories.
|
# Exclude a variety of commonly ignored directories.
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from .seautils import SeaUtils
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
|
||||||
await bot.add_cog(SeaUtils(bot))
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"author" : ["cswimr"],
|
|
||||||
"install_msg" : "Thank you for installing SeaUtils!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
|
||||||
"name" : "SeaUtils",
|
|
||||||
"short" : "A collection of useful utilities.",
|
|
||||||
"description" : "A collection of useful utilities.",
|
|
||||||
"end_user_data_statement" : "This cog does not store end user data.",
|
|
||||||
"hidden": true,
|
|
||||||
"disabled": false,
|
|
||||||
"min_bot_version": "3.5.0",
|
|
||||||
"min_python_version": [3, 8, 0],
|
|
||||||
"requirements": ["beautifulsoup4", "markdownify"]
|
|
||||||
}
|
|
|
@ -1,254 +0,0 @@
|
||||||
# _____ _
|
|
||||||
# / ____| (_)
|
|
||||||
# | (___ ___ __ _ _____ ___ _ __ ___ _ __ ___ ___ _ __
|
|
||||||
# \___ \ / _ \/ _` / __\ \ /\ / / | '_ ` _ \| '_ ` _ \ / _ \ '__|
|
|
||||||
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
|
|
||||||
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import inspect
|
|
||||||
import operator
|
|
||||||
import re
|
|
||||||
from asyncio.subprocess import Process
|
|
||||||
from functools import partial, partialmethod
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import yaml
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from discord import Color, Embed, app_commands
|
|
||||||
from discord.utils import CachedSlotProperty, cached_property
|
|
||||||
from markdownify import MarkdownConverter
|
|
||||||
from redbot.core import commands
|
|
||||||
from redbot.core.bot import Red
|
|
||||||
from redbot.core.dev_commands import cleanup_code
|
|
||||||
from redbot.core.utils import chat_formatting as cf
|
|
||||||
from redbot.core.utils.views import SimpleMenu
|
|
||||||
|
|
||||||
|
|
||||||
def md(soup: BeautifulSoup, **options) -> Any | str:
|
|
||||||
return MarkdownConverter(**options).convert_soup(soup=soup)
|
|
||||||
|
|
||||||
def format_rfc_text(text: str, number: int) -> str:
|
|
||||||
one: str = re.sub(r"\(\.\/rfc(\d+)", r"(https://www.rfc-editor.org/rfc/rfc\1.html", text)
|
|
||||||
two: str = re.sub(r"\((#(?:section|page)-\d+(?:.\d+)?)\)", f"(https://www.rfc-editor.org/rfc/rfc{number}.html\1)", one)
|
|
||||||
three: str = re.sub(r"\n{3,}", "\n\n", two)
|
|
||||||
return three
|
|
||||||
|
|
||||||
class SeaUtils(commands.Cog):
|
|
||||||
"""A collection of random utilities."""
|
|
||||||
|
|
||||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
|
||||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
|
||||||
__version__ = "1.0.1"
|
|
||||||
__documentation__ = "https://seacogs.coastalcommits.com/seautils/"
|
|
||||||
|
|
||||||
def __init__(self, bot: Red) -> None:
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
|
||||||
pre_processed = super().format_help_for_context(ctx) or ""
|
|
||||||
n = "\n" if "\n\n" not in pre_processed else ""
|
|
||||||
text = [
|
|
||||||
f"{pre_processed}{n}",
|
|
||||||
f"{cf.bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
|
||||||
f"{cf.bold('Author:')} {cf.humanize_list(self.__author__)}",
|
|
||||||
f"{cf.bold('Documentation:')} {self.__documentation__}",
|
|
||||||
]
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
|
|
||||||
def format_src(self, obj: Any) -> str:
|
|
||||||
"""A large portion of this code is repurposed from Zephyrkul's RTFS cog.
|
|
||||||
https://github.com/Zephyrkul/FluffyCogs/blob/master/rtfs/rtfs.py"""
|
|
||||||
obj = inspect.unwrap(func=obj)
|
|
||||||
src: Any = getattr(obj, "__func__", obj)
|
|
||||||
if isinstance(obj, (commands.Command, app_commands.Command)):
|
|
||||||
src = obj.callback
|
|
||||||
elif isinstance(obj, (partial, partialmethod)):
|
|
||||||
src = obj.func
|
|
||||||
elif isinstance(obj, property):
|
|
||||||
src = obj.fget
|
|
||||||
elif isinstance(obj, (cached_property, CachedSlotProperty)):
|
|
||||||
src = obj.function
|
|
||||||
return inspect.getsource(object=src)
|
|
||||||
|
|
||||||
@commands.command(aliases=["source", "src", "code", "showsource"])
|
|
||||||
@commands.is_owner()
|
|
||||||
async def showcode(self, ctx: commands.Context, *, object: str) -> None: # pylint: disable=redefined-builtin
|
|
||||||
"""Show the code for a particular object."""
|
|
||||||
try:
|
|
||||||
if object.startswith("/") and (obj := ctx.bot.tree.get_command(object[1:])):
|
|
||||||
text = self.format_src(obj)
|
|
||||||
elif obj := ctx.bot.get_cog(object):
|
|
||||||
text = self.format_src(type(obj))
|
|
||||||
elif obj := ctx.bot.get_command(object):
|
|
||||||
text = self.format_src(obj)
|
|
||||||
else:
|
|
||||||
raise AttributeError
|
|
||||||
temp_content = cf.pagify(
|
|
||||||
text=cleanup_code(text),
|
|
||||||
escape_mass_mentions=True,
|
|
||||||
page_length = 1977
|
|
||||||
)
|
|
||||||
content = []
|
|
||||||
max_i = operator.length_hint(temp_content)
|
|
||||||
i = 1
|
|
||||||
for page in temp_content:
|
|
||||||
content.append(f"**Page {i}/{max_i}**\n{cf.box(page, lang='py')}")
|
|
||||||
i += 1
|
|
||||||
await SimpleMenu(pages=content, disable_after_timeout=True, timeout=180).start(ctx)
|
|
||||||
except (OSError, AttributeError, UnboundLocalError):
|
|
||||||
if ctx.embed_requested():
|
|
||||||
embed = Embed(title="Object not found!", color=await ctx.embed_color())
|
|
||||||
await ctx.send(embed=embed, reference=ctx.message.to_reference(fail_if_not_exists=False))
|
|
||||||
else:
|
|
||||||
await ctx.send(content="Object not found!", reference=ctx.message.to_reference(fail_if_not_exists=False))
|
|
||||||
|
|
||||||
@commands.command(name='dig', aliases=['dnslookup', 'nslookup'])
|
|
||||||
@commands.is_owner()
|
|
||||||
async def dig(self, ctx: commands.Context, name: str, record_type: str | None = None, server: str | None = None, port: int = 53) -> None:
|
|
||||||
"""Retrieve DNS information for a domain.
|
|
||||||
|
|
||||||
Uses `dig` to perform a DNS query. Will fall back to `nslookup` if `dig` is not installed on the system.
|
|
||||||
`nslookup` does not provide as much information as `dig`, so only the `name` parameter will be used if `nslookup` is used.
|
|
||||||
Will return the A, AAAA, and CNAME records for a domain by default. You can specify a different record type with the `type` parameter."""
|
|
||||||
command_opts: list[str | int] = ['dig']
|
|
||||||
query_types: list[str] = [record_type] if record_type else ['A', 'AAAA', 'CNAME']
|
|
||||||
if server:
|
|
||||||
command_opts.extend(['@', server])
|
|
||||||
for query_type in query_types:
|
|
||||||
command_opts.extend([name, query_type])
|
|
||||||
command_opts.extend(['-p', str(port), '+yaml'])
|
|
||||||
|
|
||||||
try:
|
|
||||||
process: Process = await asyncio.create_subprocess_exec(*command_opts, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
|
||||||
stdout, stderr = await process.communicate()
|
|
||||||
if stderr:
|
|
||||||
await ctx.maybe_send_embed(message="An error was encountered!\n" + cf.box(text=stderr.decode()))
|
|
||||||
else:
|
|
||||||
data = yaml.safe_load(stdout.decode())
|
|
||||||
message_data: dict = data[0]['message']
|
|
||||||
response_data: dict = message_data['response_message_data']
|
|
||||||
if ctx.embed_requested():
|
|
||||||
embed = Embed(
|
|
||||||
title="DNS Query Result",
|
|
||||||
color=await ctx.embed_color(),
|
|
||||||
timestamp=message_data['response_time']
|
|
||||||
)
|
|
||||||
embed.add_field(name="Response Address", value=message_data['response_address'], inline=True)
|
|
||||||
embed.add_field(name="Response Port", value=message_data['response_port'], inline=True)
|
|
||||||
embed.add_field(name="Query Address", value=message_data['query_address'], inline=True)
|
|
||||||
embed.add_field(name="Query Port", value=message_data['query_port'], inline=True)
|
|
||||||
embed.add_field(name="Status", value=response_data['status'], inline=True)
|
|
||||||
embed.add_field(name="Flags", value=response_data['flags'], inline=True)
|
|
||||||
|
|
||||||
if response_data.get('status') != 'NOERROR':
|
|
||||||
embed.colour = Color.red()
|
|
||||||
embed.description = cf.error("Dig query did not return `NOERROR` status.")
|
|
||||||
|
|
||||||
questions = []
|
|
||||||
answers = []
|
|
||||||
authorities = []
|
|
||||||
for m in data:
|
|
||||||
response = m['message']['response_message_data']
|
|
||||||
if 'QUESTION_SECTION' in response:
|
|
||||||
for question in response['QUESTION_SECTION']:
|
|
||||||
if question not in questions:
|
|
||||||
questions.append(question)
|
|
||||||
|
|
||||||
if 'ANSWER_SECTION' in response:
|
|
||||||
for answer in response['ANSWER_SECTION']:
|
|
||||||
if answer not in answers:
|
|
||||||
answers.append(answer)
|
|
||||||
|
|
||||||
if 'AUTHORITY_SECTION' in response:
|
|
||||||
for authority in response['AUTHORITY_SECTION']:
|
|
||||||
if authority not in authorities:
|
|
||||||
authorities.append(authority)
|
|
||||||
|
|
||||||
if questions:
|
|
||||||
question_section = "\n".join(questions)
|
|
||||||
embed.add_field(name="Question Section", value=f"{cf.box(text=question_section, lang='prolog')}", inline=False)
|
|
||||||
|
|
||||||
if answers:
|
|
||||||
answer_section = "\n".join(answers)
|
|
||||||
if len(answer_section) > 1024:
|
|
||||||
embed.description = cf.warning("Answer section is too long to fit within embed field, falling back to description.") + cf.box(answer_section)
|
|
||||||
else:
|
|
||||||
embed.add_field(name="Answer Section", value=f"{cf.box(text=answer_section, lang='prolog')}", inline=False)
|
|
||||||
|
|
||||||
if authorities:
|
|
||||||
authority_section = "\n".join(authorities)
|
|
||||||
embed.add_field(name="Authority Section", value=f"{cf.box(text=authority_section, lang='prolog')}", inline=False)
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
else:
|
|
||||||
await ctx.send(content=cf.box(text=stdout, lang='yaml'))
|
|
||||||
except (FileNotFoundError):
|
|
||||||
try:
|
|
||||||
ns_process = await asyncio.create_subprocess_exec('nslookup', name, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
|
||||||
ns_stdout, ns_stderr = await ns_process.communicate()
|
|
||||||
if ns_stderr:
|
|
||||||
await ctx.maybe_send_embed(message="An error was encountered!\n" + cf.box(text=ns_stderr.decode()))
|
|
||||||
else:
|
|
||||||
warning = cf.warning("`dig` is not installed! Defaulting to `nslookup`.\nThis command provides more information when `dig` is installed on the system.\n")
|
|
||||||
if await ctx.embed_requested():
|
|
||||||
embed = Embed(
|
|
||||||
title="DNS Query Result",
|
|
||||||
color=await ctx.embed_color(),
|
|
||||||
timestamp=ctx.message.created_at
|
|
||||||
)
|
|
||||||
embed.description = warning + cf.box(text=ns_stdout.decode())
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
else:
|
|
||||||
await ctx.send(content = warning + cf.box(text=ns_stdout.decode()))
|
|
||||||
except (FileNotFoundError):
|
|
||||||
await ctx.maybe_send_embed(message=cf.error("Neither `dig` nor `nslookup` are installed on the system. Unable to resolve DNS query."))
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def rfc(self, ctx: commands.Context, number: int) -> None:
|
|
||||||
"""Retrieve the text of an RFC document.
|
|
||||||
|
|
||||||
This command uses the [RFC Editor website](https://www.rfc-editor.org/) to fetch the text of an RFC document.
|
|
||||||
A [Request for Comments (RFC)](https://en.wikipedia.org/wiki/Request_for_Comments) is a publication in a series from the principal technical development and standards-setting bodies for the [Internet](https://en.wikipedia.org/wiki/Internet), most prominently the [Internet Engineering Task Force](https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force). An RFC is authored by individuals or groups of engineers and [computer scientists](https://en.wikipedia.org/wiki/Computer_scientist) in the form of a [memorandum](https://en.wikipedia.org/wiki/Memorandum) describing methods, behaviors, research, or innovations applicable to the working of the Internet and Internet-connected systems. It is submitted either for [peer review](https://en.wikipedia.org/wiki/Peer_review) or to convey new concepts, information, or, occasionally, engineering humor.""" # noqa: E501
|
|
||||||
url = f"https://www.rfc-editor.org/rfc/rfc{number}.html"
|
|
||||||
datatracker_url = f"https://datatracker.ietf.org/doc/rfc{number}"
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(url=url) as response:
|
|
||||||
if response.status == 200:
|
|
||||||
html = await response.text()
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
pre_tags = soup.find_all('pre')
|
|
||||||
content: list[Embed | str] = []
|
|
||||||
for pre_tag in pre_tags:
|
|
||||||
text = format_rfc_text(md(pre_tag), number)
|
|
||||||
if len(text) > 4096:
|
|
||||||
pagified_text = cf.pagify(text, delims=["\n\n"], page_length=4096)
|
|
||||||
for page in pagified_text:
|
|
||||||
if await ctx.embed_requested():
|
|
||||||
embed = Embed(
|
|
||||||
title=f"RFC Document {number}",
|
|
||||||
url=datatracker_url,
|
|
||||||
description=page,
|
|
||||||
color=await ctx.embed_color()
|
|
||||||
)
|
|
||||||
content.append(embed)
|
|
||||||
else:
|
|
||||||
content.append(page)
|
|
||||||
else:
|
|
||||||
if await ctx.embed_requested():
|
|
||||||
embed = Embed(
|
|
||||||
title=f"RFC Document {number}",
|
|
||||||
url=datatracker_url,
|
|
||||||
description=text,
|
|
||||||
color=await ctx.embed_color()
|
|
||||||
)
|
|
||||||
content.append(embed)
|
|
||||||
else:
|
|
||||||
content.append(text)
|
|
||||||
if await ctx.embed_requested():
|
|
||||||
for embed in content:
|
|
||||||
embed.set_footer(text=f"Page {content.index(embed) + 1}/{len(content)}")
|
|
||||||
await SimpleMenu(pages=content, disable_after_timeout=True, timeout=300).start(ctx)
|
|
||||||
else:
|
|
||||||
await ctx.maybe_send_embed(message=cf.error(f"An error occurred while fetching RFC {number}. Status code: {response.status}."))
|
|
Loading…
Reference in a new issue