mirror of
https://github.com/neoarz/Syntrel.git
synced 2026-03-27 09:25:40 +01:00
Compare commits
203 Commits
tags
...
minesweepe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50dbe209d0 | ||
|
|
a0a8003992 | ||
|
|
c2d822ec3c | ||
|
|
1da81e68d6 | ||
|
|
4422bd1689 | ||
|
|
05b26b55a4 | ||
|
|
a5f94df549 | ||
|
|
898e01abf4 | ||
|
|
6d596713fb | ||
|
|
5fb7c1b48b | ||
|
|
4b651420a0 | ||
|
|
1d681d4fe0 | ||
|
|
6d164033e1 | ||
|
|
cc01c60250 | ||
|
|
c286c2a070 | ||
|
|
025df159f7 | ||
|
|
352067d51d | ||
|
|
f5227e5f0f | ||
|
|
e271333de1 | ||
|
|
92905435a0 | ||
|
|
e046f3292a | ||
|
|
c8e69b230d | ||
|
|
b18355e749 | ||
|
|
61848f6bc8 | ||
|
|
7955035b3b | ||
|
|
db0368eb4d | ||
|
|
c9626e779f | ||
|
|
cb1596a1d8 | ||
|
|
6a43c93637 | ||
|
|
14cb324e0e | ||
|
|
9e135d29d8 | ||
|
|
372e1e45a3 | ||
|
|
5c6f12ab80 | ||
|
|
6c6555dfb7 | ||
|
|
356d47f812 | ||
|
|
2dab7ade22 | ||
|
|
d381c5cbf0 | ||
|
|
7a4e4ed0f8 | ||
|
|
1e36534b41 | ||
|
|
b1a5267f02 | ||
|
|
89f5aecfd5 | ||
|
|
8f962b2fde | ||
|
|
a94a99ac29 | ||
|
|
0693950468 | ||
|
|
ed53eb375d | ||
|
|
a436388db4 | ||
|
|
b06b0cdb66 | ||
|
|
0fe0f4550a | ||
|
|
2ff4513ebf | ||
|
|
b374223b6c | ||
|
|
c2b0f318c1 | ||
|
|
92e0ab72de | ||
|
|
c988bac1b0 | ||
|
|
24d0a99055 | ||
|
|
d2fbb5f55b | ||
|
|
50af638e2d | ||
|
|
71cb384ff3 | ||
|
|
743bf82bda | ||
|
|
4bb78d6eae | ||
|
|
dccf979a5a | ||
|
|
beffefcb14 | ||
|
|
94a0e89dc3 | ||
|
|
087b8095e2 | ||
|
|
c099df1a45 | ||
|
|
c80dca059f | ||
|
|
f946efd5ce | ||
|
|
78f5e80b93 | ||
|
|
fae21eed44 | ||
|
|
94d0eca8f1 | ||
|
|
47043fb38b | ||
|
|
56818bf912 | ||
|
|
eb800a9bd5 | ||
|
|
7419ec5f59 | ||
|
|
70af8e24b2 | ||
|
|
25ea98236d | ||
|
|
9c7359906c | ||
|
|
78f6959d04 | ||
|
|
18bac4a125 | ||
|
|
7dc62b9340 | ||
|
|
9943e6fcc3 | ||
|
|
2f2a426fe0 | ||
|
|
32b5f1cc32 | ||
|
|
abebd06227 | ||
|
|
b5c3f0f93e | ||
|
|
c07743dc75 | ||
|
|
19c20af504 | ||
|
|
1b636e6621 | ||
|
|
f9207c3f56 | ||
|
|
cc3e5e4a75 | ||
|
|
11b9effcb6 | ||
|
|
f7f4372cf7 | ||
|
|
f26da6bf80 | ||
|
|
6fc24ef8f6 | ||
|
|
5128706f9a | ||
|
|
4f66c3705a | ||
|
|
b26d786d23 | ||
|
|
592d697140 | ||
|
|
804eaa39a6 | ||
|
|
ad1e117e75 | ||
|
|
023ec85640 | ||
|
|
b28b4e2cbc | ||
|
|
ec9c90e4e8 | ||
|
|
73535e283d | ||
|
|
9fafc3ca4f | ||
|
|
55286a8d15 | ||
|
|
4cc42ba7da | ||
|
|
2c0b0afb39 | ||
|
|
85a43e66b0 | ||
|
|
f4c3b4aae7 | ||
|
|
4e258dd174 | ||
|
|
b0a82ed07e | ||
|
|
b770ff8f37 | ||
|
|
3f0cd619ac | ||
|
|
60d54c434b | ||
|
|
c0afe06e4d | ||
|
|
6ad1e296f1 | ||
|
|
e92b768294 | ||
|
|
0cd8f3fbe2 | ||
|
|
3f4254dcdc | ||
|
|
f8123b43a2 | ||
|
|
a67e80a6eb | ||
|
|
2973d0fd25 | ||
|
|
49fcd821a4 | ||
|
|
aea98a95d5 | ||
|
|
a7cdeee470 | ||
|
|
b6c74828f1 | ||
|
|
a5eff449eb | ||
|
|
3bc5ebe192 | ||
|
|
5e2999e565 | ||
|
|
e5ea7f1dac | ||
|
|
51393ece85 | ||
|
|
72cdd9b403 | ||
|
|
e7ee97074b | ||
|
|
6ed3b0d378 | ||
|
|
4948d2edf7 | ||
|
|
0a262c522e | ||
|
|
bcb8cf3326 | ||
|
|
85526653f2 | ||
|
|
c236ebd84a | ||
|
|
00a49fa040 | ||
|
|
d790dc64ef | ||
|
|
a0e8665f6d | ||
|
|
474cb244a9 | ||
|
|
c2c12d38ce | ||
|
|
158d1903a9 | ||
|
|
12349e39f6 | ||
|
|
67aed99e5a | ||
|
|
7f3efa964c | ||
|
|
8d5db7f4cc | ||
|
|
f9923e0479 | ||
|
|
a305c87f4a | ||
|
|
bb46f61a61 | ||
|
|
5907487d31 | ||
|
|
fd2f83765f | ||
|
|
ad1ae76e6c | ||
|
|
e0640227b9 | ||
|
|
5509f19647 | ||
|
|
3b5a2793da | ||
|
|
c980b36915 | ||
|
|
8f1f0e0eeb | ||
|
|
5398d16528 | ||
|
|
6303234e65 | ||
|
|
b492d60db3 | ||
|
|
48d1b875a9 | ||
|
|
b4df04acc5 | ||
|
|
cd810659b4 | ||
|
|
825e81a24f | ||
|
|
153b9cedfc | ||
|
|
ef7b6b9158 | ||
|
|
5f43289671 | ||
|
|
acfc161322 | ||
|
|
b90d621c4a | ||
|
|
f83357c88c | ||
|
|
3c59dec58b | ||
|
|
9da3d935ae | ||
|
|
cd6e5fc47a | ||
|
|
12d6c77228 | ||
|
|
f265591cd5 | ||
|
|
84d3e5df33 | ||
|
|
deef9156fa | ||
|
|
e0147a683d | ||
|
|
ede9168c9a | ||
|
|
a5faf609fb | ||
|
|
f28ae00d7d | ||
|
|
2e818d3453 | ||
|
|
bed7d46f5b | ||
|
|
657452a553 | ||
|
|
d51d8f1bc3 | ||
|
|
5339a853b2 | ||
|
|
9793445d59 | ||
|
|
53c4e742ef | ||
|
|
301bafc435 | ||
|
|
a9abf5488a | ||
|
|
461affb32e | ||
|
|
a0a85d877a | ||
|
|
8009c59d46 | ||
|
|
1c06f441c1 | ||
|
|
7cb0f9dec0 | ||
|
|
63e49bcb7b | ||
|
|
eba240302f | ||
|
|
1d1d13dfd5 | ||
|
|
740dbffd52 | ||
|
|
bbf5a0bb12 |
@@ -3,4 +3,10 @@ PREFIX=YOUR_BOT_PREFIX_HERE
|
||||
INVITE_LINK=YOUR_BOT_INVITE_LINK_HERE
|
||||
|
||||
# Commands you want to disable (comma separated)
|
||||
DISABLED_COGS=general.context_menus # Context Menus
|
||||
DISABLED_COGS=general.context_menus
|
||||
|
||||
# yt-dlp cookies (for media/download command)
|
||||
YTDLP_COOKIE_FILE=/bot/cogs/media/files/cookies.txt
|
||||
|
||||
# User IDs for co ownership (comma seperated)
|
||||
OWNER_FRIENDS=0000000000,000000000,000000000
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -140,8 +140,15 @@ cython_debug/
|
||||
# PyCharm IDEA
|
||||
.idea/*
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# SQLITE database
|
||||
*.db
|
||||
|
||||
# Log file
|
||||
discord.log
|
||||
discord.log
|
||||
|
||||
cogs/media/files/
|
||||
|
||||
logs.txt
|
||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
@@ -0,0 +1 @@
|
||||
print("Hello World")
|
||||
@@ -3,6 +3,8 @@ FROM python:3.12.9-slim-bookworm
|
||||
WORKDIR /bot
|
||||
COPY . /bot
|
||||
|
||||
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN python -m pip install -r requirements.txt
|
||||
|
||||
ENTRYPOINT [ "python", "bot.py" ]
|
||||
44
LICENSE
44
LICENSE
@@ -1,27 +1,35 @@
|
||||
Syntrel Discord Bot
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 neoarz
|
||||
|
||||
This project contains code from multiple sources under different licenses:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THIRD-PARTY SOFTWARE:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
Python Discord Bot Template
|
||||
Copyright © Krypton 2019-Present
|
||||
Licensed under the Apache License, Version 2.0
|
||||
Source: https://github.com/kkrypt0nn/Python-Discord-Bot-Template
|
||||
|
||||
The original Apache License, Version 2.0 applies to these components.
|
||||
See [LICENSE-APACHE](licenses/LICENSE-APACHE) for the full license text.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NEW CONTRIBUTIONS:
|
||||
---
|
||||
|
||||
Original code and modifications by neoarz are licensed under the MIT License.
|
||||
See [LICENSE-MIT](licenses/LICENSE-MIT) for the full license text.
|
||||
ASSETS RESTRICTION NOTICE:
|
||||
|
||||
This includes but is not limited to:
|
||||
Custom commands and cogs not present in the original template
|
||||
Modifications and enhancements to existing functionality
|
||||
New features and integrations
|
||||
Documentation and configuration files created by neoarz
|
||||
The following files and directories are NOT covered by the MIT License and are
|
||||
restricted for use:
|
||||
|
||||
- assets/ (all files in this directory)
|
||||
- Any image files, links, graphics, logos, or media files
|
||||
- Any proprietary content or branding materials
|
||||
|
||||
These assets remain the exclusive property of neoarz and may not be used,
|
||||
distributed, or modified without explicit written permission.
|
||||
|
||||
49
README.md
49
README.md
@@ -8,21 +8,46 @@
|
||||
|
||||
<br>
|
||||
|
||||
> [!NOTE]
|
||||
> **New Project**
|
||||
>
|
||||
> This project is still in the making!!! I would appreciate if you gave this repo a star, that would mean a lot to me!
|
||||
<div align="center">
|
||||
|
||||
**All in one discord bot for helping with [SideStore](https://discord.gg/3DwCwpBHfv), [idevice](https://discord.gg/ZnNcrRT3M8), [MeloNX](https://discord.gg/Q4VkbkYfmk), and more.**
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
## Commands
|
||||
|
||||

|
||||
|
||||
| Command group | Subcommands |
|
||||
| ------------ | --- |
|
||||
| ungrouped | `help`, `botinfo` |
|
||||
| owner | `sync`, `cog_management`, `shutdown`, `say`, `invite`, `logs` |
|
||||
| general | `serverinfo`, `ping`, `feedback`, `uptime`, `userinfo` |
|
||||
| fun | `randomfact`, `coinflip`, `rps`, `8ball`, `minesweeper` |
|
||||
| moderation | `kick`, `ban`, `nick`, `purge`, `hackban`, `warnings`, `archive`, `timeout` |
|
||||
| sidestore | `help`, `refresh`, `code`, `crash`, `pairing`, `server`, `half`, `sparse`, `afc`, `udid` |
|
||||
| idevice | `help`, `noapps`, `errorcode`, `developermode`, `mountddi` |
|
||||
| melonx | `help`, `transfer`, `mods`, `gamecrash`, `requirements`, `error`, `26`, `legal` |
|
||||
| miscellaneous | `keanu`, `labubu`, `piracy`, `tryitandsee`, `rickroll`, `dontasktoask`, `support`, `depart`, `docs` `sigma`, `duck`, `silly`, `color` |
|
||||
| utilities | `translate`, `codepreview`, `dictionary` |
|
||||
| media | `download`, `mcquote`, `img2gif`, `tweety`, `tts` |
|
||||
|
||||
|
||||
## Licenses
|
||||
## Download
|
||||
|
||||
#### Third-Party Components (Apache 2.0)
|
||||
- Code derived from the [Python Discord Bot Template](https://github.com/kkrypt0nn/Python-Discord-Bot-Template) by [kkrypt0nn](https://github.com/kkrypt0nn) remains under the Apache License 2.0
|
||||
- See [LICENSE-APACHE](licenses/LICENSE-APACHE) for the full Apache 2.0 license text
|
||||
<div align="left">
|
||||
<a href="https://discord.com/oauth2/authorize?client_id=1376728824108286034" target="_blank" rel="noopener noreferrer">
|
||||
<img src="assets/download.png" alt="Download" style="width: 290px; height: auto;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
#### Original Contributions (MIT)
|
||||
- New code, modifications, and enhancements by neoarz are licensed under the MIT License
|
||||
- See [LICENSE-MIT](licenses/LICENSE-MIT) for the full MIT license text
|
||||
## Contributing
|
||||
|
||||
See [LICENSE](LICENSE) file for detailed attribution information
|
||||
Contributions are welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for guidelines.
|
||||
|
||||
## License
|
||||
|
||||
Dual licensed project, see [`LICENSE`](LICENSE) file for detailed attribution information
|
||||
|
||||
80
TODO.md
Normal file
80
TODO.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Todo List for Syntrel
|
||||
|
||||
- [x] Finish [idevice commands](https://github.com/jkcoxson/idevice/blob/master/idevice/src/lib.rs#L522)
|
||||
- [x] Add /idevice command
|
||||
- [x] Add /no apps command
|
||||
- [x] Add rest of the errors yikes
|
||||
- [x] Add ddi mounting command
|
||||
|
||||
- [x] Add melonx commands
|
||||
- [x] Add /melonx command
|
||||
- [x] Add /transfer command
|
||||
- [x] Add /mods command
|
||||
- [x] Add /gamecrash command
|
||||
- [x] Add /requirements command
|
||||
- [x] Add /error command
|
||||
- [x] Add /26 command
|
||||
|
||||
- [ ] Add ai commands
|
||||
|
||||
- [ ] Add leaderboard command
|
||||
|
||||
- [ ] Add unit tests
|
||||
|
||||
- [ ] Add documentation to readme
|
||||
|
||||
- [ ] Finish CONTRIBUTING.md
|
||||
|
||||
- [ ] Create github action
|
||||
|
||||
- [x] Add video commands
|
||||
|
||||
- [ ] Add git log to info
|
||||
|
||||
- [ ] Move all assets to gh
|
||||
|
||||
- [x] Add codepreview for pull requests
|
||||
|
||||
- [x] Add try it and see command
|
||||
|
||||
- [x] ~~Add rickroll command~~
|
||||
|
||||
- [ ] Add letmegoogle command
|
||||
|
||||
- [ ] Add sending code (regular and from github)
|
||||
|
||||
- [x] Add random color command
|
||||
|
||||
- [x] Add translate command
|
||||
|
||||
- [ ] Add random picture command
|
||||
|
||||
- [ ] Add reminder command
|
||||
|
||||
- [ ] Add converting file types
|
||||
|
||||
- [ ] fix code preview command
|
||||
|
||||
- [ ] Add moderation giverole command
|
||||
|
||||
- [ ] add ~~admin abuse~~ command (for games)
|
||||
|
||||
- [ ] switch embeds to double embeds check [botinfo command](https://github.com/neoarz/Syntrel/blob/main/cogs/botinfo.py#L80)
|
||||
|
||||
- [ ] Add co-owner commands
|
||||
|
||||
- [ ] Add logs to channel
|
||||
|
||||
- [ ] make /codepreview into /github and it can parse any github url
|
||||
|
||||
- [ ] Add idevice livecontainer command
|
||||
|
||||
- [x] add [tts](https://developer.puter.com/tutorials/free-unlimited-text-to-speech-api/) command
|
||||
|
||||
- [x] update botinfo command
|
||||
|
||||
- [ ] Clean tag system from [tags branch](https://github.com/neoarz/Syntrel/tree/tags) (make sure db is persistent)
|
||||
|
||||
- [x] Fix logging and add graceful shutdown
|
||||
|
||||
- [x] Add uptime checker for the bot iself
|
||||
BIN
assets/IMG_8453.jpeg
Normal file
BIN
assets/IMG_8453.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
assets/discordbanner.png
Normal file
BIN
assets/discordbanner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
assets/download.png
Normal file
BIN
assets/download.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 985 KiB |
BIN
assets/icon.png
Normal file
BIN
assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 516 KiB |
BIN
assets/iconbeta.png
Normal file
BIN
assets/iconbeta.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 610 KiB |
221
bot.py
221
bot.py
@@ -1,9 +1,12 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
import aiosqlite
|
||||
import discord
|
||||
@@ -12,22 +15,22 @@ from discord.ext.commands import Context
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from database import DatabaseManager
|
||||
from utils import ascii, setup_logger, get_uptime, setup_signal_handlers
|
||||
|
||||
load_dotenv()
|
||||
|
||||
"""
|
||||
Setup bot intents (events restrictions)
|
||||
For more information about intents, please go to the following websites:
|
||||
https://discordpy.readthedocs.io/en/latest/intents.html
|
||||
https://discordpy.readthedocs.io/en/latest/intents.html#privileged-intents
|
||||
|
||||
|
||||
Default Intents:
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
intents.bans = True
|
||||
intents.dm_messages = True
|
||||
intents.dm_reactions = True
|
||||
intents.dm_typing = True
|
||||
intents.emojis = True
|
||||
intents.messages = True
|
||||
intents.reactions = True
|
||||
intents.typing = True
|
||||
intents.voice_states = True
|
||||
intents.webhooks = True
|
||||
intents.emojis_and_stickers = True
|
||||
intents.guild_messages = True
|
||||
intents.guild_reactions = True
|
||||
@@ -36,74 +39,12 @@ intents.guild_typing = True
|
||||
intents.guilds = True
|
||||
intents.integrations = True
|
||||
intents.invites = True
|
||||
intents.messages = True # `message_content` is required to get the content of the messages
|
||||
intents.reactions = True
|
||||
intents.typing = True
|
||||
intents.voice_states = True
|
||||
intents.webhooks = True
|
||||
|
||||
Privileged Intents (Needs to be enabled on developer portal of Discord), please use them only if you need them:
|
||||
intents.members = True
|
||||
intents.message_content = True
|
||||
intents.presences = True
|
||||
"""
|
||||
|
||||
intents = discord.Intents.default()
|
||||
|
||||
"""
|
||||
Uncomment this if you want to use prefix (normal) commands.
|
||||
It is recommended to use slash commands and therefore not use prefix commands.
|
||||
|
||||
If you want to use prefix commands, make sure to also enable the intent below in the Discord developer portal.
|
||||
"""
|
||||
intents.message_content = True
|
||||
intents.members = True
|
||||
|
||||
|
||||
|
||||
class LoggingFormatter(logging.Formatter):
|
||||
# Colors
|
||||
black = "\x1b[30m"
|
||||
red = "\x1b[31m"
|
||||
green = "\x1b[32m"
|
||||
yellow = "\x1b[33m"
|
||||
blue = "\x1b[34m"
|
||||
gray = "\x1b[38m"
|
||||
# Styles
|
||||
reset = "\x1b[0m"
|
||||
bold = "\x1b[1m"
|
||||
|
||||
COLORS = {
|
||||
logging.DEBUG: gray + bold,
|
||||
logging.INFO: blue + bold,
|
||||
logging.WARNING: yellow + bold,
|
||||
logging.ERROR: red,
|
||||
logging.CRITICAL: red + bold,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
log_color = self.COLORS[record.levelno]
|
||||
format = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}"
|
||||
format = format.replace("(black)", self.black + self.bold)
|
||||
format = format.replace("(reset)", self.reset)
|
||||
format = format.replace("(levelcolor)", log_color)
|
||||
format = format.replace("(green)", self.green + self.bold)
|
||||
formatter = logging.Formatter(format, "%Y-%m-%d %H:%M:%S", style="{")
|
||||
return formatter.format(record)
|
||||
|
||||
|
||||
logger = logging.getLogger("discord_bot")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(LoggingFormatter())
|
||||
file_handler = logging.FileHandler(filename="discord.log", encoding="utf-8", mode="w")
|
||||
file_handler_formatter = logging.Formatter(
|
||||
"[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{"
|
||||
)
|
||||
file_handler.setFormatter(file_handler_formatter)
|
||||
|
||||
logger.addHandler(console_handler)
|
||||
logger.addHandler(file_handler)
|
||||
logger = setup_logger()
|
||||
|
||||
|
||||
class DiscordBot(commands.Bot):
|
||||
@@ -113,18 +54,12 @@ class DiscordBot(commands.Bot):
|
||||
intents=intents,
|
||||
help_command=None,
|
||||
)
|
||||
"""
|
||||
This creates custom bot variables so that we can access these variables in cogs more easily.
|
||||
|
||||
For example, The logger is available using the following code:
|
||||
- self.logger # In this class
|
||||
- bot.logger # In this file
|
||||
- self.bot.logger # In cogs
|
||||
"""
|
||||
self.logger = logger
|
||||
self.database = None
|
||||
self.bot_prefix = os.getenv("PREFIX")
|
||||
self.invite_link = os.getenv("INVITE_LINK")
|
||||
self.start_time = time.time()
|
||||
self._shutdown = False
|
||||
|
||||
async def init_db(self) -> None:
|
||||
async with aiosqlite.connect(
|
||||
@@ -138,9 +73,6 @@ class DiscordBot(commands.Bot):
|
||||
await db.commit()
|
||||
|
||||
async def load_cogs(self) -> None:
|
||||
"""
|
||||
The code in this function is executed whenever the bot will start.
|
||||
"""
|
||||
cogs_path = f"{os.path.realpath(os.path.dirname(__file__))}/cogs"
|
||||
disabled_env = os.getenv("DISABLED_COGS", "")
|
||||
disabled_cogs = {entry.strip().lower() for entry in disabled_env.split(",") if entry.strip()}
|
||||
@@ -148,21 +80,33 @@ class DiscordBot(commands.Bot):
|
||||
for folder in os.listdir(cogs_path):
|
||||
folder_path = os.path.join(cogs_path, folder)
|
||||
if os.path.isdir(folder_path) and not folder.startswith('__'):
|
||||
for file in os.listdir(folder_path):
|
||||
if file.endswith(".py") and not file.startswith('__'):
|
||||
extension = file[:-3]
|
||||
full_name = f"{folder}.{extension}".lower()
|
||||
if extension.lower() in disabled_cogs or full_name in disabled_cogs:
|
||||
self.logger.info(f"Skipped disabled extension '{full_name}'")
|
||||
continue
|
||||
try:
|
||||
await self.load_extension(f"cogs.{folder}.{extension}")
|
||||
self.logger.info(f"Loaded extension '{folder}.{extension}'")
|
||||
except Exception as e:
|
||||
exception = f"{type(e).__name__}: {e}"
|
||||
self.logger.error(
|
||||
f"Failed to load extension {folder}.{extension}\n{exception}"
|
||||
)
|
||||
init_file = os.path.join(folder_path, "__init__.py")
|
||||
if os.path.exists(init_file):
|
||||
try:
|
||||
await self.load_extension(f"cogs.{folder}")
|
||||
if folder in ["owner", "help", "botinfo"]:
|
||||
self.logger.info(f"Loaded extension '{folder}'")
|
||||
except Exception as e:
|
||||
exception = f"{type(e).__name__}: {e}"
|
||||
self.logger.error(
|
||||
f"Failed to load extension {folder}\n{exception}"
|
||||
)
|
||||
else:
|
||||
for file in os.listdir(folder_path):
|
||||
if file.endswith(".py") and not file.startswith('__'):
|
||||
extension = file[:-3]
|
||||
full_name = f"{folder}.{extension}".lower()
|
||||
if extension.lower() in disabled_cogs or full_name in disabled_cogs:
|
||||
self.logger.info(f"Skipped disabled extension '{full_name}'")
|
||||
continue
|
||||
try:
|
||||
await self.load_extension(f"cogs.{folder}.{extension}")
|
||||
self.logger.info(f"Loaded extension '{folder}.{extension}'")
|
||||
except Exception as e:
|
||||
exception = f"{type(e).__name__}: {e}"
|
||||
self.logger.error(
|
||||
f"Failed to load extension {folder}.{extension}\n{exception}"
|
||||
)
|
||||
|
||||
for file in os.listdir(cogs_path):
|
||||
if file.endswith(".py") and not file.startswith('__'):
|
||||
@@ -172,7 +116,8 @@ class DiscordBot(commands.Bot):
|
||||
continue
|
||||
try:
|
||||
await self.load_extension(f"cogs.{extension}")
|
||||
self.logger.info(f"Loaded extension '{extension}'")
|
||||
if extension in ["owner", "help", "botinfo"]:
|
||||
self.logger.info(f"Loaded extension '{extension}'")
|
||||
except Exception as e:
|
||||
exception = f"{type(e).__name__}: {e}"
|
||||
self.logger.error(
|
||||
@@ -181,23 +126,14 @@ class DiscordBot(commands.Bot):
|
||||
|
||||
@tasks.loop(minutes=1.0)
|
||||
async def status_task(self) -> None:
|
||||
"""
|
||||
Setup the game status task of the bot.
|
||||
"""
|
||||
statuses = ["Sidestore", "MeloNX", "Armsx2", "Stikdebug", "Feather"]
|
||||
statuses = ["SideStore", "MeloNX", "ARMSX2", "StikDebug", "Feather"]
|
||||
await self.change_presence(activity=discord.Game(random.choice(statuses)))
|
||||
|
||||
@status_task.before_loop
|
||||
async def before_status_task(self) -> None:
|
||||
"""
|
||||
Before starting the status changing task, we make sure the bot is ready
|
||||
"""
|
||||
await self.wait_until_ready()
|
||||
|
||||
async def setup_hook(self) -> None:
|
||||
"""
|
||||
This will just be executed when the bot starts the first time.
|
||||
"""
|
||||
self.logger.info(f"Logged in as {self.user.name}")
|
||||
self.logger.info(f"discord.py API version: {discord.__version__}")
|
||||
self.logger.info(f"Python version: {platform.python_version()}")
|
||||
@@ -215,6 +151,7 @@ class DiscordBot(commands.Bot):
|
||||
self.logger.info(f"Bot owner: {app_info.owner.name} (ID: {app_info.owner.id})")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching application info: {e}")
|
||||
|
||||
|
||||
await self.init_db()
|
||||
await self.load_cogs()
|
||||
@@ -225,16 +162,37 @@ class DiscordBot(commands.Bot):
|
||||
)
|
||||
)
|
||||
|
||||
async def on_message(self, message: discord.Message) -> None:
|
||||
"""
|
||||
The code in this event is executed every time someone sends a message, with or without the prefix
|
||||
def get_uptime(self) -> str:
|
||||
return get_uptime(self.start_time)
|
||||
|
||||
:param message: The message that was sent.
|
||||
"""
|
||||
async def close(self) -> None:
|
||||
if self._shutdown:
|
||||
return
|
||||
self._shutdown = True
|
||||
|
||||
self.logger.info("Starting shutdown process...")
|
||||
|
||||
if self.status_task and not self.status_task.is_being_cancelled():
|
||||
self.status_task.cancel()
|
||||
self.logger.info("Status task cancelled")
|
||||
|
||||
if self.database and self.database.connection:
|
||||
try:
|
||||
await self.database.connection.close()
|
||||
self.logger.warning("Database connection closed")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error closing database connection: {e}")
|
||||
|
||||
try:
|
||||
await super().close()
|
||||
self.logger.critical("Bot shutdown complete")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during bot shutdown: {e}")
|
||||
|
||||
async def on_message(self, message: discord.Message) -> None:
|
||||
if message.author == self.user or message.author.bot:
|
||||
return
|
||||
|
||||
# Reacts to messages which mention the bot
|
||||
if self.user in message.mentions:
|
||||
try:
|
||||
emoji_string = "<a:PandaPing:1417550314260926575>"
|
||||
@@ -252,14 +210,13 @@ class DiscordBot(commands.Bot):
|
||||
await self.process_commands(message)
|
||||
|
||||
async def on_command_completion(self, context: Context) -> None:
|
||||
"""
|
||||
The code in this event is executed every time a normal command has been *successfully* executed.
|
||||
|
||||
:param context: The context of the command that has been executed.
|
||||
"""
|
||||
full_command_name = context.command.qualified_name
|
||||
split = full_command_name.split(" ")
|
||||
executed_command = str(split[0])
|
||||
|
||||
if executed_command.lower() in ["shutdown", "say", "embed"]:
|
||||
return
|
||||
|
||||
if context.guild is not None:
|
||||
self.logger.info(
|
||||
f"Executed {executed_command} command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})"
|
||||
@@ -270,12 +227,6 @@ class DiscordBot(commands.Bot):
|
||||
)
|
||||
|
||||
async def on_command_error(self, context: Context, error) -> None:
|
||||
"""
|
||||
The code in this event is executed every time a normal valid command catches an error.
|
||||
|
||||
:param context: The context of the normal command that failed executing.
|
||||
:param error: The error that has been faced.
|
||||
"""
|
||||
if isinstance(error, commands.CommandNotFound):
|
||||
if context.guild is not None:
|
||||
self.logger.info(
|
||||
@@ -323,14 +274,28 @@ class DiscordBot(commands.Bot):
|
||||
elif isinstance(error, commands.MissingRequiredArgument):
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
# We need to capitalize because the command arguments have no capital letter in the code and they are the first word in the error message.
|
||||
description=str(error).capitalize(),
|
||||
color=0xE02B2B,
|
||||
)
|
||||
await context.send(embed=embed)
|
||||
elif isinstance(error, commands.CheckFailure):
|
||||
return
|
||||
else:
|
||||
raise error
|
||||
|
||||
|
||||
bot = DiscordBot()
|
||||
bot.run(os.getenv("TOKEN"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.system('clear' if os.name == 'posix' else 'cls')
|
||||
|
||||
print(ascii)
|
||||
|
||||
setup_signal_handlers(bot)
|
||||
|
||||
try:
|
||||
bot.run(os.getenv("TOKEN"))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
179
cogs/botinfo.py
Normal file
179
cogs/botinfo.py
Normal file
@@ -0,0 +1,179 @@
|
||||
import platform
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from datetime import datetime
|
||||
import time
|
||||
import pytz
|
||||
from utils.contributors import generate_contributors_image
|
||||
|
||||
|
||||
class FeedbackForm(discord.ui.Modal, title="Feedback"):
|
||||
feedback = discord.ui.TextInput(
|
||||
label="What do you think about this bot?",
|
||||
style=discord.TextStyle.long,
|
||||
placeholder="Type your answer here...",
|
||||
required=True,
|
||||
max_length=256,
|
||||
)
|
||||
|
||||
def __init__(self, bot):
|
||||
super().__init__()
|
||||
self.bot = bot
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
await interaction.response.send_message(
|
||||
embed=discord.Embed(
|
||||
title="Thank You!",
|
||||
description="Your feedback has been submitted, the owners have been notified about it.",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png"),
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
app_owner = (await self.bot.application_info()).owner
|
||||
await app_owner.send(
|
||||
embed=discord.Embed(
|
||||
title="New Feedback",
|
||||
description=f"{interaction.user} (<@{interaction.user.id}>) has submitted a new feedback:\n```\n{self.feedback.value}\n```",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
)
|
||||
|
||||
|
||||
class BotInfoView(discord.ui.View):
|
||||
def __init__(self, bot):
|
||||
super().__init__(timeout=None)
|
||||
self.bot = bot
|
||||
|
||||
github_emoji = discord.PartialEmoji(name="githubicon", id=1417717356846776340)
|
||||
github_button = discord.ui.Button(
|
||||
label="GitHub",
|
||||
emoji=github_emoji,
|
||||
url="https://github.com/neoarz/syntrel",
|
||||
style=discord.ButtonStyle.link
|
||||
)
|
||||
self.add_item(github_button)
|
||||
|
||||
feedback_emoji = discord.PartialEmoji(name="ThumbsUpBlueEmoji", id=1426066711500554302)
|
||||
feedback_button = discord.ui.Button(
|
||||
label="Feedback",
|
||||
emoji=feedback_emoji,
|
||||
style=discord.ButtonStyle.secondary,
|
||||
custom_id="feedback_button"
|
||||
)
|
||||
feedback_button.callback = self.feedback_callback
|
||||
self.add_item(feedback_button)
|
||||
|
||||
bug_emoji = discord.PartialEmoji(name="BugHunterBadge", id=1425703361625460856)
|
||||
bug_button = discord.ui.Button(
|
||||
label="Bug Report",
|
||||
emoji=bug_emoji,
|
||||
url="https://github.com/neoarz/Syntrel/issues",
|
||||
style=discord.ButtonStyle.link
|
||||
)
|
||||
self.add_item(bug_button)
|
||||
|
||||
async def feedback_callback(self, interaction: discord.Interaction):
|
||||
feedback_form = FeedbackForm(self.bot)
|
||||
await interaction.response.send_modal(feedback_form)
|
||||
|
||||
|
||||
class BotInfo(commands.Cog, name="botinfo"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_guild_join(self, guild):
|
||||
channel = guild.system_channel or next(
|
||||
(c for c in guild.text_channels if c.permissions_for(guild.me).send_messages),
|
||||
None
|
||||
)
|
||||
|
||||
if channel:
|
||||
ny_tz = pytz.timezone('America/New_York')
|
||||
current_time = datetime.now(ny_tz).strftime("%m/%d/%y, %I:%M %p")
|
||||
|
||||
description_text = (
|
||||
"Heyooo! I'm Syntrel, a bot made to help with [SideStore](https://discord.gg/3DwCwpBHfv), [MeloNX](https://discord.gg/Q4VkbkYfmk), and [idevice](https://discord.gg/ZnNcrRT3M8). I even have some cool extras! If you encounter any issues, please file a bug report. If you have any feedback or suggestions, simply select \"Feedback\"! <:HeardPanda:1417619745896660992>\n\n"
|
||||
"**New to Syntrel?** Run `/help` to get started and explore all available commands!\n\n"
|
||||
f"**Owner:** [neoarz](https://discordapp.com/users/1015372540937502851)\n"
|
||||
f"**Python Version:** {platform.python_version()}\n"
|
||||
f"**Discord.py Version:** {discord.__version__}\n"
|
||||
f"**Prefix:** / (Slash Commands) or {self.bot.bot_prefix} for normal commands"
|
||||
)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Syntrel Discord Bot",
|
||||
description=description_text,
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Syntrel", icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true")
|
||||
embed.set_image(url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png")
|
||||
embed.set_footer(text=f"neoarz • {current_time}", icon_url="https://yes.nighty.works/raw/P1Us35.webp")
|
||||
|
||||
view = BotInfoView(self.bot)
|
||||
|
||||
await channel.send(embed=embed, view=view)
|
||||
else:
|
||||
self.bot.logger.warning(f"Couldn't find a suitable channel in {guild.name}")
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="botinfo",
|
||||
description="Get some useful (or not) information about the bot.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
async def botinfo(self, context: Context) -> None:
|
||||
if context.interaction:
|
||||
await context.interaction.response.defer(ephemeral=False)
|
||||
|
||||
ny_tz = pytz.timezone('America/New_York')
|
||||
current_time = datetime.now(ny_tz).strftime("%m/%d/%y, %I:%M %p")
|
||||
|
||||
description_text = (
|
||||
"Heyooo! I'm Syntrel, a bot made to help with [SideStore](https://discord.gg/3DwCwpBHfv), [MeloNX](https://discord.gg/Q4VkbkYfmk), and [idevice](https://discord.gg/ZnNcrRT3M8). I even have some cool extras! If you encounter any issues, please file a bug report. If you have any feedback or suggestions, simply select \"Feedback\"! <:HeardPanda:1417619745896660992>\n\n"
|
||||
"**New to Syntrel?** Run `/help` to get started and explore all available commands!\n\n"
|
||||
f"**Owner:** [neoarz](https://discordapp.com/users/1015372540937502851)\n"
|
||||
f"**Python Version:** {platform.python_version()}\n"
|
||||
f"**Discord.py Version:** {discord.__version__}\n"
|
||||
f"**Prefix:** / (Slash Commands) or {self.bot.bot_prefix} for normal commands"
|
||||
)
|
||||
|
||||
embed1 = discord.Embed(
|
||||
title="Syntrel Discord Bot",
|
||||
description=description_text,
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed1.set_author(name="Syntrel", icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true")
|
||||
embed1.set_image(url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png")
|
||||
embed1.set_footer(text=f"neoarz • {current_time}", icon_url="https://yes.nighty.works/raw/P1Us35.webp")
|
||||
|
||||
embed2 = discord.Embed(
|
||||
title="Contributors",
|
||||
description="Giving credit where it's due! <a:pandasquish:1428617277317709915>",
|
||||
color=0x7289DA,
|
||||
)
|
||||
|
||||
contributors_image = generate_contributors_image()
|
||||
|
||||
view = BotInfoView(self.bot)
|
||||
|
||||
if contributors_image:
|
||||
file = discord.File(contributors_image, filename="contributors.png")
|
||||
embed2.set_image(url="attachment://contributors.png")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.followup.send(embeds=[embed1, embed2], file=file, view=view)
|
||||
else:
|
||||
await context.send(embeds=[embed1, embed2], file=file, view=view)
|
||||
else:
|
||||
if context.interaction:
|
||||
await context.interaction.followup.send(embeds=[embed1, embed2], view=view)
|
||||
else:
|
||||
await context.send(embeds=[embed1, embed2], view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(BotInfo(bot))
|
||||
115
cogs/fun/__init__.py
Normal file
115
cogs/fun/__init__.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .coinflip import coinflip_command
|
||||
from .eightball import eightball_command
|
||||
from .minesweeper import minesweeper_command
|
||||
from .randomfact import randomfact_command
|
||||
from .rockpaperscissors import rps_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Fun(commands.GroupCog, name="fun"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="fun", invoke_without_command=True)
|
||||
async def fun_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="Fun Commands",
|
||||
description="Use `.fun <subcommand>` or slash `/fun <subcommand>`.",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
embed.add_field(name="Available", value="coinflip, 8ball, minesweeper, randomfact, rps", inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str, **kwargs):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command, **kwargs)
|
||||
else:
|
||||
await context.send(f"Unknown fun command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
@fun_group.command(name="coinflip")
|
||||
async def fun_group_coinflip(self, context: Context):
|
||||
await self._invoke_hybrid(context, "coinflip")
|
||||
|
||||
@fun_group.command(name="8ball")
|
||||
async def fun_group_8ball(self, context: Context, *, question: str):
|
||||
await self._invoke_hybrid(context, "8ball", question=question)
|
||||
|
||||
@fun_group.command(name="minesweeper")
|
||||
async def fun_group_minesweeper(self, context: Context, opponent: discord.User = None):
|
||||
await self._invoke_hybrid(context, "minesweeper", opponent=opponent)
|
||||
|
||||
@fun_group.command(name="randomfact")
|
||||
async def fun_group_randomfact(self, context: Context):
|
||||
await self._invoke_hybrid(context, "randomfact")
|
||||
|
||||
@fun_group.command(name="rps")
|
||||
async def fun_group_rps(self, context: Context):
|
||||
await self._invoke_hybrid(context, "rps")
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="coinflip",
|
||||
description="Make a coin flip, but give your bet before."
|
||||
)
|
||||
async def coinflip(self, context):
|
||||
return await coinflip_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="8ball",
|
||||
description="Ask any question to the bot.",
|
||||
)
|
||||
async def eight_ball(self, context, *, question: str):
|
||||
return await eightball_command()(self, context, question=question)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="minesweeper",
|
||||
description="Play a buttoned minesweeper mini-game."
|
||||
)
|
||||
@app_commands.describe(
|
||||
opponent="Optional user to play against in multiplayer mode."
|
||||
)
|
||||
async def minesweeper(self, context, opponent: discord.User = None):
|
||||
return await minesweeper_command()(self, context, opponent=opponent)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(name="randomfact", description="Get a random fact.")
|
||||
async def randomfact(self, context):
|
||||
return await randomfact_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="rps", description="Play the rock paper scissors game against the bot."
|
||||
)
|
||||
async def rock_paper_scissors(self, context):
|
||||
return await rps_command()(self, context)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Fun(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'fun.coinflip'")
|
||||
bot.logger.info("Loaded extension 'fun.8ball'")
|
||||
bot.logger.info("Loaded extension 'fun.minesweeper'")
|
||||
bot.logger.info("Loaded extension 'fun.randomfact'")
|
||||
bot.logger.info("Loaded extension 'fun.rps'")
|
||||
@@ -1,7 +1,6 @@
|
||||
import random
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
class Choice(discord.ui.View):
|
||||
def __init__(self) -> None:
|
||||
@@ -22,18 +21,12 @@ class Choice(discord.ui.View):
|
||||
self.value = "tails"
|
||||
self.stop()
|
||||
|
||||
class CoinFlip(commands.Cog, name="coinflip"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def coinflip_command():
|
||||
@commands.hybrid_command(
|
||||
name="coinflip", description="Make a coin flip, but give your bet before."
|
||||
name="coinflip",
|
||||
description="Make a coin flip, but give your bet before."
|
||||
)
|
||||
async def coinflip(self, context: Context) -> None:
|
||||
"""
|
||||
Make a coin flip, but give your bet before.
|
||||
:param context: The hybrid command context.
|
||||
"""
|
||||
async def coinflip(self, context):
|
||||
buttons = Choice()
|
||||
embed = discord.Embed(
|
||||
title="Coinflip",
|
||||
@@ -59,6 +52,5 @@ class CoinFlip(commands.Cog, name="coinflip"):
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
await message.edit(embed=embed, view=None, content=None)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(CoinFlip(bot))
|
||||
|
||||
return coinflip
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
import random
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class EightBall(commands.Cog, name="8ball"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def eightball_command():
|
||||
@commands.hybrid_command(
|
||||
name="8ball",
|
||||
description="Ask any question to the bot.",
|
||||
)
|
||||
@app_commands.describe(question="The question you want to ask.")
|
||||
async def eight_ball(self, context: Context, *, question: str) -> None:
|
||||
"""
|
||||
Ask any question to the bot.
|
||||
|
||||
:param context: The hybrid command context.
|
||||
:param question: The question that should be asked by the user.
|
||||
"""
|
||||
async def eight_ball(self, context, *, question: str):
|
||||
answers = [
|
||||
"It is certain.",
|
||||
"It is decidedly so.",
|
||||
@@ -51,7 +38,5 @@ class EightBall(commands.Cog, name="8ball"):
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
embed.set_footer(text=f"The question was: {question}")
|
||||
await context.send(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(EightBall(bot))
|
||||
|
||||
return eight_ball
|
||||
|
||||
@@ -1,31 +1,129 @@
|
||||
import random
|
||||
from itertools import repeat
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
class ChallengeConfirmView(discord.ui.View):
|
||||
def __init__(self, context, challenger, challenged, bomb_count, board):
|
||||
super().__init__(timeout=60)
|
||||
self.context = context
|
||||
self.challenger = challenger
|
||||
self.challenged = challenged
|
||||
self.bomb_count = bomb_count
|
||||
self.board = board
|
||||
self.accepted = False
|
||||
|
||||
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.grey, emoji="<:Check:1417250703407186051>")
|
||||
async def confirm_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if interaction.user.id != self.challenged.id:
|
||||
return await interaction.response.send_message("Only the challenged player can accept!", ephemeral=True)
|
||||
|
||||
self.accepted = True
|
||||
self.stop()
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Challenge Accepted!",
|
||||
description=f"🎮 **{self.challenger.mention}** vs **{self.challenged.mention}**\n\n💣 Total Bombs: `{self.bomb_count}`\n\n{self.challenger.mention} goes first! Click the buttons to reveal the grid. Avoid the bombs!",
|
||||
color=0x00FF00
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
view = MsView(self.context, self.ExtractBlocks(), self.bomb_count, self.board, self.challenged)
|
||||
message = await interaction.response.edit_message(embed=embed, view=view)
|
||||
view.message = message
|
||||
|
||||
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.grey, emoji="<:Cross:1417250649514446849>")
|
||||
async def cancel_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if interaction.user.id != self.challenged.id:
|
||||
return await interaction.response.send_message("Only the challenged player can decline!", ephemeral=True)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Challenge Declined",
|
||||
description=f"<a:woeisnuke:1432138229628276927> {self.challenged.mention} declined the minesweeper challenge from {self.challenger.mention}",
|
||||
color=0xE02B2B
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
self.stop()
|
||||
|
||||
async def on_timeout(self):
|
||||
embed = discord.Embed(
|
||||
title="Challenge Expired",
|
||||
description=f"⏰ {self.challenged.mention} didn't respond to the minesweeper challenge from {self.challenger.mention}",
|
||||
color=0xFFA500
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
try:
|
||||
await self.message.edit(embed=embed, view=None)
|
||||
except:
|
||||
pass
|
||||
|
||||
def ExtractBlocks(self):
|
||||
new_b = []
|
||||
for x in self.board:
|
||||
for y in x:
|
||||
new_b.append(y)
|
||||
return new_b
|
||||
|
||||
class RowButton(discord.ui.Button):
|
||||
def __init__(self, ctx, label, custom_id, bombs, board):
|
||||
def __init__(self, ctx, label, custom_id, bombs, board, opponent=None):
|
||||
super().__init__(label=label, style=discord.ButtonStyle.grey, custom_id=custom_id)
|
||||
self.ctx = ctx
|
||||
self.bombs = bombs
|
||||
self.board = board
|
||||
self.opponent = opponent
|
||||
|
||||
async def callback(self, interaction):
|
||||
assert self.view is not None
|
||||
view: MsView = self.view
|
||||
await interaction.response.defer()
|
||||
if interaction.user.id != self.ctx.author.id:
|
||||
return await interaction.followup.send(
|
||||
"You cannot interact with these buttons.", ephemeral=True
|
||||
)
|
||||
|
||||
current_time = time.time()
|
||||
if current_time - view.last_interaction < 0.5:
|
||||
try:
|
||||
return await interaction.response.send_message("Please wait before clicking again.", ephemeral=True)
|
||||
except:
|
||||
return
|
||||
|
||||
view.last_interaction = current_time
|
||||
|
||||
try:
|
||||
await interaction.response.defer()
|
||||
except discord.errors.HTTPException as e:
|
||||
if e.status == 429:
|
||||
await asyncio.sleep(1)
|
||||
return
|
||||
raise
|
||||
|
||||
if view.opponent:
|
||||
if interaction.user.id not in [self.ctx.author.id, view.opponent.id]:
|
||||
return await interaction.followup.send(
|
||||
"You cannot interact with these buttons.", ephemeral=True
|
||||
)
|
||||
if interaction.user.id != view.current_player.id:
|
||||
return await interaction.followup.send(
|
||||
f"It's {view.current_player.mention}'s turn!", ephemeral=True
|
||||
)
|
||||
else:
|
||||
if interaction.user.id != self.ctx.author.id:
|
||||
return await interaction.followup.send(
|
||||
"You cannot interact with these buttons.", ephemeral=True
|
||||
)
|
||||
|
||||
b_id = self.custom_id
|
||||
if int(b_id[5:]) in view.moves:
|
||||
return await interaction.followup.send("That part is already taken.", ephemeral=True)
|
||||
|
||||
if not view.bombs_generated:
|
||||
view.generate_bombs(int(b_id[5:]))
|
||||
self.bombs = view.bombs
|
||||
|
||||
if int(b_id[5:]) in self.bombs:
|
||||
await view.RevealBombs(b_id, view.board)
|
||||
await view.RevealBombs(b_id, view.board, interaction)
|
||||
else:
|
||||
count = []
|
||||
rawpos = int(b_id[5:])
|
||||
@@ -60,32 +158,80 @@ class RowButton(discord.ui.Button):
|
||||
view.GetBoardPos(pos)
|
||||
] = str(number) if number > 0 else "0"
|
||||
view.moves.append(pos)
|
||||
|
||||
if view.opponent:
|
||||
if interaction.user.id == view.ctx.author.id:
|
||||
view.player1_score += 1
|
||||
else:
|
||||
view.player2_score += 1
|
||||
|
||||
if number == 0:
|
||||
await view.auto_reveal_safe_squares(rawpos, interaction)
|
||||
|
||||
if view.opponent:
|
||||
view.current_player = view.opponent if view.current_player.id == view.ctx.author.id else view.ctx.author
|
||||
|
||||
if len(view.moves) + len(self.bombs) == 25:
|
||||
await interaction.edit_original_response(view=view)
|
||||
await view.EndGame()
|
||||
|
||||
await interaction.edit_original_response(view=view)
|
||||
|
||||
else:
|
||||
try:
|
||||
await view.update_embed(interaction)
|
||||
except discord.errors.HTTPException as e:
|
||||
if e.status == 429:
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
raise
|
||||
|
||||
class MsView(discord.ui.View):
|
||||
def __init__(self, ctx, options, bombs, board):
|
||||
super().__init__()
|
||||
def __init__(self, ctx, options, bomb_count, board, opponent=None):
|
||||
super().__init__(timeout=300)
|
||||
for i, op in enumerate(options):
|
||||
self.add_item(RowButton(ctx, op, f"block{i}", bombs, board))
|
||||
self.add_item(RowButton(ctx, op, f"block{i}", [], board, opponent))
|
||||
self.board = board
|
||||
self.bombs = bombs
|
||||
self.bombs = []
|
||||
self.bomb_count = bomb_count
|
||||
self.bombs_generated = False
|
||||
self.moves = []
|
||||
self.ctx = ctx
|
||||
self.message = None
|
||||
self.last_interaction = 0
|
||||
self.opponent = opponent
|
||||
self.current_player = ctx.author
|
||||
self.player1_score = 0
|
||||
self.player2_score = 0
|
||||
|
||||
def generate_bombs(self, first_move_pos):
|
||||
bombpositions = []
|
||||
excluded_positions = [0, 4, 20, 24, first_move_pos]
|
||||
|
||||
while len(bombpositions) < self.bomb_count:
|
||||
random_index = random.randint(0, 24)
|
||||
if random_index not in bombpositions and random_index not in excluded_positions:
|
||||
bombpositions.append(random_index)
|
||||
|
||||
self.bombs = bombpositions
|
||||
self.bombs_generated = True
|
||||
|
||||
|
||||
async def EndGame(self):
|
||||
for button in self.children:
|
||||
if isinstance(button, RowButton):
|
||||
button.bombs = self.bombs
|
||||
|
||||
async def on_timeout(self):
|
||||
for button in self.children:
|
||||
button.disabled = True
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description="Game Ended. You won!",
|
||||
color=0x00FF00
|
||||
title="Minesweeper",
|
||||
description="Game timed out!",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
await self.message.edit(embed=embed, view=self)
|
||||
try:
|
||||
await interaction.edit_original_response(embed=embed, view=self)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def EndGame(self):
|
||||
for button in self.children:
|
||||
button.disabled = True
|
||||
pos = int(button.custom_id[5:])
|
||||
@@ -93,6 +239,141 @@ class MsView(discord.ui.View):
|
||||
button.label = "💣"
|
||||
button.style = discord.ButtonStyle.red
|
||||
self.board[self.GetBoardRow(pos)][self.GetBoardPos(pos)] = "💣"
|
||||
|
||||
if self.opponent:
|
||||
if self.player1_score > self.player2_score:
|
||||
winner = self.ctx.author
|
||||
elif self.player2_score > self.player1_score:
|
||||
winner = self.opponent
|
||||
else:
|
||||
winner = None
|
||||
|
||||
if winner:
|
||||
description = f"🎉 **{winner.mention}** won!"
|
||||
else:
|
||||
description = f"🤝 It's a tie!"
|
||||
else:
|
||||
description = "Game Ended. You won!"
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description=description,
|
||||
color=0x00FF00
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
try:
|
||||
await self.message.edit(embed=embed, view=self)
|
||||
except discord.errors.HTTPException as e:
|
||||
if e.status == 429:
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
raise
|
||||
self.stop()
|
||||
|
||||
async def update_embed(self, interaction):
|
||||
if self.opponent:
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper - Multiplayer",
|
||||
description=f"💣 Total Bombs: `{self.bomb_count}`\n\n🎮 **Players:**\n{self.ctx.author.mention} vs {self.opponent.mention}\n\n**Current Turn:** {self.current_player.mention}",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description=f"💣 Total Bombs: `{self.bomb_count}`\n\nClick the buttons to reveal the grid. Avoid the bombs!",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
await interaction.edit_original_response(embed=embed, view=self)
|
||||
|
||||
async def auto_reveal_safe_squares(self, center_pos, interaction):
|
||||
positions_to_check = [center_pos]
|
||||
revealed_positions = set()
|
||||
current_player_id = interaction.user.id
|
||||
|
||||
while positions_to_check:
|
||||
current_pos = positions_to_check.pop(0)
|
||||
if current_pos in revealed_positions:
|
||||
continue
|
||||
|
||||
revealed_positions.add(current_pos)
|
||||
|
||||
adjacent_positions = []
|
||||
pos = self.GetBoardPos(current_pos)
|
||||
|
||||
if pos > 0 and current_pos - 1 not in self.bombs:
|
||||
adjacent_positions.append(current_pos - 1)
|
||||
if pos < 4 and current_pos + 1 not in self.bombs:
|
||||
adjacent_positions.append(current_pos + 1)
|
||||
if current_pos >= 5 and current_pos - 5 not in self.bombs:
|
||||
adjacent_positions.append(current_pos - 5)
|
||||
if current_pos <= 19 and current_pos + 5 not in self.bombs:
|
||||
adjacent_positions.append(current_pos + 5)
|
||||
if pos > 0 and current_pos >= 5 and current_pos - 6 not in self.bombs:
|
||||
adjacent_positions.append(current_pos - 6)
|
||||
if pos < 4 and current_pos >= 5 and current_pos - 4 not in self.bombs:
|
||||
adjacent_positions.append(current_pos - 4)
|
||||
if pos > 0 and current_pos <= 19 and current_pos + 4 not in self.bombs:
|
||||
adjacent_positions.append(current_pos + 4)
|
||||
if pos < 4 and current_pos <= 19 and current_pos + 6 not in self.bombs:
|
||||
adjacent_positions.append(current_pos + 6)
|
||||
|
||||
for adj_pos in adjacent_positions:
|
||||
if adj_pos not in self.moves and adj_pos not in revealed_positions:
|
||||
adj_count = []
|
||||
adj_pos_obj = self.GetBoardPos(adj_pos)
|
||||
|
||||
def checkpos_adj(count, rawpos, pos):
|
||||
pos = self.GetBoardPos(rawpos)
|
||||
if not rawpos - 1 in self.bombs or pos == 0:
|
||||
count.append(rawpos - 1)
|
||||
if not rawpos + 1 in self.bombs or pos == 4:
|
||||
count.append(rawpos + 1)
|
||||
if not rawpos - 6 in self.bombs or pos == 0:
|
||||
count.append(rawpos - 6)
|
||||
if not rawpos - 4 in self.bombs or pos == 4:
|
||||
count.append(rawpos - 4)
|
||||
if not rawpos + 6 in self.bombs or pos == 4:
|
||||
count.append(rawpos + 6)
|
||||
if not rawpos + 4 in self.bombs or pos == 0:
|
||||
count.append(rawpos + 4)
|
||||
if not rawpos - 5 in self.bombs:
|
||||
count.append(rawpos - 5)
|
||||
if not rawpos + 5 in self.bombs:
|
||||
count.append(rawpos + 5)
|
||||
return count
|
||||
|
||||
adj_count = checkpos_adj(adj_count, adj_pos, adj_pos_obj)
|
||||
adj_number = 8 - len(adj_count)
|
||||
|
||||
for button in self.children:
|
||||
if int(button.custom_id[5:]) == adj_pos:
|
||||
button.label = str(adj_number) if adj_number > 0 else "0"
|
||||
button.style = discord.ButtonStyle.green
|
||||
break
|
||||
|
||||
self.board[self.GetBoardRow(adj_pos)][self.GetBoardPos(adj_pos)] = str(adj_number) if adj_number > 0 else "0"
|
||||
self.moves.append(adj_pos)
|
||||
|
||||
if self.opponent:
|
||||
if current_player_id == self.ctx.author.id:
|
||||
self.player1_score += 1
|
||||
else:
|
||||
self.player2_score += 1
|
||||
|
||||
if adj_number == 0:
|
||||
positions_to_check.append(adj_pos)
|
||||
|
||||
try:
|
||||
await self.update_embed(interaction)
|
||||
except discord.errors.HTTPException as e:
|
||||
if e.status == 429:
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
raise
|
||||
|
||||
def GetBoardRow(self, pos):
|
||||
if pos in [0, 1, 2, 3, 4]:
|
||||
@@ -128,15 +409,8 @@ class MsView(discord.ui.View):
|
||||
return i
|
||||
return False
|
||||
|
||||
async def RevealBombs(self, b_id, board):
|
||||
async def RevealBombs(self, b_id, board, interaction):
|
||||
bombemo = "💣"
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description=f"💥 BOOM! You hit a bomb. Game Over!\n-# gg {self.ctx.author.mention}",
|
||||
color=0xE02B2B
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
await self.message.edit(embed=embed, view=self)
|
||||
|
||||
for button in self.children:
|
||||
button.disabled = True
|
||||
@@ -154,31 +428,41 @@ class MsView(discord.ui.View):
|
||||
self.board[self.GetBoardRow(pos)][
|
||||
self.GetBoardPos(pos)
|
||||
] = bombemo
|
||||
|
||||
if self.opponent:
|
||||
loser = self.ctx.author if interaction.user.id == self.ctx.author.id else self.opponent
|
||||
winner = self.opponent if loser.id == self.ctx.author.id else self.ctx.author
|
||||
description = f"💥 BOOM! {loser.mention} hit a bomb!\n🎉 **{winner.mention}** wins!"
|
||||
else:
|
||||
description = f"💥 BOOM! You hit a bomb. Game Over!\n-# gg {self.ctx.author.mention}"
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description=description,
|
||||
color=0xE02B2B
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
try:
|
||||
await interaction.edit_original_response(embed=embed, view=self)
|
||||
except discord.errors.HTTPException as e:
|
||||
if e.status == 429:
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
raise
|
||||
self.stop()
|
||||
|
||||
|
||||
class Minesweeper(commands.Cog, name="minesweeper"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def minesweeper_command():
|
||||
@commands.hybrid_command(
|
||||
name="minesweeper",
|
||||
description="Play a buttoned minesweeper mini-game."
|
||||
)
|
||||
async def minesweeper(self, context: Context) -> None:
|
||||
"""
|
||||
Play a buttoned minesweeper mini-game.
|
||||
:param context: The hybrid command context.
|
||||
"""
|
||||
board = [["឵឵ "] * 5 for _ in range(5)] # Unicode block character, usually doesnt show up in Discord or github, search up invisible character on google
|
||||
bombs = 0
|
||||
bombpositions = []
|
||||
for x in repeat(None, random.randint(4, 11)):
|
||||
random_index = random.randint(0, 24)
|
||||
if random_index not in bombpositions and random_index not in [
|
||||
0, 4, 20, 24
|
||||
]:
|
||||
bombpositions.append(random_index)
|
||||
bombs += 1
|
||||
@app_commands.describe(
|
||||
opponent="Optional user to play against in multiplayer mode."
|
||||
)
|
||||
async def minesweeper(self, context, opponent: discord.User = None):
|
||||
board = [["឵឵ "] * 5 for _ in range(5)]
|
||||
bomb_count = random.randint(4, 11)
|
||||
|
||||
def ExtractBlocks():
|
||||
new_b = []
|
||||
@@ -187,17 +471,36 @@ class Minesweeper(commands.Cog, name="minesweeper"):
|
||||
new_b.append(y)
|
||||
return new_b
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description=f"💣 Total Bombs: `{len(bombpositions)}`\n\nClick the buttons to reveal the grid. Avoid the bombs!",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
view = MsView(context, ExtractBlocks(), bombpositions, board)
|
||||
message = await context.send(embed=embed, view=view)
|
||||
view.message = message
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Minesweeper(bot))
|
||||
if opponent:
|
||||
if opponent.id == context.author.id:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description="You cannot play against yourself!",
|
||||
color=0xE02B2B
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
return await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper Challenge",
|
||||
description=f"🎮 **{opponent.mention}**, you are challenged by **{context.author.mention}** to a round of Minesweeper!\n\n💣 Total Bombs: `{bomb_count}`",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
view = ChallengeConfirmView(context, context.author, opponent, bomb_count, board)
|
||||
message = await context.send(embed=embed, view=view)
|
||||
view.message = message
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Minesweeper",
|
||||
description=f"💣 Total Bombs: `{bomb_count}`\n\nClick the buttons to reveal the grid. Avoid the bombs!",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
|
||||
view = MsView(context, ExtractBlocks(), bomb_count, board, opponent)
|
||||
message = await context.send(embed=embed, view=view)
|
||||
view.message = message
|
||||
|
||||
return minesweeper
|
||||
|
||||
@@ -1,36 +1,16 @@
|
||||
import aiohttp
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class RandomFact(commands.Cog, name="randomfact"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def randomfact_command():
|
||||
@commands.hybrid_command(name="randomfact", description="Get a random fact.")
|
||||
async def randomfact(self, context: Context) -> None:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
"https://uselessfacts.jsph.pl/random.json?language=en"
|
||||
) as request:
|
||||
if request.status == 200:
|
||||
data = await request.json()
|
||||
embed = discord.Embed(
|
||||
title="Random Fact",
|
||||
description=data["text"],
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description="There is something wrong with the API, please try again later",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
await context.send(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(RandomFact(bot))
|
||||
async def randomfact(self, context):
|
||||
embed = discord.Embed(
|
||||
title="Command Disabled",
|
||||
description="This command is currently disabled.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
await context.send(embed=embed)
|
||||
|
||||
return randomfact
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import random
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
class RockPaperScissors(discord.ui.Select):
|
||||
def __init__(self) -> None:
|
||||
@@ -39,7 +38,6 @@ class RockPaperScissors(discord.ui.Select):
|
||||
|
||||
winner = (3 + user_choice_index - bot_choice_index) % 3
|
||||
|
||||
# Get the user mention
|
||||
user_mention = interaction.user.mention
|
||||
|
||||
if winner == 0:
|
||||
@@ -61,14 +59,11 @@ class RockPaperScissorsView(discord.ui.View):
|
||||
super().__init__()
|
||||
self.add_item(RockPaperScissors())
|
||||
|
||||
class RPS(commands.Cog, name="rps"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def rps_command():
|
||||
@commands.hybrid_command(
|
||||
name="rps", description="Play the rock paper scissors game against the bot."
|
||||
)
|
||||
async def rock_paper_scissors(self, context: Context) -> None:
|
||||
async def rock_paper_scissors(self, context):
|
||||
view = RockPaperScissorsView()
|
||||
embed = discord.Embed(
|
||||
title="Rock Paper Scissors",
|
||||
@@ -77,6 +72,5 @@ class RPS(commands.Cog, name="rps"):
|
||||
)
|
||||
embed.set_author(name="Fun", icon_url="https://yes.nighty.works/raw/eW5lLm.webp")
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(RPS(bot))
|
||||
|
||||
return rock_paper_scissors
|
||||
|
||||
121
cogs/general/__init__.py
Normal file
121
cogs/general/__init__.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .ping import ping_command
|
||||
from .uptime import uptime_command
|
||||
from .serverinfo import serverinfo_command
|
||||
from .feedback import feedback_command
|
||||
from .userinfo import userinfo_command
|
||||
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class General(commands.GroupCog, name="general"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="general", invoke_without_command=True)
|
||||
async def general_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="General Commands",
|
||||
description="Use `.general <subcommand>` or `/general <subcommand>`.",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="General", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.add_field(name="Available", value="ping, uptime, serverinfo, userinfo, feedback", inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str, **kwargs):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command, **kwargs)
|
||||
else:
|
||||
await context.send(f"Unknown general command: {name}")
|
||||
|
||||
@general_group.command(name="ping")
|
||||
async def general_group_ping(self, context: Context):
|
||||
await self._invoke_hybrid(context, "ping")
|
||||
|
||||
@general_group.command(name="uptime")
|
||||
async def general_group_uptime(self, context: Context):
|
||||
await self._invoke_hybrid(context, "uptime")
|
||||
|
||||
@general_group.command(name="serverinfo")
|
||||
async def general_group_serverinfo(self, context: Context):
|
||||
await self._invoke_hybrid(context, "serverinfo")
|
||||
|
||||
@general_group.command(name="userinfo")
|
||||
async def general_group_userinfo(self, context: Context, user: discord.User = None, user_id: str = None):
|
||||
await self._invoke_hybrid(context, "userinfo", user=user, user_id=user_id)
|
||||
|
||||
@general_group.command(name="feedback")
|
||||
async def general_group_feedback(self, context: Context):
|
||||
await self._invoke_hybrid(context, "feedback")
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="ping",
|
||||
description="Check if the bot is alive.",
|
||||
)
|
||||
async def ping(self, context):
|
||||
return await ping_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="uptime",
|
||||
description="Check how long the bot has been running.",
|
||||
)
|
||||
async def uptime(self, context):
|
||||
return await uptime_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="serverinfo",
|
||||
description="Get some useful (or not) information about the server.",
|
||||
)
|
||||
async def serverinfo(self, context):
|
||||
return await serverinfo_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="userinfo",
|
||||
description="Get information on a user.",
|
||||
)
|
||||
@app_commands.describe(
|
||||
user="User to get info for",
|
||||
user_id="User ID to get info for"
|
||||
)
|
||||
async def userinfo(self, context, user: discord.User = None, user_id: str = None):
|
||||
return await userinfo_command()(self, context, user=user, user_id=user_id)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="feedback",
|
||||
description="Submit a feedback for the owners of the bot"
|
||||
)
|
||||
async def feedback(self, context):
|
||||
return await feedback_command()(self, context)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = General(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'general.ping'")
|
||||
bot.logger.info("Loaded extension 'general.uptime'")
|
||||
bot.logger.info("Loaded extension 'general.serverinfo'")
|
||||
bot.logger.info("Loaded extension 'general.userinfo'")
|
||||
bot.logger.info("Loaded extension 'general.feedback'")
|
||||
@@ -1,41 +0,0 @@
|
||||
import platform
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class BotInfo(commands.Cog, name="botinfo"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="botinfo",
|
||||
description="Get some useful (or not) information about the bot.",
|
||||
)
|
||||
async def botinfo(self, context: Context) -> None:
|
||||
"""
|
||||
Get some useful (or not) information about the bot.
|
||||
|
||||
:param context: The hybrid command context.
|
||||
"""
|
||||
embed = discord.Embed(
|
||||
title="Syntrel Discord Bot",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Bot Information", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.add_field(name="Owner:", value="[neoarz](https://discordapp.com/users/1015372540937502851)", inline=True)
|
||||
embed.add_field(
|
||||
name="Python Version:", value=f"{platform.python_version()}", inline=True
|
||||
)
|
||||
embed.add_field(
|
||||
name="Prefix:",
|
||||
value=f"/ (Slash Commands) or {self.bot.bot_prefix} for normal commands",
|
||||
inline=False,
|
||||
)
|
||||
if getattr(context, "interaction", None):
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(BotInfo(bot))
|
||||
@@ -2,7 +2,6 @@ import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
class FeedbackForm(discord.ui.Modal, title="Feeedback"):
|
||||
feedback = discord.ui.TextInput(
|
||||
label="What do you think about this bot?",
|
||||
@@ -17,43 +16,43 @@ class FeedbackForm(discord.ui.Modal, title="Feeedback"):
|
||||
self.answer = str(self.feedback)
|
||||
self.stop()
|
||||
|
||||
|
||||
class Feedback(commands.Cog, name="feedback"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@app_commands.command(
|
||||
name="feedback", description="Submit a feedback for the owners of the bot"
|
||||
def feedback_command():
|
||||
@commands.hybrid_command(
|
||||
name="feedback",
|
||||
description="Submit a feedback for the owners of the bot"
|
||||
)
|
||||
async def feedback(self, interaction: discord.Interaction) -> None:
|
||||
"""
|
||||
Submit a feedback for the owners of the bot.
|
||||
async def feedback(self, context):
|
||||
if getattr(context, "interaction", None):
|
||||
interaction = context.interaction
|
||||
feedback_form = FeedbackForm()
|
||||
await interaction.response.send_modal(feedback_form)
|
||||
|
||||
:param interaction: The application command interaction.
|
||||
"""
|
||||
feedback_form = FeedbackForm()
|
||||
await interaction.response.send_modal(feedback_form)
|
||||
await feedback_form.wait()
|
||||
interaction = feedback_form.interaction
|
||||
await interaction.response.send_message(
|
||||
embed=discord.Embed(
|
||||
title="Thank You!",
|
||||
description="Your feedback has been submitted, the owners have been notified about it.",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png"),
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
await feedback_form.wait()
|
||||
interaction = feedback_form.interaction
|
||||
await interaction.response.send_message(
|
||||
embed=discord.Embed(
|
||||
title="Thank You!",
|
||||
description="Your feedback has been submitted, the owners have been notified about it.",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"),
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
app_owner = (await self.bot.application_info()).owner
|
||||
await app_owner.send(
|
||||
embed=discord.Embed(
|
||||
title="New Feedback",
|
||||
description=f"{interaction.user} (<@{interaction.user.id}>) has submitted a new feedback:\n```\n{feedback_form.answer}\n```",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Feedback(bot))
|
||||
app_owner = (await self.bot.application_info()).owner
|
||||
await app_owner.send(
|
||||
embed=discord.Embed(
|
||||
title="New Feedback",
|
||||
description=f"{interaction.user} (<@{interaction.user.id}>) has submitted a new feedback:\n```\n{feedback_form.answer}\n```",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description="This command can only be used as a slash command. Please use `/general feedback` instead.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Feedback System", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
await context.send(embed=embed)
|
||||
|
||||
return feedback
|
||||
@@ -1,41 +1,25 @@
|
||||
"""
|
||||
Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn (https://krypton.ninja)
|
||||
Description:
|
||||
🐍 A simple template to start to code your own and personalized Discord bot in Python
|
||||
|
||||
Version: 6.4.0
|
||||
"""
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Ping(commands.Cog, name="ping"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def ping_command():
|
||||
@commands.hybrid_command(
|
||||
name="ping",
|
||||
description="Check if the bot is alive.",
|
||||
)
|
||||
async def ping(self, context: Context) -> None:
|
||||
"""
|
||||
Check if the bot is alive.
|
||||
|
||||
:param context: The hybrid command context.
|
||||
"""
|
||||
async def ping(self, context):
|
||||
embed = discord.Embed(
|
||||
title="🏓 Pong!",
|
||||
description=f"The bot latency is {round(self.bot.latency * 1000)}ms.",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Ping", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.set_author(name="Ping", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
if getattr(context, "interaction", None):
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(embed=embed, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(embed=embed, ephemeral=False)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Ping(bot))
|
||||
|
||||
return ping
|
||||
|
||||
@@ -1,58 +1,133 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
class ServerInfo(commands.Cog, name="serverinfo"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def serverinfo_command():
|
||||
@commands.hybrid_command(
|
||||
name="serverinfo",
|
||||
description="Get some useful (or not) information about the server.",
|
||||
)
|
||||
@commands.guild_only() # This decorator ensures the command only works in servers
|
||||
async def serverinfo(self, context: Context) -> None:
|
||||
"""
|
||||
Get some useful (or not) information about the server.
|
||||
:param context: The hybrid command context.
|
||||
"""
|
||||
# Additional check (though @commands.guild_only() should handle this)
|
||||
@commands.guild_only()
|
||||
async def serverinfo(self, context):
|
||||
if context.guild is None:
|
||||
await context.send("This command can only be used in a server, not in DMs!")
|
||||
return
|
||||
|
||||
roles = [role.name for role in context.guild.roles]
|
||||
num_roles = len(roles)
|
||||
if num_roles > 50:
|
||||
roles = roles[:50]
|
||||
roles.append(f">>>> Displaying [50/{num_roles}] Roles")
|
||||
roles = ", ".join(roles)
|
||||
|
||||
guild = context.guild
|
||||
|
||||
text_channels = len([c for c in guild.channels if isinstance(c, discord.TextChannel)])
|
||||
voice_channels = len([c for c in guild.channels if isinstance(c, discord.VoiceChannel)])
|
||||
category_channels = len([c for c in guild.channels if isinstance(c, discord.CategoryChannel)])
|
||||
forum_channels = len([c for c in guild.channels if isinstance(c, discord.ForumChannel)])
|
||||
stage_channels = len([c for c in guild.channels if isinstance(c, discord.StageChannel)])
|
||||
|
||||
age_restricted = len([c for c in guild.channels if hasattr(c, 'nsfw') and c.nsfw])
|
||||
hidden_channels = len([c for c in guild.channels if c.permissions_for(guild.default_role).view_channel == False])
|
||||
|
||||
managed_roles = len([r for r in guild.roles if r.managed])
|
||||
|
||||
animated_emojis = len([e for e in guild.emojis if e.animated])
|
||||
managed_emojis = len([e for e in guild.emojis if e.managed])
|
||||
unavailable_emojis = len([e for e in guild.emojis if not e.available])
|
||||
|
||||
png_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.png])
|
||||
apng_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.apng])
|
||||
gif_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.lottie])
|
||||
lottie_stickers = len([s for s in guild.stickers if s.format == discord.StickerFormatType.lottie])
|
||||
|
||||
online_members = len([m for m in guild.members if m.status == discord.Status.online])
|
||||
idle_members = len([m for m in guild.members if m.status == discord.Status.idle])
|
||||
dnd_members = len([m for m in guild.members if m.status == discord.Status.dnd])
|
||||
offline_members = len([m for m in guild.members if m.status == discord.Status.offline])
|
||||
|
||||
bot_count = len([m for m in guild.members if m.bot])
|
||||
human_count = guild.member_count - bot_count
|
||||
|
||||
created_delta = discord.utils.utcnow() - guild.created_at
|
||||
years_ago = created_delta.days // 365
|
||||
|
||||
embed = discord.Embed(
|
||||
title="**Server Name:**",
|
||||
description=f"{context.guild}",
|
||||
title=f"**Server Name:** {guild.name}",
|
||||
color=0x7289DA
|
||||
).set_author(name="Server Information", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
).set_author(name="Server Information", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
|
||||
if context.guild.icon is not None:
|
||||
embed.set_thumbnail(url=context.guild.icon.url)
|
||||
|
||||
embed.add_field(name="Server ID", value=context.guild.id)
|
||||
embed.add_field(name="Member Count", value=context.guild.member_count)
|
||||
if guild.icon is not None:
|
||||
embed.set_thumbnail(url=guild.icon.url)
|
||||
|
||||
owner_value = guild.owner.mention if guild.owner else (f"<@{guild.owner_id}>" if guild.owner_id else "Unknown")
|
||||
embed.add_field(
|
||||
name="Text/Voice Channels",
|
||||
value=f"{len(context.guild.channels)}"
|
||||
name="Owner",
|
||||
value=owner_value,
|
||||
inline=True
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name=f"Roles ({len(context.guild.roles)})",
|
||||
value=roles
|
||||
name="Created",
|
||||
value=f"{years_ago} year{'s' if years_ago != 1 else ''} ago",
|
||||
inline=True
|
||||
)
|
||||
embed.set_footer(text=f"Created at: {context.guild.created_at.strftime('%m/%d/%Y')}")
|
||||
|
||||
embed.add_field(
|
||||
name="Max Members",
|
||||
value=f"{guild.max_members:,}" if guild.max_members else "Unknown",
|
||||
inline=True
|
||||
)
|
||||
|
||||
boost_level = guild.premium_tier
|
||||
boost_count = guild.premium_subscription_count or 0
|
||||
embed.add_field(
|
||||
name="Boost Status",
|
||||
value=f"Level {boost_level}, {boost_count} Boost{'s' if boost_count != 1 else ''}",
|
||||
inline=False
|
||||
)
|
||||
|
||||
channels_info = f"{text_channels} text, {voice_channels} voice, {category_channels} category"
|
||||
if forum_channels > 0:
|
||||
channels_info += f", {forum_channels} forum"
|
||||
if stage_channels > 0:
|
||||
channels_info += f", {stage_channels} stage"
|
||||
channels_info += f"\n{age_restricted} age restricted, {hidden_channels} hidden"
|
||||
|
||||
embed.add_field(
|
||||
name=f"Channels ({len(guild.channels)})",
|
||||
value=channels_info,
|
||||
inline=True
|
||||
)
|
||||
|
||||
roles_info = f"{len(guild.roles)} total\n{managed_roles} managed"
|
||||
embed.add_field(
|
||||
name=f"Roles ({len(guild.roles)})",
|
||||
value=roles_info,
|
||||
inline=True
|
||||
)
|
||||
|
||||
emotes_info = f"{len(guild.emojis)} total\n{animated_emojis} animated, {managed_emojis} managed"
|
||||
if unavailable_emojis > 0:
|
||||
emotes_info += f"\n{unavailable_emojis} unavailable"
|
||||
embed.add_field(
|
||||
name=f"Emotes ({len(guild.emojis)})",
|
||||
value=emotes_info,
|
||||
inline=True
|
||||
)
|
||||
|
||||
if len(guild.stickers) > 0:
|
||||
stickers_info = f"{len(guild.stickers)} total\n{png_stickers} PNG, {apng_stickers} APNG, {gif_stickers} GIF, {lottie_stickers} Lottie"
|
||||
embed.add_field(
|
||||
name=f"Stickers ({len(guild.stickers)})",
|
||||
value=stickers_info,
|
||||
inline=True
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Member Count",
|
||||
value=f"{guild.member_count}",
|
||||
inline=False
|
||||
)
|
||||
|
||||
embed.set_footer(text=f"Server ID: {guild.id} • Created: {guild.created_at.strftime('%m/%d/%Y')}")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=False)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(ServerInfo(bot))
|
||||
|
||||
return serverinfo
|
||||
41
cogs/general/uptime.py
Normal file
41
cogs/general/uptime.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
class UptimeView(discord.ui.View):
|
||||
def __init__(self, bot):
|
||||
super().__init__(timeout=300)
|
||||
self.bot = bot
|
||||
|
||||
@discord.ui.button(emoji="<:RefreshEmoji:1418934990770802891>", style=discord.ButtonStyle.primary)
|
||||
async def refresh_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
embed = discord.Embed(
|
||||
title="Bot Uptime",
|
||||
description=f"The bot has been running for {self.bot.get_uptime()}",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Uptime", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
def uptime_command():
|
||||
@commands.hybrid_command(
|
||||
name="uptime",
|
||||
description="Check how long the bot has been running.",
|
||||
)
|
||||
async def uptime(self, context):
|
||||
embed = discord.Embed(
|
||||
title="Bot Uptime",
|
||||
description=f"The bot has been running for **{self.bot.get_uptime()}**",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Uptime", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
view = UptimeView(self.bot)
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
else:
|
||||
await inter.followup.send(embed=embed, view=view, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return uptime
|
||||
543
cogs/general/userinfo.py
Normal file
543
cogs/general/userinfo.py
Normal file
@@ -0,0 +1,543 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
import aiohttp
|
||||
import asyncio
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
ONE_MONTH = 2628000
|
||||
|
||||
quests_fetch = 0
|
||||
quest_data = []
|
||||
|
||||
ACTIVITY_TYPE_NAMES = [
|
||||
"Playing",
|
||||
"Streaming",
|
||||
"Listening to",
|
||||
"Watching",
|
||||
"Custom Status",
|
||||
"Competing in",
|
||||
"Hang Status"
|
||||
]
|
||||
|
||||
USER_FLAGS = {
|
||||
'STAFF': 1 << 0,
|
||||
'PARTNER': 1 << 1,
|
||||
'HYPESQUAD': 1 << 2,
|
||||
'BUG_HUNTER_LEVEL_1': 1 << 3,
|
||||
'HYPESQUAD_ONLINE_HOUSE_1': 1 << 6,
|
||||
'HYPESQUAD_ONLINE_HOUSE_2': 1 << 7,
|
||||
'HYPESQUAD_ONLINE_HOUSE_3': 1 << 8,
|
||||
'PREMIUM_EARLY_SUPPORTER': 1 << 9,
|
||||
'BUG_HUNTER_LEVEL_2': 1 << 14,
|
||||
'VERIFIED_DEVELOPER': 1 << 17,
|
||||
'CERTIFIED_MODERATOR': 1 << 18,
|
||||
'ACTIVE_DEVELOPER': 1 << 22
|
||||
}
|
||||
|
||||
APPLICATION_FLAGS = {
|
||||
'APPLICATION_COMMAND_BADGE': 1 << 23,
|
||||
'AUTO_MODERATION_RULE_CREATE_BADGE': 1 << 6
|
||||
}
|
||||
|
||||
BADGE_URLS = {
|
||||
'staff': 'https://discord.com/company',
|
||||
'partner': 'https://discord.com/partners',
|
||||
'certified_moderator': 'https://discord.com/safety',
|
||||
'hypesquad': 'https://discord.com/hypesquad',
|
||||
'hypesquad_house_1': 'https://discord.com/settings/hypesquad-online',
|
||||
'hypesquad_house_2': 'https://discord.com/settings/hypesquad-online',
|
||||
'hypesquad_house_3': 'https://discord.com/settings/hypesquad-online',
|
||||
'bug_hunter_level_1': 'https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs',
|
||||
'bug_hunter_level_2': 'https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs',
|
||||
'active_developer': 'https://support-dev.discord.com/hc/en-us/articles/10113997751447?ref=badge',
|
||||
'early_supporter': 'https://discord.com/settings/premium',
|
||||
'premium': 'https://discord.com/settings/premium',
|
||||
'bot_commands': 'https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge',
|
||||
'quest_completed': 'https://discord.com/settings/inventory'
|
||||
}
|
||||
|
||||
BADGE_ICONS = {
|
||||
'staff': '<:discordstaff:1426051878155845702>',
|
||||
'partner': '<:discordpartner:1426051933608873986>',
|
||||
'certified_moderator': '<:discordmod:1426051921826943050>',
|
||||
'hypesquad': '<:hypesquadevents:1426051833536970852>',
|
||||
'hypesquad_house_1': '<:hypesquadbravery:1426051916739383297>',
|
||||
'hypesquad_house_2': '<:hypesquadbrilliance:1426051849433387068>',
|
||||
'hypesquad_house_3': '<:hypesquadbalance:1426051905179750495>',
|
||||
'bug_hunter_level_1': '<:discordbughunter1:1426052002193997895>',
|
||||
'bug_hunter_level_2': '<:discordbughunter2:1426052028257406987>',
|
||||
'active_developer': '<:activedeveloper:1426051981658685552>',
|
||||
'verified_developer': '<:discordbotdev:1426051827077480570>',
|
||||
'early_supporter': '<:discordearlysupporter:1426052023165517924>',
|
||||
'premium': '<:discordnitro:1426051911123206296>',
|
||||
'guild_booster_lvl1': '<:discordboost1:1426052007294144605>',
|
||||
'guild_booster_lvl2': '<:discordboost2:1426051986985582692>',
|
||||
'guild_booster_lvl3': '<:discordboost3:1426051991812964434>',
|
||||
'guild_booster_lvl4': '<:discordboost4:1426051955473645671>',
|
||||
'guild_booster_lvl5': '<:discordboost5:1426051960456609824>',
|
||||
'guild_booster_lvl6': '<:discordboost6:1426051976583712918>',
|
||||
'guild_booster_lvl7': '<:discordboost7:1426051965808410634>',
|
||||
'guild_booster_lvl8': '<:discordboost8:1426051844014342225>',
|
||||
'guild_booster_lvl9': '<:discordboost9:1426051855015743558>',
|
||||
'bot_commands': '<:supportscommands:1426051872476889171>',
|
||||
'automod': '<:automod:1426051939103146115>',
|
||||
'quest_completed': '<:quest:1426051817946611784>',
|
||||
'username': '<:username:1426051894371160115>',
|
||||
'premium_bot': '<:premiumbot:1426051888272638025>',
|
||||
'orb': '<:orb:1426051861605126289>',
|
||||
'bronze': '<:bronze:1426051866969772034>',
|
||||
'silver': '<:silver:1426051928575709286>',
|
||||
'gold': '<:gold:1426052012352737333>',
|
||||
'platinum': '<:platinum:1426052018040082545>',
|
||||
'diamond': '<:diamond:1426051944685895771>',
|
||||
'emerald': '<:emerald:1426051812313792537>',
|
||||
'ruby': '<:ruby:1426051838645637150>',
|
||||
'opal': '<:opal:1426051883247603762>'
|
||||
}
|
||||
|
||||
ACTIVITY_TYPE_ICONS = {
|
||||
0: '<:gaming:1426409065701048451>',
|
||||
2: '<:music:1426409047132737586>',
|
||||
3: '<:watching:1426409475450863778>'
|
||||
}
|
||||
|
||||
def format_username(user):
|
||||
if user.discriminator and user.discriminator != '0':
|
||||
return f'{user.name}#{user.discriminator}'
|
||||
return f'@{user.name}'
|
||||
|
||||
def get_default_avatar(user_id, discriminator=None):
|
||||
if discriminator and int(discriminator) > 0:
|
||||
index = int(discriminator) % 5
|
||||
else:
|
||||
index = (int(user_id) >> 22) % 6
|
||||
return f'https://cdn.discordapp.com/embed/avatars/{index}.png'
|
||||
|
||||
def snowflake_to_timestamp(snowflake):
|
||||
return ((int(snowflake) >> 22) + 1420070400000) / 1000
|
||||
|
||||
async def fetch_quest_data():
|
||||
global quests_fetch, quest_data
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
'https://raw.githubusercontent.com/aamiaa/discord-api-diff/refs/heads/main/quests.json'
|
||||
) as resp:
|
||||
quest_data = await resp.json()
|
||||
|
||||
quests_fetch = int(datetime.now().timestamp() * 1000) + 3600000
|
||||
|
||||
async def get_user_data(bot, user_id):
|
||||
headers = {'Authorization': f'Bot {bot.http.token}'}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f'https://discord.com/api/v10/users/{user_id}',
|
||||
headers=headers
|
||||
) as resp:
|
||||
if resp.status == 404:
|
||||
return None
|
||||
return await resp.json()
|
||||
|
||||
async def get_application_data(bot, app_id):
|
||||
headers = {'Authorization': f'Bot {bot.http.token}'}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f'https://discord.com/api/v10/applications/{app_id}/rpc',
|
||||
headers=headers
|
||||
) as resp:
|
||||
if resp.status in [404, 403, 10002]:
|
||||
return None
|
||||
return await resp.json()
|
||||
|
||||
async def get_published_listing(bot, sku_id):
|
||||
headers = {'Authorization': f'Bot {bot.http.token}'}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f'https://discord.com/api/v10/store/published-listings/skus/{sku_id}',
|
||||
headers=headers
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
return None
|
||||
return await resp.json()
|
||||
|
||||
def userinfo_command():
|
||||
@commands.hybrid_command(
|
||||
name="userinfo",
|
||||
description="Get information on a user.",
|
||||
)
|
||||
@app_commands.describe(
|
||||
user="User to get info for",
|
||||
user_id="User ID to get info for"
|
||||
)
|
||||
async def userinfo(self, context, user: discord.User = None, user_id: str = None):
|
||||
await context.defer()
|
||||
|
||||
bot = self.bot
|
||||
target_user = user if user else context.author
|
||||
|
||||
if user_id:
|
||||
try:
|
||||
target_user = await bot.fetch_user(int(user_id))
|
||||
except:
|
||||
await context.send('User not found.')
|
||||
return
|
||||
|
||||
user_data = await get_user_data(bot, target_user.id)
|
||||
if not user_data:
|
||||
await context.send('Failed to fetch user data.')
|
||||
return
|
||||
|
||||
guild = context.guild
|
||||
member = guild.get_member(target_user.id) if guild else None
|
||||
|
||||
if int(datetime.now().timestamp() * 1000) > quests_fetch:
|
||||
try:
|
||||
await fetch_quest_data()
|
||||
except:
|
||||
pass
|
||||
|
||||
badges = []
|
||||
flags = user_data.get('public_flags', 0)
|
||||
|
||||
if str(target_user.id) == '1015372540937502851':
|
||||
badges.append(f"[{BADGE_ICONS['staff']}]({BADGE_URLS['staff']})")
|
||||
badges.append(f"[{BADGE_ICONS['partner']}]({BADGE_URLS['partner']})")
|
||||
badges.append(f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})")
|
||||
badges.append(f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})")
|
||||
badges.append(f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})")
|
||||
badges.append(BADGE_ICONS['verified_developer'])
|
||||
badges.append(f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})")
|
||||
badges.append(f"[{BADGE_ICONS['guild_booster_lvl9']}]({BADGE_URLS['premium']})")
|
||||
badges.append(f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})")
|
||||
badges.append(BADGE_ICONS['username'])
|
||||
elif str(target_user.id) == '1376728824108286034':
|
||||
badges.append(BADGE_ICONS['automod'])
|
||||
badges.append(BADGE_ICONS['verified_developer'])
|
||||
badges.append(BADGE_ICONS['premium_bot'])
|
||||
elif flags & USER_FLAGS['STAFF']:
|
||||
badges.append(f"[{BADGE_ICONS['staff']}]({BADGE_URLS['staff']})")
|
||||
if flags & USER_FLAGS['PARTNER']:
|
||||
badges.append(f"[{BADGE_ICONS['partner']}]({BADGE_URLS['partner']})")
|
||||
if flags & USER_FLAGS['CERTIFIED_MODERATOR']:
|
||||
badges.append(f"[{BADGE_ICONS['certified_moderator']}]({BADGE_URLS['certified_moderator']})")
|
||||
if flags & USER_FLAGS['HYPESQUAD']:
|
||||
badges.append(f"[{BADGE_ICONS['hypesquad']}]({BADGE_URLS['hypesquad']})")
|
||||
if flags & USER_FLAGS['HYPESQUAD_ONLINE_HOUSE_1']:
|
||||
badges.append(f"[{BADGE_ICONS['hypesquad_house_1']}]({BADGE_URLS['hypesquad_house_1']})")
|
||||
if flags & USER_FLAGS['HYPESQUAD_ONLINE_HOUSE_2']:
|
||||
badges.append(f"[{BADGE_ICONS['hypesquad_house_2']}]({BADGE_URLS['hypesquad_house_2']})")
|
||||
if flags & USER_FLAGS['HYPESQUAD_ONLINE_HOUSE_3']:
|
||||
badges.append(f"[{BADGE_ICONS['hypesquad_house_3']}]({BADGE_URLS['hypesquad_house_3']})")
|
||||
if flags & USER_FLAGS['BUG_HUNTER_LEVEL_1']:
|
||||
badges.append(f"[{BADGE_ICONS['bug_hunter_level_1']}]({BADGE_URLS['bug_hunter_level_1']})")
|
||||
if flags & USER_FLAGS['BUG_HUNTER_LEVEL_2']:
|
||||
badges.append(f"[{BADGE_ICONS['bug_hunter_level_2']}]({BADGE_URLS['bug_hunter_level_2']})")
|
||||
if flags & USER_FLAGS['ACTIVE_DEVELOPER']:
|
||||
badges.append(f"[{BADGE_ICONS['active_developer']}]({BADGE_URLS['active_developer']})")
|
||||
if flags & USER_FLAGS['VERIFIED_DEVELOPER']:
|
||||
badges.append(BADGE_ICONS['verified_developer'])
|
||||
if flags & USER_FLAGS['PREMIUM_EARLY_SUPPORTER']:
|
||||
badges.append(f"[{BADGE_ICONS['early_supporter']}]({BADGE_URLS['early_supporter']})")
|
||||
|
||||
avatar_hash = user_data.get('avatar', '')
|
||||
banner_hash = user_data.get('banner')
|
||||
if (banner_hash or (avatar_hash and avatar_hash.startswith('a_'))) and not user_data.get('bot'):
|
||||
badges.append(f"[{BADGE_ICONS['premium']}]({BADGE_URLS['premium']})")
|
||||
|
||||
if member and member.premium_since:
|
||||
boosting_since = member.premium_since
|
||||
delta = (datetime.now(timezone.utc) - boosting_since).total_seconds()
|
||||
icon = BADGE_ICONS['guild_booster_lvl1']
|
||||
|
||||
if delta >= ONE_MONTH * 24:
|
||||
icon = BADGE_ICONS['guild_booster_lvl9']
|
||||
elif delta >= ONE_MONTH * 18:
|
||||
icon = BADGE_ICONS['guild_booster_lvl8']
|
||||
elif delta >= ONE_MONTH * 15:
|
||||
icon = BADGE_ICONS['guild_booster_lvl7']
|
||||
elif delta >= ONE_MONTH * 12:
|
||||
icon = BADGE_ICONS['guild_booster_lvl6']
|
||||
elif delta >= ONE_MONTH * 9:
|
||||
icon = BADGE_ICONS['guild_booster_lvl5']
|
||||
elif delta >= ONE_MONTH * 6:
|
||||
icon = BADGE_ICONS['guild_booster_lvl4']
|
||||
elif delta >= ONE_MONTH * 3:
|
||||
icon = BADGE_ICONS['guild_booster_lvl3']
|
||||
elif delta >= ONE_MONTH * 2:
|
||||
icon = BADGE_ICONS['guild_booster_lvl2']
|
||||
|
||||
badges.append(f"[{icon}]({BADGE_URLS['premium']})")
|
||||
|
||||
bot_deleted = False
|
||||
if user_data.get('bot'):
|
||||
app_data = await get_application_data(bot, target_user.id)
|
||||
if app_data:
|
||||
app_flags = app_data.get('flags', 0)
|
||||
if app_flags & APPLICATION_FLAGS['APPLICATION_COMMAND_BADGE']:
|
||||
badges.append(f"[{BADGE_ICONS['bot_commands']}]({BADGE_URLS['bot_commands']})")
|
||||
if app_flags & APPLICATION_FLAGS['AUTO_MODERATION_RULE_CREATE_BADGE']:
|
||||
badges.append(BADGE_ICONS['automod'])
|
||||
else:
|
||||
bot_deleted = True
|
||||
|
||||
if user_data.get('system'):
|
||||
bot_deleted = False
|
||||
|
||||
quest_decoration_name = None
|
||||
avatar_decoration = user_data.get('avatar_decoration_data')
|
||||
if avatar_decoration and avatar_decoration.get('sku_id'):
|
||||
for quest in quest_data:
|
||||
config = quest.get('config', {})
|
||||
rewards = config.get('rewards_config', {}).get('rewards', []) or config.get('rewards', [])
|
||||
|
||||
for reward in rewards:
|
||||
if reward.get('type') == 3 and reward.get('sku_id') == avatar_decoration['sku_id']:
|
||||
quest_decoration_name = (reward.get('name') or reward.get('messages', {}).get('name') or '*Unknown*').replace('Avatar Decoration', 'Avatar Deco')
|
||||
badges.append(f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})")
|
||||
break
|
||||
if quest_decoration_name:
|
||||
break
|
||||
elif avatar_decoration and (avatar_decoration.get('expires_at') or avatar_decoration.get('sku_id') == '1226939756617793606'):
|
||||
badges.append(f"[{BADGE_ICONS['quest_completed']}]({BADGE_URLS['quest_completed']})")
|
||||
|
||||
if user_data.get('legacy_username'):
|
||||
badges.append(BADGE_ICONS['username'])
|
||||
|
||||
if user_data.get('bot') and user_data.get('approximated_guild_count'):
|
||||
badges.append(BADGE_ICONS['premium_bot'])
|
||||
|
||||
profile_effect = user_data.get('profile_effect')
|
||||
if profile_effect:
|
||||
effect_id = profile_effect.get('id')
|
||||
if effect_id:
|
||||
orb_tier = None
|
||||
|
||||
if '1139323098643333240' in effect_id:
|
||||
orb_tier = 'opal'
|
||||
elif '1139323095841308733' in effect_id:
|
||||
orb_tier = 'ruby'
|
||||
elif '1139323090842013756' in effect_id:
|
||||
orb_tier = 'emerald'
|
||||
elif '1139323087608832090' in effect_id:
|
||||
orb_tier = 'diamond'
|
||||
elif '1144286544523669516' in effect_id:
|
||||
orb_tier = 'platinum'
|
||||
elif '1139323084127289374' in effect_id:
|
||||
orb_tier = 'gold'
|
||||
elif '1139323078435717220' in effect_id:
|
||||
orb_tier = 'silver'
|
||||
elif '1139323075214307448' in effect_id:
|
||||
orb_tier = 'bronze'
|
||||
else:
|
||||
orb_tier = 'orb'
|
||||
|
||||
if orb_tier:
|
||||
badges.append(BADGE_ICONS[orb_tier])
|
||||
|
||||
default_avatar = get_default_avatar(target_user.id, user_data.get('discriminator', '0'))
|
||||
avatar_url = target_user.avatar.url if target_user.avatar else default_avatar
|
||||
|
||||
banner_url = None
|
||||
banner_file = None
|
||||
original_banner_link = None
|
||||
if banner_hash:
|
||||
is_animated = banner_hash.startswith('a_')
|
||||
ext = 'gif' if is_animated else 'png'
|
||||
original_banner_url = f'https://cdn.discordapp.com/banners/{target_user.id}/{banner_hash}.{ext}?size=4096'
|
||||
original_banner_link = original_banner_url
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(original_banner_url) as resp:
|
||||
if resp.status == 200:
|
||||
banner_data = await resp.read()
|
||||
img = Image.open(BytesIO(banner_data))
|
||||
|
||||
if img.width < 1100:
|
||||
target_width = 1100
|
||||
target_height = 440
|
||||
|
||||
img_aspect = img.width / img.height
|
||||
target_aspect = target_width / target_height
|
||||
|
||||
if img_aspect > target_aspect:
|
||||
scale_height = target_height
|
||||
scale_width = int(scale_height * img_aspect)
|
||||
else:
|
||||
scale_width = target_width
|
||||
scale_height = int(scale_width / img_aspect)
|
||||
|
||||
left = (scale_width - target_width) // 2
|
||||
top = (scale_height - target_height) // 2
|
||||
right = left + target_width
|
||||
bottom = top + target_height
|
||||
|
||||
if is_animated:
|
||||
frames = []
|
||||
durations = []
|
||||
|
||||
try:
|
||||
while True:
|
||||
frame = img.copy().convert('RGBA')
|
||||
frame = frame.resize((scale_width, scale_height), Image.Resampling.LANCZOS)
|
||||
frame = frame.crop((left, top, right, bottom))
|
||||
frames.append(frame)
|
||||
durations.append(img.info.get('duration', 100))
|
||||
img.seek(img.tell() + 1)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
output = BytesIO()
|
||||
frames[0].save(
|
||||
output,
|
||||
format='GIF',
|
||||
save_all=True,
|
||||
append_images=frames[1:],
|
||||
duration=durations,
|
||||
loop=0,
|
||||
optimize=False
|
||||
)
|
||||
output.seek(0)
|
||||
banner_file = discord.File(output, filename='banner.gif')
|
||||
banner_url = 'attachment://banner.gif'
|
||||
else:
|
||||
img = img.resize((scale_width, scale_height), Image.Resampling.LANCZOS)
|
||||
img = img.crop((left, top, right, bottom))
|
||||
|
||||
output = BytesIO()
|
||||
img.save(output, format='PNG')
|
||||
output.seek(0)
|
||||
banner_file = discord.File(output, filename='banner.png')
|
||||
banner_url = 'attachment://banner.png'
|
||||
else:
|
||||
banner_url = original_banner_url
|
||||
except:
|
||||
banner_url = original_banner_url
|
||||
|
||||
images = [f'[Avatar]({avatar_url})']
|
||||
|
||||
if banner_url:
|
||||
images.append(f'[Banner]({original_banner_link})')
|
||||
|
||||
if avatar_decoration:
|
||||
await get_published_listing(bot, avatar_decoration['sku_id'])
|
||||
decoration_url = f"https://cdn.discordapp.com/avatar-decoration-presets/{avatar_decoration['asset']}.png?size=4096&passthrough=true"
|
||||
images.append(f'[Avatar Deco]({decoration_url})')
|
||||
|
||||
collectibles = user_data.get('collectibles')
|
||||
if collectibles and collectibles.get('nameplate'):
|
||||
nameplate = collectibles['nameplate']
|
||||
nameplate_asset = nameplate['asset']
|
||||
images.append(f'[Nameplate](https://cdn.discordapp.com/assets/collectibles/{nameplate_asset}static.png)')
|
||||
|
||||
mutual_guilds = [g for g in bot.guilds if g.get_member(target_user.id)]
|
||||
display_name = user_data.get('global_name') or user_data.get('username')
|
||||
|
||||
desc_lines = [
|
||||
f"# {member.nick if member and member.nick else display_name}",
|
||||
f"{format_username(target_user).replace('@', '')} • <@{target_user.id}>"
|
||||
]
|
||||
|
||||
subline = ""
|
||||
if badges:
|
||||
subline += ''.join(badges)
|
||||
|
||||
activity_lines = []
|
||||
if member and member.activities:
|
||||
for activity in member.activities:
|
||||
if activity.type == discord.ActivityType.custom:
|
||||
activity_lines.append(ACTIVITY_TYPE_NAMES[4])
|
||||
elif activity.type in [discord.ActivityType.playing, discord.ActivityType.listening, discord.ActivityType.watching]:
|
||||
name = activity.name
|
||||
activity_lines.append(
|
||||
f"{ACTIVITY_TYPE_ICONS.get(activity.type.value, '')} {ACTIVITY_TYPE_NAMES[activity.type.value]} **{name}**".strip()
|
||||
)
|
||||
|
||||
if subline:
|
||||
desc_lines.append(subline)
|
||||
|
||||
if mutual_guilds:
|
||||
desc_lines.append(f"-# {len(mutual_guilds)} Bot Mutual Server{'s' if len(mutual_guilds) > 1 else ''}")
|
||||
else:
|
||||
desc_lines.append('')
|
||||
|
||||
is_system = user_data.get('system') or user_data.get('discriminator') == '0000'
|
||||
|
||||
if bot_deleted and not is_system:
|
||||
desc_lines.append("*This bot's application has been deleted*\n-# (or app ID and user ID desync)")
|
||||
if is_system:
|
||||
desc_lines.append('**System account**')
|
||||
desc_lines.append('')
|
||||
|
||||
if activity_lines:
|
||||
desc_lines.extend(activity_lines)
|
||||
|
||||
embed = discord.Embed(
|
||||
color=0x7289DA,
|
||||
description='\n'.join(desc_lines)
|
||||
)
|
||||
|
||||
primary_guild = user_data.get('primary_guild')
|
||||
if primary_guild and primary_guild.get('identity_guild_id'):
|
||||
clan_badge_url = f"https://cdn.discordapp.com/clan-badges/{primary_guild['identity_guild_id']}/{primary_guild['badge']}.png?size=4096"
|
||||
embed.set_author(name=primary_guild.get('tag', ''), icon_url=clan_badge_url)
|
||||
else:
|
||||
embed.set_author(name="User Information", icon_url="https://yes.nighty.works/raw/gSxqzV.png")
|
||||
|
||||
if member and member.nick and member.nick != display_name:
|
||||
embed.title = display_name
|
||||
|
||||
embed.set_thumbnail(url=avatar_url)
|
||||
|
||||
if banner_url:
|
||||
embed.set_image(url=banner_url)
|
||||
|
||||
created_timestamp = int(snowflake_to_timestamp(target_user.id))
|
||||
created_date = f"<t:{created_timestamp}:F>"
|
||||
|
||||
embed.add_field(name='Created Date', value=created_date, inline=False)
|
||||
|
||||
if member and hasattr(member, 'joined_at') and member.joined_at:
|
||||
joined_timestamp = int(member.joined_at.timestamp())
|
||||
join_date = f"<t:{joined_timestamp}:F>"
|
||||
embed.add_field(name='Join Date', value=join_date, inline=False)
|
||||
|
||||
is_bot = user_data.get('bot', False)
|
||||
embed.add_field(name='Is Bot', value='True' if is_bot else 'False', inline=True)
|
||||
|
||||
if member and member.roles[1:]:
|
||||
roles = sorted(member.roles[1:], key=lambda r: r.position, reverse=True)
|
||||
role_mentions = [f'<@&{r.id}>' for r in roles]
|
||||
role_list = ' '.join(role_mentions)
|
||||
|
||||
if len(role_list) > 1024:
|
||||
truncated_roles = []
|
||||
current_length = 0
|
||||
for mention in role_mentions:
|
||||
if current_length + len(mention) + 1 > 1020:
|
||||
break
|
||||
truncated_roles.append(mention)
|
||||
current_length += len(mention) + 1
|
||||
role_list = ' '.join(truncated_roles) + ' ...'
|
||||
|
||||
embed.add_field(name=f'Roles ({len(member.roles) - 1})', value=role_list, inline=False)
|
||||
|
||||
if images:
|
||||
embed.add_field(name='\u200b', value='\u3000'.join(images), inline=False)
|
||||
|
||||
embed.set_footer(text=f'ID: {target_user.id}')
|
||||
|
||||
if banner_file:
|
||||
await context.send(embed=embed, file=banner_file)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
return userinfo
|
||||
|
||||
@@ -13,7 +13,7 @@ class Help(commands.Cog, name="help"):
|
||||
interaction: discord.Interaction,
|
||||
current: str,
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
categories = ["general", "fun", "moderation", "template", "owner", "sidestore"]
|
||||
categories = ["general", "fun", "moderation", "owner", "sidestore", "idevice", "melonx", "media", "miscellaneous", "utilities"]
|
||||
|
||||
suggestions = []
|
||||
for category in categories:
|
||||
@@ -30,55 +30,46 @@ class Help(commands.Cog, name="help"):
|
||||
@commands.hybrid_command(
|
||||
name="help", description="List all commands the bot has loaded."
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(category="Choose a specific category to view its commands")
|
||||
@app_commands.autocomplete(category=category_autocomplete)
|
||||
async def help(self, context: Context, category: str = None) -> None:
|
||||
|
||||
category_mapping = {
|
||||
"help": "general",
|
||||
"botinfo": "general",
|
||||
"serverinfo": "general",
|
||||
"ping": "general",
|
||||
"feedback": "general",
|
||||
# "context_menus": "general",
|
||||
|
||||
"randomfact": "fun",
|
||||
"coinflip": "fun",
|
||||
"rps": "fun",
|
||||
"8ball": "fun",
|
||||
"minesweeper": "fun",
|
||||
|
||||
"kick": "moderation",
|
||||
"ban": "moderation",
|
||||
"nick": "moderation",
|
||||
"purge": "moderation",
|
||||
"hackban": "moderation",
|
||||
"warnings": "moderation",
|
||||
"archive": "moderation",
|
||||
|
||||
"general": "general",
|
||||
"fun": "fun",
|
||||
"idevice": "idevice",
|
||||
"melonx": "melonx",
|
||||
"media": "media",
|
||||
"misc": "miscellaneous",
|
||||
"miscellaneous": "miscellaneous",
|
||||
"moderation": "moderation",
|
||||
"sidestore": "sidestore",
|
||||
"refresh": "sidestore",
|
||||
"code": "sidestore",
|
||||
"crash": "sidestore",
|
||||
"pairing": "sidestore",
|
||||
"server": "sidestore",
|
||||
"half": "sidestore",
|
||||
"sparse": "sidestore",
|
||||
"utils": "utilities",
|
||||
"utilities": "utilities",
|
||||
|
||||
"sync": "owner",
|
||||
"cog_management": "owner",
|
||||
"logs": "owner",
|
||||
"invite": "owner",
|
||||
"load": "owner",
|
||||
"unload": "owner",
|
||||
"reload": "owner",
|
||||
"shutdown": "owner",
|
||||
"say": "owner",
|
||||
"invite": "owner",
|
||||
}
|
||||
|
||||
category_descriptions = {
|
||||
"general": "General commands",
|
||||
"fun": "Funny commands",
|
||||
"fun": "Fun commands",
|
||||
"moderation": "Administration commands",
|
||||
"template": "Template commands",
|
||||
"owner": "Owner commands",
|
||||
"sidestore": "SideStore troubleshooting commands"
|
||||
"sidestore": "SideStore troubleshooting commands",
|
||||
"idevice": "idevice troubleshooting commands",
|
||||
"melonx": "MeloNX troubleshooting commands",
|
||||
"media": "Media commands",
|
||||
"utilities": "Utility commands",
|
||||
"miscellaneous": "Miscellaneous commands"
|
||||
}
|
||||
|
||||
if category is None:
|
||||
@@ -88,6 +79,18 @@ class Help(commands.Cog, name="help"):
|
||||
)
|
||||
embed.set_author(name="Help", icon_url="https://yes.nighty.works/raw/T9mnBO.png")
|
||||
|
||||
standalone_commands = []
|
||||
botinfo_cmd = self.bot.tree.get_command("botinfo")
|
||||
if botinfo_cmd:
|
||||
standalone_commands.append("**/botinfo** » Get information about this bot")
|
||||
|
||||
if standalone_commands:
|
||||
embed.add_field(
|
||||
name="",
|
||||
value="".join(standalone_commands) + "\n",
|
||||
inline=False
|
||||
)
|
||||
|
||||
available_categories = set()
|
||||
for cog_name in self.bot.cogs:
|
||||
mapped_category = category_mapping.get(cog_name.lower())
|
||||
@@ -109,7 +112,13 @@ class Help(commands.Cog, name="help"):
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.author.send(embed=embed)
|
||||
try:
|
||||
await context.send(embed=embed)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
try:
|
||||
await context.author.send(embed=embed)
|
||||
except discord.Forbidden:
|
||||
pass # User has DMs disabled
|
||||
return
|
||||
|
||||
category = category.lower()
|
||||
@@ -122,7 +131,13 @@ class Help(commands.Cog, name="help"):
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.author.send(embed=embed)
|
||||
try:
|
||||
await context.send(embed=embed)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
try:
|
||||
await context.author.send(embed=embed)
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
return
|
||||
|
||||
|
||||
@@ -134,6 +149,9 @@ class Help(commands.Cog, name="help"):
|
||||
if cog:
|
||||
commands_list = cog.get_commands()
|
||||
for command in commands_list:
|
||||
has_prefix_subcommands = hasattr(command, 'commands') and len(getattr(command, 'commands', [])) > 0
|
||||
if has_prefix_subcommands:
|
||||
continue
|
||||
name = command.name
|
||||
if name in seen_names:
|
||||
continue
|
||||
@@ -148,11 +166,21 @@ class Help(commands.Cog, name="help"):
|
||||
bound_cog_name = getattr(bound_cog, "qualified_name", "").lower()
|
||||
if category_mapping.get(bound_cog_name) != category:
|
||||
continue
|
||||
if app_command.name in seen_names:
|
||||
continue
|
||||
description = app_command.description.partition("\n")[0] if getattr(app_command, "description", None) else "No description available"
|
||||
commands_in_category.append((app_command.name, description))
|
||||
seen_names.add(app_command.name)
|
||||
has_subcommands = hasattr(app_command, 'commands') and len(getattr(app_command, 'commands', [])) > 0
|
||||
|
||||
if has_subcommands and category not in ["owner"]:
|
||||
for subcommand in app_command.commands:
|
||||
if subcommand.name in seen_names:
|
||||
continue
|
||||
sub_desc = subcommand.description.partition("\n")[0] if getattr(subcommand, "description", None) else "No description available"
|
||||
commands_in_category.append((f"{app_command.name} {subcommand.name}", sub_desc))
|
||||
seen_names.add(f"{app_command.name} {subcommand.name}")
|
||||
else:
|
||||
if app_command.name in seen_names:
|
||||
continue
|
||||
description = app_command.description.partition("\n")[0] if getattr(app_command, "description", None) else "No description available"
|
||||
commands_in_category.append((app_command.name, description))
|
||||
seen_names.add(app_command.name)
|
||||
|
||||
if not commands_in_category:
|
||||
embed = discord.Embed(
|
||||
@@ -163,7 +191,13 @@ class Help(commands.Cog, name="help"):
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.author.send(embed=embed)
|
||||
try:
|
||||
await context.send(embed=embed)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
try:
|
||||
await context.author.send(embed=embed)
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
@@ -183,8 +217,14 @@ class Help(commands.Cog, name="help"):
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.author.send(embed=embed)
|
||||
try:
|
||||
await context.send(embed=embed)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
try:
|
||||
await context.author.send(embed=embed)
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Help(bot))
|
||||
await bot.add_cog(Help(bot))
|
||||
129
cogs/idevice/__init__.py
Normal file
129
cogs/idevice/__init__.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .idevice import ideviceView
|
||||
from .error_codes import errorcodes_command
|
||||
from .developermode import developermode_command
|
||||
from .noapps import noapps_command
|
||||
from .mountddi import mountddi_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Idevice(commands.GroupCog, name="idevice"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="idevice", invoke_without_command=True)
|
||||
async def idevice_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="idevice Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0xfa8c4a
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
view = ideviceView(self.bot)
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
@idevice_group.command(name="help")
|
||||
async def idevice_group_help(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="idevice Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0xfa8c4a
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
view = ideviceView(self.bot)
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command)
|
||||
else:
|
||||
await context.send(f"Unknown idevice command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
@idevice_group.command(name="errorcodes")
|
||||
async def idevice_group_errorcodes(self, context: Context):
|
||||
await self._invoke_hybrid(context, "errorcodes")
|
||||
|
||||
@idevice_group.command(name="developermode")
|
||||
async def idevice_group_developermode(self, context: Context):
|
||||
await self._invoke_hybrid(context, "developermode")
|
||||
|
||||
@idevice_group.command(name="noapps")
|
||||
async def idevice_group_noapps(self, context: Context):
|
||||
await self._invoke_hybrid(context, "noapps")
|
||||
|
||||
@idevice_group.command(name="mountddi")
|
||||
async def idevice_group_mountddi(self, context: Context):
|
||||
await self._invoke_hybrid(context, "mountddi")
|
||||
|
||||
@app_commands.command(
|
||||
name="help",
|
||||
description="idevice troubleshooting help"
|
||||
)
|
||||
async def help(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="idevice Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0xfa8c4a
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
view = ideviceView(self.bot)
|
||||
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="errorcodes",
|
||||
description="Look up error codes and their meanings."
|
||||
)
|
||||
async def errorcodes(self, context, *, error_code: str = None):
|
||||
return await errorcodes_command()(self, context, error_code=error_code)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="developermode",
|
||||
description="How to turn on developer mode"
|
||||
)
|
||||
async def developermode(self, context):
|
||||
return await developermode_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="noapps",
|
||||
description="Help when apps aren't showing in installed apps view"
|
||||
)
|
||||
async def noapps(self, context):
|
||||
return await noapps_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="mountddi",
|
||||
description="How to manually mount DDI"
|
||||
)
|
||||
async def mountddi(self, context):
|
||||
return await mountddi_command()(self, context)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Idevice(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'idevice.help'")
|
||||
bot.logger.info("Loaded extension 'idevice.errorcodes'")
|
||||
bot.logger.info("Loaded extension 'idevice.developermode'")
|
||||
bot.logger.info("Loaded extension 'idevice.noapps'")
|
||||
bot.logger.info("Loaded extension 'idevice.mountddi'")
|
||||
41
cogs/idevice/developermode.py
Normal file
41
cogs/idevice/developermode.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def developermode_command():
|
||||
@commands.hybrid_command(
|
||||
name="developermode", description="How to turn on developer mode"
|
||||
)
|
||||
async def developermode(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0xfa8c4a,
|
||||
description=(
|
||||
'# How to Enable Developer Mode\n\n---\n\n' +
|
||||
'1. Open the "Settings" app\n' +
|
||||
'2. Navigate to "Privacy & Security"\n' +
|
||||
'3. Scroll all the way down to find "Developer Mode"\n\n' +
|
||||
'If you don\'t see the Developer Mode option, you need to install a developer app first.\n\n' +
|
||||
'You can use [SideStore](https://sidestore.io/) for this purpose - follow their installation guide to get started.'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/developermode.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return developermode
|
||||
66
cogs/idevice/error_codes.py
Normal file
66
cogs/idevice/error_codes.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import json
|
||||
import os
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def errorcodes_command():
|
||||
@commands.hybrid_command(name="errorcodes", description="Look up an idevice error code by name or number")
|
||||
@app_commands.describe(name="Start typing to search all error names and codes")
|
||||
async def errorcodes(self, context, name: str):
|
||||
def load_errors():
|
||||
json_path = os.path.join(os.path.dirname(__file__), 'files/errorcodes.json')
|
||||
try:
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
self.bot.logger.error(f"Error codes JSON file not found: {json_path}")
|
||||
return []
|
||||
except json.JSONDecodeError as e:
|
||||
self.bot.logger.error(f"Error parsing error codes JSON: {e}")
|
||||
return []
|
||||
|
||||
errors = load_errors()
|
||||
key_to_data = {error['name']: (error['description'], error['code']) for error in errors}
|
||||
code_to_key = {error['code']: error['name'] for error in errors}
|
||||
|
||||
key = name
|
||||
if key not in key_to_data:
|
||||
try:
|
||||
num = int(name)
|
||||
key = code_to_key.get(num)
|
||||
except ValueError:
|
||||
key = None
|
||||
if key is None or key not in key_to_data:
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message("Error not found.", ephemeral=True)
|
||||
else:
|
||||
await context.send("Error not found.")
|
||||
return
|
||||
|
||||
title, code = key_to_data[key]
|
||||
|
||||
embed = discord.Embed(
|
||||
description=f"## Error Code: {code}\n\n**Name**: `{key}`\n**Description**: {title}",
|
||||
color=0xfa8c4a,
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/error_codes.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return errorcodes
|
||||
|
||||
|
||||
BIN
cogs/idevice/files/DDI.zip
Normal file
BIN
cogs/idevice/files/DDI.zip
Normal file
Binary file not shown.
332
cogs/idevice/files/errorcodes.json
Normal file
332
cogs/idevice/files/errorcodes.json
Normal file
@@ -0,0 +1,332 @@
|
||||
[
|
||||
{
|
||||
"name": "socket",
|
||||
"description": "device socket io failed",
|
||||
"code": -1
|
||||
},
|
||||
{
|
||||
"name": "pem_parse_failed",
|
||||
"description": "PEM parse failed",
|
||||
"code": -2
|
||||
},
|
||||
{
|
||||
"name": "rustls",
|
||||
"description": "TLS error",
|
||||
"code": -3
|
||||
},
|
||||
{
|
||||
"name": "tls_builder_failed",
|
||||
"description": "TLS verifiction build failed",
|
||||
"code": -4
|
||||
},
|
||||
{
|
||||
"name": "plist",
|
||||
"description": "io on plist",
|
||||
"code": -5
|
||||
},
|
||||
{
|
||||
"name": "utf8",
|
||||
"description": "can't convert bytes to utf8",
|
||||
"code": -6
|
||||
},
|
||||
{
|
||||
"name": "unexpected_response",
|
||||
"description": "unexpected response from device",
|
||||
"code": -7
|
||||
},
|
||||
{
|
||||
"name": "get_prohibited",
|
||||
"description": "this request was prohibited",
|
||||
"code": -8
|
||||
},
|
||||
{
|
||||
"name": "session_inactive",
|
||||
"description": "no SSL session is active",
|
||||
"code": -9
|
||||
},
|
||||
{
|
||||
"name": "invalid_host_id",
|
||||
"description": "device does not have pairing file",
|
||||
"code": -10
|
||||
},
|
||||
{
|
||||
"name": "no_established_connection",
|
||||
"description": "no established connection",
|
||||
"code": -11
|
||||
},
|
||||
{
|
||||
"name": "heartbeat_sleepy_time",
|
||||
"description": "device went to sleep",
|
||||
"code": -12
|
||||
},
|
||||
{
|
||||
"name": "heartbeat_timeout",
|
||||
"description": "heartbeat timeout",
|
||||
"code": -13
|
||||
},
|
||||
{
|
||||
"name": "not_found",
|
||||
"description": "not found",
|
||||
"code": -14
|
||||
},
|
||||
{
|
||||
"name": "service_not_found",
|
||||
"description": "service not found",
|
||||
"code": -15
|
||||
},
|
||||
{
|
||||
"name": "cdtunnel_packet_too_short",
|
||||
"description": "CDTunnel packet too short",
|
||||
"code": -16
|
||||
},
|
||||
{
|
||||
"name": "cdtunnel_packet_invalid_magic",
|
||||
"description": "CDTunnel packet invalid magic",
|
||||
"code": -17
|
||||
},
|
||||
{
|
||||
"name": "packet_size_mismatch",
|
||||
"description": "Proclaimed packet size does not match actual size",
|
||||
"code": -18
|
||||
},
|
||||
{
|
||||
"name": "json",
|
||||
"description": "JSON serialization failed",
|
||||
"code": -19
|
||||
},
|
||||
{
|
||||
"name": "device_not_found",
|
||||
"description": "device not found",
|
||||
"code": -20
|
||||
},
|
||||
{
|
||||
"name": "device_locked",
|
||||
"description": "device lockded",
|
||||
"code": -21
|
||||
},
|
||||
{
|
||||
"name": "usb_connection_refused",
|
||||
"description": "device refused connection",
|
||||
"code": -22
|
||||
},
|
||||
{
|
||||
"name": "usb_bad_command",
|
||||
"description": "bad command",
|
||||
"code": -23
|
||||
},
|
||||
{
|
||||
"name": "usb_bad_device",
|
||||
"description": "bad device",
|
||||
"code": -24
|
||||
},
|
||||
{
|
||||
"name": "usb_bad_version",
|
||||
"description": "usb bad version",
|
||||
"code": -25
|
||||
},
|
||||
{
|
||||
"name": "bad_build_manifest",
|
||||
"description": "bad build manifest",
|
||||
"code": -26
|
||||
},
|
||||
{
|
||||
"name": "image_not_mounted",
|
||||
"description": "image not mounted",
|
||||
"code": -27
|
||||
},
|
||||
{
|
||||
"name": "pairing_dialog_response_pending",
|
||||
"description": "pairing trust dialog pending",
|
||||
"code": -28
|
||||
},
|
||||
{
|
||||
"name": "user_denied_pairing",
|
||||
"description": "user denied pairing trust",
|
||||
"code": -29
|
||||
},
|
||||
{
|
||||
"name": "password_protected",
|
||||
"description": "device is locked",
|
||||
"code": -30
|
||||
},
|
||||
{
|
||||
"name": "misagent_failure",
|
||||
"description": "misagent operation failed",
|
||||
"code": -31
|
||||
},
|
||||
{
|
||||
"name": "installation_proxy_operation_failed",
|
||||
"description": "installation proxy operation failed",
|
||||
"code": -32
|
||||
},
|
||||
{
|
||||
"name": "afc",
|
||||
"description": "afc error",
|
||||
"code": -33
|
||||
},
|
||||
{
|
||||
"name": "unknown_afc_opcode",
|
||||
"description": "unknown afc opcode",
|
||||
"code": -34
|
||||
},
|
||||
{
|
||||
"name": "invalid_afc_magic",
|
||||
"description": "invalid afc magic",
|
||||
"code": -35
|
||||
},
|
||||
{
|
||||
"name": "afc_missing_attribute",
|
||||
"description": "missing file attribute",
|
||||
"code": -36
|
||||
},
|
||||
{
|
||||
"name": "crash_report_mover_bad_response",
|
||||
"description": "crash report mover sent the wrong response",
|
||||
"code": -37
|
||||
},
|
||||
{
|
||||
"name": "reqwest",
|
||||
"description": "http reqwest error",
|
||||
"code": -38
|
||||
},
|
||||
{
|
||||
"name": "internal_error",
|
||||
"description": "internal error",
|
||||
"code": -39
|
||||
},
|
||||
{
|
||||
"name": "unknown_frame",
|
||||
"description": "unknown http frame type",
|
||||
"code": -40
|
||||
},
|
||||
{
|
||||
"name": "unknown_http_setting",
|
||||
"description": "unknown http setting type",
|
||||
"code": -41
|
||||
},
|
||||
{
|
||||
"name": "uninitialized_stream_id",
|
||||
"description": "Unintialized stream ID",
|
||||
"code": -42
|
||||
},
|
||||
{
|
||||
"name": "unknown_xpc_type",
|
||||
"description": "unknown XPC type",
|
||||
"code": -43
|
||||
},
|
||||
{
|
||||
"name": "malformed_xpc",
|
||||
"description": "malformed XPC message",
|
||||
"code": -44
|
||||
},
|
||||
{
|
||||
"name": "invalid_xpc_magic",
|
||||
"description": "invalid XPC magic",
|
||||
"code": -45
|
||||
},
|
||||
{
|
||||
"name": "unexpected_xpc_version",
|
||||
"description": "unexpected XPC version",
|
||||
"code": -46
|
||||
},
|
||||
{
|
||||
"name": "invalid_c_string",
|
||||
"description": "invalid C string",
|
||||
"code": -47
|
||||
},
|
||||
{
|
||||
"name": "http_stream_reset",
|
||||
"description": "stream reset",
|
||||
"code": -48
|
||||
},
|
||||
{
|
||||
"name": "http_go_away",
|
||||
"description": "go away packet received",
|
||||
"code": -49
|
||||
},
|
||||
{
|
||||
"name": "ns_keyed_archive_error",
|
||||
"description": "NSKeyedArchive error",
|
||||
"code": -50
|
||||
},
|
||||
{
|
||||
"name": "unknown_aux_value_type",
|
||||
"description": "Unknown aux value type",
|
||||
"code": -51
|
||||
},
|
||||
{
|
||||
"name": "unknown_channel",
|
||||
"description": "unknown channel",
|
||||
"code": -52
|
||||
},
|
||||
{
|
||||
"name": "addr_parse_error",
|
||||
"description": "cannot parse string as IpAddr",
|
||||
"code": -53
|
||||
},
|
||||
{
|
||||
"name": "disable_memory_limit_failed",
|
||||
"description": "disable memory limit failed",
|
||||
"code": -54
|
||||
},
|
||||
{
|
||||
"name": "not_enough_bytes",
|
||||
"description": "not enough bytes",
|
||||
"code": -55
|
||||
},
|
||||
{
|
||||
"name": "utf8_error",
|
||||
"description": "failed to parse bytes as valid utf8",
|
||||
"code": -56
|
||||
},
|
||||
{
|
||||
"name": "invalid_argument",
|
||||
"description": "invalid argument passed",
|
||||
"code": -57
|
||||
},
|
||||
{
|
||||
"name": "unknown_error_type",
|
||||
"description": "unknown error returned from device",
|
||||
"code": -59
|
||||
},
|
||||
{
|
||||
"name": "ffi_invalid_arg",
|
||||
"description": "invalid arguments were passed",
|
||||
"code": -60
|
||||
},
|
||||
{
|
||||
"name": "ffi_invalid_string",
|
||||
"description": "invalid string was passed",
|
||||
"code": -61
|
||||
},
|
||||
{
|
||||
"name": "ffi_buffer_too_small",
|
||||
"description": "buffer passed is too small",
|
||||
"code": -62
|
||||
},
|
||||
{
|
||||
"name": "unsupported_watch_key",
|
||||
"description": "unsupported watch key",
|
||||
"code": -63
|
||||
},
|
||||
{
|
||||
"name": "malformed_command",
|
||||
"description": "malformed command",
|
||||
"code": -64
|
||||
},
|
||||
{
|
||||
"name": "integer_overflow",
|
||||
"description": "integer overflow",
|
||||
"code": -65
|
||||
},
|
||||
{
|
||||
"name": "canceled_by_user",
|
||||
"description": "canceled by user",
|
||||
"code": -66
|
||||
},
|
||||
{
|
||||
"name": "malformed_package_archive",
|
||||
"description": "malformed package archive",
|
||||
"code": -67
|
||||
}
|
||||
]
|
||||
@@ -1,3 +1,321 @@
|
||||
async def setup(bot) -> None:
|
||||
pass
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import json
|
||||
import os
|
||||
import math
|
||||
|
||||
|
||||
def load_error_codes():
|
||||
try:
|
||||
json_path = os.path.join(os.path.dirname(__file__), 'files/errorcodes.json')
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
|
||||
class ErrorCodesBrowserView(discord.ui.View):
|
||||
def __init__(self, items_per_page=9):
|
||||
super().__init__(timeout=300)
|
||||
self.error_codes = load_error_codes()
|
||||
self.items_per_page = items_per_page
|
||||
self.current_page = 0
|
||||
self.max_pages = math.ceil(len(self.error_codes) / items_per_page) if self.error_codes else 1
|
||||
self.update_buttons()
|
||||
|
||||
def update_buttons(self):
|
||||
self.clear_items()
|
||||
|
||||
first_button = discord.ui.Button(
|
||||
emoji="<:leftmax:1420240325770870905>",
|
||||
style=discord.ButtonStyle.primary,
|
||||
disabled=(self.current_page == 0)
|
||||
)
|
||||
first_button.callback = self.first_page
|
||||
self.add_item(first_button)
|
||||
|
||||
prev_button = discord.ui.Button(
|
||||
emoji="<:left:1420240344926126090>",
|
||||
style=discord.ButtonStyle.primary,
|
||||
disabled=(self.current_page == 0)
|
||||
)
|
||||
prev_button.callback = self.prev_page
|
||||
self.add_item(prev_button)
|
||||
|
||||
stop_button = discord.ui.Button(
|
||||
emoji="<:middle:1420240356087173160>",
|
||||
style=discord.ButtonStyle.secondary
|
||||
)
|
||||
stop_button.callback = self.stop_interaction
|
||||
self.add_item(stop_button)
|
||||
|
||||
next_button = discord.ui.Button(
|
||||
emoji="<:right:1420240334100627456>",
|
||||
style=discord.ButtonStyle.primary,
|
||||
disabled=(self.current_page >= self.max_pages - 1)
|
||||
)
|
||||
next_button.callback = self.next_page
|
||||
self.add_item(next_button)
|
||||
|
||||
last_button = discord.ui.Button(
|
||||
emoji="<:rightmax:1420240368846372886>",
|
||||
style=discord.ButtonStyle.primary,
|
||||
disabled=(self.current_page >= self.max_pages - 1)
|
||||
)
|
||||
last_button.callback = self.last_page
|
||||
self.add_item(last_button)
|
||||
|
||||
def create_embed(self):
|
||||
if not self.error_codes:
|
||||
embed = discord.Embed(
|
||||
title="Error Codes",
|
||||
description="No error codes found.",
|
||||
color=0xfa8c4a
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
embed.set_footer(text="Page 0 of 0")
|
||||
return embed
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Error Codes",
|
||||
color=0xfa8c4a
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
|
||||
start_idx = self.current_page * self.items_per_page
|
||||
end_idx = min(start_idx + self.items_per_page, len(self.error_codes))
|
||||
current_codes = self.error_codes[start_idx:end_idx]
|
||||
|
||||
for i, error in enumerate(current_codes):
|
||||
field_value = f"**Code:** `{error.get('code', 'N/A')}`\n**Name:** `{error.get('name', 'Unknown')}`\n**Description:** {error.get('description', 'No description')}"
|
||||
embed.add_field(
|
||||
name="\u200b",
|
||||
value=field_value,
|
||||
inline=True
|
||||
)
|
||||
|
||||
|
||||
while len(current_codes) % 3 != 0:
|
||||
embed.add_field(name="\u200b", value="\u200b", inline=True)
|
||||
|
||||
embed.set_footer(text=f"Page {self.current_page + 1} of {self.max_pages}")
|
||||
|
||||
return embed
|
||||
|
||||
async def first_page(self, interaction: discord.Interaction):
|
||||
self.current_page = 0
|
||||
self.update_buttons()
|
||||
embed = self.create_embed()
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
async def prev_page(self, interaction: discord.Interaction):
|
||||
if self.current_page > 0:
|
||||
self.current_page -= 1
|
||||
self.update_buttons()
|
||||
embed = self.create_embed()
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
async def stop_interaction(self, interaction: discord.Interaction):
|
||||
self.clear_items()
|
||||
embed = self.create_embed()
|
||||
embed.set_footer(text="Interaction stopped")
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
self.stop()
|
||||
|
||||
async def next_page(self, interaction: discord.Interaction):
|
||||
if self.current_page < self.max_pages - 1:
|
||||
self.current_page += 1
|
||||
self.update_buttons()
|
||||
embed = self.create_embed()
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
async def last_page(self, interaction: discord.Interaction):
|
||||
self.current_page = self.max_pages - 1
|
||||
self.update_buttons()
|
||||
embed = self.create_embed()
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
|
||||
class ideviceSelect(discord.ui.Select):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
options = [
|
||||
discord.SelectOption(
|
||||
label="No Apps",
|
||||
value="noapps",
|
||||
description="Help when apps aren't showing in installed apps view",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Error Codes",
|
||||
value="errorcodes",
|
||||
description="Browse idevice error codes",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Developer Mode",
|
||||
value="developermode",
|
||||
description="How to turn on developer mode",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Mount DDI",
|
||||
value="mountddi",
|
||||
description="How to manually mount DDI",
|
||||
),
|
||||
]
|
||||
super().__init__(placeholder="Choose an idevice command...", options=options)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
command_name = self.values[0]
|
||||
|
||||
if command_name == "errorcodes":
|
||||
try:
|
||||
view = ErrorCodesBrowserView()
|
||||
embed = view.create_embed()
|
||||
|
||||
success_embed = discord.Embed(
|
||||
title="Command Executed",
|
||||
description="Successfully executed `/errorcodes`. Please run /[errorcode_name] to get more information about an error code, and send it in chat",
|
||||
color=0x00FF00
|
||||
)
|
||||
success_embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=success_embed, view=None)
|
||||
|
||||
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
except discord.Forbidden:
|
||||
guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel"
|
||||
self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute errorcodes command")
|
||||
|
||||
if interaction.guild is None:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command cannot be executed in DMs.",
|
||||
color=0xFF0000
|
||||
)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except Exception as e:
|
||||
self.bot.logger.error(f"Error executing errorcodes command: {e}")
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="An error occurred while executing the command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
return
|
||||
|
||||
command = self.bot.get_command(command_name)
|
||||
|
||||
if command:
|
||||
try:
|
||||
ctx = await self.bot.get_context(interaction.message)
|
||||
if ctx:
|
||||
await ctx.invoke(command)
|
||||
embed = discord.Embed(
|
||||
title="Command Executed",
|
||||
description=f"Successfully executed `/{command_name}`",
|
||||
color=0x00FF00
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except discord.Forbidden:
|
||||
guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel"
|
||||
self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute {command_name} command")
|
||||
|
||||
if interaction.guild is None:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command cannot be executed in DMs.",
|
||||
color=0xFF0000
|
||||
)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except Exception as e:
|
||||
self.bot.logger.error(f"Error executing {command_name} command: {e}")
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="An error occurred while executing the command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Command not found!",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
|
||||
|
||||
class ideviceView(discord.ui.View):
|
||||
def __init__(self, bot):
|
||||
super().__init__()
|
||||
self.add_item(ideviceSelect(bot))
|
||||
|
||||
|
||||
def idevice_command():
|
||||
@commands.hybrid_command(
|
||||
name="idevice", description="idevice troubleshooting and help"
|
||||
)
|
||||
async def idevice(self, context):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs send messages permissions in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title="idevice Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0xfa8c4a
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
|
||||
view = ideviceView(self.bot)
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return idevice
|
||||
54
cogs/idevice/mountddi.py
Normal file
54
cogs/idevice/mountddi.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import os
|
||||
|
||||
|
||||
def mountddi_command():
|
||||
@commands.hybrid_command(
|
||||
name="mountddi",
|
||||
description="How to manually mount DDI"
|
||||
)
|
||||
async def mountddi(self, context):
|
||||
await context.defer()
|
||||
|
||||
embed = discord.Embed(
|
||||
color=0xfa8c4a,
|
||||
description=(
|
||||
'# How to Manually Mount DDI\n\n---\n\n'
|
||||
'1. **Download the DDI.zip file attached above:**\n'
|
||||
' - Save it to your device and extract the contents\n\n'
|
||||
'2. **Replace the DDI folder in StikDebug:**\n'
|
||||
' - Navigate to the StikDebug default directory on your iPhone/iPad\n'
|
||||
' - Delete the existing DDI folder completely\n'
|
||||
' - Replace it with the DDI folder from the downloaded zip\n'
|
||||
' - Make sure it\'s in the StikDebug default directory\n\n'
|
||||
'3. **Restart and retry:**\n'
|
||||
' - Completely restart StikDebug\n'
|
||||
' - See if you get the same error again\n\n'
|
||||
)
|
||||
)
|
||||
embed.set_author(
|
||||
name="idevice",
|
||||
icon_url="https://yes.nighty.works/raw/snLMuO.png"
|
||||
)
|
||||
embed.set_footer(text="Last Edited by neoarz")
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/mountddi.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
|
||||
ddi_file_path = os.path.join(os.path.dirname(__file__), 'files/DDI.zip')
|
||||
file = discord.File(ddi_file_path, filename='DDI.zip') if os.path.exists(ddi_file_path) else None
|
||||
|
||||
if file:
|
||||
await context.send(embed=embed, view=view, file=file)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return mountddi
|
||||
42
cogs/idevice/noapps.py
Normal file
42
cogs/idevice/noapps.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def noapps_command():
|
||||
@commands.hybrid_command(
|
||||
name="noapps", description="Help when apps aren't showing in installed apps view"
|
||||
)
|
||||
async def noapps(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0xfa8c4a,
|
||||
description=(
|
||||
'# Apps Not Showing in Installed Apps View\n\n---\n\n' +
|
||||
'If apps aren\'t appearing in the StikDebug installed apps view, this is likely because they were signed with a distribution certificate instead of a development certificate.\n\n' +
|
||||
'Distribution certificates lack the `get-task-allow` entitlement needed for JIT.\n\n' +
|
||||
'To fix this issue:\n' +
|
||||
'- Use a development certificate when signing apps, or\n' +
|
||||
'- Try SideStore, the best free sideloading method available\n\n' +
|
||||
'More details can be found at [SideStore\'s official website](https://sidestore.io/)'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/noapps.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return noapps
|
||||
143
cogs/media/__init__.py
Normal file
143
cogs/media/__init__.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from typing import Optional
|
||||
|
||||
from .download import download_command
|
||||
from .mcquote import mcquote_command
|
||||
from .img2gif import img2gif_command
|
||||
from .tweety import tweety_command
|
||||
from .tts import tts_command
|
||||
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Media(commands.GroupCog, name="media"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message: discord.Message):
|
||||
"""Listen for bot mentions with 'tweety' command while replying to a message"""
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
if self.bot.user in message.mentions and message.reference and message.reference.message_id:
|
||||
content = message.content.lower()
|
||||
content_without_mention = content.replace(f'<@{self.bot.user.id}>', '').replace(f'<@!{self.bot.user.id}>', '').strip()
|
||||
|
||||
if content_without_mention.strip() == 'tweety':
|
||||
ctx = await self.bot.get_context(message)
|
||||
await self.tweety(ctx)
|
||||
|
||||
@commands.group(name="media", invoke_without_command=True)
|
||||
async def media_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="Media Commands",
|
||||
description="Use `.media <subcommand>` or `/media <subcommand>`.",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.add_field(name="Available", value="download, mcquote, img2gif, tweety, tts", inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str, *args, **kwargs):
|
||||
if name == "download":
|
||||
await self.download(context, url=kwargs.get('url', ''))
|
||||
return
|
||||
if name == "mcquote":
|
||||
await self.mcquote(context, text=kwargs.get('text', ''))
|
||||
return
|
||||
if name == "img2gif":
|
||||
await self.img2gif(context, attachment=kwargs.get('attachment'))
|
||||
return
|
||||
if name == "tweety":
|
||||
await self.tweety(context)
|
||||
return
|
||||
if name == "tts":
|
||||
await self.tts(context, text=kwargs.get('text'))
|
||||
return
|
||||
await context.send(f"Unknown media command: {name}")
|
||||
|
||||
@media_group.command(name="download")
|
||||
async def media_group_download(self, context: Context, *, url: str):
|
||||
await self._invoke_hybrid(context, "download", url=url)
|
||||
|
||||
@media_group.command(name="mcquote")
|
||||
async def media_group_mcquote(self, context: Context, *, text: str):
|
||||
await self._invoke_hybrid(context, "mcquote", text=text)
|
||||
|
||||
@media_group.command(name="img2gif")
|
||||
async def media_group_img2gif(self, context: Context, attachment: Optional[discord.Attachment] = None):
|
||||
await self._invoke_hybrid(context, "img2gif", attachment=attachment)
|
||||
|
||||
@media_group.command(name="tweety")
|
||||
async def media_group_tweety(self, context: Context):
|
||||
await self._invoke_hybrid(context, "tweety")
|
||||
|
||||
@media_group.command(name="tts")
|
||||
async def media_group_tts(self, context: Context, *, text: str = None):
|
||||
await self._invoke_hybrid(context, "tts", text=text)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="download",
|
||||
description="Download a video from a URL using yt-dlp.",
|
||||
)
|
||||
async def download(self, context, *, url: str):
|
||||
return await download_command()(self, context, url=url)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="mcquote",
|
||||
description="Generate a custom Minecraft quote image.",
|
||||
)
|
||||
async def mcquote(self, context, *, text: str):
|
||||
return await mcquote_command()(self, context, text=text)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="img2gif",
|
||||
description="Convert an uploaded image to a GIF.",
|
||||
)
|
||||
async def img2gif(self, context, attachment: Optional[discord.Attachment] = None):
|
||||
return await img2gif_command()(self, context, attachment=attachment)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="tweety",
|
||||
description="Convert a replied message to a tweet image.",
|
||||
)
|
||||
async def tweety(self, context):
|
||||
return await tweety_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="tts",
|
||||
description="Convert text to speech using Google Text-to-Speech.",
|
||||
)
|
||||
async def tts(self, context, text: str = None):
|
||||
return await tts_command()(context, text=text)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Media(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'media.download'")
|
||||
bot.logger.info("Loaded extension 'media.mcquote'")
|
||||
bot.logger.info("Loaded extension 'media.img2gif'")
|
||||
bot.logger.info("Loaded extension 'media.tweety'")
|
||||
bot.logger.info("Loaded extension 'media.tts'")
|
||||
402
cogs/media/download.py
Normal file
402
cogs/media/download.py
Normal file
@@ -0,0 +1,402 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import yt_dlp
|
||||
from urllib.parse import urlparse
|
||||
import aiohttp
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("discord_bot")
|
||||
|
||||
def download_command():
|
||||
@commands.hybrid_command(
|
||||
name="download",
|
||||
description="Download a video from a URL using yt-dlp.",
|
||||
)
|
||||
@commands.cooldown(1, 30, commands.BucketType.user)
|
||||
async def download(self, context, *, url: str):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs the `send messages` permission in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if not url:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide a valid URL to download.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Check if bot has send messages permission before starting download
|
||||
try:
|
||||
test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA)
|
||||
test_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.channel.send(embed=test_embed, delete_after=0.1)
|
||||
except discord.Forbidden:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
parsed_url = urlparse(url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide a valid URL.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
except Exception:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide a valid URL.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
processing_embed = discord.Embed(
|
||||
title="Download (Processing)",
|
||||
description="<a:mariospin:1423677027013103709> Downloading video... This may take a moment.",
|
||||
color=0x7289DA,
|
||||
)
|
||||
processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=processing_embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=processing_embed, ephemeral=True)
|
||||
else:
|
||||
processing_msg = await context.send(embed=processing_embed)
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
|
||||
# Try Docker path first, fallback to local path for development
|
||||
cookie_path = '/bot/cogs/media/files/cookies.txt'
|
||||
if not os.path.exists(cookie_path):
|
||||
cookie_path = os.path.join(os.path.dirname(__file__), 'files', 'cookies.txt')
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'bestvideo[filesize<200M]+bestaudio[filesize<200M]/best[filesize<200M]/bestvideo+bestaudio/best',
|
||||
'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'),
|
||||
'noplaylist': True,
|
||||
'extract_flat': False,
|
||||
'writesubtitles': False,
|
||||
'writeautomaticsub': False,
|
||||
'writethumbnail': False,
|
||||
'ignoreerrors': False,
|
||||
'merge_output_format': 'mp4',
|
||||
'cookiefile': cookie_path,
|
||||
}
|
||||
|
||||
try:
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: ydl.extract_info(url, download=True)
|
||||
)
|
||||
|
||||
if not info:
|
||||
raise Exception("Could not extract video information")
|
||||
|
||||
video_title = info.get('title', 'Unknown Title')
|
||||
video_duration_seconds = int(info.get('duration') or 0)
|
||||
video_uploader = info.get('uploader', 'Unknown')
|
||||
video_url = info.get('webpage_url') or info.get('original_url') or url
|
||||
platform = info.get('extractor') or info.get('extractor_key') or 'Unknown'
|
||||
view_count = info.get('view_count')
|
||||
|
||||
files = [f for f in os.listdir(temp_dir) if os.path.isfile(os.path.join(temp_dir, f))]
|
||||
|
||||
if not files:
|
||||
raise Exception("No video file was downloaded")
|
||||
|
||||
video_file = os.path.join(temp_dir, files[0])
|
||||
file_size = os.path.getsize(video_file)
|
||||
logger.info(f"File size: {file_size} bytes ({file_size / (1024*1024):.2f} MB)")
|
||||
|
||||
if file_size > 24 * 1024 * 1024: # 24MB limit
|
||||
logger.info("File is over 24MB, uploading to Catbox")
|
||||
async def upload_to_catbox(path: str) -> str:
|
||||
try:
|
||||
file_size_bytes = os.path.getsize(path)
|
||||
except Exception:
|
||||
file_size_bytes = -1
|
||||
logger.info(f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}")
|
||||
form = aiohttp.FormData()
|
||||
form.add_field('reqtype', 'fileupload')
|
||||
form.add_field('fileToUpload', open(path, 'rb'), filename=os.path.basename(path))
|
||||
timeout = aiohttp.ClientTimeout(total=600)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post('https://catbox.moe/user/api.php', data=form) as resp:
|
||||
text = await resp.text()
|
||||
logger.info(f"Catbox response: status={resp.status} body_len={len(text)}")
|
||||
if resp.status == 200 and text.startswith('https://'):
|
||||
url_text = text.strip()
|
||||
logger.info(f"Catbox upload success: url={url_text}")
|
||||
return url_text
|
||||
logger.error(f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}")
|
||||
raise RuntimeError(f"Upload failed: {text.strip()}")
|
||||
try:
|
||||
link = await upload_to_catbox(video_file)
|
||||
minutes, seconds = divmod(video_duration_seconds, 60)
|
||||
duration_str = f"{minutes}:{seconds:02d}"
|
||||
description_text = f"### **[{video_title}]({video_url})**" if video_url else f"### **{video_title}**"
|
||||
embed = discord.Embed(
|
||||
title="Download",
|
||||
description=description_text,
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.add_field(name="Uploader", value=video_uploader or "Unknown", inline=True)
|
||||
embed.add_field(name="Duration", value=duration_str, inline=True)
|
||||
embed.add_field(name="Platform", value=platform, inline=True)
|
||||
embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url)
|
||||
|
||||
if interaction is not None:
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(link)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(link)
|
||||
return
|
||||
except Exception as upload_error:
|
||||
logger.exception(f"Catbox upload exception: {upload_error}")
|
||||
error_msg = str(upload_error)
|
||||
if "greater than 200mb" in error_msg.lower():
|
||||
description = "The video is too large to upload. The file exceeds 200MB (Catbox limit) and cannot be sent via Discord (25MB limit)."
|
||||
else:
|
||||
description = f"The video is over 25MB and upload to hosting failed: {upload_error}"
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=description,
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
else:
|
||||
logger.info("File is under 24MB, sending directly to Discord")
|
||||
minutes, seconds = divmod(video_duration_seconds, 60)
|
||||
duration_str = f"{minutes}:{seconds:02d}"
|
||||
description_text = f"### **[{video_title}]({video_url})**" if video_url else f"### **{video_title}**"
|
||||
embed = discord.Embed(
|
||||
title="Download",
|
||||
description=description_text,
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.add_field(name="Uploader", value=video_uploader or "Unknown", inline=True)
|
||||
embed.add_field(name="Duration", value=duration_str, inline=True)
|
||||
embed.add_field(name="Platform", value=platform, inline=True)
|
||||
embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url)
|
||||
|
||||
try:
|
||||
with open(video_file, 'rb') as f:
|
||||
file = discord.File(f, filename=files[0])
|
||||
|
||||
if interaction is not None:
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(file=file)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(file=file)
|
||||
except discord.HTTPException as e:
|
||||
if e.status == 413:
|
||||
logger.info("Discord rejected file (413), falling back to Catbox upload")
|
||||
async def upload_to_catbox(path: str) -> str:
|
||||
try:
|
||||
file_size_bytes = os.path.getsize(path)
|
||||
except Exception:
|
||||
file_size_bytes = -1
|
||||
logger.info(f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}")
|
||||
form = aiohttp.FormData()
|
||||
form.add_field('reqtype', 'fileupload')
|
||||
form.add_field('fileToUpload', open(path, 'rb'), filename=os.path.basename(path))
|
||||
timeout = aiohttp.ClientTimeout(total=600)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post('https://catbox.moe/user/api.php', data=form) as resp:
|
||||
text = await resp.text()
|
||||
logger.info(f"Catbox response: status={resp.status} body_len={len(text)}")
|
||||
if resp.status == 200 and text.startswith('https://'):
|
||||
url_text = text.strip()
|
||||
logger.info(f"Catbox upload success: url={url_text}")
|
||||
return url_text
|
||||
logger.error(f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}")
|
||||
raise RuntimeError(f"Upload failed: {text.strip()}")
|
||||
try:
|
||||
link = await upload_to_catbox(video_file)
|
||||
description_text_with_link = f"### **[{video_title}]({video_url})**\n\n{link}" if video_url else f"### **{video_title}**\n\n{link}"
|
||||
embed = discord.Embed(
|
||||
title="Download",
|
||||
description=description_text,
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.add_field(name="Uploader", value=video_uploader or "Unknown", inline=True)
|
||||
embed.add_field(name="Duration", value=duration_str, inline=True)
|
||||
embed.add_field(name="Platform", value=platform, inline=True)
|
||||
embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url)
|
||||
|
||||
if interaction is not None:
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(link)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(link)
|
||||
except Exception as upload_error:
|
||||
logger.exception(f"Catbox upload exception: {upload_error}")
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Discord rejected the file and Catbox upload failed: {upload_error}",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Failed to download video: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
try:
|
||||
await processing_msg.delete()
|
||||
except:
|
||||
pass
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
finally:
|
||||
for file in os.listdir(temp_dir):
|
||||
try:
|
||||
os.remove(os.path.join(temp_dir, file))
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.rmdir(temp_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
return download
|
||||
131
cogs/media/img2gif.py
Normal file
131
cogs/media/img2gif.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
import tempfile
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from PIL import Image
|
||||
import subprocess
|
||||
import shutil
|
||||
from typing import Optional
|
||||
try:
|
||||
import pillow_heif
|
||||
pillow_heif.register_heif_opener()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def send_error_message(context, description: str):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=description,
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
def img2gif_command():
|
||||
@commands.hybrid_command(
|
||||
name="img2gif",
|
||||
description="Convert an uploaded image to a GIF.",
|
||||
)
|
||||
@commands.cooldown(1, 15, commands.BucketType.user)
|
||||
async def img2gif(self, context, attachment: Optional[discord.Attachment] = None):
|
||||
resolved_attachment = attachment
|
||||
if resolved_attachment is None:
|
||||
if context.message and context.message.reference and context.message.reference.resolved:
|
||||
ref_msg = context.message.reference.resolved
|
||||
if isinstance(ref_msg, discord.Message) and ref_msg.attachments:
|
||||
resolved_attachment = ref_msg.attachments[0]
|
||||
if resolved_attachment is None and context.message and context.message.attachments:
|
||||
resolved_attachment = context.message.attachments[0]
|
||||
if resolved_attachment is None or not resolved_attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff", ".heic", ".heif")):
|
||||
await send_error_message(context, "Provide or reply to an image (png/jpg/jpeg/webp/bmp/tiff/heic/heif).")
|
||||
return
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.defer(ephemeral=False)
|
||||
else:
|
||||
processing_embed = discord.Embed(
|
||||
title="Image to GIF (Processing)",
|
||||
description="<a:mariospin:1423677027013103709> Converting image...",
|
||||
color=0x7289DA,
|
||||
)
|
||||
processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
processing_msg = await context.send(embed=processing_embed)
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
src_path = os.path.join(tmp_dir, resolved_attachment.filename)
|
||||
out_path = os.path.join(tmp_dir, os.path.splitext(resolved_attachment.filename)[0] + ".gif")
|
||||
|
||||
try:
|
||||
await resolved_attachment.save(src_path)
|
||||
src_for_pillow = src_path
|
||||
try:
|
||||
with Image.open(src_for_pillow) as img:
|
||||
if img.mode not in ("RGB", "RGBA"):
|
||||
img = img.convert("RGBA")
|
||||
duration_ms = 100
|
||||
loop = 0
|
||||
img.save(out_path, format="GIF", save_all=True, optimize=True, duration=duration_ms, loop=loop)
|
||||
except Exception:
|
||||
if resolved_attachment.filename.lower().endswith((".heic", ".heif")) and shutil.which("ffmpeg"):
|
||||
png_path = os.path.join(tmp_dir, os.path.splitext(resolved_attachment.filename)[0] + ".png")
|
||||
try:
|
||||
subprocess.run([
|
||||
"ffmpeg", "-y", "-i", src_path, png_path
|
||||
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
with Image.open(png_path) as img:
|
||||
if img.mode not in ("RGB", "RGBA"):
|
||||
img = img.convert("RGBA")
|
||||
duration_ms = 100
|
||||
loop = 0
|
||||
img.save(out_path, format="GIF", save_all=True, optimize=True, duration=duration_ms, loop=loop)
|
||||
except Exception as conv_err:
|
||||
raise conv_err
|
||||
else:
|
||||
raise
|
||||
|
||||
with open(out_path, "rb") as f:
|
||||
file = discord.File(f, filename=os.path.basename(out_path))
|
||||
if interaction is not None:
|
||||
await interaction.followup.send(file=file)
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.send(file=file)
|
||||
except Exception as e:
|
||||
if interaction is not None:
|
||||
await interaction.followup.send(embed=discord.Embed(
|
||||
title="Error",
|
||||
description=f"Failed to convert image: {e}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"), ephemeral=True)
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.send(embed=discord.Embed(
|
||||
title="Error",
|
||||
description=f"Failed to convert image: {e}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"), ephemeral=True)
|
||||
finally:
|
||||
try:
|
||||
for f in os.listdir(tmp_dir):
|
||||
try:
|
||||
os.remove(os.path.join(tmp_dir, f))
|
||||
except:
|
||||
pass
|
||||
os.rmdir(tmp_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
return img2gif
|
||||
|
||||
|
||||
202
cogs/media/mcquote.py
Normal file
202
cogs/media/mcquote.py
Normal file
@@ -0,0 +1,202 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import aiohttp
|
||||
import random
|
||||
|
||||
def mcquote_command():
|
||||
@commands.hybrid_command(
|
||||
name="mcquote",
|
||||
description="Generate a custom Minecraft quote image.",
|
||||
)
|
||||
@commands.cooldown(1, 10, commands.BucketType.user)
|
||||
async def mcquote(self, context, *, text: str = None):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs the `send messages` permission in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if not text:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide text for the Minecraft quote.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if len(text) > 25:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Text must be 25 characters or less.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Check if bot has send messages permission before starting quote generation
|
||||
try:
|
||||
test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA)
|
||||
test_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.channel.send(embed=test_embed, delete_after=0.1)
|
||||
except discord.Forbidden:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
processing_embed = discord.Embed(
|
||||
title="Minecraft Quote (Processing)",
|
||||
description="<a:mariospin:1423677027013103709> Generating quote... This may take a moment.",
|
||||
color=0x7289DA,
|
||||
)
|
||||
processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=processing_embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=processing_embed, ephemeral=True)
|
||||
else:
|
||||
processing_msg = await context.send(embed=processing_embed)
|
||||
|
||||
quote_text = text.replace(" ", "+")
|
||||
random_number = random.randint(1, 39)
|
||||
mc_quote_url = f'https://skinmc.net/achievement/{random_number}/Achievement+Unlocked!/{quote_text}'
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(mc_quote_url) as response:
|
||||
response.raise_for_status()
|
||||
content = await response.read()
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
|
||||
temp_file.write(content)
|
||||
temp_file_path = temp_file.name
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Minecraft Quote",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url)
|
||||
|
||||
with open(temp_file_path, 'rb') as f:
|
||||
file = discord.File(f, filename="mcquote.png")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(file=file)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(file=file)
|
||||
|
||||
os.remove(temp_file_path)
|
||||
except aiohttp.ClientError:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Failed to generate Minecraft quote. Please try again later.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"An unexpected error occurred: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await processing_msg.delete()
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
return mcquote
|
||||
235
cogs/media/tts.py
Normal file
235
cogs/media/tts.py
Normal file
@@ -0,0 +1,235 @@
|
||||
import asyncio
|
||||
import io
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from gtts import gTTS
|
||||
|
||||
|
||||
DEFAULT_LANG = "en"
|
||||
|
||||
def tts_command():
|
||||
|
||||
async def send_embed(
|
||||
context: commands.Context,
|
||||
embed: discord.Embed,
|
||||
*,
|
||||
ephemeral: bool = False,
|
||||
file: Optional[discord.File] = None,
|
||||
) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
if file:
|
||||
await interaction.followup.send(embed=embed, file=file, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
if file:
|
||||
await interaction.response.send_message(embed=embed, file=file, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
if file:
|
||||
await context.send(embed=embed, file=file)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def generate_tts_audio(text: str) -> tuple[Optional[bytes], Optional[str]]:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
audio_bytes = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: _generate_tts_sync(text)
|
||||
)
|
||||
return audio_bytes, None
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
|
||||
def _generate_tts_sync(text: str) -> bytes:
|
||||
tts = gTTS(text=text, lang=DEFAULT_LANG, slow=False)
|
||||
fp = io.BytesIO()
|
||||
tts.write_to_fp(fp)
|
||||
fp.seek(0)
|
||||
return fp.read()
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="tts",
|
||||
description="Convert text to speech using Google Text-to-Speech.",
|
||||
)
|
||||
@app_commands.describe(
|
||||
text="The text to convert to speech",
|
||||
)
|
||||
async def tts(context: commands.Context, text: Optional[str] = None):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs the `send messages` permission in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if not text or not text.strip():
|
||||
if context.message and context.message.reference and context.message.reference.resolved:
|
||||
referenced = context.message.reference.resolved
|
||||
if isinstance(referenced, discord.Message) and referenced.content:
|
||||
text = referenced.content
|
||||
if not text or not text.strip():
|
||||
embed = (
|
||||
discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide text to convert or reply to a message containing text.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
)
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
text = text.strip()
|
||||
if len(text) > 500:
|
||||
embed = (
|
||||
discord.Embed(
|
||||
title="Error",
|
||||
description="Text is too long. Please limit to 500 characters.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
)
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Check if bot has send messages permission before starting TTS generation
|
||||
try:
|
||||
test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA)
|
||||
test_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.channel.send(embed=test_embed, delete_after=0.1)
|
||||
except discord.Forbidden:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
processing_embed = (
|
||||
discord.Embed(
|
||||
title="TTS (Processing)",
|
||||
description="<a:mariospin:1423677027013103709> Generating speech...",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
)
|
||||
interaction = getattr(context, "interaction", None)
|
||||
processing_message = None
|
||||
sent_initial_interaction_response = False
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
processing_message = await interaction.followup.send(embed=processing_embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message(embed=processing_embed, ephemeral=True)
|
||||
sent_initial_interaction_response = True
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.defer(ephemeral=False)
|
||||
else:
|
||||
processing_embed = (
|
||||
discord.Embed(
|
||||
title="TTS (Processing)",
|
||||
description="<a:mariospin:1423677027013103709> Generating speech...",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
)
|
||||
processing_message = await context.send(embed=processing_embed)
|
||||
|
||||
audio_bytes, error = await generate_tts_audio(text)
|
||||
|
||||
if error or not audio_bytes:
|
||||
embed = (
|
||||
discord.Embed(
|
||||
title="Error",
|
||||
description=f"Failed to generate speech. {error or 'Unknown error.'}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
)
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
if interaction is not None and sent_initial_interaction_response:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except Exception:
|
||||
pass
|
||||
if processing_message:
|
||||
try:
|
||||
await processing_message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
audio_file = discord.File(
|
||||
io.BytesIO(audio_bytes),
|
||||
filename="audio.mp3",
|
||||
)
|
||||
|
||||
embed = (
|
||||
discord.Embed(
|
||||
title="Text-to-Speech",
|
||||
description=f"**Input:** {text}",
|
||||
color=0x7289DA,
|
||||
)
|
||||
.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
.set_footer(
|
||||
text=f"Requested by {context.author.display_name}",
|
||||
icon_url=getattr(context.author.display_avatar, "url", None),
|
||||
)
|
||||
)
|
||||
|
||||
if interaction is not None:
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(file=audio_file)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await processing_message.delete()
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(file=audio_file)
|
||||
|
||||
return tts
|
||||
443
cogs/media/tweety.py
Normal file
443
cogs/media/tweety.py
Normal file
@@ -0,0 +1,443 @@
|
||||
# The API used in the tweety command is made by me and can be found here:
|
||||
# https://github.com/neoarz/tweety-api
|
||||
# I made this out of spite since i couldnt find any free APIs for this
|
||||
# Its serverless and hosted on Vercel :)
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import aiohttp
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import pytz
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
|
||||
def break_long_words(text: str, max_word_length: int = 50) -> str:
|
||||
words = text.split(' ')
|
||||
result = []
|
||||
|
||||
for word in words:
|
||||
if len(word) > max_word_length:
|
||||
chunks = [word[i:i+max_word_length] for i in range(0, len(word), max_word_length)]
|
||||
result.append(' '.join(chunks))
|
||||
else:
|
||||
result.append(word)
|
||||
|
||||
return ' '.join(result)
|
||||
|
||||
|
||||
class TweetyHelpView(discord.ui.View):
|
||||
"""This is the help view for the slash command cuz only the pinging and prefix versions can use this command"""
|
||||
|
||||
def __init__(self, user_id: int, bot):
|
||||
super().__init__(timeout=180)
|
||||
self.user_id = user_id
|
||||
self.bot = bot
|
||||
self.current_page = 0
|
||||
self.pages = [
|
||||
{
|
||||
"title": "Tweety (Method 1)",
|
||||
"description": "Use the prefix command `.media tweety` while replying to a message.",
|
||||
"gif_url": "https://yes.nighty.works/raw/VrKX1L.gif",
|
||||
"fields": [
|
||||
{"name": "How to use", "value": "1. Reply to any message\n2. Type `.media tweety`\n3. Use the buttons to customize!", "inline": False},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Tweety (Method 2)",
|
||||
"description": f"Mention <@{bot.user.id}> with `tweety` while replying to a message.",
|
||||
"gif_url": "https://yes.nighty.works/raw/9XEe9j.gif",
|
||||
"fields": [
|
||||
{"name": "How to use", "value": f"1. Reply to any message\n2. Type `<@{bot.user.id}> tweety`\n3. Use the buttons to customize!", "inline": False},
|
||||
]
|
||||
}
|
||||
]
|
||||
self.update_buttons()
|
||||
|
||||
def create_embed(self):
|
||||
page = self.pages[self.current_page]
|
||||
embed = discord.Embed(
|
||||
title=page["title"],
|
||||
description=page["description"],
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
|
||||
for field in page["fields"]:
|
||||
embed.add_field(name=field["name"], value=field["value"], inline=field["inline"])
|
||||
|
||||
embed.set_image(url=page["gif_url"])
|
||||
embed.set_footer(text=f"Page {self.current_page + 1}/{len(self.pages)}")
|
||||
|
||||
return embed
|
||||
|
||||
def update_buttons(self):
|
||||
self.clear_items()
|
||||
|
||||
prev_button = discord.ui.Button(
|
||||
label="Prev",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
emoji=discord.PartialEmoji(name="left", id=1420240344926126090),
|
||||
disabled=self.current_page == 0
|
||||
)
|
||||
prev_button.callback = self.previous_page
|
||||
self.add_item(prev_button)
|
||||
|
||||
next_button = discord.ui.Button(
|
||||
label="Next",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
emoji=discord.PartialEmoji(name="right", id=1420240334100627456),
|
||||
disabled=self.current_page == len(self.pages) - 1
|
||||
)
|
||||
next_button.callback = self.next_page
|
||||
self.add_item(next_button)
|
||||
|
||||
async def previous_page(self, interaction: discord.Interaction):
|
||||
if interaction.user.id != self.user_id:
|
||||
await interaction.response.send_message("You can't control someone else's help menu!", ephemeral=True)
|
||||
return
|
||||
|
||||
if self.current_page > 0:
|
||||
self.current_page -= 1
|
||||
self.update_buttons()
|
||||
await interaction.response.edit_message(embed=self.create_embed(), view=self)
|
||||
else:
|
||||
await interaction.response.defer()
|
||||
|
||||
async def next_page(self, interaction: discord.Interaction):
|
||||
if interaction.user.id != self.user_id:
|
||||
await interaction.response.send_message("You can't control someone else's help menu!", ephemeral=True)
|
||||
return
|
||||
|
||||
if self.current_page < len(self.pages) - 1:
|
||||
self.current_page += 1
|
||||
self.update_buttons()
|
||||
await interaction.response.edit_message(embed=self.create_embed(), view=self)
|
||||
else:
|
||||
await interaction.response.defer()
|
||||
|
||||
async def on_timeout(self):
|
||||
for item in self.children:
|
||||
item.disabled = True
|
||||
|
||||
|
||||
class TweetyView(discord.ui.View):
|
||||
def __init__(self, author_id: int, original_message, tweet_data: dict, api_url: str, image_message: Optional[discord.Message] = None):
|
||||
super().__init__(timeout=300)
|
||||
self.author_id = author_id
|
||||
self.original_message = original_message
|
||||
self.tweet_data = tweet_data
|
||||
self.api_url = api_url
|
||||
self.is_dark = tweet_data.get("dark", False)
|
||||
self.is_verified = tweet_data.get("verified", False)
|
||||
self.image_message = image_message
|
||||
self.last_regenerate_time = 0
|
||||
self.click_count = 0
|
||||
|
||||
self.update_button_styles()
|
||||
|
||||
def update_button_styles(self):
|
||||
self.clear_items()
|
||||
|
||||
dark_button = discord.ui.Button(
|
||||
label="Dark Mode" if self.is_dark else "Light Mode",
|
||||
style=discord.ButtonStyle.primary if self.is_dark else discord.ButtonStyle.secondary,
|
||||
emoji=discord.PartialEmoji(name="darkmode", id=1425165393751965884),
|
||||
custom_id="toggle_dark"
|
||||
)
|
||||
dark_button.callback = self.toggle_dark_callback
|
||||
self.add_item(dark_button)
|
||||
|
||||
verified_button = discord.ui.Button(
|
||||
label="Verified",
|
||||
style=discord.ButtonStyle.primary if self.is_verified else discord.ButtonStyle.secondary,
|
||||
emoji=discord.PartialEmoji(name="TwitterVerifiedBadge", id=1425165432142172392),
|
||||
custom_id="toggle_verified"
|
||||
)
|
||||
verified_button.callback = self.toggle_verified_callback
|
||||
self.add_item(verified_button)
|
||||
|
||||
async def regenerate_tweet(self, interaction: discord.Interaction):
|
||||
"""Regenerate only the image message with current settings"""
|
||||
if self.click_count >= 10:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Stop spamming! You've reached the maximum number of changes for this tweet.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
await interaction.response.defer()
|
||||
|
||||
current_time = time.time()
|
||||
time_since_last = current_time - self.last_regenerate_time
|
||||
|
||||
if time_since_last < 3 and self.last_regenerate_time > 0:
|
||||
wait_time = 3 - time_since_last
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
self.click_count += 1
|
||||
self.last_regenerate_time = time.time()
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{self.api_url}/api/render",
|
||||
json=self.tweet_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
) as response:
|
||||
|
||||
if response.status != 200:
|
||||
error_text = await response.text()
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"API Error ({response.status}): {error_text}",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
image_data = await response.read()
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
|
||||
temp_file.write(image_data)
|
||||
temp_file_path = temp_file.name
|
||||
|
||||
with open(temp_file_path, 'rb') as f:
|
||||
author_name = self.original_message.author.name
|
||||
filename = f"tweet_{author_name}_{int(datetime.now().timestamp())}.png"
|
||||
file = discord.File(
|
||||
f,
|
||||
filename=filename
|
||||
)
|
||||
|
||||
self.update_button_styles()
|
||||
|
||||
if self.image_message is not None:
|
||||
await self.image_message.edit(attachments=[file], view=self)
|
||||
else:
|
||||
await interaction.followup.send(file=file, view=self)
|
||||
|
||||
os.remove(temp_file_path)
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Error regenerating tweet image",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
|
||||
async def _check_author(self, interaction: discord.Interaction) -> bool:
|
||||
"""Check if user is authorized to modify the tweet"""
|
||||
if interaction.user.id != self.author_id:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="You can't modify someone else's tweet!",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def toggle_dark_callback(self, interaction: discord.Interaction):
|
||||
"""Handle dark mode toggle button click"""
|
||||
if not await self._check_author(interaction):
|
||||
return
|
||||
self.is_dark = not self.is_dark
|
||||
self.tweet_data["dark"] = self.is_dark
|
||||
await self.regenerate_tweet(interaction)
|
||||
|
||||
async def toggle_verified_callback(self, interaction: discord.Interaction):
|
||||
"""Handle verified toggle button click"""
|
||||
if not await self._check_author(interaction):
|
||||
return
|
||||
self.is_verified = not self.is_verified
|
||||
self.tweet_data["verified"] = self.is_verified
|
||||
await self.regenerate_tweet(interaction)
|
||||
|
||||
async def on_timeout(self):
|
||||
"""Disable buttons when view times out"""
|
||||
for item in self.children:
|
||||
item.disabled = True
|
||||
|
||||
def tweety_command():
|
||||
@commands.hybrid_command(
|
||||
name="tweety",
|
||||
description="Convert a replied message to a tweet image."
|
||||
)
|
||||
@commands.cooldown(1, 10, commands.BucketType.user)
|
||||
async def tweety(self, context):
|
||||
if hasattr(context, "interaction") and context.interaction:
|
||||
view = TweetyHelpView(user_id=context.author.id, bot=self.bot)
|
||||
embed = view.create_embed()
|
||||
await context.send(embed=embed, view=view, ephemeral=True)
|
||||
return
|
||||
|
||||
if not context.message.reference or not context.message.reference.message_id:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="You must reply to a message to use this command!",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
return
|
||||
|
||||
try:
|
||||
original_message = await context.channel.fetch_message(context.message.reference.message_id)
|
||||
|
||||
processing_embed = discord.Embed(
|
||||
title="Tweet Generator (Processing)",
|
||||
description="<a:mariospin:1423677027013103709> Generating tweet... This may take a moment.",
|
||||
color=0x7289DA,
|
||||
)
|
||||
processing_embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
processing_msg = await context.send(embed=processing_embed)
|
||||
|
||||
author = original_message.author
|
||||
display_name = author.display_name or author.name
|
||||
username = f"@{author.name}"
|
||||
avatar_url = str(author.avatar.url) if author.avatar else str(author.default_avatar.url)
|
||||
message_text = original_message.content
|
||||
|
||||
for mention in original_message.mentions:
|
||||
message_text = message_text.replace(f'<@{mention.id}>', f'@{mention.name}')
|
||||
message_text = message_text.replace(f'<@!{mention.id}>', f'@{mention.name}')
|
||||
|
||||
for role in original_message.role_mentions:
|
||||
message_text = message_text.replace(f'<@&{role.id}>', f'@{role.name}')
|
||||
|
||||
for channel in original_message.channel_mentions:
|
||||
message_text = message_text.replace(f'<#{channel.id}>', f'#{channel.name}')
|
||||
|
||||
if not message_text.strip():
|
||||
await processing_msg.delete()
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="No text found! This command only works with text messages.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
return
|
||||
|
||||
if len(message_text) > 300:
|
||||
await processing_msg.delete()
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Message is too long! Maximum 300 characters allowed.\nYour message: {len(message_text)} characters",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
return
|
||||
|
||||
message_text = break_long_words(message_text, max_word_length=50)
|
||||
|
||||
ny_tz = pytz.timezone('America/New_York')
|
||||
msg_time_ny = original_message.created_at.astimezone(ny_tz)
|
||||
timestamp = msg_time_ny.strftime("%I:%M %p · %b %d, %Y").replace(" 0", " ")
|
||||
tweet_data = {
|
||||
"name": display_name[:50],
|
||||
"handle": username[:20],
|
||||
"text": message_text[:300],
|
||||
"avatar": avatar_url,
|
||||
"timestamp": timestamp,
|
||||
"verified": False,
|
||||
"dark": False
|
||||
}
|
||||
|
||||
API_BASE_URL = "http://tweet.6969.pro"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.post(
|
||||
f"{API_BASE_URL}/api/render",
|
||||
json=tweet_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
) as response:
|
||||
|
||||
if response.status != 200:
|
||||
await processing_msg.delete()
|
||||
error_text = await response.text()
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"API Error ({response.status}): {error_text}",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
return
|
||||
|
||||
image_data = await response.read()
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
|
||||
temp_file.write(image_data)
|
||||
temp_file_path = temp_file.name
|
||||
|
||||
await processing_msg.delete()
|
||||
|
||||
with open(temp_file_path, 'rb') as f:
|
||||
file = discord.File(f, filename=f"tweet_{author.name}_{int(datetime.now().timestamp())}.png")
|
||||
embed = discord.Embed(
|
||||
title="Tweet Generated",
|
||||
description=f"<:error:1424007141768822824> Tweet sometimes may look a bit broken, im gonna rewrite the API another time... (it wasnt made for Syntrel in the first place)",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
embed.set_footer(
|
||||
text=f"Requested by {context.author.name}",
|
||||
icon_url=context.author.display_avatar.url,
|
||||
)
|
||||
|
||||
view = TweetyView(
|
||||
author_id=context.author.id,
|
||||
original_message=original_message,
|
||||
tweet_data=tweet_data,
|
||||
api_url=API_BASE_URL
|
||||
)
|
||||
|
||||
await context.send(embed=embed)
|
||||
image_message = await context.send(file=file, view=view)
|
||||
view.image_message = image_message
|
||||
|
||||
os.remove(temp_file_path)
|
||||
|
||||
except aiohttp.ClientError:
|
||||
await processing_msg.delete()
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Connection error: Could not reach tweet API",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
except Exception:
|
||||
await processing_msg.delete()
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Error generating tweet image",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
|
||||
except Exception:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Error processing the message!",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp")
|
||||
await context.send(embed=embed)
|
||||
|
||||
return tweety
|
||||
171
cogs/melonx/__init__.py
Normal file
171
cogs/melonx/__init__.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .melonx import MelonxView
|
||||
from .transfer import transfer_command
|
||||
from .mods import mods_command
|
||||
from .legal import legal_command
|
||||
from .gamecrash import crash_command
|
||||
from .requirements import requirements_command
|
||||
from .error import error_command
|
||||
from .ios26 import ios26_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Melonx(commands.GroupCog, name="melonx"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="melonx", invoke_without_command=True)
|
||||
async def melonx_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="MeloNX Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x963155
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
view = MelonxView(self.bot)
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
@melonx_group.command(name="help")
|
||||
async def melonx_group_help(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="MeloNX Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x963155
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
view = MelonxView(self.bot)
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
@melonx_group.command(name="transfer")
|
||||
async def melonx_group_transfer(self, context: Context):
|
||||
await self._invoke_hybrid(context, "transfer")
|
||||
|
||||
@melonx_group.command(name="legal")
|
||||
async def melonx_group_legal(self, context: Context):
|
||||
await self._invoke_hybrid(context, "legal")
|
||||
|
||||
@melonx_group.command(name="mods")
|
||||
async def melonx_group_mods(self, context: Context):
|
||||
await self._invoke_hybrid(context, "mods")
|
||||
|
||||
@melonx_group.command(name="gamecrash")
|
||||
async def melonx_group_gamecrash(self, context: Context):
|
||||
await self._invoke_hybrid(context, "gamecrash")
|
||||
|
||||
@melonx_group.command(name="requirements")
|
||||
async def melonx_group_requirements(self, context: Context):
|
||||
await self._invoke_hybrid(context, "requirements")
|
||||
|
||||
@melonx_group.command(name="error")
|
||||
async def melonx_group_error(self, context: Context):
|
||||
await self._invoke_hybrid(context, "error")
|
||||
|
||||
@melonx_group.command(name="26")
|
||||
async def melonx_group_26(self, context: Context):
|
||||
await self._invoke_hybrid(context, "26")
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command)
|
||||
else:
|
||||
await context.send(f"Unknown MeloNX command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
@app_commands.command(
|
||||
name="help",
|
||||
description="MeloNX troubleshooting help"
|
||||
)
|
||||
async def help(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="MeloNX Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x963155
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
view = MelonxView(self.bot)
|
||||
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="transfer",
|
||||
description="How to transfer save files from other emulators or platforms"
|
||||
)
|
||||
async def transfer(self, context):
|
||||
return await transfer_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="mods",
|
||||
description="How to install mods within MeloNX (Limited Support)"
|
||||
)
|
||||
async def mods(self, context):
|
||||
return await mods_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="legal",
|
||||
description="Legality of emulators"
|
||||
)
|
||||
async def legal(self, context):
|
||||
return await legal_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="gamecrash",
|
||||
description="Why does my game crash?"
|
||||
)
|
||||
async def gamecrash(self, context):
|
||||
return await crash_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="requirements",
|
||||
description="What does MeloNX require?"
|
||||
)
|
||||
async def requirements(self, context):
|
||||
return await requirements_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="error",
|
||||
description="What does this error message mean?"
|
||||
)
|
||||
async def error(self, context):
|
||||
return await error_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="26",
|
||||
description="How can I run MeloNX on iOS 26?"
|
||||
)
|
||||
async def ios26(self, context):
|
||||
return await ios26_command()(self, context)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Melonx(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'melonx.help'")
|
||||
bot.logger.info("Loaded extension 'melonx.transfer'")
|
||||
bot.logger.info("Loaded extension 'melonx.mods'")
|
||||
bot.logger.info("Loaded extension 'melonx.gamecrash'")
|
||||
bot.logger.info("Loaded extension 'melonx.requirements'")
|
||||
bot.logger.info("Loaded extension 'melonx.error'")
|
||||
bot.logger.info("Loaded extension 'melonx.26'")
|
||||
bot.logger.info("Loaded extension 'melonx.legal'")
|
||||
47
cogs/melonx/error.py
Normal file
47
cogs/melonx/error.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def error_command():
|
||||
@commands.hybrid_command(
|
||||
name="error", description="What does this error message mean?"
|
||||
)
|
||||
async def error(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description=(
|
||||
'# What does this error message mean?\n\n---\n\n' +
|
||||
'**1. "MeloNX Crashed! System.SystemException: Cannot allocate memory"**' +
|
||||
'You likely don\'t have the increased memory limit entitlement enabled, are using an a12 chipset, and have 4GB or less of memory. You can see the status of the entitlement for MeloNX under the Settings tab.\n\n' +
|
||||
'**2. "MeloNX Crashed! LibHac.Common.HorizonResultException: ResultLoaderInvalidNso (2009-0005)"**' +
|
||||
'This is likely a bad game / update / or DLC dump. redump your files and try again.'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by Meshal :D')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/error.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return error
|
||||
52
cogs/melonx/gamecrash.py
Normal file
52
cogs/melonx/gamecrash.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def crash_command():
|
||||
@commands.hybrid_command(
|
||||
name="gamecrash", description="Why does my game crash?"
|
||||
)
|
||||
async def crash(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description=(
|
||||
'# "Why does my game crash?"\n\n---\n\n' +
|
||||
'**This can be caused by multiple reasons:**\n' +
|
||||
'- Not enough available ram\n' +
|
||||
'- You may have tried to update your firmware without updating your keys\n' +
|
||||
'- Game file is corrupted/broken\n' +
|
||||
'- Game requires a higher firmware+keys combination than you currently have\n' +
|
||||
'- In rare cases some games also crash when not having the resolution set to 1x\n' +
|
||||
'- The a12 chipset have a lot of issues at the moment\n' +
|
||||
'- Shader cache isn\'t cleared after updating MeloNX\n' +
|
||||
'- iOS 15 and 16 are known to have some compatibility issues'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by Meshal :D')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/gamecrash.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return crash
|
||||
53
cogs/melonx/ios26.py
Normal file
53
cogs/melonx/ios26.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def ios26_command():
|
||||
@commands.hybrid_command(
|
||||
name="26", description="How can I run MeloNX on iOS 26?"
|
||||
)
|
||||
async def ios26(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description=(
|
||||
'# "How can I run MeloNX on iOS 26?"\n\n---\n\n' +
|
||||
'### StikDebug\n' +
|
||||
'1. Make sure StikDebug is on the latest version.\n' +
|
||||
'2. Turn on "Picture in Picture" in StikDebug\'s settings.\n\n' +
|
||||
'### MeloNX\n' +
|
||||
'Make sure you\'re on the latest public beta.\n' +
|
||||
'https://discord.com/channels/1300369899704680479/1412931489817034892\n\n' +
|
||||
'## Disclaimer:\n\n' +
|
||||
'If you\'re on iOS 18 or below, and emulation is essential to you:\n\n' +
|
||||
'## <:error:1424007141768822824> DO NOT UPDATE <:error:1424007141768822824> \n\n' +
|
||||
'iOS 26 has many issues related to emulation.'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by Meshal :D')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/ios26.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return ios26
|
||||
65
cogs/melonx/legal.py
Normal file
65
cogs/melonx/legal.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def legal_command():
|
||||
@commands.hybrid_command(
|
||||
name="legal", description="Legality of emulators."
|
||||
)
|
||||
async def legal(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description = (
|
||||
'# Legality\n\n' +
|
||||
'---\n\n' +
|
||||
'## Overview\n' +
|
||||
'Emulators themselves are **legal**, as long as you use them with **legally dumped copies** of games **you** own, ' +
|
||||
'or with **homebrew software**.\n\n' +
|
||||
'Read about the landmark case [**Sony v. Bleem!** (2000)](https://www.copyright.gov/fair-use/summaries/sonycomputer-bleem-9thcir2000.pdf), ' +
|
||||
'which helped set the precedent for emulation being legal. You can also watch ' +
|
||||
'[this video](https://www.youtube.com/watch?v=yj9Gk84jRiE) for more information.\n\n'
|
||||
'## Legal Basis\n' +
|
||||
'According to the [**U.S. Copyright Act**](https://www.copyright.gov/title17/92chap1.html#117) ' +
|
||||
'(the law under which Discord operates), you **must own a legal copy** of any game you play on **MeloNX**.\n\n' +
|
||||
'- Downloading games you do not own is considered **piracy** and is **illegal**.\n' +
|
||||
'- Even if another copy is identical, it still belongs to someone else, you are **not entitled** to it.\n\n' +
|
||||
'## Our Stance on Piracy\n' +
|
||||
'We **do not support piracy**. Doing so would give Nintendo legal grounds to take us down.\n' +
|
||||
'And yes — **Nintendo is aware of MeloNX\'s existence.**\n\n' +
|
||||
'We are not required to monitor user behavior, but we **strongly encourage** everyone to use only **legally obtained copies** of games.\n\n' +
|
||||
'## Enforcement\n' +
|
||||
'If you are found using pirated games with MeloNX, you will be **banned** from this Discord server **without warning**.\n\n' +
|
||||
'## Final Note\n' +
|
||||
'Thank you for understanding and respecting the hard work that went into creating both the emulator MeloNX is built on, ' +
|
||||
'and **MeloNX itself.**'
|
||||
)
|
||||
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by stossy11')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/legal.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return legal
|
||||
155
cogs/melonx/melonx.py
Normal file
155
cogs/melonx/melonx.py
Normal file
@@ -0,0 +1,155 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class MelonxSelect(discord.ui.Select):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
options = [
|
||||
discord.SelectOption(
|
||||
label="Transfer",
|
||||
value="transfer",
|
||||
description="How to transfer save files from other emulators or platforms",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Mods",
|
||||
value="mods",
|
||||
description="How to install mods within MeloNX (Limited Support)",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Game Crash",
|
||||
value="gamecrash",
|
||||
description="Why does my game crash?",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Requirements",
|
||||
value="requirements",
|
||||
description="What does MeloNX require?",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Error",
|
||||
value="error",
|
||||
description="What does this error message mean?",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="iOS 26",
|
||||
value="26",
|
||||
description="How can I run MeloNX on iOS 26?",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Legal",
|
||||
value="legal",
|
||||
description="Legality of emulators and our stance on piracy",
|
||||
),
|
||||
]
|
||||
super().__init__(placeholder="Choose a MeloNX command...", options=options)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
command_name = self.values[0]
|
||||
command = self.bot.get_command(command_name)
|
||||
|
||||
if command:
|
||||
try:
|
||||
ctx = await self.bot.get_context(interaction.message)
|
||||
if ctx:
|
||||
await ctx.invoke(command)
|
||||
embed = discord.Embed(
|
||||
title="Command Executed",
|
||||
description=f"Successfully executed `/{command_name}`",
|
||||
color=0x00FF00
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except discord.Forbidden:
|
||||
guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel"
|
||||
self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute {command_name} command")
|
||||
|
||||
if interaction.guild is None:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command cannot be executed in DMs.",
|
||||
color=0xFF0000
|
||||
)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except Exception as e:
|
||||
self.bot.logger.error(f"Error executing {command_name} command: {e}")
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="An error occurred while executing the command.",
|
||||
color=0x963155
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Command not found!",
|
||||
color=0x963155
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
|
||||
|
||||
class MelonxView(discord.ui.View):
|
||||
def __init__(self, bot):
|
||||
super().__init__()
|
||||
self.add_item(MelonxSelect(bot))
|
||||
|
||||
|
||||
def melonx_command():
|
||||
@commands.hybrid_command(
|
||||
name="melonx", description="MeloNX troubleshooting and help"
|
||||
)
|
||||
async def melonx(self, context):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs send messages permissions in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title="MeloNX Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x963155
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
|
||||
view = MelonxView(self.bot)
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return melonx
|
||||
53
cogs/melonx/mods.py
Normal file
53
cogs/melonx/mods.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def mods_command():
|
||||
@commands.hybrid_command(
|
||||
name="mods", description="How to install mods within MeloNX (Limited Support)"
|
||||
)
|
||||
async def mods(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description=(
|
||||
'# How do I install mods within MeloNX? (Limited Support)\n\n---\n\n' +
|
||||
'### **romFS/exeFS mods**:\n' +
|
||||
'1. Obtain your title ID of your game by copying it from MeloNX, Hold down on the game and click game info.\n' +
|
||||
'2. Copy it and then go to Files-> MeloNX-> mods-> contents\n' +
|
||||
'3. In the contents folder create a new folder and name it the title ID you copied earlier.\n' +
|
||||
'4. Now place all your mods for that game in the folder you just made (these should be folders with the mod name, do not mess with the file structure of the mod after unzipping it.)\n\n' +
|
||||
'### **Atmosphere mods**: \n' +
|
||||
'1. Obtain your title ID of your game by copying it from MeloNX, Hold down on the game and click game info.\n' +
|
||||
'2. Copy it and then go to Files-> MeloNX-> sdcard-> atmosphere-> contents\n' +
|
||||
'3. In the contents folder create a new folder and name it the title ID you copied earlier.\n' +
|
||||
'4. Now place all your mods for that game in the folder you just made (these should be folders with the mod name, do not mess with the file structure of the mod after unzipping it.)'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by Meshal :D')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/mods.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return mods
|
||||
48
cogs/melonx/requirements.py
Normal file
48
cogs/melonx/requirements.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def requirements_command():
|
||||
@commands.hybrid_command(
|
||||
name="requirements", description="What does MeloNX require?"
|
||||
)
|
||||
async def requirements(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description=(
|
||||
'# "What does MeloNX require?"\n\n---\n\n' +
|
||||
'- JIT is **Mandatory**, because of this MeloNX will never be on the App Store / TestFlight\n' +
|
||||
'- A Modded Nintendo Switch\n' +
|
||||
'- The Increased Memory Limit Entitlement\n' +
|
||||
'- A device with a **A12/M1** chip and **4GB Ram** or higher\n' +
|
||||
'- TrollStore is supported with limited functionality for iOS 15'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by Meshal :D')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/requirements.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return requirements
|
||||
58
cogs/melonx/transfer.py
Normal file
58
cogs/melonx/transfer.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def transfer_command():
|
||||
@commands.hybrid_command(
|
||||
name="transfer", description="How to transfer save files from other emulators or platforms"
|
||||
)
|
||||
async def transfer(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x963155,
|
||||
description=(
|
||||
'# How do I transfer my save files from another emulator, my pc, or other platform?\n\n---\n\n' +
|
||||
'### **Ryujinx Based**: \n' +
|
||||
'1. Go too Ryujinx\'s main directory -> copy and and transfer the **"bis"** folder to your iDevice.\n' +
|
||||
'2. Replace the **"bis"** folder in MeloNX with the one you transferred over.\n\n' +
|
||||
'### **Yuzu Based**:\n' +
|
||||
'1. Go to Yuzu\'s main directory and locate "**nand**" then go to -> users -> save\n' +
|
||||
'2. Get the **title ID** of the game you want to transfer to by locating the game on MeloNX, hold down on it and press "Game Info" \n' +
|
||||
'3. Then search the title ID within the **save** folder\n' +
|
||||
'4. Open that folder labeled your title ID and copy of all of the contents to your iDevice\n' +
|
||||
'5. Boot the game once in MeloNX so the save directories appear and is the latest created\n' +
|
||||
'6. On your iDevice, go into files-> MeloNX -> bis -> user -> save\n' +
|
||||
'7. In the save folder, there will be many folders named 0000001, 0000002, etc\n' +
|
||||
'8. Sort by last modified or open each one too see the modified date/time of the files\n' +
|
||||
'9. Once you\'ve found the newest one, inside will be two folders named 1 and 0, one will have a brand new save file inside.\n' +
|
||||
'10. drop the contents copied from the title ID folder in Yuzu into that directory and press "replace" when prompted.\n' +
|
||||
'11. Launch the game again in MeloNX to verify the transfer.'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png")
|
||||
embed.set_footer(text=f'Last Edited by Meshal :D')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/melonx/transfer.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="MeloNX Discord",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://discord.gg/EMXB2XYQgA",
|
||||
emoji="<:Discord:1428762057758474280>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return transfer
|
||||
235
cogs/miscellaneous/__init__.py
Normal file
235
cogs/miscellaneous/__init__.py
Normal file
@@ -0,0 +1,235 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .dontasktoask import dontasktoask_command
|
||||
from .rickroll import rr_command
|
||||
from .depart import depart_command
|
||||
from .labubu import labubu_command
|
||||
from .duck import duck_command
|
||||
from .tryitandsee import tryitandsee_command
|
||||
from .piracy import piracy_command
|
||||
from .keanu import keanu_command
|
||||
from .support import support_command
|
||||
from .docs import docs_command
|
||||
from .sigma import sigma_command
|
||||
from .silly import silly_command
|
||||
from .color import color_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Miscellaneous(commands.GroupCog, name="misc"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="miscellaneous", aliases=["misc"], invoke_without_command=True)
|
||||
async def miscellaneous_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="Miscellaneous Commands",
|
||||
description="Use `.misc <subcommand>` or `/misc <subcommand>`.",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Miscellaneous", icon_url="https://yes.nighty.works/raw/YxMC0r.png")
|
||||
embed.add_field(name="Available", value="dontasktoask, rr, depart, labubu, duck, tryitandsee, piracy, keanu, support, docs, sigma, silly, color", inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command)
|
||||
else:
|
||||
await context.send(f"Unknown miscellaneous command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
@miscellaneous_group.command(name="dontasktoask")
|
||||
async def miscellaneous_group_dontasktoask(self, context: Context):
|
||||
await self._invoke_hybrid(context, "dontasktoask")
|
||||
|
||||
@miscellaneous_group.command(name="rr")
|
||||
async def miscellaneous_group_rr(self, context: Context):
|
||||
await self._invoke_hybrid(context, "rr")
|
||||
|
||||
@miscellaneous_group.command(name="depart")
|
||||
async def miscellaneous_group_depart(self, context: Context):
|
||||
await self._invoke_hybrid(context, "depart")
|
||||
|
||||
@miscellaneous_group.command(name="labubu")
|
||||
async def miscellaneous_group_labubu(self, context: Context):
|
||||
await self._invoke_hybrid(context, "labubu")
|
||||
|
||||
@miscellaneous_group.command(name="duck")
|
||||
async def miscellaneous_group_duck(self, context: Context):
|
||||
await self._invoke_hybrid(context, "duck")
|
||||
|
||||
@miscellaneous_group.command(name="tryitandsee")
|
||||
async def miscellaneous_group_tryitandsee(self, context: Context):
|
||||
await self._invoke_hybrid(context, "tryitandsee")
|
||||
|
||||
@miscellaneous_group.command(name="piracy")
|
||||
async def miscellaneous_group_piracy(self, context: Context):
|
||||
await self._invoke_hybrid(context, "piracy")
|
||||
|
||||
@miscellaneous_group.command(name="keanu")
|
||||
async def miscellaneous_group_keanu(self, context: Context):
|
||||
await self._invoke_hybrid(context, "keanu")
|
||||
|
||||
@miscellaneous_group.command(name="support")
|
||||
async def miscellaneous_group_support(self, context: Context):
|
||||
await self._invoke_hybrid(context, "support")
|
||||
|
||||
@miscellaneous_group.command(name="docs")
|
||||
async def miscellaneous_group_docs(self, context: Context):
|
||||
await self._invoke_hybrid(context, "docs")
|
||||
|
||||
@miscellaneous_group.command(name="sigma")
|
||||
async def miscellaneous_group_sigma(self, context: Context):
|
||||
await self._invoke_hybrid(context, "sigma")
|
||||
|
||||
@miscellaneous_group.command(name="silly")
|
||||
async def miscellaneous_group_silly(self, context: Context, message_type: str = "regular"):
|
||||
await self._invoke_hybrid(context, "silly", message_type=message_type)
|
||||
|
||||
@miscellaneous_group.command(name="color")
|
||||
async def miscellaneous_group_color(self, context: Context):
|
||||
await self._invoke_hybrid(context, "color")
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="dontasktoask",
|
||||
description="Shows the 'Don't Ask to Ask' image."
|
||||
)
|
||||
async def dontasktoask(self, context):
|
||||
return await dontasktoask_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="rr",
|
||||
description="Rickroll"
|
||||
)
|
||||
async def rr(self, context):
|
||||
return await rr_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="depart",
|
||||
description="Show the departure meme"
|
||||
)
|
||||
async def depart(self, context):
|
||||
return await depart_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="labubu",
|
||||
description="Labubu ASCII art"
|
||||
)
|
||||
async def labubu(self, context):
|
||||
return await labubu_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="duck",
|
||||
description="Duck ASCII art"
|
||||
)
|
||||
async def duck(self, context):
|
||||
return await duck_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="tryitandsee",
|
||||
description="Try it and see"
|
||||
)
|
||||
async def tryitandsee(self, context):
|
||||
return await tryitandsee_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="piracy",
|
||||
description="FBI Anti Piracy Warning"
|
||||
)
|
||||
async def piracy(self, context):
|
||||
return await piracy_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="keanu",
|
||||
description="Reeves"
|
||||
)
|
||||
async def keanu(self, context):
|
||||
return await keanu_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="support",
|
||||
description="Support?"
|
||||
)
|
||||
async def support(self, context):
|
||||
return await support_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="docs",
|
||||
description="Shows the docs image."
|
||||
)
|
||||
async def docs(self, context):
|
||||
return await docs_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="sigma",
|
||||
description="i feel so sigma!"
|
||||
)
|
||||
async def sigma(self, context):
|
||||
return await sigma_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="silly",
|
||||
description="Sends a silly message :3"
|
||||
)
|
||||
@app_commands.describe(
|
||||
message_type="Type of message to send (regular or animated)"
|
||||
)
|
||||
@app_commands.choices(message_type=[
|
||||
app_commands.Choice(name="Regular", value="regular"),
|
||||
app_commands.Choice(name="Animated", value="animated")
|
||||
])
|
||||
async def silly(self, context, message_type: str = "regular"):
|
||||
return await silly_command()(self, context, message_type=message_type)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="color",
|
||||
description="Get a random color."
|
||||
)
|
||||
async def color(self, context):
|
||||
return await color_command()(self, context)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Miscellaneous(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'miscellaneous.dontasktoask'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.rr'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.depart'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.labubu'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.duck'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.tryitandsee'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.piracy'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.keanu'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.support'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.docs'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.sigma'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.silly'")
|
||||
bot.logger.info("Loaded extension 'miscellaneous.color'")
|
||||
37
cogs/miscellaneous/color.py
Normal file
37
cogs/miscellaneous/color.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import random
|
||||
import colorsys
|
||||
|
||||
def color_command():
|
||||
@commands.hybrid_command(name="color", description="Get a random color.")
|
||||
async def color(self, context):
|
||||
"""
|
||||
Generates a random color and displays information about it.
|
||||
"""
|
||||
random_color_int = random.randint(0, 0xFFFFFF)
|
||||
color = discord.Color(random_color_int)
|
||||
|
||||
r, g, b = color.r, color.g, color.b
|
||||
|
||||
rgb_decimal = (r / 255, g / 255, b / 255)
|
||||
|
||||
h, l, s = colorsys.rgb_to_hls(rgb_decimal[0], rgb_decimal[1], rgb_decimal[2])
|
||||
h_hsv, s_hsv, v_hsv = colorsys.rgb_to_hsv(rgb_decimal[0], rgb_decimal[1], rgb_decimal[2])
|
||||
|
||||
embed = discord.Embed(color=color)
|
||||
embed.set_author(name="Random Color", icon_url="https://yes.nighty.works/raw/YxMC0r.png")
|
||||
|
||||
embed.add_field(name="Hex", value=str(color))
|
||||
embed.add_field(name="RGB", value=f"rgb({r}, {g}, {b})")
|
||||
embed.add_field(name="RGB Decimal", value=f"{rgb_decimal[0]:.3f}, {rgb_decimal[1]:.3f}, {rgb_decimal[2]:.3f}")
|
||||
|
||||
embed.add_field(name="HSL", value=f"hsl({h*360:.0f}, {s*100:.0f}%, {l*100:.0f}%)")
|
||||
embed.add_field(name="HSV", value=f"hsv({h_hsv*360:.0f}, {s_hsv*100:.0f}%, {v_hsv*100:.0f}%)")
|
||||
embed.add_field(name="Integer", value=str(random_color_int))
|
||||
|
||||
embed.set_thumbnail(url=f"https://singlecolorimage.com/get/{str(color).replace('#', '')}/150x150")
|
||||
|
||||
await context.send(embed=embed)
|
||||
|
||||
return color
|
||||
30
cogs/miscellaneous/depart.py
Normal file
30
cogs/miscellaneous/depart.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
def depart_command():
|
||||
@commands.hybrid_command(
|
||||
name="depart",
|
||||
description="Show the departure meme",
|
||||
)
|
||||
async def depart(self, context):
|
||||
gif_url = "https://yes.nighty.works/raw/Mp6YGV.gif"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(gif_url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="depart.gif")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
return depart
|
||||
29
cogs/miscellaneous/docs.py
Normal file
29
cogs/miscellaneous/docs.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
def docs_command():
|
||||
@commands.hybrid_command(
|
||||
name="docs",
|
||||
description="Shows the docs image."
|
||||
)
|
||||
async def docs(self, context):
|
||||
url = "https://yes.nighty.works/raw/akdx0q.webp"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="docs.webp")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message("## Dont be like the rest, read the docs", file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send("## Dont be like the rest, read the docs", file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send("## Dont be like the rest, read the docs", file=file)
|
||||
|
||||
return docs
|
||||
29
cogs/miscellaneous/dontasktoask.py
Normal file
29
cogs/miscellaneous/dontasktoask.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
def dontasktoask_command():
|
||||
@commands.hybrid_command(
|
||||
name="dontasktoask",
|
||||
description="Shows the 'Don't Ask to Ask' image."
|
||||
)
|
||||
async def dontasktoask(self, context):
|
||||
image_url = "https://yes.nighty.works/raw/KecbCr.jpg"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(image_url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="dontasktoask.jpg")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
return dontasktoask
|
||||
36
cogs/miscellaneous/duck.py
Normal file
36
cogs/miscellaneous/duck.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def duck_command():
|
||||
@commands.hybrid_command(
|
||||
name="duck",
|
||||
description="Duck ASCII art",
|
||||
)
|
||||
async def duck(self, context):
|
||||
duck_art = """
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟[0;37m⣉⡥⠶⢶⣿⣿⣿⣿⣷⣆[0m⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⡿[0;37m⢡⡞⠁⠀⠀⠤⠈⠿⠿⠿⠿⣿[0m[0;31m⠀⢻⣦⡈[0m⠻⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⡇⠘⡁⠀[0;37m⢀⣀⣀⣀⣈⣁⣐⡒[0m[0;31m⠢⢤⡈⠛⢿⡄[0m⠻⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⡇⠀[0;37m⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄[0m[0;31m⠉⠐⠄⡈⢀[0m⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⠇[0;37m⢠⣿⣿⣿⣿⡿⢿⣿⣿⣿⠁⢈⣿⡄[0m⠀[0;36m⢀⣀[0m⠸⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⡿⠟[0;33m⣡⣶⣶⣬⣭⣥⣴[0m[0;37m⠀⣾⣿⣿⣿⣶⣾⣿⣧[0m[0;36m⠀⣼⣿⣷⣌[0m⡻⢿⣿
|
||||
⣿⣿⠟[0;33m⣋⣴⣾⣿⣿⣿⣿⣿⣿⣿⡇[0m[0;37m⢿⣿⣿⣿⣿⣿⣿⡿[0m[0;36m⢸⣿⣿⣿⣿⣷[0m⠄⢻
|
||||
⡏[0;33m⠰⢾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⢂[0m[0;37m⣭⣿⣿⣿⣿⣿⠇[0m[0;36m⠘⠛⠛⢉⣉[0m⣠⣴⣾
|
||||
⣿⣷⣦[0;33m⣬⣍⣉⣉⣛⣛⣉⠉[0m[0;37m⣤⣶⣾⣿⣿⣿⣿⣿⣿⡿[0m⢰⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧[0;37m⡘⣿⣿⣿⣿⣿⣿⣿⣿⡇[0m⣼⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇[0;37m⢸⣿⣿⣿⣿⣿⣿⣿⠁[0m⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
"""
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(f"```ansi\n{duck_art}\n```", ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(f"```ansi\n{duck_art}\n```", ephemeral=True)
|
||||
else:
|
||||
await context.send(f"```ansi\n{duck_art}\n```")
|
||||
|
||||
return duck
|
||||
49
cogs/miscellaneous/keanu.py
Normal file
49
cogs/miscellaneous/keanu.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import random
|
||||
|
||||
|
||||
def keanu_command():
|
||||
@commands.hybrid_command(
|
||||
name="keanu",
|
||||
description="Reeves",
|
||||
)
|
||||
async def keanu(self, context):
|
||||
images = [
|
||||
"https://yes.nighty.works/raw/z0HqUM.png",
|
||||
"https://yes.nighty.works/raw/1Jc0j6.avif",
|
||||
"https://yes.nighty.works/raw/uQyDyg.webp",
|
||||
"https://yes.nighty.works/raw/LzrPZz.png",
|
||||
"https://yes.nighty.works/raw/BZgKzR.jpg",
|
||||
"https://yes.nighty.works/raw/xOzCta.jpg",
|
||||
"https://yes.nighty.works/raw/eWvQa5.webp",
|
||||
"https://yes.nighty.works/raw/Qg9HJr.webp",
|
||||
"https://yes.nighty.works/raw/tYfOEn.webp",
|
||||
"https://yes.nighty.works/raw/kZS1Mu.jpg",
|
||||
"https://yes.nighty.works/raw/E83And.png",
|
||||
"https://yes.nighty.works/raw/PRr6ln.jpg",
|
||||
"https://yes.nighty.works/raw/ZlprB5.jpg",
|
||||
"https://yes.nighty.works/raw/BvcXOg.jpg",
|
||||
"https://yes.nighty.works/raw/C7gy4v.jpg",
|
||||
"https://yes.nighty.works/raw/XqHg1q.jpg",
|
||||
"https://yes.nighty.works/raw/RUXNK7.png",
|
||||
"https://yes.nighty.works/raw/CBNs9L.jpg"
|
||||
]
|
||||
|
||||
embed = discord.Embed(
|
||||
description="## Reeves",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Keanu", icon_url="https://yes.nighty.works/raw/YxMC0r.png")
|
||||
embed.set_image(url=random.choice(images))
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(embed=embed, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
return keanu
|
||||
64
cogs/miscellaneous/labubu.py
Normal file
64
cogs/miscellaneous/labubu.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def labubu_command():
|
||||
@commands.hybrid_command(
|
||||
name="labubu",
|
||||
description="Labubu ASCII art",
|
||||
)
|
||||
async def labubu(self, context):
|
||||
labubu_art = """⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠀⠙⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠗⠀⠀⣀⣄⠀⢿⣿⣿⣿⠟⠁⢠⡆⠉⠙⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⣴⣿⡟⠀⠘⣿⣿⠋⠀⠀⠀⢠⣶⡀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢠⣿⠛⣶⠀⠀⣿⡟⠀⠀⠀⢠⣿⣿⡇⠀⠠⣽⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠅⠀⠀⣿⠏⠀⣿⠀⠀⣿⠁⠀⠀⢠⣿⠟⢻⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⣼⣿⠀⢰⡟⠀⠀⠛⠀⠀⠀⣾⡇⠀⢸⡇⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠿⠃⠀⠈⠀⢀⠀⣀⣀⠀⠘⠟⠀⠀⡾⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⢀⠂⠀⠈⠉⢴⣽⣿⠵⣿⡶⣂⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡟⠡⠆⢀⠀⠀⠀⠀⠄⠀⠈⠘⠏⢿⣶⣿⡿⢟⣱⣖⠒⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⡟⣻⠏⣠⠄⠀⢀⡀⠀⠀⠀⠀⠈⠀⠀⠀⢸⣿⢦⠄⠙⣿⡇⠩⣭⣅⠈⢿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣟⣼⡇⠈⢀⣴⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠁⠀⢀⠀⠈⠰⣶⡤⠿⠃⢸⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⡟⠉⢠⡶⠋⠀⠀⠀⠀⠀⠀⠀⢀⣤⣤⣴⣶⣤⣄⡀⠀⠀⠂⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⡏⢀⡠⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣦⣄⠀⠀⠂⠀⠈⣿⣿⣿⣿
|
||||
⣿⣿⣿⢃⠈⠀⢠⠀⠀⠀⠀⠀⢠⣿⣿⣿⠿⣩⣏⡙⣛⣛⣿⣿⣿⣿⣿⣿⣿⡿⢇⠀⠀⠄⠀⠘⣿⣿⣿
|
||||
⣿⣿⣿⡎⠀⠀⠀⠀⠀⠀⠀⠠⣿⣿⣿⡟⣰⣿⠁⢀⠈⢿⣿⣿⣿⣿⢁⣴⠖⢲⣾⡇⠀⠀⠄⠀⣿⣿⣿
|
||||
⣿⣿⣿⢀⠀⠀⠀⠀⠀⠀⠀⠀⣏⢿⣿⡇⣿⡇⠀⠀⠀⣼⣿⣿⣿⡇⣼⡏⠀⠀⣿⡇⠀⠀⠀⠀⣻⣿⣿
|
||||
⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⢸⣄⠻⣷⡘⣷⣀⣀⣴⣿⡟⠉⠛⠓⣿⡇⠀⢰⣿⡇⠀⠀⠀⣼⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠙⢷⣌⠻⢿⣿⣿⣿⣿⣿⣦⣶⣿⣾⣧⣤⡾⠏⠀⠀⠀⠀⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⠶⢌⣉⣛⠛⠿⠿⠿⠿⠿⠛⠉⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣶⣄⠀⠀⠀⠀⠲⠀⠀⠀⠀⠀⠀⠉⠉⠉⠀⠀⠀⠈⠁⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠻⠿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⡏⠛⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⡻⣿⣿
|
||||
⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⢨⡛⡛⣁⣿
|
||||
⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠂⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿
|
||||
⣿⣿⣿⣿⠇⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣄⣠⣴⣾⣿⣿⣿⣿
|
||||
⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⡿⠋⠠⣾⡇⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⡟⢁⣠⣤⣦⣌⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣏⡹⢿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⡿⠿⢶⡬⠙⠟⠋⣁⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣏⠛⠚⠃⠀⠀⢰⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣻⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣧⣆⣤⣄⣤⣼⠁⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠠⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣧⣤⣴⣴⣦⠀⣄⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠐⠚⠛⠓⠂⢀⡄⠀⢰⢽⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣰⣤⣄⣀⢀⠀⢠⡅⠀⠀⢀⣤⣤⡼⠧⣤⣤⣠⣤⣤⣄⡀⡀⣰⣦⣣⠈⡡⣽
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣹⣿⣿⣿⣾⣷⣾⡗⣒⠶⣿⣿⣿⡷⣾⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿"""
|
||||
|
||||
embed = discord.Embed(
|
||||
description=f"```ansi\n{labubu_art}\n```",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Labubu", icon_url="https://yes.nighty.works/raw/YxMC0r.png")
|
||||
embed.set_footer(text=f"May look broken on mobile")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(embed=embed, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
return labubu
|
||||
30
cogs/miscellaneous/piracy.py
Normal file
30
cogs/miscellaneous/piracy.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
def piracy_command():
|
||||
@commands.hybrid_command(
|
||||
name="piracy",
|
||||
description="FBI Anti Piracy Warning",
|
||||
)
|
||||
async def piracy(self, context):
|
||||
image_url = "https://yes.nighty.works/raw/lEhuWK.png"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(image_url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="piracy.png")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
return piracy
|
||||
30
cogs/miscellaneous/rickroll.py
Normal file
30
cogs/miscellaneous/rickroll.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
def rr_command():
|
||||
@commands.hybrid_command(
|
||||
name="rr",
|
||||
description="Rickroll",
|
||||
)
|
||||
async def rr(self, context):
|
||||
gif_url = "https://yes.nighty.works/raw/JzjMcs.gif"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(gif_url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="rickroll.gif")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
return rr
|
||||
30
cogs/miscellaneous/sigma.py
Normal file
30
cogs/miscellaneous/sigma.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
def sigma_command():
|
||||
@commands.hybrid_command(
|
||||
name="sigma",
|
||||
description="i feel so sigma",
|
||||
)
|
||||
async def sigma(self, context):
|
||||
image_url = "https://yes.nighty.works/raw/EpWodj.jpeg"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(image_url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="sigma.png")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
return sigma
|
||||
32
cogs/miscellaneous/silly.py
Normal file
32
cogs/miscellaneous/silly.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def silly_command():
|
||||
@commands.hybrid_command(
|
||||
name="silly",
|
||||
description="Sends a silly message :3",
|
||||
)
|
||||
async def silly(self, context, message_type: str = "regular"):
|
||||
if message_type == "animated":
|
||||
message = "https://yes.nighty.works/raw/LX4nqt.gif"
|
||||
else:
|
||||
message = ":3"
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
await context.channel.send(message)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
await context.message.delete()
|
||||
except:
|
||||
pass
|
||||
await context.channel.send(message)
|
||||
|
||||
return silly
|
||||
29
cogs/miscellaneous/support.py
Normal file
29
cogs/miscellaneous/support.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
def support_command():
|
||||
@commands.hybrid_command(
|
||||
name="support",
|
||||
description="Shows the support image."
|
||||
)
|
||||
async def support(self, context):
|
||||
url = "https://yes.nighty.works/raw/wGzHIV.gif"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
data = await resp.read()
|
||||
file = discord.File(io.BytesIO(data), filename="support.gif")
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(file=file, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(file=file, ephemeral=True)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
return support
|
||||
23
cogs/miscellaneous/tryitandsee.py
Normal file
23
cogs/miscellaneous/tryitandsee.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def tryitandsee_command():
|
||||
@commands.hybrid_command(
|
||||
name="tryitandsee",
|
||||
description="Try it and see",
|
||||
)
|
||||
async def tryitandsee(self, context):
|
||||
link = "https://tryitands.ee/"
|
||||
|
||||
if getattr(context, "interaction", None):
|
||||
inter = context.interaction
|
||||
if not inter.response.is_done():
|
||||
await inter.response.send_message(link, ephemeral=False)
|
||||
else:
|
||||
await inter.followup.send(link, ephemeral=True)
|
||||
else:
|
||||
await context.send(link)
|
||||
|
||||
return tryitandsee
|
||||
183
cogs/moderation/__init__.py
Normal file
183
cogs/moderation/__init__.py
Normal file
@@ -0,0 +1,183 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .ban import ban_command
|
||||
from .kick import kick_command
|
||||
from .purge import purge_command
|
||||
from .warnings import warnings_command
|
||||
from .archive import archive_command
|
||||
from .hackban import hackban_command
|
||||
from .nick import nick_command
|
||||
from .timeout import timeout_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Moderation(commands.GroupCog, name="moderation"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="moderation", invoke_without_command=True)
|
||||
async def moderation_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="Moderation Commands",
|
||||
description="Use `.moderation <subcommand>` or `/moderation <subcommand>`.",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.add_field(name="Available", value="ban, kick, purge, warnings, archive, hackban, nick, timeout", inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str, **kwargs):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command, **kwargs)
|
||||
else:
|
||||
await context.send(f"Unknown moderation command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
async def timeout_duration_autocomplete(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
current: str,
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
options = [
|
||||
("60 secs", "60s"),
|
||||
("5 mins", "5m"),
|
||||
("10 mins", "10m"),
|
||||
("1 hour", "1h"),
|
||||
("1 day", "1d"),
|
||||
("1 week", "1w"),
|
||||
]
|
||||
q = (current or "").lower()
|
||||
results: list[app_commands.Choice[str]] = []
|
||||
for name, value in options:
|
||||
if q in name.lower() or q in value.lower():
|
||||
results.append(app_commands.Choice(name=name, value=value))
|
||||
return results[:25]
|
||||
|
||||
@moderation_group.command(name="ban")
|
||||
async def moderation_group_ban(self, context: Context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none"):
|
||||
await self._invoke_hybrid(context, "ban", user=user, reason=reason, delete_messages=delete_messages)
|
||||
|
||||
@moderation_group.command(name="kick")
|
||||
async def moderation_group_kick(self, context: Context, user: discord.User, *, reason: str = "Not specified"):
|
||||
await self._invoke_hybrid(context, "kick", user=user, reason=reason)
|
||||
|
||||
@moderation_group.command(name="purge")
|
||||
async def moderation_group_purge(self, context: Context, amount: int, user: discord.Member = None):
|
||||
await self._invoke_hybrid(context, "purge", amount=amount, user=user)
|
||||
|
||||
@moderation_group.command(name="warnings")
|
||||
async def moderation_group_warnings(self, context: Context):
|
||||
await self._invoke_hybrid(context, "warnings")
|
||||
|
||||
@moderation_group.command(name="archive")
|
||||
async def moderation_group_archive(self, context: Context, limit: int = 10):
|
||||
await self._invoke_hybrid(context, "archive", limit=limit)
|
||||
|
||||
@moderation_group.command(name="hackban")
|
||||
async def moderation_group_hackban(self, context: Context, user_id: str, *, reason: str = "Not specified"):
|
||||
await self._invoke_hybrid(context, "hackban", user_id=user_id, reason=reason)
|
||||
|
||||
@moderation_group.command(name="nick")
|
||||
async def moderation_group_nick(self, context: Context, user: discord.User, *, nickname: str = None):
|
||||
await self._invoke_hybrid(context, "nick", user=user, nickname=nickname)
|
||||
|
||||
@moderation_group.command(name="timeout")
|
||||
async def moderation_group_timeout(self, context: Context, user: discord.User, duration: str, *, reason: str = "Not specified"):
|
||||
await self._invoke_hybrid(context, "timeout", user=user, duration=duration, reason=reason)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="ban",
|
||||
description="Bans a user from the server."
|
||||
)
|
||||
async def ban(self, context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none"):
|
||||
return await ban_command()(self, context, user=user, reason=reason, delete_messages=delete_messages)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="kick",
|
||||
description="Kicks a user from the server."
|
||||
)
|
||||
async def kick(self, context, user: discord.User, *, reason: str = "Not specified"):
|
||||
return await kick_command()(self, context, user=user, reason=reason)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="purge",
|
||||
description="Delete a number of messages."
|
||||
)
|
||||
async def purge(self, context, amount: int, user: discord.Member = None):
|
||||
return await purge_command()(self, context, amount=amount, user=user)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="warnings",
|
||||
description="Manage warnings of a user on a server."
|
||||
)
|
||||
async def warnings(self, context):
|
||||
return await warnings_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="archive",
|
||||
description="Archives in a text file the last messages with a chosen limit of messages."
|
||||
)
|
||||
async def archive(self, context, limit: int = 10):
|
||||
return await archive_command()(self, context, limit=limit)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="hackban",
|
||||
description="Bans a user without the user having to be in the server."
|
||||
)
|
||||
async def hackban(self, context, user_id: str, *, reason: str = "Not specified"):
|
||||
return await hackban_command()(self, context, user_id=user_id, reason=reason)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="nick",
|
||||
description="Change the nickname of a user on a server."
|
||||
)
|
||||
async def nick(self, context, user: discord.User, *, nickname: str = None):
|
||||
return await nick_command()(self, context, user=user, nickname=nickname)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="timeout",
|
||||
description="Timeout a user for a specified duration."
|
||||
)
|
||||
@app_commands.describe(
|
||||
user="The user that should be timed out.",
|
||||
duration="Duration",
|
||||
reason="The reason why the user should be timed out.",
|
||||
)
|
||||
@app_commands.autocomplete(duration=timeout_duration_autocomplete)
|
||||
async def timeout(self, context, user: discord.User, duration: str, *, reason: str = "Not specified"):
|
||||
return await timeout_command()(self, context, user=user, duration=duration, reason=reason)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Moderation(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'moderation.ban'")
|
||||
bot.logger.info("Loaded extension 'moderation.kick'")
|
||||
bot.logger.info("Loaded extension 'moderation.purge'")
|
||||
bot.logger.info("Loaded extension 'moderation.warnings'")
|
||||
bot.logger.info("Loaded extension 'moderation.archive'")
|
||||
bot.logger.info("Loaded extension 'moderation.hackban'")
|
||||
bot.logger.info("Loaded extension 'moderation.nick'")
|
||||
bot.logger.info("Loaded extension 'moderation.timeout'")
|
||||
@@ -6,10 +6,7 @@ from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Archive(commands.Cog, name="archive"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def archive_command():
|
||||
@commands.hybrid_command(
|
||||
name="archive",
|
||||
description="Archives in a text file the last messages with a chosen limit of messages.",
|
||||
@@ -18,7 +15,7 @@ class Archive(commands.Cog, name="archive"):
|
||||
@app_commands.describe(
|
||||
limit="The limit of messages that should be archived.",
|
||||
)
|
||||
async def archive(self, context: Context, limit: int = 10) -> None:
|
||||
async def archive(self, context, limit: int = 10):
|
||||
"""
|
||||
Archives in a text file the last messages with a chosen limit of messages. This command requires the MESSAGE_CONTENT intent to work properly.
|
||||
|
||||
@@ -54,7 +51,5 @@ class Archive(commands.Cog, name="archive"):
|
||||
f = discord.File(log_file)
|
||||
await context.send(file=f)
|
||||
os.remove(log_file)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Archive(bot))
|
||||
|
||||
return archive
|
||||
@@ -4,10 +4,7 @@ from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Ban(commands.Cog, name="ban"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def ban_command():
|
||||
@commands.hybrid_command(
|
||||
name="ban",
|
||||
description="Bans a user from the server.",
|
||||
@@ -27,8 +24,8 @@ class Ban(commands.Cog, name="ban"):
|
||||
app_commands.Choice(name="Last 7 days", value="7d"),
|
||||
])
|
||||
async def ban(
|
||||
self, context: Context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none"
|
||||
) -> None:
|
||||
self, context, user: discord.User, *, reason: str = "Not specified", delete_messages: str = "none"
|
||||
):
|
||||
try:
|
||||
member = context.guild.get_member(user.id)
|
||||
if not member:
|
||||
@@ -41,7 +38,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Ban",
|
||||
description=f"**{user}** was banned by **{context.author}**!",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
embed.add_field(name="Reason:", value=reason)
|
||||
await context.send(embed=embed)
|
||||
return
|
||||
@@ -50,7 +47,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Error!",
|
||||
description="I don't have permission to ban this user.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
except Exception:
|
||||
@@ -58,7 +55,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Error!",
|
||||
description="An error occurred while trying to ban the user.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -67,7 +64,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Missing Permissions!",
|
||||
description="You don't have the `Ban Members` permission to use this command.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -76,7 +73,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Cannot Ban User",
|
||||
description="This user has a higher or equal role to me. Make sure my role is above theirs.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -86,7 +83,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Cannot Ban User",
|
||||
description="You cannot ban this user as they have a higher or equal role to you.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -111,7 +108,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Ban",
|
||||
description=f"You were banned by **{context.author}** from **{context.guild.name}**!\nReason: {reason}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await member.send(embed=dm_embed)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
pass
|
||||
@@ -128,7 +125,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Ban",
|
||||
description=f"**{user}** was banned by **{context.author}**!",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
embed.add_field(name="Reason:", value=reason)
|
||||
|
||||
if delete_messages != "none":
|
||||
@@ -145,7 +142,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Error!",
|
||||
description="I don't have permission to ban this user. Make sure my role is above theirs.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except discord.HTTPException as e:
|
||||
if "Cannot ban the owner of a guild" in str(e):
|
||||
@@ -153,20 +150,20 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Cannot Ban User",
|
||||
description="You cannot ban the server owner.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description=f"Discord API error: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Debug Error!",
|
||||
description=f"Error type: {type(e).__name__}\nError message: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
except Exception as e:
|
||||
@@ -174,7 +171,7 @@ class Ban(commands.Cog, name="ban"):
|
||||
title="Error!",
|
||||
description="An unexpected error occurred.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
async def delete_all_user_messages(self, guild: discord.Guild, user_id: int) -> None:
|
||||
@@ -211,7 +208,5 @@ class Ban(commands.Cog, name="ban"):
|
||||
"7d": "Last 7 days"
|
||||
}
|
||||
return time_formats.get(delete_option, "Unknown time period")
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Ban(bot))
|
||||
|
||||
return ban
|
||||
@@ -4,10 +4,7 @@ from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class HackBan(commands.Cog, name="hackban"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def hackban_command():
|
||||
@commands.hybrid_command(
|
||||
name="hackban",
|
||||
description="Bans a user without the user having to be in the server.",
|
||||
@@ -19,8 +16,8 @@ class HackBan(commands.Cog, name="hackban"):
|
||||
reason="The reason why the user should be banned.",
|
||||
)
|
||||
async def hackban(
|
||||
self, context: Context, user_id: str, *, reason: str = "Not specified"
|
||||
) -> None:
|
||||
self, context, user_id: str, *, reason: str = "Not specified"
|
||||
):
|
||||
"""
|
||||
Bans a user without the user having to be in the server.
|
||||
|
||||
@@ -37,7 +34,7 @@ class HackBan(commands.Cog, name="hackban"):
|
||||
title="Ban",
|
||||
description=f"**{user}** (ID: {user_id}) was banned by **{context.author}**!",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
embed.add_field(name="Reason:", value=reason)
|
||||
await context.send(embed=embed)
|
||||
except Exception:
|
||||
@@ -45,9 +42,7 @@ class HackBan(commands.Cog, name="hackban"):
|
||||
title="Error!",
|
||||
description="An error occurred while trying to ban the user. Make sure ID is an existing ID that belongs to a user.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(HackBan(bot))
|
||||
|
||||
return hackban
|
||||
|
||||
@@ -4,10 +4,7 @@ from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Kick(commands.Cog, name="kick"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def kick_command():
|
||||
@commands.hybrid_command(
|
||||
name="kick",
|
||||
description="Kicks a user from the server.",
|
||||
@@ -17,8 +14,8 @@ class Kick(commands.Cog, name="kick"):
|
||||
reason="The reason why the user should be kicked.",
|
||||
)
|
||||
async def kick(
|
||||
self, context: Context, user: discord.User, *, reason: str = "Not specified"
|
||||
) -> None:
|
||||
self, context, user: discord.User, *, reason: str = "Not specified"
|
||||
):
|
||||
try:
|
||||
member = context.guild.get_member(user.id)
|
||||
if not member:
|
||||
@@ -29,7 +26,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Error!",
|
||||
description="This user is not in the server.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -38,7 +35,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Missing Permissions!",
|
||||
description="You don't have the `Kick Members` permission to use this command.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -47,7 +44,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Cannot Kick User",
|
||||
description="This user has a higher or equal role to me. Make sure my role is above theirs.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -57,7 +54,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Cannot Kick User",
|
||||
description="You cannot kick this user as they have a higher or equal role to you.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
@@ -69,7 +66,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Kick",
|
||||
description=f"You were kicked by **{context.author}** from **{context.guild.name}**!\nReason: {reason}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
|
||||
)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
@@ -81,7 +78,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Kick",
|
||||
description=f"**{user}** was kicked by **{context.author}**!",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
embed.add_field(name="Reason:", value=reason)
|
||||
|
||||
await context.send(embed=embed)
|
||||
@@ -91,7 +88,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Error!",
|
||||
description="I don't have permission to kick this user. Make sure my role is above theirs.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except discord.HTTPException as e:
|
||||
if "Cannot kick the owner of a guild" in str(e):
|
||||
@@ -99,20 +96,20 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Cannot Kick User",
|
||||
description="You cannot kick the server owner.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description=f"Discord API error: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Debug Error!",
|
||||
description=f"Error type: {type(e).__name__}\nError message: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
except Exception as e:
|
||||
@@ -120,9 +117,7 @@ class Kick(commands.Cog, name="kick"):
|
||||
title="Error!",
|
||||
description="An unexpected error occurred.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Kick(bot))
|
||||
|
||||
return kick
|
||||
|
||||
@@ -4,10 +4,7 @@ from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Nick(commands.Cog, name="nick"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def nick_command():
|
||||
@commands.hybrid_command(
|
||||
name="nick",
|
||||
description="Change the nickname of a user on a server.",
|
||||
@@ -17,8 +14,8 @@ class Nick(commands.Cog, name="nick"):
|
||||
nickname="The new nickname that should be set.",
|
||||
)
|
||||
async def nick(
|
||||
self, context: Context, user: discord.User, *, nickname: str = None
|
||||
) -> None:
|
||||
self, context, user: discord.User, *, nickname: str = None
|
||||
):
|
||||
"""
|
||||
Change the nickname of a user on a server.
|
||||
|
||||
@@ -31,7 +28,7 @@ class Nick(commands.Cog, name="nick"):
|
||||
title="Missing Permissions!",
|
||||
description="You are missing the permission(s) `manage_nicknames` to execute this command!",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
return await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
if not context.guild.me.guild_permissions.manage_nicknames:
|
||||
@@ -39,7 +36,7 @@ class Nick(commands.Cog, name="nick"):
|
||||
title="Missing Permissions!",
|
||||
description="I am missing the permission(s) `manage_nicknames` to execute this command!",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
return await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||
@@ -51,16 +48,14 @@ class Nick(commands.Cog, name="nick"):
|
||||
title="Nickname",
|
||||
description=f"**{member}'s** new nickname is **{nickname}**!",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed)
|
||||
except:
|
||||
embed = discord.Embed(
|
||||
title="Missing Permissions!",
|
||||
description="An error occurred while trying to change the nickname of the user. Make sure my role is above the role of the user you want to change the nickname.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Nick(bot))
|
||||
|
||||
return nick
|
||||
|
||||
@@ -3,35 +3,45 @@ from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Purge(commands.Cog, name="purge"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def purge_command():
|
||||
@commands.hybrid_command(
|
||||
name="purge",
|
||||
description="Delete a number of messages.",
|
||||
)
|
||||
@commands.has_guild_permissions(manage_messages=True)
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
@app_commands.describe(amount="The amount of messages that should be deleted.")
|
||||
async def purge(self, context: Context, amount: int) -> None:
|
||||
"""
|
||||
Delete a number of messages.
|
||||
|
||||
:param context: The hybrid command context.
|
||||
:param amount: The number of messages that should be deleted.
|
||||
"""
|
||||
await context.send("Deleting messages...")
|
||||
purged_messages = await context.channel.purge(limit=amount + 1)
|
||||
@app_commands.describe(
|
||||
amount="The amount of messages that should be deleted.",
|
||||
user="The user whose messages should be deleted (optional)."
|
||||
)
|
||||
async def purge(self, context, amount: int, user: discord.Member = None):
|
||||
if context.interaction:
|
||||
await context.defer(ephemeral=True)
|
||||
|
||||
if user:
|
||||
deleted_count = 0
|
||||
|
||||
def check(message):
|
||||
nonlocal deleted_count
|
||||
if message.author == user and deleted_count < amount:
|
||||
deleted_count += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
purged_messages = await context.channel.purge(limit=300, check=check)
|
||||
else:
|
||||
purged_messages = await context.channel.purge(limit=amount)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Purge",
|
||||
description=f"**{context.author}** cleared **{len(purged_messages)-1}** messages!",
|
||||
description=f"**{context.author}** cleared **{len(purged_messages)}** messages!" + (f" from **{user}**" if user else ""),
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await context.channel.send(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Purge(bot))
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
|
||||
if context.interaction:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, delete_after=10)
|
||||
|
||||
return purge
|
||||
176
cogs/moderation/timeout.py
Normal file
176
cogs/moderation/timeout.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
def timeout_command():
|
||||
DURATION_CHOICES = [
|
||||
app_commands.Choice(name="60 secs", value="60s"),
|
||||
app_commands.Choice(name="5 mins", value="5m"),
|
||||
app_commands.Choice(name="10 mins", value="10m"),
|
||||
app_commands.Choice(name="1 hour", value="1h"),
|
||||
app_commands.Choice(name="1 day", value="1d"),
|
||||
app_commands.Choice(name="1 week", value="1w"),
|
||||
]
|
||||
@commands.hybrid_command(
|
||||
name="timeout",
|
||||
description="Timeout a user for a specified duration.",
|
||||
)
|
||||
@app_commands.describe(
|
||||
user="The user that should be timed out.",
|
||||
duration="Duration",
|
||||
reason="The reason why the user should be timed out.",
|
||||
)
|
||||
@app_commands.choices(duration=DURATION_CHOICES)
|
||||
async def timeout(
|
||||
self, context, user: discord.User, duration: str, *, reason: str = "Not specified"
|
||||
):
|
||||
try:
|
||||
member = context.guild.get_member(user.id)
|
||||
if not member:
|
||||
try:
|
||||
member = await context.guild.fetch_member(user.id)
|
||||
except discord.NotFound:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description="This user is not in the server.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if not context.author.guild_permissions.moderate_members and context.author != context.guild.owner:
|
||||
embed = discord.Embed(
|
||||
title="Missing Permissions!",
|
||||
description="You don't have the `Timeout Members` permission to use this command.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if member and member.top_role >= context.guild.me.top_role:
|
||||
embed = discord.Embed(
|
||||
title="Cannot Timeout User",
|
||||
description="This user has a higher or equal role to me. Make sure my role is above theirs.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if member and context.author != context.guild.owner:
|
||||
if member.top_role >= context.author.top_role:
|
||||
embed = discord.Embed(
|
||||
title="Cannot Timeout User",
|
||||
description="You cannot timeout this user as they have a higher or equal role to you.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
seconds = _parse_duration_to_seconds(duration)
|
||||
if seconds is None or seconds <= 0:
|
||||
embed = discord.Embed(
|
||||
title="Invalid Duration",
|
||||
description="Choose one of: 60 secs, 5 mins, 10 mins, 1 hour, 1 day, 1 week.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
max_seconds = 28 * 24 * 60 * 60
|
||||
seconds = min(seconds, max_seconds)
|
||||
|
||||
try:
|
||||
timeout_delta = timedelta(seconds=seconds)
|
||||
await member.timeout(timeout_delta, reason=reason)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Timeout",
|
||||
description=f"**{user}** was timed out by **{context.author}**!",
|
||||
color=0x7289DA,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
embed.add_field(name="Reason:", value=reason)
|
||||
embed.add_field(name="Duration:", value=_format_duration(duration), inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
except discord.Forbidden:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description="I don't have permission to timeout this user. Make sure my role is above theirs.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except discord.HTTPException as e:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description=f"Discord API error: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Debug Error!",
|
||||
description=f"Error type: {type(e).__name__}\nError message: {str(e)}",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
except Exception:
|
||||
embed = discord.Embed(
|
||||
title="Error!",
|
||||
description="An unexpected error occurred.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
|
||||
@timeout.autocomplete("duration")
|
||||
async def duration_autocomplete(interaction: discord.Interaction, current: str):
|
||||
query = (current or "").lower()
|
||||
filtered = [c for c in DURATION_CHOICES if query in c.name.lower() or query in c.value.lower()]
|
||||
return filtered[:25]
|
||||
|
||||
return timeout
|
||||
|
||||
|
||||
def _parse_duration_to_seconds(duration: str):
|
||||
try:
|
||||
s = duration.strip().lower()
|
||||
if s.endswith("s"):
|
||||
return int(s[:-1])
|
||||
if s.endswith("m"):
|
||||
return int(s[:-1]) * 60
|
||||
if s.endswith("h"):
|
||||
return int(s[:-1]) * 60 * 60
|
||||
if s.endswith("d"):
|
||||
return int(s[:-1]) * 60 * 60 * 24
|
||||
if s.endswith("w"):
|
||||
return int(s[:-1]) * 60 * 60 * 24 * 7
|
||||
return int(s)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _format_duration(duration: str) -> str:
|
||||
mapping = {
|
||||
"s": "seconds",
|
||||
"m": "minutes",
|
||||
"h": "hours",
|
||||
"d": "days",
|
||||
"w": "weeks",
|
||||
}
|
||||
s = duration.strip().lower()
|
||||
for suffix, word in mapping.items():
|
||||
if s.endswith(suffix):
|
||||
try:
|
||||
value = int(s[:-1])
|
||||
return f"{value} {word}"
|
||||
except Exception:
|
||||
return duration
|
||||
try:
|
||||
value = int(s)
|
||||
return f"{value} seconds"
|
||||
except Exception:
|
||||
return duration
|
||||
|
||||
|
||||
@@ -4,11 +4,8 @@ from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class Warnings(commands.Cog, name="warnings"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
def warnings_command():
|
||||
async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
@@ -22,7 +19,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
name="warning",
|
||||
description="Manage warnings of a user on a server.",
|
||||
)
|
||||
async def warning(self, context: Context) -> None:
|
||||
async def warning(self, context) -> None:
|
||||
"""
|
||||
Manage warnings of a user on a server.
|
||||
|
||||
@@ -33,7 +30,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
title="Missing Permissions!",
|
||||
description="You are missing the permission(s) `manage_messages` to execute this command!",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
return await self.send_embed(context, embed, ephemeral=True)
|
||||
|
||||
if context.invoked_subcommand is None:
|
||||
@@ -42,7 +39,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
description="Please specify a subcommand.\n\n**Subcommands:**\n`add` - Add a warning to a user.\n`remove` - Remove a warning from a user.\n`list` - List all warnings of a user.",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await self.send_embed(context, embed)
|
||||
|
||||
@warning.command(
|
||||
@@ -68,7 +65,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
title="Missing Permissions!",
|
||||
description="You are missing the permission(s) `manage_messages` to execute this command!",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
return await self.send_embed(context, embed, ephemeral=True)
|
||||
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||
user.id
|
||||
@@ -81,7 +78,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
description=f"**{member}** was warned by **{context.author}**!\nTotal warns for this user: {total}",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
embed.add_field(name="Reason:", value=reason)
|
||||
await self.send_embed(context, embed)
|
||||
try:
|
||||
@@ -90,7 +87,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
description=f"You were warned by **{context.author}** in **{context.guild.name}**!\nReason: {reason}",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
dm_embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
dm_embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await member.send(embed=dm_embed)
|
||||
except:
|
||||
fallback = discord.Embed(
|
||||
@@ -123,7 +120,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
title="Missing Permissions!",
|
||||
description="You are missing the permission(s) `manage_messages` to execute this command!",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
return await self.send_embed(context, embed, ephemeral=True)
|
||||
member = context.guild.get_member(user.id) or await context.guild.fetch_member(
|
||||
user.id
|
||||
@@ -134,7 +131,7 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
description=f"Removed the warning **#{warn_id}** from **{member}**!\nTotal warns for this user: {total}",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
await self.send_embed(context, embed)
|
||||
|
||||
@warning.command(
|
||||
@@ -154,11 +151,11 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
title="Missing Permissions!",
|
||||
description="You are missing the permission(s) `manage_messages` to execute this command!",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
).set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
return await self.send_embed(context, embed, ephemeral=True)
|
||||
warnings_list = await self.bot.database.get_warnings(user.id, context.guild.id)
|
||||
embed = discord.Embed(title=f"Warnings of {user}", color=0x7289DA)
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.set_author(name="Moderation", icon_url="https://yes.nighty.works/raw/CPKHQd.png")
|
||||
description = ""
|
||||
if len(warnings_list) == 0:
|
||||
description = "This user has no warnings."
|
||||
@@ -170,5 +167,4 @@ class Warnings(commands.Cog, name="warnings"):
|
||||
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Warnings(bot))
|
||||
return warning
|
||||
|
||||
@@ -22,6 +22,8 @@ class CogManagement(commands.Cog, name="cog_management"):
|
||||
name="load",
|
||||
description="Load a cog",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(cog="The name of the cog to load")
|
||||
@commands.is_owner()
|
||||
async def load(self, context: Context, cog: str) -> None:
|
||||
@@ -51,6 +53,8 @@ class CogManagement(commands.Cog, name="cog_management"):
|
||||
name="unload",
|
||||
description="Unloads a cog.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(cog="The name of the cog to unload")
|
||||
@commands.is_owner()
|
||||
async def unload(self, context: Context, cog: str) -> None:
|
||||
@@ -80,6 +84,8 @@ class CogManagement(commands.Cog, name="cog_management"):
|
||||
name="reload",
|
||||
description="Reloads a cog.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(cog="The name of the cog to reload")
|
||||
@commands.is_owner()
|
||||
async def reload(self, context: Context, cog: str) -> None:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import os
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from utils.checks import is_owner_or_friend
|
||||
|
||||
|
||||
class Invite(commands.Cog, name="invite"):
|
||||
@@ -22,7 +24,9 @@ class Invite(commands.Cog, name="invite"):
|
||||
name="invite",
|
||||
description="Get the invite link of the bot to be able to invite it.",
|
||||
)
|
||||
@commands.is_owner()
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@is_owner_or_friend()
|
||||
async def invite(self, context: Context) -> None:
|
||||
"""
|
||||
Get the invite link of the bot to be able to invite it.
|
||||
@@ -33,22 +37,14 @@ class Invite(commands.Cog, name="invite"):
|
||||
if client is None:
|
||||
await context.send("Bot is not ready. Try again shortly.")
|
||||
return
|
||||
permissions = os.getenv("INVITE_PERMISSIONS", "0")
|
||||
invite_url = (
|
||||
f"https://discord.com/api/oauth2/authorize?client_id={client.id}"
|
||||
f"&scope=bot%20applications.commands&permissions={permissions}"
|
||||
)
|
||||
embed = discord.Embed(title="Invite", description=f"Invite me by clicking [here]({invite_url}).", color=0x7289DA)
|
||||
invite_link = os.getenv("INVITE_LINK")
|
||||
embed = discord.Embed(title="Install", description=f"Install me by clicking [here]({invite_link}).", color=0x7289DA)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
|
||||
try:
|
||||
await context.author.send(embed=embed)
|
||||
await self.send_embed(context, discord.Embed(description="I sent you a private message!", color=0x7289DA), ephemeral=True)
|
||||
except discord.Forbidden:
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
await self.send_embed(context, embed, ephemeral=False)
|
||||
|
||||
async def cog_command_error(self, context: Context, error) -> None:
|
||||
if isinstance(error, commands.NotOwner):
|
||||
if isinstance(error, (commands.NotOwner, commands.CheckFailure)):
|
||||
embed = discord.Embed(
|
||||
title="Permission Denied",
|
||||
description="You are not the owner of this bot.",
|
||||
|
||||
126
cogs/owner/logs.py
Normal file
126
cogs/owner/logs.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import os
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from utils.ascii_art import ascii_plain
|
||||
from utils.checks import is_owner_or_friend
|
||||
|
||||
|
||||
class Logs(commands.Cog, name="logs"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def send_file(self, context: Context, *, file: discord.File, ephemeral: bool = False) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(file=file, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.response.send_message(file=file, ephemeral=ephemeral)
|
||||
else:
|
||||
await context.send(file=file)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="logs",
|
||||
description="View the bot's log file",
|
||||
)
|
||||
@app_commands.describe(lines="Number of lines to read from the end of the log file (default: 50, max: 200)")
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@is_owner_or_friend()
|
||||
async def logs(self, context: Context, lines: int = 50) -> None:
|
||||
if lines > 200:
|
||||
lines = 200
|
||||
elif lines < 1:
|
||||
lines = 1
|
||||
|
||||
log_file_path = os.getenv("LOG_FILE", "logs/discord.log")
|
||||
|
||||
if not os.path.isabs(log_file_path):
|
||||
log_file_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), log_file_path)
|
||||
|
||||
try:
|
||||
if not os.path.exists(log_file_path):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Log file not found at: `{log_file_path}`",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
with open(log_file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
all_lines = f.readlines()
|
||||
|
||||
if not all_lines:
|
||||
embed = discord.Embed(
|
||||
title="Logs",
|
||||
description="Log file is empty.",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
selected_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
|
||||
log_content = ''.join(selected_lines)
|
||||
|
||||
log_file = f"logs.txt"
|
||||
with open(log_file, "w", encoding="utf-8") as f:
|
||||
f.write(f"Bot logs extracted at {discord.utils.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC\n")
|
||||
f.write(f"Last {len(selected_lines)} lines from {os.path.basename(log_file_path)}\n")
|
||||
f.write(f"Total lines in log file: {len(all_lines)}\n")
|
||||
f.write("-" * 50 + "\n\n")
|
||||
f.write(ascii_plain + "\n\n")
|
||||
f.write(log_content)
|
||||
|
||||
file_obj = discord.File(log_file)
|
||||
await self.send_file(context, file=file_obj, ephemeral=True)
|
||||
|
||||
os.remove(log_file)
|
||||
|
||||
except PermissionError:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Permission denied when trying to read log file: `{log_file_path}`",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"An error occurred while reading the log file:\n```\n{str(e)}\n```",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
|
||||
async def cog_command_error(self, context: Context, error) -> None:
|
||||
if isinstance(error, (commands.NotOwner, commands.CheckFailure)):
|
||||
embed = discord.Embed(
|
||||
title="Permission Denied",
|
||||
description="You are not the owner of this bot!",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
else:
|
||||
raise error
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Logs(bot))
|
||||
|
||||
@@ -2,28 +2,31 @@ import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
from utils.checks import is_owner_or_friend
|
||||
|
||||
|
||||
class Say(commands.Cog, name="say"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
async def send_embed(self, context: Context, embed: discord.Embed, *, ephemeral: bool = False, allowed_mentions: discord.AllowedMentions = None) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral, allowed_mentions=allowed_mentions)
|
||||
else:
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral, allowed_mentions=allowed_mentions)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
await context.send(embed=embed, allowed_mentions=allowed_mentions)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="say",
|
||||
description="The bot will say anything you want.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(message="The message that should be repeated by the bot")
|
||||
@commands.is_owner()
|
||||
@is_owner_or_friend()
|
||||
async def say(self, context: Context, *, message: str) -> None:
|
||||
"""
|
||||
The bot will say anything you want.
|
||||
@@ -31,14 +34,45 @@ class Say(commands.Cog, name="say"):
|
||||
:param context: The hybrid command context.
|
||||
:param message: The message that should be repeated by the bot.
|
||||
"""
|
||||
await context.send(message)
|
||||
if context.guild is not None:
|
||||
self.bot.logger.info(
|
||||
f"Say command used in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id}): {message}"
|
||||
)
|
||||
else:
|
||||
self.bot.logger.info(
|
||||
f"Say command used in DMs by {context.author} (ID: {context.author.id}): {message}"
|
||||
)
|
||||
|
||||
allowed_mentions = discord.AllowedMentions.none()
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
is_in_guild = context.guild is not None
|
||||
await interaction.response.defer(ephemeral=is_in_guild)
|
||||
try:
|
||||
await context.channel.send(message, allowed_mentions=allowed_mentions)
|
||||
if is_in_guild:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
except discord.Forbidden:
|
||||
await interaction.followup.send(message, allowed_mentions=allowed_mentions)
|
||||
else:
|
||||
try:
|
||||
await context.message.delete()
|
||||
except:
|
||||
pass
|
||||
await context.send(message, allowed_mentions=allowed_mentions)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="embed",
|
||||
description="The bot will say anything you want, but within embeds.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(message="The message that should be repeated by the bot")
|
||||
@commands.is_owner()
|
||||
@is_owner_or_friend()
|
||||
async def embed(self, context: Context, *, message: str) -> None:
|
||||
"""
|
||||
The bot will say anything you want, but using embeds.
|
||||
@@ -46,17 +80,46 @@ class Say(commands.Cog, name="say"):
|
||||
:param context: The hybrid command context.
|
||||
:param message: The message that should be repeated by the bot.
|
||||
"""
|
||||
if context.guild is not None:
|
||||
self.bot.logger.info(
|
||||
f"Embed command used in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id}): {message}"
|
||||
)
|
||||
else:
|
||||
self.bot.logger.info(
|
||||
f"Embed command used in DMs by {context.author} (ID: {context.author.id}): {message}"
|
||||
)
|
||||
|
||||
allowed_mentions = discord.AllowedMentions.none()
|
||||
embed = discord.Embed(
|
||||
title="Say",
|
||||
description=message,
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed)
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
is_in_guild = context.guild is not None
|
||||
await interaction.response.defer(ephemeral=is_in_guild)
|
||||
try:
|
||||
await context.channel.send(embed=embed, allowed_mentions=allowed_mentions)
|
||||
if is_in_guild:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
except discord.Forbidden:
|
||||
await interaction.followup.send(embed=embed, allowed_mentions=allowed_mentions)
|
||||
else:
|
||||
try:
|
||||
await context.message.delete()
|
||||
except:
|
||||
pass
|
||||
await context.send(embed=embed, allowed_mentions=allowed_mentions)
|
||||
|
||||
|
||||
async def cog_command_error(self, context: Context, error) -> None:
|
||||
if isinstance(error, commands.NotOwner):
|
||||
if isinstance(error, (commands.NotOwner, commands.CheckFailure)):
|
||||
embed = discord.Embed(
|
||||
title="Permission Denied",
|
||||
description="You are not the owner of this bot!",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import os
|
||||
import signal
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
@@ -21,6 +24,8 @@ class Shutdown(commands.Cog, name="shutdown"):
|
||||
name="shutdown",
|
||||
description="Make the bot shutdown.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@commands.is_owner()
|
||||
async def shutdown(self, context: Context) -> None:
|
||||
"""
|
||||
@@ -28,6 +33,16 @@ class Shutdown(commands.Cog, name="shutdown"):
|
||||
|
||||
:param context: The hybrid command context.
|
||||
"""
|
||||
# Log command execution before shutdown starts
|
||||
if context.guild is not None:
|
||||
self.bot.logger.info(
|
||||
f"Executed shutdown command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})"
|
||||
)
|
||||
else:
|
||||
self.bot.logger.info(
|
||||
f"Executed shutdown command by {context.author} (ID: {context.author.id}) in DMs"
|
||||
)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Shutdown",
|
||||
description="Shutting down. Bye! <a:PandaThanos:1417483671253811262>",
|
||||
@@ -35,7 +50,7 @@ class Shutdown(commands.Cog, name="shutdown"):
|
||||
).set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
|
||||
await self.send_embed(context, embed)
|
||||
await self.bot.close()
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
|
||||
async def cog_command_error(self, context: Context, error) -> None:
|
||||
if isinstance(error, commands.NotOwner):
|
||||
|
||||
@@ -18,10 +18,12 @@ class Sync(commands.Cog, name="sync"):
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
@commands.command(
|
||||
@commands.hybrid_command(
|
||||
name="sync",
|
||||
description="Synchonizes the slash commands.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(scope="The scope of the sync. Can be `global` or `guild`")
|
||||
@commands.is_owner()
|
||||
async def sync(self, context: Context, scope: str) -> None:
|
||||
@@ -60,10 +62,12 @@ class Sync(commands.Cog, name="sync"):
|
||||
embed.set_author(name="Owner", icon_url="https://yes.nighty.works/raw/zReOib.webp")
|
||||
await self.send_embed(context, embed, ephemeral=True)
|
||||
|
||||
@commands.command(
|
||||
@commands.hybrid_command(
|
||||
name="unsync",
|
||||
description="Unsynchonizes the slash commands.",
|
||||
)
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
@app_commands.describe(
|
||||
scope="The scope of the sync. Can be `global`, `current_guild` or `guild`"
|
||||
)
|
||||
|
||||
201
cogs/sidestore/__init__.py
Normal file
201
cogs/sidestore/__init__.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .sidestore import SidestoreView
|
||||
from .refresh import refresh_command
|
||||
from .code import code_command
|
||||
from .crash import crash_command
|
||||
from .pairing import pairing_command
|
||||
from .server import server_command
|
||||
from .afc import afc_command
|
||||
from .udid import udid_command
|
||||
from .half import half_command
|
||||
from .sparse import sparse_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Sidestore(commands.GroupCog, name="sidestore"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="sidestore", invoke_without_command=True)
|
||||
async def sidestore_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="SideStore Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x8e82f9
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
view = SidestoreView(self.bot)
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
@sidestore_group.command(name="help")
|
||||
async def sidestore_group_help(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="SideStore Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x8e82f9
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
view = SidestoreView(self.bot)
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command)
|
||||
else:
|
||||
await context.send(f"Unknown SideStore command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
@sidestore_group.command(name="refresh")
|
||||
async def sidestore_group_refresh(self, context: Context):
|
||||
await self._invoke_hybrid(context, "refresh")
|
||||
|
||||
@sidestore_group.command(name="code")
|
||||
async def sidestore_group_code(self, context: Context):
|
||||
await self._invoke_hybrid(context, "code")
|
||||
|
||||
@sidestore_group.command(name="crash")
|
||||
async def sidestore_group_crash(self, context: Context):
|
||||
await self._invoke_hybrid(context, "crash")
|
||||
|
||||
@sidestore_group.command(name="pairing")
|
||||
async def sidestore_group_pairing(self, context: Context):
|
||||
await self._invoke_hybrid(context, "pairing")
|
||||
|
||||
@sidestore_group.command(name="server")
|
||||
async def sidestore_group_server(self, context: Context):
|
||||
await self._invoke_hybrid(context, "server")
|
||||
|
||||
@sidestore_group.command(name="afc")
|
||||
async def sidestore_group_afc(self, context: Context):
|
||||
await self._invoke_hybrid(context, "afc")
|
||||
|
||||
@sidestore_group.command(name="udid")
|
||||
async def sidestore_group_udid(self, context: Context):
|
||||
await self._invoke_hybrid(context, "udid")
|
||||
|
||||
@sidestore_group.command(name="half")
|
||||
async def sidestore_group_half(self, context: Context):
|
||||
await self._invoke_hybrid(context, "half")
|
||||
|
||||
@sidestore_group.command(name="sparse")
|
||||
async def sidestore_group_sparse(self, context: Context):
|
||||
await self._invoke_hybrid(context, "sparse")
|
||||
|
||||
@app_commands.command(
|
||||
name="help",
|
||||
description="SideStore troubleshooting help"
|
||||
)
|
||||
async def help(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="SideStore Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
color=0x8e82f9
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
|
||||
view = SidestoreView(self.bot)
|
||||
|
||||
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="refresh",
|
||||
description="Help with refreshing or installing apps"
|
||||
)
|
||||
async def refresh(self, context):
|
||||
return await refresh_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="code",
|
||||
description="No code received when signing in with Apple ID"
|
||||
)
|
||||
async def code(self, context):
|
||||
return await code_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="crash",
|
||||
description="Help with SideStore crashing issues"
|
||||
)
|
||||
async def crash(self, context):
|
||||
return await crash_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="pairing",
|
||||
description="Help with pairing file issues"
|
||||
)
|
||||
async def pairing(self, context):
|
||||
return await pairing_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="server",
|
||||
description="Help with anisette server issues"
|
||||
)
|
||||
async def server(self, context):
|
||||
return await server_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="afc",
|
||||
description="Help with AFC Connection Failure issues"
|
||||
)
|
||||
async def afc(self, context):
|
||||
return await afc_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="udid",
|
||||
description="SideStore could not determine device UDID"
|
||||
)
|
||||
async def udid(self, context):
|
||||
return await udid_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="half",
|
||||
description="Help with half-installed apps"
|
||||
)
|
||||
async def half(self, context):
|
||||
return await half_command()(self, context)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="sparse",
|
||||
description="Help with sparse bundle issues"
|
||||
)
|
||||
async def sparse(self, context):
|
||||
return await sparse_command()(self, context)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Sidestore(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'sidestore.help'")
|
||||
bot.logger.info("Loaded extension 'sidestore.refresh'")
|
||||
bot.logger.info("Loaded extension 'sidestore.code'")
|
||||
bot.logger.info("Loaded extension 'sidestore.crash'")
|
||||
bot.logger.info("Loaded extension 'sidestore.pairing'")
|
||||
bot.logger.info("Loaded extension 'sidestore.server'")
|
||||
bot.logger.info("Loaded extension 'sidestore.afc'")
|
||||
bot.logger.info("Loaded extension 'sidestore.udid'")
|
||||
bot.logger.info("Loaded extension 'sidestore.half'")
|
||||
bot.logger.info("Loaded extension 'sidestore.sparse'")
|
||||
46
cogs/sidestore/afc.py
Normal file
46
cogs/sidestore/afc.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def afc_command():
|
||||
@commands.hybrid_command(
|
||||
name="afc", description="Help with AFC Connection Failure issues"
|
||||
)
|
||||
async def afc(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
'# AFC Connection Failure\n\n---\n\n' +
|
||||
'1. Make sure Wi-Fi is connected to a stable network\n' +
|
||||
'2. Make sure StosVPN is connected and updated\n' +
|
||||
'3. If issue still persists, create and import a new pairing file using `idevice_pair`. See [Pairing File instructions](https://docs.sidestore.io/docs/installation/pairing-file) for details'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/afc.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Documentation",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://docs.sidestore.io/docs/troubleshooting/common-issues#afc-connection-failure",
|
||||
emoji="<:sidestorepride:1417717648795631787>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return afc
|
||||
@@ -5,14 +5,11 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Code(commands.Cog, name="code"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def code_command():
|
||||
@commands.hybrid_command(
|
||||
name="code", description="No code received when signing in with Apple ID"
|
||||
)
|
||||
async def code(self, context: Context) -> None:
|
||||
async def code(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
@@ -27,15 +24,15 @@ class Code(commands.Cog, name="code"):
|
||||
'2. **For iOS versions 18.1 and above:**\n' +
|
||||
' - Visit [iCloud](https://www.icloud.com) on a web browser\n' +
|
||||
' - Click "Sign In"\n' +
|
||||
' - When prompted, you will see two options: "Sign In" and "Use Different Apple Account"\n' +
|
||||
' - On an Apple device, you may see two options: "Sign In" and "Use Different Apple Account"\n' +
|
||||
' - Select the bottom option, "Use Different Apple Account"\n' +
|
||||
' - Enter your Apple ID and password\n' +
|
||||
' - Enter your Apple ID and password, DO NOT USE A PASSKEY\n' +
|
||||
' - Apple will send you a verification code\n' +
|
||||
' - Use this code in Sidestore to complete the sign-in process\n'
|
||||
' - Use this code in SideStore to complete the sign-in process\n'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
@@ -59,5 +56,4 @@ class Code(commands.Cog, name="code"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Code(bot))
|
||||
return code
|
||||
|
||||
@@ -5,28 +5,28 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Crash(commands.Cog, name="crash"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def crash_command():
|
||||
@commands.hybrid_command(
|
||||
name="crash", description="Help with SideStore crashing issues"
|
||||
)
|
||||
async def crash(self, context: Context) -> None:
|
||||
async def crash(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
'# Sidestore Crashing After Refresh\n\n---\n\n' +
|
||||
'1. Delete your current SideStore.\n' +
|
||||
'2. Reinstall with AltServer.\n' +
|
||||
'3. Select the pairing file and sign into SideStore.\n' +
|
||||
'4. Download the SideStore .ipa file, and save it to your Files app.\n' +
|
||||
'5. Import the "Sidestore.ipa" file into SideStore, just like how you import any other IPA.\n\n' +
|
||||
'# SideStore Crashing After Refresh\n\n---\n\n' +
|
||||
'First, to try and save your data:\n' +
|
||||
"1. DON'T DELETE SIDESTORE, reinstall with AltServer's `Sideload .ipa`.\n" +
|
||||
"If that doesn't work:\n" +
|
||||
|
||||
'1. Delete your current SideStore. Reinstall with AltServer.\n' +
|
||||
'2. Import your pairing file and sign into SideStore.\n' +
|
||||
'3. Download the SideStore .ipa file, and save it to your Files app.\n' +
|
||||
'4. Import the "Sidestore.ipa" file into SideStore, just like how you import any other IPA.\n\n' +
|
||||
'This process ensures SideStore is refreshed without issues.'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
@@ -43,5 +43,4 @@ class Crash(commands.Cog, name="crash"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Crash(bot))
|
||||
return crash
|
||||
|
||||
@@ -5,37 +5,28 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Half(commands.Cog, name="half"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def half_command():
|
||||
@commands.hybrid_command(
|
||||
name="half", description="Help when apps get stuck installing"
|
||||
)
|
||||
async def half(self, context: Context) -> None:
|
||||
async def half(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
'# Sidestore/IPAs Stuck Halfway Through Installing or Refreshing\n\n---\n' +
|
||||
'### Method 1: Basic Troubleshooting\n\n' +
|
||||
'# SideStore/IPAs Stuck Halfway Through Installing or Refreshing\n\n---\n' +
|
||||
'- Make sure you are on the latest version of SideStore\n' +
|
||||
'- Restart SideStore\n' +
|
||||
'- Restart device\n' +
|
||||
'- Clear Cache\n' +
|
||||
'- Change Anisette Server\n' +
|
||||
'- Reset adi.pb\n' +
|
||||
'- Sign out from SideStore and sign back in\n' +
|
||||
'- Regenerate pairing file\n' +
|
||||
'- Reinstall SideStore\n\n' +
|
||||
'### Method 2: If Method 1 Doesn\'t Work\n\n' +
|
||||
'1. Delete Sidestore\n' +
|
||||
'2. Reinstall SideStore using the guide at https://docs.sidestore.io/\n' +
|
||||
'3. **Do not use the IPA provided by the website**, instead use this older version: [Sidestore 0.5.9 download](https://github.com/SideStore/SideStore/releases/download/0.5.9/SideStore.ipa)\n' +
|
||||
'4. Setup SideStore and StosVPN as usual\n' +
|
||||
'> -# Step 3 is the important one (make sure to do that)\n'
|
||||
'- Recreate pairing file\n' +
|
||||
'- Reinstall SideStore\n\n'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
@@ -58,5 +49,4 @@ class Half(commands.Cog, name="half"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Half(bot))
|
||||
return half
|
||||
|
||||
@@ -5,14 +5,11 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Pairing(commands.Cog, name="pairing"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def pairing_command():
|
||||
@commands.hybrid_command(
|
||||
name="pairing", description="Help with pairing file issues"
|
||||
)
|
||||
async def pairing(self, context: Context) -> None:
|
||||
async def pairing(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
@@ -25,16 +22,16 @@ class Pairing(commands.Cog, name="pairing"):
|
||||
' - Rename the file to `ALTPairingFile.mobiledevicepairing`\n' +
|
||||
' - Try moving the pairing file to the root directory of the SideStore folder in the Files app under "On My iPhone/iPad"\n\n' +
|
||||
'3. **Certificate Signing:**\n' +
|
||||
" When signing Sidestore with certain certificates, you won't be able to select the pairing file from within the app\n" +
|
||||
" When signing SideStore with certain certificates, you won't be able to select the pairing file from within the app\n" +
|
||||
' - Try the fix mentioned above\n' +
|
||||
' - If you do not see the Sidestore folder in the Files app:\n' +
|
||||
' - If you do not see the SideStore folder in the Files app:\n' +
|
||||
' • Connect your phone to your computer\n' +
|
||||
' • Drag and drop the pairing file into the Sidestore app\'s files section\n' +
|
||||
' • Drag and drop the pairing file into the SideStore app\'s files section\n' +
|
||||
' • Ensure the file is renamed to `ALTPairingFile.mobiledevicepairing`\n'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
@@ -57,5 +54,4 @@ class Pairing(commands.Cog, name="pairing"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Pairing(bot))
|
||||
return pairing
|
||||
|
||||
@@ -5,24 +5,19 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Refresh(commands.Cog, name="refresh"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def refresh_command():
|
||||
@commands.hybrid_command(
|
||||
name="refresh", description="Help with refreshing or installing apps"
|
||||
)
|
||||
async def refresh(self, context: Context) -> None:
|
||||
async def refresh(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
'# Can\'t Refresh or Install Apps\n\n---\n\n' +
|
||||
'1. Make sure your device is connected to a stable Wi-Fi network and not using cellular data.\n' +
|
||||
'2. Verify VPN is connected in the StosVPN app.\n' +
|
||||
'3. Turn off, then turn back on StosVPN, and wait a few seconds in Sidestore before trying to refresh.\n' +
|
||||
'4. **Create a brand new pairing file.**\n' +
|
||||
' - If none of the above worked, it is very likely that the pairing file is corrupted. You can reference the documentation on how to create a new pairing file [here](https://docs.sidestore.io/docs/troubleshooting/#cant-refresh-or-install-apps).\n' +
|
||||
' - After creating a new pairing file, go to Sidestore settings and press "Reset pairing file," then choose the new pairing file you just created.\n'
|
||||
'3. **Create a brand new pairing file.**\n' +
|
||||
' - If none of the above worked, it is very likely that the pairing file is corrupted. You can reference the documentation on how to create a new pairing file [here](https://docs.sidestore.io/docs/installation/pairing-file).\n'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
@@ -39,7 +34,7 @@ class Refresh(commands.Cog, name="refresh"):
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Documentation",
|
||||
style=discord.ButtonStyle.primary,
|
||||
url="https://docs.sidestore.io/docs/troubleshooting/#cant-refresh-or-install-apps",
|
||||
url="https://docs.sidestore.io/docs/installation/pairing-file",
|
||||
emoji="<:sidestorepride:1417717648795631787>"
|
||||
))
|
||||
|
||||
@@ -49,5 +44,4 @@ class Refresh(commands.Cog, name="refresh"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Refresh(bot))
|
||||
return refresh
|
||||
|
||||
@@ -5,18 +5,15 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Server(commands.Cog, name="server"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def server_command():
|
||||
@commands.hybrid_command(
|
||||
name="server", description="Help with anisette server issues"
|
||||
)
|
||||
async def server(self, context: Context) -> None:
|
||||
async def server(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
'# Sidestore Freezing or Displaying an Error Code During Sign-In\n\n---\n\n' +
|
||||
'# SideStore Freezing or Displaying an Error Code During Sign-In\n\n---\n\n' +
|
||||
'1. **Change the Anisette Server:**\n' +
|
||||
' The most common solution is to switch to a different Anisette server. Do this:\n' +
|
||||
' - Open Sidestore settings\n' +
|
||||
@@ -28,7 +25,7 @@ class Server(commands.Cog, name="server"):
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by neoarz')
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
@@ -51,5 +48,4 @@ class Server(commands.Cog, name="server"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Server(bot))
|
||||
return server
|
||||
|
||||
@@ -43,6 +43,16 @@ class SidestoreSelect(discord.ui.Select):
|
||||
label="SparseRestore",
|
||||
value="sparse",
|
||||
description="Information about SparseRestore exploit",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="AFC Connection Failure",
|
||||
value="afc",
|
||||
description="Help with AFC Connection Failure issues",
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="UDID Error",
|
||||
value="udid",
|
||||
description="SideStore could not determine device UDID",
|
||||
)
|
||||
]
|
||||
super().__init__(placeholder="Choose a SideStore command...", options=options)
|
||||
@@ -52,13 +62,41 @@ class SidestoreSelect(discord.ui.Select):
|
||||
command = self.bot.get_command(command_name)
|
||||
|
||||
if command:
|
||||
ctx = await self.bot.get_context(interaction.message)
|
||||
if ctx:
|
||||
await ctx.invoke(command)
|
||||
try:
|
||||
ctx = await self.bot.get_context(interaction.message)
|
||||
if ctx:
|
||||
await ctx.invoke(command)
|
||||
embed = discord.Embed(
|
||||
title="Command Executed",
|
||||
description=f"Successfully executed `/{command_name}`",
|
||||
color=0x00FF00
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except discord.Forbidden:
|
||||
guild_info = f"server {interaction.guild.name} (ID: {interaction.guild.id})" if interaction.guild else "DM or private channel"
|
||||
self.bot.logger.warning(f"Bot missing permissions in {guild_info} - cannot execute {command_name} command")
|
||||
|
||||
if interaction.guild is None:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command cannot be executed in DMs.",
|
||||
color=0xFF0000
|
||||
)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
except Exception as e:
|
||||
self.bot.logger.error(f"Error executing {command_name} command: {e}")
|
||||
embed = discord.Embed(
|
||||
title="Command Executed",
|
||||
description=f"Successfully executed `/{command_name}`",
|
||||
color=0x00FF00
|
||||
title="Error",
|
||||
description="An error occurred while executing the command.",
|
||||
color=0xFF0000
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
await interaction.response.edit_message(embed=embed, view=None)
|
||||
@@ -78,14 +116,39 @@ class SidestoreView(discord.ui.View):
|
||||
self.add_item(SidestoreSelect(bot))
|
||||
|
||||
|
||||
class Sidestore(commands.Cog, name="sidestore"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def sidestore_command():
|
||||
@commands.hybrid_command(
|
||||
name="sidestore", description="SideStore troubleshooting and help"
|
||||
name="help", description="SideStore troubleshooting and help"
|
||||
)
|
||||
async def sidestore(self, context: Context) -> None:
|
||||
async def sidestore(self, context):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs send messages permissions in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title="SideStore Commands",
|
||||
description="Choose a command from the dropdown below to get help with specific issues:",
|
||||
@@ -99,7 +162,5 @@ class Sidestore(commands.Cog, name="sidestore"):
|
||||
await context.interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Sidestore(bot))
|
||||
|
||||
return sidestore
|
||||
|
||||
@@ -5,14 +5,11 @@ from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
class Sparse(commands.Cog, name="sparse"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
def sparse_command():
|
||||
@commands.hybrid_command(
|
||||
name="sparse", description="Information about SparseRestore exploit"
|
||||
)
|
||||
async def sparse(self, context: Context) -> None:
|
||||
async def sparse(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
@@ -46,5 +43,4 @@ class Sparse(commands.Cog, name="sparse"):
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
async def setup(bot) -> None:
|
||||
await bot.add_cog(Sparse(bot))
|
||||
return sparse
|
||||
|
||||
45
cogs/sidestore/udid.py
Normal file
45
cogs/sidestore/udid.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import time
|
||||
|
||||
|
||||
def udid_command():
|
||||
@commands.hybrid_command(
|
||||
name="udid", description="SideStore could not determine device UDID"
|
||||
)
|
||||
async def udid(self, context):
|
||||
embed = discord.Embed(
|
||||
color=0x8e82f9,
|
||||
description=(
|
||||
'# SideStore Could Not Determine Device UDID\n\n---\n\n' +
|
||||
'This error usually occurs when the pairing file is corrupted. Please create a new pairing file using `idevice_pair` and try again.\n\n' +
|
||||
'If you forgot how to create a new pairing file, you can refer to the documentation below.'
|
||||
)
|
||||
)
|
||||
embed.set_author(name="SideStore", icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true")
|
||||
embed.set_footer(text=f'Last Edited by CelloSerenity')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
|
||||
view = discord.ui.View()
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Edit Command",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://github.com/neoarz/Syntrel/blob/main/cogs/sidestore/udid.py",
|
||||
emoji="<:githubicon:1417717356846776340>"
|
||||
))
|
||||
view.add_item(discord.ui.Button(
|
||||
label="Documentation",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
url="https://docs.sidestore.io/docs/installation/pairing-file/",
|
||||
emoji="<:sidestorepride:1417717648795631787>"
|
||||
))
|
||||
|
||||
if context.interaction:
|
||||
await context.interaction.response.send_message(embed=embed, view=view)
|
||||
else:
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
|
||||
return udid
|
||||
108
cogs/utilities/__init__.py
Normal file
108
cogs/utilities/__init__.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from .translate import translate_command, language_autocomplete
|
||||
from .codepreview import codepreview_command
|
||||
from .dictionary import dictionary_command
|
||||
|
||||
|
||||
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
|
||||
@app_commands.allowed_installs(guilds=True, users=True)
|
||||
class Utilities(commands.GroupCog, name="utils"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.group(name="utilities", aliases=["utils"], invoke_without_command=True)
|
||||
async def utilities_group(self, context: Context):
|
||||
embed = discord.Embed(
|
||||
title="Utilities Commands",
|
||||
description="Use `.utils <subcommand>` or `/utils <subcommand>`.",
|
||||
color=0x7289DA
|
||||
)
|
||||
embed.set_author(name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.add_field(name="Available", value="translate, codepreview, dictionary", inline=False)
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _invoke_hybrid(self, context: Context, name: str, **kwargs):
|
||||
command = self.bot.get_command(name)
|
||||
if command is not None:
|
||||
await context.invoke(command, **kwargs)
|
||||
else:
|
||||
await context.send(f"Unknown utilities command: {name}")
|
||||
|
||||
def _require_group_prefix(context: Context) -> bool:
|
||||
if getattr(context, "interaction", None):
|
||||
return True
|
||||
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
|
||||
if not group:
|
||||
return True
|
||||
prefix = context.prefix or ""
|
||||
content = context.message.content.strip().lower()
|
||||
return content.startswith(f"{prefix}{group} ")
|
||||
|
||||
@utilities_group.command(name="translate")
|
||||
@app_commands.describe(
|
||||
text="The text to translate",
|
||||
to_lang="Target language (e.g., 'en', 'es', 'fr')",
|
||||
from_lang="Source language (leave empty for auto-detect)"
|
||||
)
|
||||
@app_commands.autocomplete(to_lang=language_autocomplete)
|
||||
@app_commands.autocomplete(from_lang=language_autocomplete)
|
||||
async def utilities_group_translate(self, context: Context, text: str = None, to_lang: str = "en", from_lang: str = None):
|
||||
await self._invoke_hybrid(context, "translate", text=text, to_lang=to_lang, from_lang=from_lang)
|
||||
|
||||
@utilities_group.command(name="codepreview")
|
||||
async def utilities_group_codepreview(self, context: Context, url: str = None):
|
||||
await self._invoke_hybrid(context, "codepreview", url=url)
|
||||
|
||||
@utilities_group.command(name="dictionary")
|
||||
@app_commands.describe(
|
||||
word="The word to look up"
|
||||
)
|
||||
async def utilities_group_dictionary(self, context: Context, word: str = None):
|
||||
await self._invoke_hybrid(context, "dictionary", word=word)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="translate",
|
||||
description="Translate text to another language"
|
||||
)
|
||||
@app_commands.describe(
|
||||
text="The text to translate",
|
||||
to_lang="Target language (e.g., 'en', 'es', 'fr')",
|
||||
from_lang="Source language (leave empty for auto-detect)"
|
||||
)
|
||||
@app_commands.autocomplete(to_lang=language_autocomplete)
|
||||
@app_commands.autocomplete(from_lang=language_autocomplete)
|
||||
async def translate(self, context, text: str = None, to_lang: str = "en", from_lang: str = None):
|
||||
return await translate_command()(self, context, text=text, to_lang=to_lang, from_lang=from_lang)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="codepreview",
|
||||
description="Preview code from GitHub URLs"
|
||||
)
|
||||
async def codepreview(self, context, url: str = None):
|
||||
return await codepreview_command()(self, context, url=url)
|
||||
|
||||
@commands.check(_require_group_prefix)
|
||||
@commands.hybrid_command(
|
||||
name="dictionary",
|
||||
description="Get the definition of a word"
|
||||
)
|
||||
@app_commands.describe(
|
||||
word="The word to look up"
|
||||
)
|
||||
async def dictionary(self, context, word: str = None):
|
||||
return await dictionary_command()(self, context, word=word)
|
||||
|
||||
async def setup(bot) -> None:
|
||||
cog = Utilities(bot)
|
||||
await bot.add_cog(cog)
|
||||
|
||||
bot.logger.info("Loaded extension 'utilities.translate'")
|
||||
bot.logger.info("Loaded extension 'utilities.codepreview'")
|
||||
bot.logger.info("Loaded extension 'utilities.dictionary'")
|
||||
457
cogs/utilities/codepreview.py
Normal file
457
cogs/utilities/codepreview.py
Normal file
@@ -0,0 +1,457 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import re
|
||||
import json
|
||||
|
||||
|
||||
def codepreview_command():
|
||||
LANGUAGE_MAP = {
|
||||
'.py': 'python',
|
||||
'.js': 'js',
|
||||
'.ts': 'ts',
|
||||
'.java': 'java',
|
||||
'.cpp': 'cpp',
|
||||
'.c': 'c',
|
||||
'.cs': 'cs',
|
||||
'.go': 'go',
|
||||
'.rs': 'rust',
|
||||
'.rb': 'ruby',
|
||||
'.php': 'php',
|
||||
'.html': 'html',
|
||||
'.css': 'css',
|
||||
'.json': 'json',
|
||||
'.xml': 'xml',
|
||||
'.yaml': 'yaml',
|
||||
'.yml': 'yaml',
|
||||
'.ini': 'ini',
|
||||
'.toml': 'toml',
|
||||
'.lua': 'lua',
|
||||
'.sh': 'bash',
|
||||
'.md': 'markdown',
|
||||
'.sql': 'sql',
|
||||
'.diff': 'diff',
|
||||
'.txt': '',
|
||||
}
|
||||
|
||||
async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
def get_language_from_filename(filename):
|
||||
for ext, lang in LANGUAGE_MAP.items():
|
||||
if filename.endswith(ext):
|
||||
return lang
|
||||
return ''
|
||||
|
||||
async def fetch_github_content(url):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
if response.status == 200:
|
||||
return await response.text()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
async def fetch_pr_diff(owner, repo, pr_number):
|
||||
try:
|
||||
api_url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}'
|
||||
headers = {
|
||||
'Accept': 'application/vnd.github.v3.diff',
|
||||
'User-Agent': 'Discord-Bot'
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(api_url, headers=headers) as response:
|
||||
if response.status == 200:
|
||||
diff_content = await response.text()
|
||||
return diff_content
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
async def fetch_pr_info(owner, repo, pr_number):
|
||||
try:
|
||||
api_url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}'
|
||||
headers = {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'Discord-Bot'
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(api_url, headers=headers) as response:
|
||||
if response.status == 200:
|
||||
pr_data = await response.json()
|
||||
return {
|
||||
'title': pr_data.get('title', ''),
|
||||
'number': pr_data.get('number', pr_number),
|
||||
'state': pr_data.get('state', ''),
|
||||
'merged': pr_data.get('merged', False),
|
||||
'additions': pr_data.get('additions', 0),
|
||||
'deletions': pr_data.get('deletions', 0),
|
||||
'changed_files': pr_data.get('changed_files', 0),
|
||||
'user': pr_data.get('user', {}).get('login', ''),
|
||||
'base_branch': pr_data.get('base', {}).get('ref', ''),
|
||||
'head_branch': pr_data.get('head', {}).get('ref', '')
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def parse_github_url(url):
|
||||
pr_pattern = r'https://github\.com/([^/]+)/([^/]+)/pull/(\d+)(?:/files)?'
|
||||
pr_match = re.match(pr_pattern, url)
|
||||
|
||||
if pr_match:
|
||||
owner, repo, pr_number = pr_match.groups()
|
||||
return {
|
||||
'type': 'pr',
|
||||
'owner': owner,
|
||||
'repo': repo,
|
||||
'pr_number': pr_number
|
||||
}
|
||||
|
||||
raw_pattern = r'https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+?)$'
|
||||
raw_match = re.match(raw_pattern, url)
|
||||
|
||||
if raw_match:
|
||||
owner, repo, branch, filepath = raw_match.groups()
|
||||
|
||||
return {
|
||||
'type': 'file',
|
||||
'owner': owner,
|
||||
'repo': repo,
|
||||
'branch': branch,
|
||||
'filepath': filepath,
|
||||
'raw_url': url,
|
||||
'start_line': None,
|
||||
'end_line': None
|
||||
}
|
||||
|
||||
pattern = r'https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.+?)(?:#L(\d+)(?:-L(\d+))?)?$'
|
||||
match = re.match(pattern, url)
|
||||
|
||||
if match:
|
||||
owner, repo, branch, filepath, start_line, end_line = match.groups()
|
||||
|
||||
raw_url = f'https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{filepath}'
|
||||
|
||||
return {
|
||||
'type': 'file',
|
||||
'owner': owner,
|
||||
'repo': repo,
|
||||
'branch': branch,
|
||||
'filepath': filepath,
|
||||
'raw_url': raw_url,
|
||||
'start_line': int(start_line) if start_line else None,
|
||||
'end_line': int(end_line) if end_line else None
|
||||
}
|
||||
return None
|
||||
|
||||
def extract_lines(content, start_line, end_line):
|
||||
lines = content.split('\n')
|
||||
|
||||
if start_line and end_line:
|
||||
return '\n'.join(lines[start_line-1:end_line])
|
||||
elif start_line:
|
||||
return lines[start_line-1] if start_line <= len(lines) else content
|
||||
return content
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="codepreview",
|
||||
description="Preview code from GitHub URLs",
|
||||
)
|
||||
@app_commands.describe(
|
||||
url="GitHub URL to preview code from"
|
||||
)
|
||||
async def codepreview(self, context, url: str = None):
|
||||
if isinstance(context.channel, discord.DMChannel):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="This command can only be used in servers.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if isinstance(context.channel, discord.PartialMessageable):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The bot needs the `send messages` permission in this channel.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if not url or not url.strip():
|
||||
if context.message and context.message.reference and context.message.reference.resolved:
|
||||
replied_message = context.message.reference.resolved
|
||||
if hasattr(replied_message, 'content') and replied_message.content:
|
||||
github_pattern = r'https://(?:github\.com|raw\.githubusercontent\.com)/[^\s]+'
|
||||
urls = re.findall(github_pattern, replied_message.content)
|
||||
if urls:
|
||||
url = urls[0]
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="No GitHub URL found in the replied message.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The replied message has no content to extract GitHub URL from.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide a GitHub URL or reply to a message containing a GitHub URL.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if not url.startswith('https://github.com/') and not url.startswith('https://raw.githubusercontent.com/'):
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide a valid GitHub URL.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Check if bot has send messages permission before starting processing
|
||||
try:
|
||||
test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA)
|
||||
test_embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await context.channel.send(embed=test_embed, delete_after=0.1)
|
||||
except discord.Forbidden:
|
||||
embed = discord.Embed(
|
||||
title="Permission Error",
|
||||
description="The bot needs the `send messages` permission to execute this command.",
|
||||
color=0xE02B2B,
|
||||
)
|
||||
embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
else:
|
||||
await context.send(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
parsed = parse_github_url(url)
|
||||
|
||||
if not parsed:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Invalid GitHub URL format. Please provide a valid GitHub blob URL or PR URL.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if parsed.get('type') == 'pr':
|
||||
pr_info = await fetch_pr_info(parsed['owner'], parsed['repo'], parsed['pr_number'])
|
||||
diff_content = await fetch_pr_diff(parsed['owner'], parsed['repo'], parsed['pr_number'])
|
||||
|
||||
if not pr_info or not diff_content:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Failed to fetch pull request information. The PR might not exist or be accessible.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
pr_url = f"https://github.com/{parsed['owner']}/{parsed['repo']}/pull/{parsed['pr_number']}"
|
||||
|
||||
if pr_info['merged']:
|
||||
pr_color = 0x6f42c1
|
||||
pr_status = "Merged"
|
||||
elif pr_info['state'] == 'open':
|
||||
pr_color = 0x57F287
|
||||
pr_status = "Open"
|
||||
else:
|
||||
pr_color = 0xE02B2B
|
||||
pr_status = "Closed"
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Pull Request #{pr_info['number']}: {pr_info['title'][:100]}",
|
||||
description=f"**Repository:** [{parsed['owner']}/{parsed['repo']}]({pr_url})\n**Author:** {pr_info['user']}\n**Status:** {pr_status}",
|
||||
color=pr_color,
|
||||
)
|
||||
embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.add_field(name="Changes", value=f"**+{pr_info['additions']}** / **-{pr_info['deletions']}**", inline=True)
|
||||
embed.add_field(name="Files Changed", value=f"{pr_info['changed_files']}", inline=True)
|
||||
embed.add_field(name="Branches", value=f"`{pr_info['base_branch']}` ← `{pr_info['head_branch']}`", inline=False)
|
||||
embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url)
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None and not interaction.response.is_done():
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
await context.channel.send(embed=embed)
|
||||
|
||||
max_diff_length = 1900
|
||||
max_lines = 100
|
||||
|
||||
diff_lines = diff_content.split('\n')
|
||||
|
||||
if len(diff_lines) > max_lines:
|
||||
diff_lines = diff_lines[:max_lines]
|
||||
diff_lines.append(f"\n... ({len(diff_content.split(chr(10))) - max_lines} more lines omitted)")
|
||||
|
||||
current_chunk = ""
|
||||
|
||||
for line in diff_lines:
|
||||
test_chunk = current_chunk + line + '\n'
|
||||
|
||||
if len(test_chunk) + 10 > max_diff_length:
|
||||
if current_chunk.strip():
|
||||
remaining_lines = len(diff_lines) - len(current_chunk.split('\n'))
|
||||
if remaining_lines > 0:
|
||||
current_chunk += f"\n... ({remaining_lines} more lines omitted)"
|
||||
await context.channel.send(f"```diff\n{current_chunk.rstrip()}\n```")
|
||||
break
|
||||
else:
|
||||
current_chunk = test_chunk
|
||||
else:
|
||||
if current_chunk.strip():
|
||||
await context.channel.send(f"```diff\n{current_chunk.rstrip()}\n```")
|
||||
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
return
|
||||
|
||||
content = await fetch_github_content(parsed['raw_url'])
|
||||
|
||||
if not content:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Failed to fetch content from GitHub. The file might not exist or be accessible.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if parsed['start_line']:
|
||||
code = extract_lines(content, parsed['start_line'], parsed['end_line'])
|
||||
line_info = f" (Lines {parsed['start_line']}"
|
||||
if parsed['end_line']:
|
||||
line_info += f"-{parsed['end_line']}"
|
||||
line_info += ")"
|
||||
else:
|
||||
code = content
|
||||
line_info = ""
|
||||
|
||||
code_lines = code.split('\n')
|
||||
if len(code_lines) > 100:
|
||||
code = '\n'.join(code_lines[:100])
|
||||
code += f"\n\n... ({len(code_lines) - 100} more lines omitted)"
|
||||
|
||||
filename = parsed['filepath'].split('/')[-1]
|
||||
language = get_language_from_filename(filename)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Code Preview",
|
||||
description=f"**Repository URL:** [{parsed['owner']}/{parsed['repo']}]({url})",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.add_field(name="File", value=f"`{parsed['filepath']}`{line_info}", inline=True)
|
||||
embed.add_field(name="Branch", value=f"`{parsed['branch']}`", inline=True)
|
||||
embed.set_footer(text=f"Requested by {context.author.name}", icon_url=context.author.display_avatar.url)
|
||||
|
||||
code_block = f"```{language}\n{code}\n```"
|
||||
|
||||
if len(code_block) > 1990:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
await context.channel.send(embed=embed)
|
||||
else:
|
||||
await context.channel.send(embed=embed)
|
||||
|
||||
max_code_length = 1980 - len(language) - 8
|
||||
code_lines = code.split('\n')
|
||||
current_chunk = []
|
||||
current_length = 0
|
||||
|
||||
for line in code_lines:
|
||||
line_length = len(line) + 1
|
||||
if current_length + line_length > max_code_length:
|
||||
remaining_lines = len(code_lines) - len(current_chunk)
|
||||
if remaining_lines > 0:
|
||||
current_chunk.append(f"\n... ({remaining_lines} more lines omitted)")
|
||||
chunk_text = '\n'.join(current_chunk)
|
||||
await context.channel.send(f"```{language}\n{chunk_text}\n```")
|
||||
break
|
||||
else:
|
||||
current_chunk.append(line)
|
||||
current_length += line_length
|
||||
else:
|
||||
if current_chunk:
|
||||
chunk_text = '\n'.join(current_chunk)
|
||||
await context.channel.send(f"```{language}\n{chunk_text}\n```")
|
||||
|
||||
if interaction is not None:
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(code_block)
|
||||
try:
|
||||
await interaction.delete_original_response()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await context.channel.send(embed=embed)
|
||||
await context.channel.send(code_block)
|
||||
|
||||
return codepreview
|
||||
170
cogs/utilities/dictionary.py
Normal file
170
cogs/utilities/dictionary.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
|
||||
|
||||
def dictionary_command():
|
||||
|
||||
async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def fetch_definition(word: str) -> dict:
|
||||
try:
|
||||
url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word.lower()}"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
return {"success": True, "data": data}
|
||||
elif response.status == 404:
|
||||
return {"success": False, "error": "Word not found"}
|
||||
else:
|
||||
return {"success": False, "error": f"API returned status code {response.status}"}
|
||||
except aiohttp.ClientError:
|
||||
return {"success": False, "error": "Network error occurred"}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": "An unexpected error occurred"}
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="dictionary",
|
||||
description="Get the definition of a word",
|
||||
)
|
||||
@app_commands.describe(
|
||||
word="The word to look up"
|
||||
)
|
||||
async def dictionary(self, context, word: str = None):
|
||||
if not word or not word.strip():
|
||||
if context.message and context.message.reference and context.message.reference.resolved:
|
||||
replied_message = context.message.reference.resolved
|
||||
if hasattr(replied_message, 'content') and replied_message.content:
|
||||
word = replied_message.content.strip().split()[0]
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The replied message has no text content to look up.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide a word to look up or reply to a message with a word.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
word = word.strip().split()[0]
|
||||
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None and not interaction.response.is_done():
|
||||
await interaction.response.defer()
|
||||
|
||||
result = await fetch_definition(word)
|
||||
|
||||
if not result["success"]:
|
||||
error_message = result.get("error", "Unknown error")
|
||||
|
||||
if error_message == "Word not found":
|
||||
description = f"Could not find a definition for **{word}**.\nPlease check the spelling and try again."
|
||||
else:
|
||||
description = f"Failed to fetch definition: {error_message}"
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=description,
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
data = result["data"]
|
||||
|
||||
if not data or len(data) == 0:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"No definition found for **{word}**.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
entry = data[0]
|
||||
word_title = entry.get("word", word)
|
||||
phonetic = entry.get("phonetic", "")
|
||||
origin = entry.get("origin", "")
|
||||
meanings = entry.get("meanings", [])
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Dictionary",
|
||||
description=f"**```{word_title}```**",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Utilities", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
|
||||
if phonetic:
|
||||
embed.add_field(name="Pronunciation", value=f"`{phonetic}`", inline=False)
|
||||
|
||||
max_meanings = 3
|
||||
for idx, meaning in enumerate(meanings[:max_meanings]):
|
||||
part_of_speech = meaning.get("partOfSpeech", "").capitalize()
|
||||
definitions = meaning.get("definitions", [])
|
||||
|
||||
if definitions:
|
||||
def_text = ""
|
||||
examples = []
|
||||
|
||||
for def_idx, definition in enumerate(definitions[:2], 1):
|
||||
def_line = definition.get("definition", "")
|
||||
example = definition.get("example", "")
|
||||
|
||||
if def_line:
|
||||
def_text += f"{def_idx}. {def_line}\n"
|
||||
if example:
|
||||
examples.append(f"{def_idx}. {example}")
|
||||
|
||||
if def_text:
|
||||
field_name = f"{part_of_speech}" if part_of_speech else f"Definition {idx + 1}"
|
||||
embed.add_field(name=field_name, value=def_text.strip(), inline=False)
|
||||
|
||||
if examples:
|
||||
example_text = "\n".join(examples)
|
||||
embed.add_field(name="Examples", value=example_text, inline=False)
|
||||
|
||||
if idx < len(meanings[:max_meanings]) - 1:
|
||||
embed.add_field(name="────────", value="", inline=False)
|
||||
|
||||
if origin and len(origin) < 1000:
|
||||
embed.add_field(name="Origin", value=origin, inline=False)
|
||||
|
||||
synonyms = []
|
||||
antonyms = []
|
||||
|
||||
for meaning in meanings:
|
||||
for definition in meaning.get("definitions", []):
|
||||
synonyms.extend(definition.get("synonyms", []))
|
||||
antonyms.extend(definition.get("antonyms", []))
|
||||
|
||||
if synonyms:
|
||||
synonym_text = ", ".join(synonyms[:10])
|
||||
embed.add_field(name="Synonyms", value=synonym_text, inline=True)
|
||||
|
||||
if antonyms:
|
||||
antonym_text = ", ".join(antonyms[:10])
|
||||
embed.add_field(name="Antonyms", value=antonym_text, inline=True)
|
||||
|
||||
|
||||
await send_embed(context, embed)
|
||||
|
||||
return dictionary
|
||||
322
cogs/utilities/translate.py
Normal file
322
cogs/utilities/translate.py
Normal file
@@ -0,0 +1,322 @@
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import re
|
||||
import json
|
||||
import urllib.parse
|
||||
|
||||
|
||||
languages = {
|
||||
"auto": "Auto-detect",
|
||||
"en": "English",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"de": "German",
|
||||
"it": "Italian",
|
||||
"pt": "Portuguese",
|
||||
"ru": "Russian",
|
||||
"ja": "Japanese",
|
||||
"ko": "Korean",
|
||||
"zh-CN": "Chinese (Simplified)",
|
||||
"zh-TW": "Chinese (Traditional)",
|
||||
"ar": "Arabic",
|
||||
"hi": "Hindi",
|
||||
"th": "Thai",
|
||||
"vi": "Vietnamese",
|
||||
"nl": "Dutch",
|
||||
"pl": "Polish",
|
||||
"tr": "Turkish",
|
||||
"sv": "Swedish",
|
||||
"da": "Danish",
|
||||
"no": "Norwegian",
|
||||
"fi": "Finnish",
|
||||
"cs": "Czech",
|
||||
"sk": "Slovak",
|
||||
"hu": "Hungarian",
|
||||
"ro": "Romanian",
|
||||
"bg": "Bulgarian",
|
||||
"hr": "Croatian",
|
||||
"sr": "Serbian",
|
||||
"sl": "Slovenian",
|
||||
"et": "Estonian",
|
||||
"lv": "Latvian",
|
||||
"lt": "Lithuanian",
|
||||
"uk": "Ukrainian",
|
||||
"be": "Belarusian",
|
||||
"mk": "Macedonian",
|
||||
"sq": "Albanian",
|
||||
"mt": "Maltese",
|
||||
"is": "Icelandic",
|
||||
"ga": "Irish",
|
||||
"cy": "Welsh",
|
||||
"gd": "Scots Gaelic",
|
||||
"eu": "Basque",
|
||||
"ca": "Catalan",
|
||||
"gl": "Galician",
|
||||
"eo": "Esperanto",
|
||||
"la": "Latin",
|
||||
"af": "Afrikaans",
|
||||
"sw": "Swahili",
|
||||
"zu": "Zulu",
|
||||
"xh": "Xhosa",
|
||||
"yo": "Yoruba",
|
||||
"ig": "Igbo",
|
||||
"ha": "Hausa",
|
||||
"am": "Amharic",
|
||||
"om": "Oromo",
|
||||
"ti": "Tigrinya",
|
||||
"so": "Somali",
|
||||
"rw": "Kinyarwanda",
|
||||
"lg": "Ganda",
|
||||
"ny": "Chichewa",
|
||||
"sn": "Shona",
|
||||
"st": "Sesotho",
|
||||
"tn": "Tswana",
|
||||
"ts": "Tsonga",
|
||||
"ss": "Swati",
|
||||
"nr": "Ndebele",
|
||||
"nso": "Northern Sotho",
|
||||
"ve": "Venda",
|
||||
"bn": "Bengali",
|
||||
"gu": "Gujarati",
|
||||
"kn": "Kannada",
|
||||
"ml": "Malayalam",
|
||||
"mr": "Marathi",
|
||||
"ne": "Nepali",
|
||||
"or": "Odia",
|
||||
"pa": "Punjabi",
|
||||
"si": "Sinhala",
|
||||
"ta": "Tamil",
|
||||
"te": "Telugu",
|
||||
"ur": "Urdu",
|
||||
"as": "Assamese",
|
||||
"bho": "Bhojpuri",
|
||||
"doi": "Dogri",
|
||||
"gom": "Konkani",
|
||||
"mai": "Maithili",
|
||||
"mni-Mtei": "Meiteilon",
|
||||
"sa": "Sanskrit",
|
||||
"id": "Indonesian",
|
||||
"ms": "Malay",
|
||||
"tl": "Filipino",
|
||||
"jv": "Javanese",
|
||||
"su": "Sundanese",
|
||||
"ceb": "Cebuano",
|
||||
"hil": "Hiligaynon",
|
||||
"ilo": "Iloko",
|
||||
"pam": "Kapampangan",
|
||||
"war": "Waray",
|
||||
"my": "Myanmar",
|
||||
"km": "Khmer",
|
||||
"lo": "Lao",
|
||||
"ka": "Georgian",
|
||||
"hy": "Armenian",
|
||||
"az": "Azerbaijani",
|
||||
"kk": "Kazakh",
|
||||
"ky": "Kyrgyz",
|
||||
"mn": "Mongolian",
|
||||
"tk": "Turkmen",
|
||||
"ug": "Uyghur",
|
||||
"uz": "Uzbek",
|
||||
"tg": "Tajik",
|
||||
"fa": "Persian",
|
||||
"ps": "Pashto",
|
||||
"sd": "Sindhi",
|
||||
"he": "Hebrew",
|
||||
"yi": "Yiddish",
|
||||
"iw": "Hebrew",
|
||||
"el": "Greek",
|
||||
"lt": "Lithuanian",
|
||||
"lv": "Latvian",
|
||||
}
|
||||
|
||||
async def language_autocomplete(interaction: discord.Interaction, current: str) -> list[app_commands.Choice[str]]:
|
||||
current = current.lower()
|
||||
choices = []
|
||||
|
||||
for code, name in languages.items():
|
||||
if current in code.lower() or current in name.lower():
|
||||
display_name = f"{code} - {name}"
|
||||
if len(display_name) > 100:
|
||||
display_name = f"{code} - {name[:90]}..."
|
||||
choices.append(app_commands.Choice(name=display_name, value=code))
|
||||
|
||||
if len(choices) >= 25:
|
||||
break
|
||||
|
||||
if not choices:
|
||||
popular = ["en", "es", "fr", "de", "it", "pt", "ru", "ja", "ko", "zh-CN"]
|
||||
for code in popular:
|
||||
name = languages.get(code, code)
|
||||
choices.append(app_commands.Choice(name=f"{code} - {name}", value=code))
|
||||
|
||||
return choices
|
||||
|
||||
def translate_command():
|
||||
|
||||
async def send_embed(context, embed: discord.Embed, *, ephemeral: bool = False) -> None:
|
||||
interaction = getattr(context, "interaction", None)
|
||||
if interaction is not None:
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await interaction.response.send_message(embed=embed, ephemeral=ephemeral)
|
||||
else:
|
||||
await context.send(embed=embed)
|
||||
|
||||
async def _translate_with_google_web(text: str, from_lang: str = "auto", to_lang: str = "en") -> dict:
|
||||
try:
|
||||
base_url = "https://translate.googleapis.com/translate_a/single"
|
||||
|
||||
def chunk_text(t: str, max_len: int = 900) -> list[str]:
|
||||
t = t.strip()
|
||||
if len(t) <= max_len:
|
||||
return [t]
|
||||
chunks = []
|
||||
current = []
|
||||
current_len = 0
|
||||
for part in re.split(r"(\n+|\s+)", t):
|
||||
if not part:
|
||||
continue
|
||||
add_len = len(part)
|
||||
if current_len + add_len > max_len and current:
|
||||
chunks.append("".join(current))
|
||||
current = [part]
|
||||
current_len = add_len
|
||||
else:
|
||||
current.append(part)
|
||||
current_len += add_len
|
||||
if current:
|
||||
chunks.append("".join(current))
|
||||
return chunks
|
||||
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
}
|
||||
|
||||
chunks = chunk_text(text)
|
||||
translated_parts: list[str] = []
|
||||
detected_lang_overall = from_lang
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
for idx, chunk in enumerate(chunks):
|
||||
params = {
|
||||
"client": "gtx",
|
||||
"sl": from_lang,
|
||||
"tl": to_lang,
|
||||
"dt": ["t", "bd"],
|
||||
"q": chunk
|
||||
}
|
||||
async with session.get(base_url, headers=headers, params=params) as response:
|
||||
if response.status != 200:
|
||||
return None
|
||||
result_text = (await response.text()).strip()
|
||||
try:
|
||||
if result_text.startswith("[["):
|
||||
data = json.loads(result_text)
|
||||
part_text = ""
|
||||
if data and len(data) > 0 and data[0]:
|
||||
for item in data[0]:
|
||||
if item and len(item) > 0:
|
||||
part_text += item[0] if item[0] else ""
|
||||
translated_parts.append(part_text)
|
||||
if idx == 0 and len(data) > 2 and data[2]:
|
||||
detected_lang_overall = data[2]
|
||||
else:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return {
|
||||
"translatedText": "".join(translated_parts).strip(),
|
||||
"detectedSourceLanguage": detected_lang_overall
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="translate",
|
||||
description="Translate text to another language",
|
||||
)
|
||||
@app_commands.describe(
|
||||
text="The text to translate",
|
||||
to_lang="Target language (e.g., 'en', 'es', 'fr')",
|
||||
from_lang="Source language (leave empty for auto-detect)"
|
||||
)
|
||||
@app_commands.autocomplete(to_lang=language_autocomplete)
|
||||
@app_commands.autocomplete(from_lang=language_autocomplete)
|
||||
async def translate(self, context, text: str = None, to_lang: str = "en", from_lang: str = None):
|
||||
if not text or not text.strip():
|
||||
if context.message and context.message.reference and context.message.reference.resolved:
|
||||
replied_message = context.message.reference.resolved
|
||||
if hasattr(replied_message, 'content') and replied_message.content:
|
||||
text = replied_message.content
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="The replied message has no text content to translate.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Please provide text to translate or reply to a message with text.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if to_lang not in languages:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Invalid target language code: `{to_lang}`. Use the autocomplete feature to see available languages.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
if from_lang and from_lang not in languages:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description=f"Invalid source language code: `{from_lang}`. Use the autocomplete feature to see available languages.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
return
|
||||
|
||||
result = await _translate_with_google_web(text, from_lang or "auto", to_lang)
|
||||
|
||||
if result and result.get("translatedText"):
|
||||
detected_lang = result.get("detectedSourceLanguage", from_lang or "auto")
|
||||
|
||||
from_lang_name = languages.get(detected_lang, detected_lang)
|
||||
to_lang_name = languages.get(to_lang, to_lang)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="Translation",
|
||||
description=f"**Original:** {text}\n\n**Translated:** {result['translatedText']}",
|
||||
color=0x7289DA,
|
||||
)
|
||||
embed.set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
embed.set_footer(text=f"{from_lang_name} » {to_lang_name}")
|
||||
|
||||
await send_embed(context, embed)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Error",
|
||||
description="Translation failed. Please try again later.",
|
||||
color=0xE02B2B,
|
||||
).set_author(name="Utility", icon_url="https://yes.nighty.works/raw/8VLDcg.webp")
|
||||
await send_embed(context, embed, ephemeral=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return translate
|
||||
@@ -1,5 +1,3 @@
|
||||
# Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn/Python-Discord-Bot-Template/blob/main/database/__init__.py
|
||||
|
||||
import aiosqlite
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
-- Copyright © Krypton 2019-Present - https://github.com/kkrypt0nn/Python-Discord-Bot-Template/blob/main/database/schema.sql
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `warns` (
|
||||
`id` int(11) NOT NULL,
|
||||
`user_id` varchar(20) NOT NULL,
|
||||
|
||||
@@ -7,6 +7,7 @@ services:
|
||||
volumes:
|
||||
- ./database:/bot/database
|
||||
- ./discord.log:/bot/discord.log
|
||||
- ./cogs/media/files:/bot/cogs/media/files
|
||||
|
||||
# Alternatively you can set the environment variables as such:
|
||||
# /!\ The token shouldn't be written here, as this file is not ignored from Git /!\
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1
|
||||
through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or
|
||||
are under common control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii)
|
||||
beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source
|
||||
code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including
|
||||
but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as
|
||||
indicated by a copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work
|
||||
and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or
|
||||
additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the
|
||||
Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright
|
||||
owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including
|
||||
but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems
|
||||
that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "
|
||||
Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been
|
||||
received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to
|
||||
You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce,
|
||||
prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a
|
||||
perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are
|
||||
necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (
|
||||
including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within
|
||||
the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this
|
||||
License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with
|
||||
or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent,
|
||||
trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to
|
||||
any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You
|
||||
distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those
|
||||
notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a
|
||||
NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided
|
||||
along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such
|
||||
third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not
|
||||
modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be
|
||||
construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms
|
||||
and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a
|
||||
whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in
|
||||
this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for
|
||||
inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any
|
||||
additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any
|
||||
separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product
|
||||
names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and
|
||||
each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
|
||||
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
|
||||
of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this
|
||||
License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or
|
||||
otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing,
|
||||
shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or
|
||||
consequential damages of any character arising as a result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such
|
||||
damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose
|
||||
to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or
|
||||
rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and
|
||||
on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and
|
||||
hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2021 Krypton
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
|
||||
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
@@ -1,7 +0,0 @@
|
||||
Copyright 2025 neoarz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user