Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
7e5ad5ece2
|
|||
|
9f5131346f
|
|||
|
51fbd0524e
|
|||
|
c0e90d633e
|
|||
|
fd9c18d953
|
|||
|
459973b40c
|
|||
| 1dfd00a256 | |||
| 19e95473b5 | |||
| 28c59d90f3 | |||
| 74f193ccb2 | |||
| 675ea71948 | |||
| f7fd877023 | |||
| 4f41c465d1 | |||
| 44393f1745 | |||
| 042b4dceaf |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,6 +2,6 @@
|
|||||||
.gitignore
|
.gitignore
|
||||||
**/bin/
|
**/bin/
|
||||||
**/obj/
|
**/obj/
|
||||||
**/SkyBot*.sln
|
**.sln
|
||||||
**/database.db
|
**/database.db
|
||||||
**/Config.cs
|
|
||||||
|
|||||||
142
COMMANDS.md
142
COMMANDS.md
@@ -1,6 +1,6 @@
|
|||||||
# SkyBot Commands
|
# SkyBot Commands
|
||||||
|
|
||||||
All commands are prefixed with your configured prefix (e.g. `s/`). Arguments in `[brackets]` are optional.
|
All commands are prefixed with your configured prefix (e.g. `sd/`). Arguments in `[brackets]` are optional.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,21 +8,9 @@ All commands are prefixed with your configured prefix (e.g. `s/`). Arguments in
|
|||||||
|
|
||||||
| Command | Aliases | Description |
|
| Command | Aliases | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `8ball [question]` | — | Ask the magic 8 ball a question |
|
| `8ball <question>` | — | Ask the magic 8ball a question. Replies with a random response after a short delay. |
|
||||||
| `cat` | — | Post a random cat picture |
|
| `echo <text>` | `say` | Repeat text through the bot. If the command message is deleted, the echoed message is also deleted. |
|
||||||
| `choose <options>` | — | Pick one of the given options |
|
| `mock <text\|reply>` | — | Mocks text by alternating upper and lower case. Provide text or reply to a message. |
|
||||||
| `coinflip` | — | Flip a coin |
|
|
||||||
| `dice` | — | Roll a die |
|
|
||||||
| `echo <text>` | — | Repeat text through the bot |
|
|
||||||
| `hangman [category]` | — | Start a channel-wide game of hangman (`hg <letter or word>` to guess) |
|
|
||||||
| `image <query>` | `img` | Fetch a random image matching your search |
|
|
||||||
| `mock` | — | mOcK tExT of a message (reply to one) |
|
|
||||||
| `reverse` | — | Reverse a message (reply to one or pass text) |
|
|
||||||
| `rockpaperscissors` | `rps` | Play rock paper scissors against the bot |
|
|
||||||
| `t9encode <text>` | — | Encode text into old phone keypad multi-tap digits |
|
|
||||||
| `t9decode <digits>` | — | Decode old phone keypad multi-tap digits |
|
|
||||||
| `trivia` | — | Start a channel-wide trivia question with 30 seconds to answer (`tg <A/B/C/D>` to guess) |
|
|
||||||
| `wordle` | — | Start a channel-wide Wordle; guess the 5-letter word in 6 tries (`wg <word>` to guess) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -30,85 +18,77 @@ All commands are prefixed with your configured prefix (e.g. `s/`). Arguments in
|
|||||||
|
|
||||||
| Command | Aliases | Description |
|
| Command | Aliases | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `botguide` | `bot`, `bguide` | Link to the Valour bot guide |
|
| `help` | `cmds` | Shows all commands organised by category with an interactive embed |
|
||||||
| `devcentral` | — | Invite link to the Dev Central planet |
|
| `info <planet\|user> [@user]` | — | Shows detailed info about the current planet or a user |
|
||||||
| `info` | — | User and planet info |
|
| `ping` | — | Shows the bot's response time |
|
||||||
| `joinsite` | — | Link to a site to help bots join a planet |
|
| `source` | `src` | Link to the bot's source code |
|
||||||
| `minecraft` | — | Unofficial ValourSMP server IPs |
|
| `uptime` | — | Shows how long the bot has been running |
|
||||||
| `ping` | — | Check bot latency |
|
| `version` | `ver` | Shows the bot version, Valour server version, and Valour SDK version (current and latest) |
|
||||||
| `source` | — | Link to the bot's source code |
|
|
||||||
| `suggest` | — | Sends a link to where you can make suggestions |
|
|
||||||
| `swagger` | — | Link to the Valour API docs |
|
|
||||||
| `uptime` | — | Show how long the bot has been running |
|
|
||||||
| `usercount` | — | Show the total Valour user count |
|
|
||||||
| `version` | — | Show the current bot and Valour SDK version |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Moderation
|
## Moderation
|
||||||
|
|
||||||
|
> Requires the appropriate planet permissions.
|
||||||
|
|
||||||
| Command | Aliases | Description |
|
| Command | Aliases | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `ban <@user> [reason]` | — | Ban a member from the planet |
|
| `ban <@member\|id> [duration] [reason]` | — | Bans a member from the planet. Duration is optional (e.g. `7d`, `2h`, `30m`). Permanent if omitted. |
|
||||||
| `bans` | — | List all bans in the planet |
|
| `kick <@member\|id> [reason]` | — | Kicks a member from the planet. Sends them a DM embed before kicking. |
|
||||||
| `kick <@user> [reason]` | — | Kick a member from the planet |
|
| `restrict <disable\|enable\|list> <category> [all\|command]` | `cr`, `channelrestrict` | Disable or enable command categories or individual commands in the current channel. Requires Manage Channel permission. |
|
||||||
| `setwelcome` | — | Configure a welcome channel and message |
|
|
||||||
| `unban <user id>` | — | Unban a member from the planet |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## RP
|
## RP
|
||||||
|
|
||||||
All RP commands accept an optional `[@user]` argument or a message reply to target someone.
|
| Command | Aliases | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `emote <action> [@user]` | `e` | Send an animated RP emote. Target is optional for most actions. |
|
||||||
|
| `marriage <propose\|status\|divorce\|force> [...]` | `marry` | Marriage system — propose, check status, or divorce. |
|
||||||
|
|
||||||
### Marriage
|
### emote actions
|
||||||
|
|
||||||
| Command | Description |
|
`angry`, `baka`, `bite`, `blowkiss`, `blush`, `bonk`, `carry`, `clap`, `cry`, `cuddle`, `dance`, `facepalm`, `happy`, `holdhand`, `hug`, `kiss`, `laugh`, `lurk`, `nom`, `nya`, `pat`, `poke`, `pout`, `punch`, `run`, `shocked`, `sleep`, `smug`, `spin`, `tableflip`, `teehee`, `tickle`, `wave`, `wink`, `yawn`
|
||||||
|---|---|
|
|
||||||
| `marriage propose @user` | Propose to someone (reply to their message or mention them). Proposal expires after 5 minutes. |
|
|
||||||
| `marriage accept` | Accept a pending proposal |
|
|
||||||
| `marriage decline` | Decline a pending proposal |
|
|
||||||
| `marriage status` | Show your current marriage status |
|
|
||||||
| `marriage divorce` | End your current marriage |
|
|
||||||
|
|
||||||
Marriages persist across bot restarts.
|
### marriage subcommands
|
||||||
|
|
||||||
### GIF Commands
|
| Subcommand | Usage | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `propose` | `marriage propose` | Propose to someone (reply to their message or mention them) |
|
||||||
|
| `status` | `marriage status [@user]` | Check your own or another user's marriage status |
|
||||||
|
| `divorce` | `marriage divorce` | Begin a divorce (requires `confirm` to finalise) |
|
||||||
|
| `force` | `marriage force marry @u1 @u2` / `marriage force divorce @user` | Owner only — force marry or divorce users |
|
||||||
|
|
||||||
| Command | Description |
|
---
|
||||||
|---|---|
|
|
||||||
| `angry` | Express anger |
|
## Utils
|
||||||
| `baka` | Call someone a baka |
|
|
||||||
| `bite` | Bite someone |
|
> These commands respond to pending actions created by other commands.
|
||||||
| `blowkiss` | Blow a kiss |
|
|
||||||
| `blush` | Blush |
|
| Command | Aliases | Description |
|
||||||
| `bonk` | Bonk someone |
|
|---|---|---|
|
||||||
| `carry` | Carry someone |
|
| `accept` | — | Accept a pending action (e.g. a marriage proposal) |
|
||||||
| `clap` | Clap |
|
| `decline` | — | Decline a pending action |
|
||||||
| `cry` | Cry |
|
| `confirm` | — | Confirm a pending action (e.g. a divorce) |
|
||||||
| `cuddle` | Cuddle someone |
|
| `cancel` | — | Cancel a pending action |
|
||||||
| `dance` | Dance |
|
|
||||||
| `facepalm` | Facepalm |
|
---
|
||||||
| `happy` | Express happiness |
|
|
||||||
| `holdhand` | Hold hands with someone |
|
## Dev
|
||||||
| `hug` | Hug someone |
|
|
||||||
| `kiss` | Kiss someone |
|
> These commands are only accessible to the bot owner.
|
||||||
| `laugh` | Laugh |
|
|
||||||
| `lurk` | Lurk |
|
| Command | Aliases | Description |
|
||||||
| `nom` | Nom someone |
|
|---|---|---|
|
||||||
| `nya` | Nya |
|
| `blacklist <add\|remove> <@user\|userid>` | — | Add or remove a user from the bot blacklist |
|
||||||
| `pat` | Pat someone |
|
| `delete` | — | Deletes a replied-to message. Deletes bot messages directly; requires `ManageMessages` for other members' messages. Reply to a message to use. |
|
||||||
| `poke` | Poke someone |
|
| `planet <join\|leave\|list>` | — | Manage the planets the bot is a member of |
|
||||||
| `pout` | Pout |
|
| `react <emoji> [amount]` | — | Add a reaction to a replied-to message a given number of times |
|
||||||
| `punch` | Punch someone |
|
|
||||||
| `run` | Run |
|
### planet subcommands
|
||||||
| `shocked` | Act shocked |
|
|
||||||
| `sleep` | Sleep |
|
| Subcommand | Usage | Description |
|
||||||
| `smug` | Be smug |
|
|---|---|---|
|
||||||
| `spin` | Spin |
|
| `join` | `planet join <id> [invite]` | Join a planet by ID, optionally with an invite code |
|
||||||
| `tableflip` | Flip a table |
|
| `leave` | `planet leave [id]` | Leave a planet by ID, or the current planet if no ID is given |
|
||||||
| `teehee` | Teehee |
|
| `list` | `planet list` | List all planets the bot is currently a member of |
|
||||||
| `tickle` | Tickle someone |
|
|
||||||
| `wave` | Wave |
|
|
||||||
| `wink` | Wink |
|
|
||||||
| `yawn` | Yawn |
|
|
||||||
43
PRIVACY.md
43
PRIVACY.md
@@ -1,6 +1,6 @@
|
|||||||
# Privacy Policy
|
# Privacy Policy
|
||||||
|
|
||||||
**Effective Date:** March 20, 2026
|
**Effective Date:** April 18, 2026
|
||||||
|
|
||||||
This Privacy Policy describes how SkyBot ("the Bot") collects, uses, and stores information when used within a Valour planet.
|
This Privacy Policy describes how SkyBot ("the Bot") collects, uses, and stores information when used within a Valour planet.
|
||||||
|
|
||||||
@@ -8,30 +8,29 @@ This Privacy Policy describes how SkyBot ("the Bot") collects, uses, and stores
|
|||||||
|
|
||||||
## 1. Information Collected
|
## 1. Information Collected
|
||||||
|
|
||||||
The Bot collects only the minimum data necessary to provide its intended functionality. Most data is stored in-memory and is lost when the Bot restarts. A small amount of server configuration data is persisted to a local SQLite database for features that require it.
|
The Bot collects only the minimum data necessary to provide its intended functionality. All data is stored in-memory and is lost when the Bot restarts. No data is persisted to disk.
|
||||||
|
|
||||||
### Information Temporarily Held in Memory
|
### Information Temporarily Held in Memory
|
||||||
|
|
||||||
1. Channel IDs (for routing messages and commands)
|
1. Channel IDs (for routing messages and commands)
|
||||||
2. Planet IDs (for planet-specific operations)
|
2. Planet IDs (for planet-specific operations)
|
||||||
3. Member IDs (for moderation commands and game session tracking)
|
3. Member IDs (for command execution context)
|
||||||
4. Member display names (for game contributor lists in Hangman, Wordle, and Trivia)
|
4. Echo message mappings (command message ID → echoed message ID, used to delete echoed messages if the original is deleted)
|
||||||
|
5. Pending marriage proposals (user ID pairs and expiry times, cleared on acceptance, decline, expiry, or restart)
|
||||||
|
6. Pending divorce confirmations (user ID and expiry time, cleared on confirm, cancel, expiry, or restart)
|
||||||
|
|
||||||
### Information Persisted to Disk
|
### Information Persisted to Disk
|
||||||
|
|
||||||
The following server configuration data is saved to a local SQLite database so that it survives restarts:
|
The following data is written to a local SQLite database (`database.db`) and is retained across restarts:
|
||||||
|
|
||||||
1. Planet IDs (to associate configuration with a planet)
|
1. Marriage records — the Valour user IDs of both partners and the timestamp the marriage was created
|
||||||
2. Channel IDs (to remember the configured welcome channel)
|
2. Blacklist entries — the Valour user ID of each blacklisted user
|
||||||
3. Welcome message template (the text set by a moderator via `setwelcome message`)
|
3. Channel restrictions — channel IDs and the categories or commands disabled within them
|
||||||
4. Welcome system active state (enabled/disabled)
|
|
||||||
|
|
||||||
This data contains no personal user information. It is server configuration set by planet moderators and is stored locally on the host running the Bot.
|
|
||||||
|
|
||||||
### Information Never Stored
|
### Information Never Stored
|
||||||
|
|
||||||
1. Message content
|
1. Message content
|
||||||
2. Direct Messages ("DMs")
|
2. Direct messages sent by the bot to users as part of moderation actions
|
||||||
3. Personal account information (including usernames, email addresses, or other personally identifiable information)
|
3. Personal account information (including usernames, email addresses, or other personally identifiable information)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -41,9 +40,10 @@ This data contains no personal user information. It is server configuration set
|
|||||||
Temporarily held information is used exclusively to:
|
Temporarily held information is used exclusively to:
|
||||||
|
|
||||||
1. Route commands to the correct channels and planets
|
1. Route commands to the correct channels and planets
|
||||||
2. Enable moderation commands such as ban, unban, and kick
|
2. Enable core bot functionality during the current session
|
||||||
3. Track active game sessions (Hangman, Wordle, Trivia) and display contributor lists
|
3. Track echo message pairs to support automatic cleanup when the original command message is deleted
|
||||||
4. Enable core bot functionality during the current session
|
4. Operate the marriage system (tracking active marriages and pending proposals/divorces)
|
||||||
|
5. Enforce the blacklist (preventing blacklisted users from using bot commands)
|
||||||
|
|
||||||
The Bot does not use any information for profiling, marketing, analytics, or tracking purposes.
|
The Bot does not use any information for profiling, marketing, analytics, or tracking purposes.
|
||||||
|
|
||||||
@@ -51,23 +51,20 @@ The Bot does not use any information for profiling, marketing, analytics, or tra
|
|||||||
|
|
||||||
## 3. Data Storage and Security
|
## 3. Data Storage and Security
|
||||||
|
|
||||||
Most data is stored in-memory only and is automatically cleared when the Bot restarts. The exception is server configuration for the welcome system (see Section 1), which is written to a local SQLite database on the host machine. No data is sent to any external storage or cloud service.
|
In-memory data is automatically cleared when the Bot restarts. Marriage records, blacklist entries, and channel restrictions are written to a local SQLite database file (`database.db`) on the machine running the bot. No data is sent to any external storage or cloud service.
|
||||||
|
|
||||||
The Bot does not sell, rent, trade, or otherwise share any data with third parties.
|
The Bot does not sell, rent, trade, or otherwise share any data with third parties.
|
||||||
|
|
||||||
Some features make outbound requests to third-party APIs to fetch content. These requests do not include any user data:
|
The Bot makes outbound requests to the following third-party APIs:
|
||||||
|
|
||||||
- **Datamuse** (datamuse.com) — word lists for Hangman and Wordle
|
- **NuGet** (api.nuget.org) — fetched by the `version` command to check the latest Valour SDK version. No user data is included.
|
||||||
- **Open Trivia Database** (opentdb.com) — trivia questions for Trivia
|
- **nekos.best** (nekos.best) — fetched by the `emote` command to retrieve animated GIFs. No user data is included.
|
||||||
- **The Cat API** (thecatapi.com) — random cat images for the cat command
|
|
||||||
- **nekos.best** (nekos.best) — GIFs for all RP commands (hug, pat, kiss, dance, etc.)
|
|
||||||
- **Pixabay** (pixabay.com) — images for the image command
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Data Retention
|
## 4. Data Retention
|
||||||
|
|
||||||
In-memory data (game sessions, moderation context, etc.) is held only for the duration of the Bot's current session and is cleared on restart. Server configuration data for the welcome system is retained in a local SQLite database until explicitly changed or deleted by a planet moderator.
|
All in-memory data is held only for the duration of the Bot's current session and is cleared on restart. Echo message mappings are additionally cleared as soon as the original command message is deleted or the session ends.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -1,3 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# SkyBot
|
# SkyBot
|
||||||
|
|
||||||
SkyBot is a Valour.gg bot built with .NET 10.
|
SkyBot is a Valour.gg bot built with .NET 10.
|
||||||
@@ -13,10 +21,12 @@ SkyBot is a Valour.gg bot built with .NET 10.
|
|||||||
|
|
||||||
### Command Categories
|
### Command Categories
|
||||||
|
|
||||||
- **Fun** — games, utilities, and silly stuff (8ball, hangman, wordle, trivia, and more)
|
- **Fun** — echo, 8ball, mock and similar utilities
|
||||||
- **Info** — bot and platform info commands
|
- **Info** — bot info, ping, uptime, command listing, planet/user info
|
||||||
- **Moderation** — ban, kick, welcome messages
|
- **Moderation** — kick, ban, and per-channel command restrictions
|
||||||
- **RP** — roleplay GIF commands powered by nekos.best (35+ commands)
|
- **RP** — emotes (35 actions via nekos.best) and a marriage system
|
||||||
|
- **Utils** — accept, decline, confirm, cancel for pending actions
|
||||||
|
- **Dev** — owner-only tools including blacklist management, planet control, and message utilities
|
||||||
|
|
||||||
Full command list: [COMMANDS.md](COMMANDS.md)
|
Full command list: [COMMANDS.md](COMMANDS.md)
|
||||||
|
|
||||||
@@ -24,16 +34,15 @@ Full command list: [COMMANDS.md](COMMANDS.md)
|
|||||||
|
|
||||||
## Data & Privacy
|
## Data & Privacy
|
||||||
|
|
||||||
SkyBot stores only the minimum data required for operation. Most data is stored in-memory and is lost on restart. A small amount of server configuration data is persisted to a local SQLite database for the welcome system.
|
SkyBot stores only the minimum data required for operation. Marriage records, blacklist entries, and channel restrictions are persisted to a local SQLite database. All other data is stored in-memory and is lost on restart.
|
||||||
|
|
||||||
SkyBot does **not** store:
|
SkyBot does **not** store:
|
||||||
|
|
||||||
- Message content
|
- Message content
|
||||||
- Direct messages
|
- Direct messages
|
||||||
- Personal user data
|
- Personal user data beyond what is needed for the marriage and blacklist systems
|
||||||
|
|
||||||
Full privacy policy:
|
Full privacy policy: [PRIVACY.md](PRIVACY.md)
|
||||||
https://git.skyjoshua.xyz/SkyJoshua/SkyBot/blob/main/PRIVACY.md
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,8 +50,7 @@ https://git.skyjoshua.xyz/SkyJoshua/SkyBot/blob/main/PRIVACY.md
|
|||||||
|
|
||||||
This project is licensed under the **GNU Affero General Public License v3.0 (AGPL-3.0)**.
|
This project is licensed under the **GNU Affero General Public License v3.0 (AGPL-3.0)**.
|
||||||
|
|
||||||
See the LICENSE file for details:
|
See the LICENSE file for details.
|
||||||
https://git.skyjoshua.xyz/SkyJoshua/SkyBot/blob/main/LICENSE
|
|
||||||
|
|
||||||
Because this project is licensed under AGPL-3.0, if you modify and deploy it publicly (including as a hosted service), you must make your source code available under the same license.
|
Because this project is licensed under AGPL-3.0, if you modify and deploy it publicly (including as a hosted service), you must make your source code available under the same license.
|
||||||
|
|
||||||
@@ -52,14 +60,13 @@ Because this project is licensed under AGPL-3.0, if you modify and deploy it pub
|
|||||||
|
|
||||||
- .NET 10
|
- .NET 10
|
||||||
- A Valour bot token
|
- A Valour bot token
|
||||||
- A [Pixabay API key](https://pixabay.com/api/docs/) (free) — required for the `image` command
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.skyjoshua.xyz/SkyJoshua/SkyBot.git
|
git clone <your-repo-url>
|
||||||
cd SkyBot/SkyBot
|
cd SkyBot/SkyBot
|
||||||
dotnet restore
|
dotnet restore
|
||||||
```
|
```
|
||||||
@@ -73,25 +80,23 @@ All required NuGet packages will be installed automatically using the provided `
|
|||||||
Create a `.env` file in the root directory of the project:
|
Create a `.env` file in the root directory of the project:
|
||||||
```
|
```
|
||||||
TOKEN=your-bot-token-here
|
TOKEN=your-bot-token-here
|
||||||
PIXABAY_API_KEY=your-pixabay-api-key-here
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open `Config.cs` and update the following values:
|
Then open `Config.cs` and update the following values:
|
||||||
```cs
|
```cs
|
||||||
public static readonly long OwnerId = your-owner-id-here;
|
public static readonly long OwnerId = your-owner-id-here;
|
||||||
public static readonly string Prefix = "your-prefix-here";
|
public static readonly string Prefix = "your-prefix-here";
|
||||||
public static readonly string SourceLink = "your-source-link-here";
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `your-owner-id-here` with your Valour user ID.
|
- Replace `your-owner-id-here` with your Valour user ID.
|
||||||
- Replace `your-prefix-here` with your desired command prefix (e.g. `s/`).
|
- Replace `your-prefix-here` with your desired command prefix (e.g. `sd/`).
|
||||||
- Replace `your-source-link-here` with a link to your fork of the repository.
|
|
||||||
|
|
||||||
Never commit your `.env` file to the repository. Ensure it is listed in your `.gitignore`.
|
Never commit your `.env` file to the repository. Ensure it is listed in your `.gitignore`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Running the Bot
|
## Running the Bot
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dotnet run
|
dotnet run
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ namespace SkyBot.Commands
|
|||||||
public static class CommandRegistry
|
public static class CommandRegistry
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<string, ICommand> Commands = new();
|
public static readonly Dictionary<string, ICommand> Commands = new();
|
||||||
public static readonly Dictionary<string, List<ICommand>> Sections = new();
|
public static readonly Dictionary<string, List<ICommand>> Categories = new();
|
||||||
|
|
||||||
static CommandRegistry()
|
static CommandRegistry()
|
||||||
{
|
{
|
||||||
var allCommands = AppDomain.CurrentDomain.GetAssemblies()
|
var commands = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
.SelectMany(a => a.GetTypes())
|
.SelectMany(a => a.GetTypes())
|
||||||
.Where(t => typeof(ICommand).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
|
.Where(t => typeof(ICommand).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
|
||||||
.Select(t => (ICommand?)Activator.CreateInstance(t))
|
.Select(t => (ICommand?)Activator.CreateInstance(t))
|
||||||
.Select(c => c!);
|
.Select(c => c!);
|
||||||
|
|
||||||
foreach (var cmd in allCommands)
|
foreach (var cmd in commands)
|
||||||
{
|
{
|
||||||
Commands[cmd.Name.ToLower()] = cmd;
|
Commands[cmd.Name.ToLower()] = cmd;
|
||||||
foreach (var alias in cmd.Aliases)
|
foreach (var alias in cmd.Aliases)
|
||||||
@@ -23,9 +23,9 @@ namespace SkyBot.Commands
|
|||||||
Commands[alias.ToLower()] = cmd;
|
Commands[alias.ToLower()] = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sections = Commands.Values
|
Categories = Commands.Values
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.GroupBy(c => c.Section.ToLower())
|
.GroupBy(c => c.Category.ToLower())
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Client;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
@@ -10,21 +7,12 @@ namespace SkyBot.Commands
|
|||||||
public string Name => "template";
|
public string Name => "template";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
public string Description => "";
|
public string Description => "";
|
||||||
public string Section => "template";
|
public string Category => "template";
|
||||||
public string Usage => "";
|
public string Usage => "";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ValourClient Client = ctx.Client;
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
PlanetMember Member = ctx.Member;
|
|
||||||
Message Message = ctx.Message;
|
|
||||||
Planet Planet = ctx.Planet;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] Args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
91
SkyBot/Commands/Dev/Blacklist.cs
Normal file
91
SkyBot/Commands/Dev/Blacklist.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using SkyBot.Services;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Blacklist : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "blacklist";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "adds or removes a user from the blacklist";
|
||||||
|
public string Category => "Dev";
|
||||||
|
public string Usage => "blacklist <add|remove> <@user|userid>";
|
||||||
|
public string[] SubCommands => ["add", "remove"];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (!PermissionHelper.IsOwner(ctx.Member))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You do not have permission to execute this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long? userId = null;
|
||||||
|
|
||||||
|
if (ctx.Message.Mentions?.Any() == true)
|
||||||
|
{
|
||||||
|
var member = await ctx.Planet.FetchMemberAsync(ctx.Message.Mentions.First().TargetId);
|
||||||
|
if (member is null) { await MessageHelper.ReplyAsync(ctx, "Could not find that member."); return; }
|
||||||
|
userId = member.UserId;
|
||||||
|
}
|
||||||
|
else if (long.TryParse(ctx.Args.ElementAtOrDefault(1), out long parsed))
|
||||||
|
{
|
||||||
|
var member = await ctx.Planet.FetchMemberAsync(parsed);
|
||||||
|
userId = member?.UserId ?? parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId is null)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Mention a user or provide their user ID.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = await ctx.Client.UserService.FetchUserAsync(userId.Value);
|
||||||
|
if (user is null) { await MessageHelper.ReplyAsync(ctx, "Could not find that user."); return; }
|
||||||
|
|
||||||
|
switch (ctx.Args[0])
|
||||||
|
{
|
||||||
|
case "add":
|
||||||
|
await handleAdd(ctx, user);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
await handleRemove(ctx, user);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Usage: blacklist <add|remove> <@user|userid>");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task handleAdd(CommandContext ctx, User user)
|
||||||
|
{
|
||||||
|
var result = await BlacklistService.Blacklist(user.Id);
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case BlacklistService.BlacklistResult.AlreadyBlacklisted:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "User is already blacklisted");
|
||||||
|
break;
|
||||||
|
case BlacklistService.BlacklistResult.Ok:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Blacklisted {user.Name} successfully.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task handleRemove(CommandContext ctx, User user)
|
||||||
|
{
|
||||||
|
var result = await BlacklistService.UnBlacklist(user.Id);
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case BlacklistService.UnBlacklistResult.NotBlacklisted:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "User is not blacklisted.");
|
||||||
|
break;
|
||||||
|
case BlacklistService.UnBlacklistResult.Ok:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Removed {user.Name} from the blacklist.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using SkyBot.Services;
|
|
||||||
using SkyBot.Services.Messages;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class ChannelCmds : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "channel";
|
|
||||||
public string[] Aliases => ["ch"];
|
|
||||||
public string Description => "Enable or disable bot commands in a channel";
|
|
||||||
public string Section => "Dev";
|
|
||||||
public string Usage => "channel <enable|disable>";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (!PermissionHelper.IsOwner(member) && !await PermissionHelper.HasPermAsync(member, channel, [ChatChannelPermissions.ManageMessages]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Usage: `channel <enable|disable>`");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (args[0].ToLower())
|
|
||||||
{
|
|
||||||
case "disable":
|
|
||||||
if (Create.disabledChannels.Contains(channelId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This channel is already disabled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Create.disabledChannels.Add(channelId);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Bot commands disabled in this channel.");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "enable":
|
|
||||||
if (!Create.disabledChannels.Contains(channelId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This channel is not disabled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Create.disabledChannels.Remove(channelId);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Bot commands enabled in this channel.");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Usage: `channel <enable|disable>`");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,53 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Client;
|
using Valour.Shared.Authorization;
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Delete : ICommand
|
public class Delete : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "delete";
|
public string Name => "delete";
|
||||||
public string[] Aliases => ["del"];
|
public string[] Aliases => [];
|
||||||
public string Description => "Delete a bot message";
|
public string Description => "Deletes a message by the bot";
|
||||||
public string Section => "Dev";
|
public string Category => "Dev";
|
||||||
public string Usage => "reply -> delete";
|
public string Usage => "delete";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
if (!PermissionHelper.IsOwner(ctx.Member))
|
||||||
ValourClient client = ctx.Client;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
{
|
||||||
if (!PermissionHelper.IsOwner(member))
|
await MessageHelper.ReplyAsync(ctx, "You do not have permission to execute this command.");
|
||||||
{
|
return;
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.ReplyToId == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please reply to a message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.Cache.Messages.TryGet(message.ReplyToId.Value, out var msg))
|
|
||||||
{
|
|
||||||
await msg.DeleteAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (ctx.Message.ReplyToId is null)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Please reply to the message you would like to delete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Message.ReplyToId is not long replyToId)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Please reply to the message you want to delete.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctx.Client.Cache.Messages.TryGet(replyToId, out var replyMsg))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Could not find the replied message in cache.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replyMsg!.AuthorUserId != ctx.Client.Me.Id && !await PermissionHelper.HasPermAsync(ctx.Planet.MyMember, [ChatChannelPermissions.ManageMessages], ctx.Channel))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "I do not have permission to delete other members' messages in this channel.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await replyMsg!.DeleteAsync();
|
||||||
|
await ctx.Message.AddReactionAsync("👍");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Client;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Edit : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "edit";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Edit the bots message";
|
|
||||||
public string Section => "Dev";
|
|
||||||
public string Usage => "reply -> edit <message>";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
ValourClient client = ctx.Client;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
if(!PermissionHelper.IsOwner(member))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.ReplyToId == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please reply to a message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.Cache.Messages.TryGet(message.ReplyToId.Value, out var msg))
|
|
||||||
{
|
|
||||||
await MessageHelper.EditAsync(channel, msg, string.Join(" ", args));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
139
SkyBot/Commands/Dev/Planet.cs
Normal file
139
SkyBot/Commands/Dev/Planet.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds;
|
||||||
|
using Valour.Shared;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class PlanetCmds : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "planet";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Planet Commands";
|
||||||
|
public string Category => "Dev";
|
||||||
|
public string Usage => "planet <sub>";
|
||||||
|
public string[] SubCommands => ["join", "leave", "list"];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (ctx.Message.AuthorUserId != Config.OwnerId) return;
|
||||||
|
|
||||||
|
string sub = ctx.Args.Length > 0 ? ctx.Args[0].ToLower() : "";
|
||||||
|
|
||||||
|
switch (sub)
|
||||||
|
{
|
||||||
|
case "join":
|
||||||
|
await HandleJoin(ctx);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "leave":
|
||||||
|
await HandleLeave(ctx);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "list":
|
||||||
|
await HandleList(ctx);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Usage: {Config.Prefix}planet <join|leave|list>");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleJoin(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (!long.TryParse(ctx.Args.Length > 1 ? ctx.Args[1] : null, out long planetId))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Please provide a valid planet ID.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Client.PlanetService.JoinedPlanets.Any(p => p.Id == planetId))
|
||||||
|
{
|
||||||
|
Planet planet = await ctx.Client.PlanetService.FetchPlanetAsync(planetId);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Bot is already a member of {planet.Name} (ID: {planet.Id})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? inviteCode = ctx.Args.Length > 2 ? ctx.Args[2] : null;
|
||||||
|
|
||||||
|
TaskResult<PlanetMember> joinResult = inviteCode is null
|
||||||
|
? await ctx.Client.PlanetService.JoinPlanetAsync(planetId)
|
||||||
|
: await ctx.Client.PlanetService.JoinPlanetAsync(planetId, inviteCode);
|
||||||
|
|
||||||
|
if (!joinResult.Success)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Failed to join planet: {joinResult.Message}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Planet planet = await ctx.Client.PlanetService.FetchPlanetAsync(planetId);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Successfully joined planet: {planet.Name} (ID {planet.Id})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleLeave(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (!long.TryParse(ctx.Args.Length > 1 ? ctx.Args[1] : ctx.Planet.Id.ToString(), out long planetId))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Please provide a valid planet ID or no planet ID to leave this planet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Planet planet = await ctx.Client.PlanetService.FetchPlanetAsync(planetId);
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Are you sure you want to leave planet: {planet.Name}? Type `{Config.Prefix}confirm within 30 seconds to confirm.");
|
||||||
|
bool confirmed = await PendingConfirmations.WaitAsync(ctx.Member.UserId, TimeSpan.FromSeconds(30));
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Confirmation timed out.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Leaving planet: {planet.Name}...");
|
||||||
|
await ctx.Client.PlanetService.LeavePlanetAsync(planet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleList(CommandContext ctx)
|
||||||
|
{
|
||||||
|
const int PageSize = 10;
|
||||||
|
|
||||||
|
var planets = ctx.Client.PlanetService.JoinedPlanets.ToList();
|
||||||
|
|
||||||
|
var chunks = planets
|
||||||
|
.Select((p, i) => (p, i))
|
||||||
|
.GroupBy(x => x.i / PageSize)
|
||||||
|
.Select(g => g.Select(x => x.p).ToList())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (chunks.Count == 0)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Bot is not a member of any planets.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new EmbedBuilder();
|
||||||
|
builder.embed.HideChangePageArrows = true;
|
||||||
|
|
||||||
|
for (int chunkIndex = 0; chunkIndex < chunks.Count; chunkIndex++)
|
||||||
|
{
|
||||||
|
int embedPage = chunkIndex;
|
||||||
|
string? footer = chunks.Count > 1 ? $"Page {chunkIndex + 1}/{chunks.Count}" : null;
|
||||||
|
|
||||||
|
builder.AddPage($"Planets ({planets.Count} total)", footer);
|
||||||
|
|
||||||
|
foreach (var planet in chunks[chunkIndex])
|
||||||
|
builder.AddText(planet.Name, $"ID: {planet.Id}");
|
||||||
|
|
||||||
|
if (chunkIndex > 0)
|
||||||
|
builder.AddButton("← Prev").OnClickGoToEmbedPage(embedPage - 1);
|
||||||
|
if (chunkIndex < chunks.Count - 1)
|
||||||
|
builder.AddButton("Next →").OnClickGoToEmbedPage(embedPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, null, builder.embed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
@@ -10,63 +7,50 @@ namespace SkyBot.Commands
|
|||||||
{
|
{
|
||||||
public string Name => "react";
|
public string Name => "react";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
public string Description => "Send a message with a reaction at a set count.";
|
public string Description => "Adds a reaction to a replied message.";
|
||||||
public string Section => "Dev";
|
public string Category => "Dev";
|
||||||
public string Usage => "react <emoji> <amount> <message>";
|
public string Usage => "react <emoji> [amount]";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (!PermissionHelper.IsOwner(ctx.Member))
|
if (!PermissionHelper.IsOwner(ctx.Member))
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command.");
|
await MessageHelper.ReplyAsync(ctx, "You do not have permission to execute this command.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] args = ctx.Args;
|
if (ctx.Args.Length < 1)
|
||||||
|
|
||||||
if (args.Length < 3)
|
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Usage: `{Config.Prefix}react <emoji> <amount> <message>`");
|
await MessageHelper.ReplyAsync(ctx, $"Usage: `{Config.Prefix}react <emoji> [amount]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string emoji = args[0];
|
string emoji = ctx.Args[0];
|
||||||
|
|
||||||
if (!int.TryParse(args[1], out int amount) || amount < 1)
|
int amount = 1;
|
||||||
|
if (ctx.Args.Length >= 2 && !int.TryParse(ctx.Args[1], out amount))
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Amount must be a number between 1 and 100.");
|
await MessageHelper.ReplyAsync(ctx, $"`{ctx.Args[1]}` is not a valid number. Defaulting to `1`");
|
||||||
|
amount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Message.ReplyToId is not long replyToId)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Please reply to the message you want to add the reaction to.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctx.Client.Cache.Messages.TryGet(replyToId, out var replyMsg))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Could not find the replied message in cache.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string content = string.Join(" ", args.Skip(2));
|
for (int i = 0; i < amount; i++)
|
||||||
|
|
||||||
var reactions = Enumerable.Range(0, amount)
|
|
||||||
.Select(_ => new MessageReaction
|
|
||||||
{
|
|
||||||
Emoji = emoji,
|
|
||||||
AuthorUserId = ctx.Client.Me.Id,
|
|
||||||
AuthorMemberId = ctx.Planet.MyMember?.Id,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var msg = new Message(ctx.Client)
|
|
||||||
{
|
{
|
||||||
Content = content,
|
await replyMsg!.AddReactionAsync(emoji);
|
||||||
ChannelId = channelId,
|
}
|
||||||
PlanetId = ctx.Planet.Id,
|
await ctx.Message.AddReactionAsync("👍");
|
||||||
AuthorUserId = ctx.Client.Me.Id,
|
|
||||||
AuthorMemberId = ctx.Planet.MyMember?.Id,
|
|
||||||
Reactions = reactions,
|
|
||||||
Fingerprint = Guid.NewGuid().ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
await ctx.Client.MessageService.SendMessage(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
@@ -11,10 +10,11 @@ namespace SkyBot.Commands
|
|||||||
public string Name => "8ball";
|
public string Name => "8ball";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
public string Description => "Ask the magic 8ball a question.";
|
public string Description => "Ask the magic 8ball a question.";
|
||||||
public string Section => "Fun";
|
public string Category => "Fun";
|
||||||
public string Usage => "8ball <question>";
|
public string Usage => "8ball <question>";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
private static readonly string[] Responses =
|
private static readonly string[] Responses =
|
||||||
[
|
[
|
||||||
"It is certain.",
|
"It is certain.",
|
||||||
"It is decidedly so.",
|
"It is decidedly so.",
|
||||||
@@ -40,25 +40,17 @@ namespace SkyBot.Commands
|
|||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
if (ctx.Args.Length == 0)
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
{
|
||||||
if (args.Length == 0)
|
await MessageHelper.ReplyAsync(ctx, "Please ask a question.");
|
||||||
{
|
return;
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please ask a question.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TaskResult<Message> result = await MessageHelper.ReplyAsync(ctx, channel, $"🎱 Thinking...");
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
await Task.Delay(2000);
|
|
||||||
string response = Responses[Random.Shared.Next(Responses.Length)];
|
|
||||||
await MessageHelper.EditAsync(channel, result.Data, $"🎱 {response}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TaskResult<Message> result = await MessageHelper.ReplyAsync(ctx, $"🎱 Thinking...", reply: true);
|
||||||
|
await ctx.Channel.SendIsTyping();
|
||||||
|
await Task.Delay(2000);
|
||||||
|
string response = Responses[Random.Shared.Next(Responses.Length)];
|
||||||
|
await MessageHelper.EditAsync(ctx.Channel, result.Data, $"🎱 {response}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Cat : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "cat";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Posts a random cat picture.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "cat";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new();
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
// Fetch a random cat from TheCatAPI
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://api.thecatapi.com/v1/images/search");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not fetch a cat image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var root = doc.RootElement[0];
|
|
||||||
|
|
||||||
string catUrl = root.GetProperty("url").GetString()!;
|
|
||||||
int width = root.TryGetProperty("width", out var w) ? w.GetInt32() : 0;
|
|
||||||
int height = root.TryGetProperty("height", out var h) ? h.GetInt32() : 0;
|
|
||||||
|
|
||||||
string ext = Path.GetExtension(catUrl.Split('?')[0]).ToLowerInvariant();
|
|
||||||
string mime = ext == ".png" ? "image/png"
|
|
||||||
: ext == ".gif" ? "image/gif"
|
|
||||||
: "image/jpeg";
|
|
||||||
string fileName = $"cat{ext}";
|
|
||||||
|
|
||||||
// Download the image bytes
|
|
||||||
byte[] imageBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
imageBytes = await _http.GetByteArrayAsync(catUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not download the cat image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload to Valour CDN so the server can scan/serve it
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(imageBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mime);
|
|
||||||
form.Add(fileContent, "file", fileName);
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not upload the cat image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not upload the cat image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = mime,
|
|
||||||
FileName = fileName,
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "", [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Choose : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "choose";
|
|
||||||
public string[] Aliases => ["pick"];
|
|
||||||
public string Description => "Picks one of the given options.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "choose <option1> <option2> ...";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (ctx.Args.Length < 2)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide at least two options.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskResult<Message> result = await MessageHelper.ReplyAsync(ctx, channel, "🤔 Choosing...");
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
string choice = args[Random.Shared.Next(args.Length)];
|
|
||||||
await MessageHelper.EditAsync(channel, result.Data, $"I choose **{choice}**!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class CoinFlip : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "coinflip";
|
|
||||||
public string[] Aliases => ["cf"];
|
|
||||||
public string Description => "Flips a coin.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "coinflip";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
TaskResult<Message> result = await MessageHelper.ReplyAsync(ctx, channel, "🪙 Flipping...");
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
await Task.Delay(3000);
|
|
||||||
|
|
||||||
string outcome = Random.Shared.Next(2) == 0 ? "Heads" : "Tails";
|
|
||||||
await MessageHelper.EditAsync(channel, result.Data, $"🪙 {outcome}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Runtime.InteropServices.Marshalling;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Dice : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "dice";
|
|
||||||
public string[] Aliases => ["roll"];
|
|
||||||
public string Description => "Rolls dice.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "roll <dice> (e.g. 2d6, d20, 3d8)";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string input = args.Length > 0 ? args[0].ToLower() : "1d6";
|
|
||||||
|
|
||||||
string[] parts = input.Split('d');
|
|
||||||
if (parts.Length != 2 || !int.TryParse(parts[1], out int sides) || sides < 2)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid dice format. Use something like `2d6` or `d20`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = 1;
|
|
||||||
if (!string.IsNullOrWhiteSpace(parts[0]) && !int.TryParse(parts[0], out count))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid dice format. Use something like `2d6` or `d20`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count < 1 || count > 100)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You can only roll between 1 and 100 dice at a time.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<int> rolls = Enumerable.Range(0, count).Select(_ => Random.Shared.Next(1, sides+1)).ToList();
|
|
||||||
int total = rolls.Sum();
|
|
||||||
|
|
||||||
TaskResult<Message> rolling = await MessageHelper.ReplyAsync(ctx, channel, "🎲 Rolling...");
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
await Task.Delay(2000);
|
|
||||||
|
|
||||||
string rollDisplay = count > 1 ? $"({string.Join(" + ", rolls)}) = **{total}**" : $"**{total}**";
|
|
||||||
await MessageHelper.EditAsync(channel, rolling.Data, $"🎲 Rolled {input}: {rollDisplay}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,38 +2,43 @@ using System.Collections.Concurrent;
|
|||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
using Valour.Shared.Authorization;
|
using Valour.Shared;
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Echo : ICommand
|
public class Echo : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "echo";
|
public string Name => "echo";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => ["say"];
|
||||||
public string Description => "Echos what you said through the bot.";
|
public string Description => "Echos your message as the bot";
|
||||||
public string Section => "Fun";
|
public string Category => "Fun";
|
||||||
public string Usage => "echo <message>";
|
public string Usage => "echo <text>";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public static readonly ConcurrentDictionary<long, long> EchoMap = new();
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
string reply = string.Join(' ', ctx.Args);
|
||||||
long channelId = ctx.ChannelId;
|
if (string.IsNullOrWhiteSpace(reply))
|
||||||
PlanetMember member = ctx.Member;
|
{
|
||||||
string[] args = ctx.Args;
|
await MessageHelper.ReplyAsync(ctx, "Please enter a message to echo.");
|
||||||
Message message = ctx.Message;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string reply = string.Join(" ", args);
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
if (string.IsNullOrWhiteSpace(reply)) await MessageHelper.ReplyAsync(ctx, channel, $"Enter a message to echo.");
|
|
||||||
|
|
||||||
reply = $"{member.Name} » {reply}";
|
|
||||||
if (reply.Length > 2048)
|
if (reply.Length > 2048)
|
||||||
{
|
{
|
||||||
reply = reply.Substring(0, 2048);
|
reply = reply.Substring(0, 2048);
|
||||||
}
|
}
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, reply);
|
TaskResult<Message> echoedMsg = await MessageHelper.ReplyAsync(ctx, reply);
|
||||||
|
|
||||||
|
if (echoedMsg.Success && echoedMsg.Data is not null)
|
||||||
|
{
|
||||||
|
EchoMap[ctx.Message.Id] = echoedMsg.Data.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Client;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Hangman : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "hangman";
|
|
||||||
public string[] Aliases => ["hm"];
|
|
||||||
public string Description => "Starts a channel-wide game of hangman. Optionally specify a category.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "hangman [category] | hangman end";
|
|
||||||
|
|
||||||
private record HangmanSession(
|
|
||||||
string Word,
|
|
||||||
string? Category,
|
|
||||||
HashSet<char> Guessed,
|
|
||||||
HashSet<char> Wrong,
|
|
||||||
HashSet<string> Contributors,
|
|
||||||
long StarterId,
|
|
||||||
Message BotMessage,
|
|
||||||
Channel Channel,
|
|
||||||
ValourClient Client);
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<long, HangmanSession> _sessions = new();
|
|
||||||
private static readonly HttpClient _http = new();
|
|
||||||
|
|
||||||
private const int MaxWrong = 6;
|
|
||||||
|
|
||||||
private static readonly string[] Topics =
|
|
||||||
[
|
|
||||||
"animals", "food", "sports", "music", "science", "geography",
|
|
||||||
"movies", "nature", "technology", "history", "mythology", "space",
|
|
||||||
"ocean", "weather", "games", "art", "clothing", "vehicles",
|
|
||||||
];
|
|
||||||
|
|
||||||
private static readonly string[] FallbackWords =
|
|
||||||
[
|
|
||||||
"APPLE", "BRIDGE", "CASTLE", "DRAGON", "ELEPHANT", "FOREST", "GUITAR",
|
|
||||||
"HARBOR", "ISLAND", "JUNGLE", "KNIGHT", "LEMON", "MANGO", "OCEAN",
|
|
||||||
"PLANET", "ROBOT", "SNAKE", "TIGER", "UMBRELLA", "WIZARD", "ANCHOR",
|
|
||||||
"BUTTER", "CANDLE", "DONKEY", "ENGINE", "FALCON", "GOBLIN", "HAMMER",
|
|
||||||
"IGLOO", "JACKET", "KITTEN", "LADDER", "MIRROR", "NEEDLE", "ORANGE",
|
|
||||||
"PENCIL", "RABBIT", "SILVER", "TEMPLE", "TURTLE", "VALLEY", "WALRUS",
|
|
||||||
"ZIPPER", "BLANKET", "CACTUS", "DAISY", "GLOVES", "HOCKEY", "INSECT",
|
|
||||||
"JELLY", "KETTLE", "LOBSTER", "MARBLE", "NAPKIN", "OYSTER", "PEPPER",
|
|
||||||
"QUARTZ", "ROCKET", "SALMON", "THRONE", "VELVET", "WINDOW", "YOGURT",
|
|
||||||
"ZOMBIE", "ALMOND", "BISON", "COBRA", "DAGGER", "EMBER", "FROST",
|
|
||||||
"GHOST", "HONEY", "IVORY", "JEWEL", "KOALA", "MAPLE", "NINJA",
|
|
||||||
"OLIVE", "PIRATE", "RAVEN", "SPHINX", "TORNADO", "UNICORN", "VENOM",
|
|
||||||
"WITCH", "PIXEL", "STORM", "CLOUD", "FLAME", "COMET", "DUSK",
|
|
||||||
"ECHO", "FABLE", "GLYPH", "HAZE", "JINX", "KNACK", "LUNAR",
|
|
||||||
"MYTH", "NEON", "ORBIT", "PRISM", "QUEST", "RIDGE", "SHARD",
|
|
||||||
];
|
|
||||||
|
|
||||||
// 7 stages: 0 wrong → 6 wrong (using +--+ to avoid markdown eating underscores)
|
|
||||||
private static readonly string[] Stages =
|
|
||||||
[
|
|
||||||
"```\n +--------+\n | |\n | \n | \n | \n | \n==+==\n```",
|
|
||||||
"```\n +--------+\n | |\n | O\n | \n | \n | \n==+==\n```",
|
|
||||||
"```\n +--------+\n | |\n | O\n | |\n | \n | \n==+==\n```",
|
|
||||||
"```\n +--------+\n | |\n | O\n | /|\n | \n | \n==+==\n```",
|
|
||||||
"```\n +--------+\n | |\n | O\n | /|\\\n | \n | \n==+==\n```",
|
|
||||||
"```\n +--------+\n | |\n | O\n | /|\\\n | / \n | \n==+==\n```",
|
|
||||||
"```\n +--------+\n | |\n | O\n | /|\\\n | / \\\n | \n==+==\n```",
|
|
||||||
];
|
|
||||||
|
|
||||||
private static async Task DeleteBotMessageAsync(CommandContext ctx, Message botMessage)
|
|
||||||
{
|
|
||||||
if (ctx.Client.Cache.Messages.TryGet(botMessage.Id, out var cached) && cached is not null)
|
|
||||||
try { await cached.DeleteAsync(); } catch { }
|
|
||||||
else
|
|
||||||
try { await botMessage.DeleteAsync(); } catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Message?> RepostAsync(CommandContext ctx, Channel channel, Message botMessage, string content)
|
|
||||||
{
|
|
||||||
await DeleteBotMessageAsync(ctx, botMessage);
|
|
||||||
var result = await MessageHelper.ReplyAsync(ctx, channel, content);
|
|
||||||
if (!result.Success || result.Data is null) return null;
|
|
||||||
return ctx.Client.Cache.Messages.TryGet(result.Data.Id, out var cached) ? cached : result.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<string> FetchWord(string topic)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string url = $"https://api.datamuse.com/words?ml={Uri.EscapeDataString(topic)}&topic={Uri.EscapeDataString(topic)}&max=500";
|
|
||||||
string json = await _http.GetStringAsync(url);
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var words = doc.RootElement.EnumerateArray()
|
|
||||||
.Select(e => e.GetProperty("word").GetString() ?? "")
|
|
||||||
.Where(w => w.Length >= 4 && w.Length <= 12 && w.All(char.IsLetter))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (words.Count > 0)
|
|
||||||
return words[Random.Shared.Next(words.Count)].ToUpper();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
return FallbackWords[Random.Shared.Next(FallbackWords.Length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildDisplay(string word, string? category, HashSet<char> guessed, HashSet<char> wrong)
|
|
||||||
{
|
|
||||||
string wordDisplay = string.Join(" ", word.Select(c => guessed.Contains(c) ? c.ToString() : "_"));
|
|
||||||
string wrongLetters = wrong.Count > 0 ? string.Join(", ", wrong.OrderBy(c => c)) : "none";
|
|
||||||
string categoryLine = category is not null ? $"📂 **Category**: {category.ToTitleCase()}\n" : "";
|
|
||||||
|
|
||||||
return string.Join("\n",
|
|
||||||
$"🎮 **HANGMAN**",
|
|
||||||
categoryLine,
|
|
||||||
$"`{wordDisplay}`",
|
|
||||||
"",
|
|
||||||
Stages[wrong.Count],
|
|
||||||
"",
|
|
||||||
$"❌ Wrong ({wrong.Count}/{MaxWrong}): {wrongLetters}",
|
|
||||||
"",
|
|
||||||
$"*Use `{Config.Prefix}hg <letter or word>` to guess!*"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
// sd/hangman end — end the current game
|
|
||||||
if (args.Length >= 1 && args[0].ToLower() == "end")
|
|
||||||
{
|
|
||||||
if (!_sessions.TryGetValue(channelId, out var session))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's no active hangman game in this channel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isStarter = member.UserId == session.StarterId;
|
|
||||||
bool isMod = await PermissionHelper.HasPermAsync(member, channel, [ChatChannelPermissions.ManageMessages]);
|
|
||||||
|
|
||||||
if (!isStarter && !isMod)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Only the person who started the game (or a moderator) can end it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
await DeleteBotMessageAsync(ctx, session.BotMessage);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"🛑 Hangman ended by {member.Name}. The word was `{session.Word}`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sd/hangman [category] — start a new game
|
|
||||||
if (_sessions.ContainsKey(channelId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's already an active hangman game in this channel!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? category = args.Length >= 1
|
|
||||||
? args[0].ToLower()
|
|
||||||
: Topics[Random.Shared.Next(Topics.Length)];
|
|
||||||
|
|
||||||
string word = await FetchWord(category);
|
|
||||||
|
|
||||||
var guessed = new HashSet<char>();
|
|
||||||
var wrong = new HashSet<char>();
|
|
||||||
var contributors = new HashSet<string>();
|
|
||||||
|
|
||||||
string display = BuildDisplay(word, category, guessed, wrong);
|
|
||||||
var sent = await MessageHelper.ReplyAsync(ctx, channel, display);
|
|
||||||
if (!sent.Success || sent.Data is null) return;
|
|
||||||
|
|
||||||
_sessions[channelId] = new HangmanSession(word, category, guessed, wrong, contributors, member.UserId, sent.Data, channel, ctx.Client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task ProcessGuessAsync(CommandContext ctx, Channel channel, string rawGuess)
|
|
||||||
{
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!_sessions.TryGetValue(channelId, out var session))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's no active hangman game in this channel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string guess = rawGuess.ToUpper();
|
|
||||||
string memberName = ctx.Member.Name ?? "Unknown";
|
|
||||||
|
|
||||||
// Full word guess
|
|
||||||
if (guess.Length > 1)
|
|
||||||
{
|
|
||||||
if (guess == session.Word)
|
|
||||||
{
|
|
||||||
foreach (char c in session.Word) session.Guessed.Add(c);
|
|
||||||
session.Contributors.Add(memberName);
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
|
|
||||||
string contributorList = string.Join(", ", session.Contributors);
|
|
||||||
await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong)
|
|
||||||
+ $"\n\n🎉 **{memberName} guessed the word! The word was `{session.Word}`!**\nContributors: {contributorList}");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"🎉 **{memberName} guessed the word!** The word was `{session.Word}`!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
session.Wrong.Add(guess[0]);
|
|
||||||
|
|
||||||
if (session.Wrong.Count >= MaxWrong)
|
|
||||||
{
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong)
|
|
||||||
+ $"\n\n💀 **Game over!** The word was `{session.Word}`.");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"💀 **Game Over!** Out of guesses. The word was `{session.Word}`");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var newMsg = await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong));
|
|
||||||
if (newMsg is not null)
|
|
||||||
_sessions[channelId] = session with { BotMessage = newMsg };
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"❌ `{guess}` is not the word!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single letter guess
|
|
||||||
char letter = guess[0];
|
|
||||||
|
|
||||||
if (!char.IsLetter(letter))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please guess a letter or a full word.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.Guessed.Contains(letter) || session.Wrong.Contains(letter))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"`{letter}` has already been guessed!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.Word.Contains(letter))
|
|
||||||
{
|
|
||||||
session.Guessed.Add(letter);
|
|
||||||
session.Contributors.Add(memberName);
|
|
||||||
|
|
||||||
bool won = session.Word.All(c => session.Guessed.Contains(c));
|
|
||||||
if (won)
|
|
||||||
{
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
string contributorList = string.Join(", ", session.Contributors);
|
|
||||||
await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong)
|
|
||||||
+ $"\n\n🎉 **The channel wins! The word was `{session.Word}`!**\nContributors: {contributorList}");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"🎉 **The channel wins!** The word was `{session.Word}`!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var newMsg = await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong));
|
|
||||||
if (newMsg is not null)
|
|
||||||
_sessions[channelId] = session with { BotMessage = newMsg };
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"✅ `{letter}` is in the word!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
session.Wrong.Add(letter);
|
|
||||||
|
|
||||||
if (session.Wrong.Count >= MaxWrong)
|
|
||||||
{
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong)
|
|
||||||
+ $"\n\n💀 **Game over!** The word was `{session.Word}`.");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"💀 **Game over!** The word was `{session.Word}`.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var newMsg = await RepostAsync(ctx, channel, session.BotMessage,
|
|
||||||
BuildDisplay(session.Word, session.Category, session.Guessed, session.Wrong));
|
|
||||||
if (newMsg is not null)
|
|
||||||
_sessions[channelId] = session with { BotMessage = newMsg };
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"❌ No `{letter}` in the word!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HangmanGuess : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "hg";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Guess a letter or word in the active Hangman game.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "hg <letter or word>";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
if (!ctx.ChannelCache.TryGetValue(ctx.ChannelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (ctx.Args.Length == 0 || string.IsNullOrWhiteSpace(ctx.Args[0]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a letter or word to guess.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Hangman.ProcessGuessAsync(ctx, channel, ctx.Args[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Image : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "image";
|
|
||||||
public string[] Aliases => ["img"];
|
|
||||||
public string Description => "Fetches a random image matching your search.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "image <query>";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new();
|
|
||||||
private static readonly Random _rng = new();
|
|
||||||
|
|
||||||
private record ImageResult(string Url, int Width, int Height, string Mime);
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Usage: `{Config.Prefix}image <query>`");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string query = string.Join(" ", args);
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
var result = await FetchPixabayAsync(query) ?? await FetchWikimediaAsync(query);
|
|
||||||
|
|
||||||
if (result is null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"🔍 No images found for **{query}**.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string ext = Path.GetExtension(result.Url.Split('?')[0]).ToLowerInvariant();
|
|
||||||
string fileName = $"image{(string.IsNullOrEmpty(ext) ? ".jpg" : ext)}";
|
|
||||||
|
|
||||||
byte[] imageBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
imageBytes = await _http.GetByteArrayAsync(result.Url);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "🖼️ Could not download the image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(imageBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(result.Mime);
|
|
||||||
form.Add(fileContent, "file", fileName);
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "🖼️ Could not upload the image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "🖼️ Could not upload the image. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = result.Mime,
|
|
||||||
FileName = fileName,
|
|
||||||
Width = result.Width,
|
|
||||||
Height = result.Height
|
|
||||||
};
|
|
||||||
|
|
||||||
await channel.SendMessageAsync($"🖼️ **{query}**", attachments: [attachment]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<ImageResult?> FetchPixabayAsync(string query)
|
|
||||||
{
|
|
||||||
string? key = Environment.GetEnvironmentVariable("PIXABAY_API_KEY");
|
|
||||||
if (string.IsNullOrWhiteSpace(key)) return null;
|
|
||||||
|
|
||||||
string url = $"https://pixabay.com/api/?key={key}" +
|
|
||||||
$"&q={Uri.EscapeDataString(query)}&image_type=photo&per_page=20&safesearch=true";
|
|
||||||
|
|
||||||
HttpResponseMessage response;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response = await _http.GetAsync(url);
|
|
||||||
}
|
|
||||||
catch { return null; }
|
|
||||||
|
|
||||||
// 429 = rate limited — fall through to Wikimedia
|
|
||||||
if (!response.IsSuccessStatusCode) return null;
|
|
||||||
|
|
||||||
string json = await response.Content.ReadAsStringAsync();
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var hits = doc.RootElement.GetProperty("hits");
|
|
||||||
if (hits.GetArrayLength() == 0) return null;
|
|
||||||
|
|
||||||
var hit = hits[_rng.Next(hits.GetArrayLength())];
|
|
||||||
string imgUrl = hit.GetProperty("webformatURL").GetString()!;
|
|
||||||
int width = hit.TryGetProperty("webformatWidth", out var w) ? w.GetInt32() : 0;
|
|
||||||
int height = hit.TryGetProperty("webformatHeight", out var h) ? h.GetInt32() : 0;
|
|
||||||
string ext = Path.GetExtension(imgUrl.Split('?')[0]).ToLowerInvariant();
|
|
||||||
string mime = ext == ".png" ? "image/png" : ext == ".gif" ? "image/gif" : "image/jpeg";
|
|
||||||
|
|
||||||
return new ImageResult(imgUrl, width, height, mime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<ImageResult?> FetchWikimediaAsync(string query)
|
|
||||||
{
|
|
||||||
string url = "https://commons.wikimedia.org/w/api.php" +
|
|
||||||
$"?action=query&generator=search&gsrsearch=intitle:{Uri.EscapeDataString(query)}" +
|
|
||||||
"&gsrnamespace=6&gsrlimit=30&prop=imageinfo&iiprop=url|size|mime&format=json";
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "SkyBot/1.0 (https://github.com/SkyJoshua/SkyBot)");
|
|
||||||
json = await _http.GetStringAsync(url);
|
|
||||||
}
|
|
||||||
catch { return null; }
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
if (!doc.RootElement.TryGetProperty("query", out var queryEl) ||
|
|
||||||
!queryEl.TryGetProperty("pages", out var pages)) return null;
|
|
||||||
|
|
||||||
var candidates = new List<ImageResult>();
|
|
||||||
foreach (var page in pages.EnumerateObject())
|
|
||||||
{
|
|
||||||
if (!page.Value.TryGetProperty("imageinfo", out var infoArr)) continue;
|
|
||||||
var info = infoArr[0];
|
|
||||||
|
|
||||||
string mime = info.TryGetProperty("mime", out var m) ? m.GetString() ?? "" : "";
|
|
||||||
if (mime is not ("image/jpeg" or "image/png" or "image/gif" or "image/webp")) continue;
|
|
||||||
|
|
||||||
string imgUrl = info.TryGetProperty("url", out var u) ? u.GetString() ?? "" : "";
|
|
||||||
int width = info.TryGetProperty("width", out var w) ? w.GetInt32() : 0;
|
|
||||||
int height = info.TryGetProperty("height", out var h) ? h.GetInt32() : 0;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(imgUrl))
|
|
||||||
candidates.Add(new ImageResult(imgUrl, width, height, mime));
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates.Count == 0 ? null : candidates[_rng.Next(candidates.Count)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
@@ -9,43 +8,37 @@ namespace SkyBot.Commands
|
|||||||
{
|
{
|
||||||
public string Name => "mock";
|
public string Name => "mock";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
public string Description => "Mock text";
|
public string Description => "Mocks the text a user has sent or entered text";
|
||||||
public string Section => "Fun";
|
public string Category => "Fun";
|
||||||
public string Usage => "mock [text] (Or reply to a message)";
|
public string Usage => "mock <text|reply>";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string text;
|
string text;
|
||||||
|
|
||||||
if (message.ReplyToId.HasValue)
|
if (ctx.Message.ReplyToId.HasValue)
|
||||||
{
|
{
|
||||||
var replyMessage = await message.FetchReplyMessageAsync();
|
var replyMessage = await ctx.Message.FetchReplyMessageAsync();
|
||||||
text = replyMessage?.Content ?? "";
|
text = replyMessage?.Content ?? "";
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
if (args.Length == 0)
|
if (ctx.Args.Length == 0)
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide some text to mock or reply to a message.");
|
await MessageHelper.ReplyAsync(ctx, "Please provide some text to mock or reply to a message.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
text = string.Join(" ", args);
|
text = string.Join(" ", ctx.Args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "No text to mock.");
|
await MessageHelper.ReplyAsync(ctx, "No text to mock.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string mocked = new string(text.Select((c, i) => i % 2 == 0 ? char.ToLower(c) : char.ToUpper(c)).ToArray());
|
string mocked = new string(text.Select((c, i) => i % 2 == 0 ? char.ToLower(c) : char.ToUpper(c)).ToArray());
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, mocked);
|
await MessageHelper.ReplyAsync(ctx, mocked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Reverse : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "reverse";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Reverses yours or a replied text.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "reverse [text] (Or reply to a message)";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string text;
|
|
||||||
|
|
||||||
if (message.ReplyToId.HasValue)
|
|
||||||
{
|
|
||||||
var replyMessage = await message.FetchReplyMessageAsync();
|
|
||||||
text = replyMessage?.Content ?? "";
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide some text to reverse or reply to a message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
text = string.Join(" ", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "No text to reverse.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string reversed = new string(text.Reverse().ToArray());
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Reversed: {reversed}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class RockPaperScissors : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "rps";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Play Rock Paper Scissors against the bot.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "rps <rock|paper|scissors>";
|
|
||||||
|
|
||||||
private static readonly string[] Choices = ["rock", "paper", "scissors"];
|
|
||||||
private static readonly string[] Emojis = ["🪨", "📄", "✂️"];
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please choose `Rock`, `Paper`, or `Scissors`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string input = args[0].ToLower();
|
|
||||||
int playerIndex = Array.IndexOf(Choices, input);
|
|
||||||
|
|
||||||
if (playerIndex == -1)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid choice. Please choose `Rock`, `Paper`, or `Scissors`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskResult<Message> result = await MessageHelper.ReplyAsync(ctx, channel, "🤔 Thinking...");
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
int botIndex = Random.Shared.Next(3);
|
|
||||||
|
|
||||||
string playerChoice = $"{Emojis[playerIndex]} {Choices[playerIndex].ToTitleCase()}";
|
|
||||||
string botChoice = $"{Emojis[botIndex]} {Choices[botIndex].ToTitleCase()}";
|
|
||||||
|
|
||||||
string outcome;
|
|
||||||
if (playerIndex == botIndex) outcome = "It's a tie!";
|
|
||||||
else if ((playerIndex == 0 && botIndex == 2) ||
|
|
||||||
(playerIndex == 1 && botIndex == 0) ||
|
|
||||||
(playerIndex == 2 && botIndex == 1)) outcome = "You win! 🎉";
|
|
||||||
else outcome = "You Lose! 🥲";
|
|
||||||
|
|
||||||
await MessageHelper.EditAsync(channel, result.Data, $"**You**: {playerChoice}\nvs\n**Bot**: {botChoice}\n[]()\n{outcome}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class MultiTap : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "t9decode";
|
|
||||||
public string[] Aliases => ["t9d"];
|
|
||||||
public string Description => "Decodes old phone keypad multi-tap input into text.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "multitap <digits> (e.g. 44 3 555 555 666 or reply to a message)";
|
|
||||||
|
|
||||||
private static readonly Dictionary<char, string> Keymap = new()
|
|
||||||
{
|
|
||||||
['2'] = "ABC",
|
|
||||||
['3'] = "DEF",
|
|
||||||
['4'] = "GHI",
|
|
||||||
['5'] = "JKL",
|
|
||||||
['6'] = "MNO",
|
|
||||||
['7'] = "PQRS",
|
|
||||||
['8'] = "TUV",
|
|
||||||
['9'] = "WXYZ",
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string raw;
|
|
||||||
|
|
||||||
if (message.ReplyToId.HasValue)
|
|
||||||
{
|
|
||||||
var replyMessage = await message.FetchReplyMessageAsync();
|
|
||||||
raw = replyMessage?.Content ?? "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide digits to decode, or reply to a message. Example: `multitap 44 3 555 555 666`");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
raw = string.Join("", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip anything that isn't a digit
|
|
||||||
string input = new([..raw.Where(char.IsDigit)]);
|
|
||||||
|
|
||||||
var result = new StringBuilder();
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
while (i < input.Length)
|
|
||||||
{
|
|
||||||
char digit = input[i];
|
|
||||||
|
|
||||||
if (digit == '0')
|
|
||||||
{
|
|
||||||
result.Append(' ');
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1 = silent same-key separator if surrounded by the same digit, otherwise a space
|
|
||||||
if (digit == '1')
|
|
||||||
{
|
|
||||||
int j = i;
|
|
||||||
while (j < input.Length && input[j] == '1') j++;
|
|
||||||
|
|
||||||
char before = i > 0 ? input[i - 1] : '\0';
|
|
||||||
char after = j < input.Length ? input[j] : '\0';
|
|
||||||
bool sameKey = before >= '2' && before <= '9' && before == after;
|
|
||||||
|
|
||||||
if (!sameKey) result.Append(' ');
|
|
||||||
i = j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Keymap.TryGetValue(digit, out string? letters))
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count consecutive presses of the same digit
|
|
||||||
int count = 0;
|
|
||||||
while (i + count < input.Length && input[i + count] == digit)
|
|
||||||
count++;
|
|
||||||
|
|
||||||
int letterIndex = (count - 1) % letters.Length;
|
|
||||||
result.Append(letters[letterIndex]);
|
|
||||||
i += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
string decoded = result.ToString().Trim();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(decoded))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Couldn't decode anything from that input.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"📱 **{decoded}**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class T9Encode : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "t9encode";
|
|
||||||
public string[] Aliases => ["t9e"];
|
|
||||||
public string Description => "Encodes text into old phone keypad multi-tap digits.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "t9encode <text> (or reply to a message)";
|
|
||||||
|
|
||||||
// Maps each letter to (key digit, press count)
|
|
||||||
private static readonly Dictionary<char, (char Key, int Presses)> Charmap = BuildCharmap();
|
|
||||||
|
|
||||||
private static Dictionary<char, (char Key, int Presses)> BuildCharmap()
|
|
||||||
{
|
|
||||||
Dictionary<char, string> keymap = new()
|
|
||||||
{
|
|
||||||
['2'] = "ABC",
|
|
||||||
['3'] = "DEF",
|
|
||||||
['4'] = "GHI",
|
|
||||||
['5'] = "JKL",
|
|
||||||
['6'] = "MNO",
|
|
||||||
['7'] = "PQRS",
|
|
||||||
['8'] = "TUV",
|
|
||||||
['9'] = "WXYZ",
|
|
||||||
};
|
|
||||||
|
|
||||||
var map = new Dictionary<char, (char, int)>();
|
|
||||||
foreach (var (key, letters) in keymap)
|
|
||||||
for (int i = 0; i < letters.Length; i++)
|
|
||||||
map[letters[i]] = (key, i + 1);
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string raw;
|
|
||||||
|
|
||||||
if (message.ReplyToId.HasValue)
|
|
||||||
{
|
|
||||||
var replyMessage = await message.FetchReplyMessageAsync();
|
|
||||||
raw = replyMessage?.Content ?? "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide text to encode, or reply to a message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
raw = string.Join(" ", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(raw))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "No text to encode.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new StringBuilder();
|
|
||||||
char prevKey = '\0';
|
|
||||||
|
|
||||||
foreach (char c in raw.ToUpper())
|
|
||||||
{
|
|
||||||
if (c == ' ')
|
|
||||||
{
|
|
||||||
result.Append('0');
|
|
||||||
prevKey = '\0';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Charmap.TryGetValue(c, out var entry))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// If this letter uses the same key as the previous one, insert a 1 separator
|
|
||||||
if (entry.Key == prevKey)
|
|
||||||
result.Append('1');
|
|
||||||
|
|
||||||
result.Append(entry.Key, entry.Presses);
|
|
||||||
prevKey = entry.Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
string encoded = result.ToString().Trim('0');
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(encoded))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Couldn't encode anything from that input.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"📱 `{encoded}`");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Client;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Trivia : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "trivia";
|
|
||||||
public string[] Aliases => ["triv"];
|
|
||||||
public string Description => "Starts a channel-wide trivia question. Everyone has 30 seconds to guess.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "trivia [easy|medium|hard] [topic] | trivia topics";
|
|
||||||
|
|
||||||
private record GuessEntry(char Letter, string MemberName);
|
|
||||||
|
|
||||||
private record TriviaSession(
|
|
||||||
char CorrectLetter,
|
|
||||||
string QuestionText,
|
|
||||||
List<string> Answers,
|
|
||||||
Message BotMessage,
|
|
||||||
Channel Channel,
|
|
||||||
ValourClient Client,
|
|
||||||
ConcurrentDictionary<long, GuessEntry> Guesses);
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<long, TriviaSession> _sessions = new();
|
|
||||||
private static readonly HttpClient _http = new();
|
|
||||||
|
|
||||||
private static readonly string[] Difficulties = ["easy", "medium", "hard"];
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, int> Categories = new()
|
|
||||||
{
|
|
||||||
["general"] = 9,
|
|
||||||
["books"] = 10,
|
|
||||||
["film"] = 11,
|
|
||||||
["movies"] = 11,
|
|
||||||
["music"] = 12,
|
|
||||||
["musicals"] = 13,
|
|
||||||
["tv"] = 14,
|
|
||||||
["television"] = 14,
|
|
||||||
["games"] = 15,
|
|
||||||
["videogames"] = 15,
|
|
||||||
["boardgames"] = 16,
|
|
||||||
["science"] = 17,
|
|
||||||
["nature"] = 17,
|
|
||||||
["computers"] = 18,
|
|
||||||
["tech"] = 18,
|
|
||||||
["math"] = 19,
|
|
||||||
["maths"] = 19,
|
|
||||||
["mythology"] = 20,
|
|
||||||
["sports"] = 21,
|
|
||||||
["geography"] = 22,
|
|
||||||
["geo"] = 22,
|
|
||||||
["history"] = 23,
|
|
||||||
["politics"] = 24,
|
|
||||||
["art"] = 25,
|
|
||||||
["celebrities"] = 26,
|
|
||||||
["animals"] = 27,
|
|
||||||
["vehicles"] = 28,
|
|
||||||
["cars"] = 28,
|
|
||||||
["comics"] = 29,
|
|
||||||
["anime"] = 31,
|
|
||||||
["manga"] = 31,
|
|
||||||
["cartoons"] = 32,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static async Task ProcessGuessAsync(CommandContext ctx, Channel channel, string rawGuess)
|
|
||||||
{
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!_sessions.TryGetValue(channelId, out var session))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's no active trivia question in this channel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.Guesses.ContainsKey(ctx.Message.AuthorUserId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You've already submitted an answer!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(rawGuess))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a letter. `A, B, C, or D`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char given = char.ToUpper(rawGuess[0]);
|
|
||||||
if (given < 'A' || given > 'D')
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid choice. Please guess A, B, C, or D.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Guesses[ctx.Message.AuthorUserId] = new GuessEntry(given, ctx.Member.Name ?? "Unknown");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "📬 Answer submitted!");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
// sd/trivia topics
|
|
||||||
if (args.Length >= 1 && (args[0].ToLower() == "topics" || args[0].ToLower() == "t"))
|
|
||||||
{
|
|
||||||
string topicList = string.Join(", ", Categories.Keys.Order());
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"**Available topics:** {topicList}\n**Difficulties:** easy, medium, hard");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sd/trivia — fetch a new question
|
|
||||||
if (_sessions.ContainsKey(channelId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's already an active trivia question in this channel!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? difficulty = null;
|
|
||||||
int? categoryId = null;
|
|
||||||
|
|
||||||
foreach (string arg in args.Select(a => a.ToLower()))
|
|
||||||
{
|
|
||||||
if (Difficulties.Contains(arg))
|
|
||||||
difficulty = arg;
|
|
||||||
else if (Categories.TryGetValue(arg, out int id))
|
|
||||||
categoryId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick a random category if none was specified
|
|
||||||
if (categoryId is null)
|
|
||||||
{
|
|
||||||
var ids = Categories.Values.Distinct().ToArray();
|
|
||||||
categoryId = ids[Random.Shared.Next(ids.Length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
string url = "https://opentdb.com/api.php?amount=1&type=multiple";
|
|
||||||
if (difficulty is not null) url += $"&difficulty={difficulty}";
|
|
||||||
if (categoryId is not null) url += $"&category={categoryId}";
|
|
||||||
|
|
||||||
string rawJson;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
rawJson = await _http.GetStringAsync(url);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Failed to fetch a trivia question. Try again in a moment.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(rawJson);
|
|
||||||
var result = doc.RootElement.GetProperty("results")[0];
|
|
||||||
|
|
||||||
string question = WebUtility.HtmlDecode(result.GetProperty("question").GetString()!);
|
|
||||||
string correct = WebUtility.HtmlDecode(result.GetProperty("correct_answer").GetString()!);
|
|
||||||
string category = WebUtility.HtmlDecode(result.GetProperty("category").GetString()!);
|
|
||||||
string fetchedDifficulty = result.GetProperty("difficulty").GetString() ?? "unknown";
|
|
||||||
|
|
||||||
List<string> answers = result.GetProperty("incorrect_answers").EnumerateArray()
|
|
||||||
.Select(x => WebUtility.HtmlDecode(x.GetString()!))
|
|
||||||
.Append(correct)
|
|
||||||
.OrderBy(_ => Random.Shared.Next())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
char correctLetter = (char)('A' + answers.IndexOf(correct));
|
|
||||||
|
|
||||||
string questionText = string.Join("\n",
|
|
||||||
$"**Category**: {category} | **Difficulty**: {fetchedDifficulty.ToTitleCase()}",
|
|
||||||
"",
|
|
||||||
$"**{question}**",
|
|
||||||
"",
|
|
||||||
string.Join("\n", answers.Select((a, i) => $"{(char)('A' + i)}) {a}")),
|
|
||||||
"",
|
|
||||||
$"*Use `{Config.Prefix}tg <A/B/C/D>` — you have 30 seconds!*"
|
|
||||||
);
|
|
||||||
|
|
||||||
var sent = await MessageHelper.ReplyAsync(ctx, channel, questionText);
|
|
||||||
if (!sent.Success || sent.Data is null) return;
|
|
||||||
|
|
||||||
Message botMessage = ctx.Client.Cache.Messages.TryGet(sent.Data.Id, out var cachedSent) && cachedSent is not null
|
|
||||||
? cachedSent : sent.Data;
|
|
||||||
|
|
||||||
var newSession = new TriviaSession(correctLetter, questionText, answers, botMessage, channel, ctx.Client, new());
|
|
||||||
_sessions[channelId] = newSession;
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(30_000);
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
|
|
||||||
List<string> corrects = [..newSession.Guesses
|
|
||||||
.Where(kv => kv.Value.Letter == newSession.CorrectLetter)
|
|
||||||
.Select(kv => kv.Value.MemberName)];
|
|
||||||
|
|
||||||
List<string> wrongs = [..newSession.Guesses
|
|
||||||
.Where(kv => kv.Value.Letter != newSession.CorrectLetter)
|
|
||||||
.Select(kv => kv.Value.MemberName)];
|
|
||||||
|
|
||||||
string correctAnswer = newSession.Answers[newSession.CorrectLetter - 'A'];
|
|
||||||
string resultsText = $"⏰ **Time's up!** The answer was **{newSession.CorrectLetter}) {correctAnswer}**\n";
|
|
||||||
|
|
||||||
if (corrects.Count > 0)
|
|
||||||
resultsText += $"\n✅ **Correct:** {string.Join(", ", corrects)}";
|
|
||||||
if (wrongs.Count > 0)
|
|
||||||
resultsText += $"\n❌ **Wrong:** {string.Join(", ", wrongs)}";
|
|
||||||
if (newSession.Guesses.IsEmpty)
|
|
||||||
resultsText += "\n😶 Nobody answered!";
|
|
||||||
|
|
||||||
var resultMsg = new Message(newSession.Client)
|
|
||||||
{
|
|
||||||
Content = resultsText,
|
|
||||||
ChannelId = newSession.Channel.Id,
|
|
||||||
PlanetId = newSession.Channel.Planet!.Id,
|
|
||||||
AuthorUserId = newSession.Client.Me.Id,
|
|
||||||
AuthorMemberId = newSession.Channel.Planet?.MyMember.Id,
|
|
||||||
ReplyToId = newSession.BotMessage.Id,
|
|
||||||
Fingerprint = Guid.NewGuid().ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
await newSession.Client.MessageService.SendMessage(resultMsg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TriviaGuess : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "tg";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Submit your answer to the active Trivia question.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "tg <A/B/C/D>";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
if (!ctx.ChannelCache.TryGetValue(ctx.ChannelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (ctx.Args.Length == 0 || string.IsNullOrWhiteSpace(ctx.Args[0]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a letter. `A, B, C, or D`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Trivia.ProcessGuessAsync(ctx, channel, ctx.Args[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Client;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Wordle : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "wordle";
|
|
||||||
public string[] Aliases => ["wd"];
|
|
||||||
public string Description => "Starts a channel-wide game of Wordle. Guess the 5-letter word in 6 tries!";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "wordle | wordle board";
|
|
||||||
|
|
||||||
private record WordleSession(
|
|
||||||
string Word,
|
|
||||||
List<string> Guesses,
|
|
||||||
List<string[]> Feedback,
|
|
||||||
HashSet<string> Contributors,
|
|
||||||
long StarterId,
|
|
||||||
Message BotMessage,
|
|
||||||
Channel Channel,
|
|
||||||
ValourClient Client);
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<long, WordleSession> _sessions = new();
|
|
||||||
private static readonly HttpClient _http = new();
|
|
||||||
private static readonly SemaphoreSlim _fetchLock = new(1, 1);
|
|
||||||
private static string[]? _cachedWords;
|
|
||||||
|
|
||||||
private const int MaxGuesses = 6;
|
|
||||||
private const int WordLength = 5;
|
|
||||||
|
|
||||||
private static readonly string[] WordList =
|
|
||||||
[
|
|
||||||
"ABOUT", "ABUSE", "ACUTE", "ADMIT", "ADOPT", "AFTER", "AGENT", "AGREE",
|
|
||||||
"AHEAD", "ALIKE", "ALIVE", "ALONE", "ALONG", "ALTER", "ANGEL", "ANGER",
|
|
||||||
"ANGLE", "APART", "APPLE", "APPLY", "ARGUE", "ARISE", "ARMOR", "ASIDE",
|
|
||||||
"ASSET", "AVOID", "AWARD", "AWARE", "BADLY", "BASIC", "BEACH", "BEARD",
|
|
||||||
"BEGAN", "BEGIN", "BEING", "BELOW", "BENCH", "BLACK", "BLADE", "BLAME",
|
|
||||||
"BLANK", "BLAST", "BLAZE", "BLEED", "BLESS", "BLIND", "BLOCK", "BLOOD",
|
|
||||||
"BLOOM", "BLUNT", "BOARD", "BOOST", "BOUND", "BRAND", "BRAVE", "BREAD",
|
|
||||||
"BREAK", "BREED", "BRICK", "BRIDE", "BRIEF", "BRING", "BROAD", "BROOK",
|
|
||||||
"BROWN", "BRUSH", "BUILD", "BUILT", "BURST", "BUYER", "CABIN", "CABLE",
|
|
||||||
"CANDY", "CARRY", "CHAIN", "CHAIR", "CHAOS", "CHARM", "CHEAP", "CHECK",
|
|
||||||
"CHESS", "CHEST", "CHIEF", "CHILD", "CHILL", "CIVIC", "CIVIL", "CLAIM",
|
|
||||||
"CLASS", "CLEAN", "CLEAR", "CLIMB", "CLOCK", "CLOSE", "CLOUD", "COACH",
|
|
||||||
"COAST", "COUNT", "COURT", "COVER", "CRACK", "CRAFT", "CRANE", "CRAZY",
|
|
||||||
"CREAM", "CREEK", "CRIME", "CROSS", "CROWD", "CRUSH", "CURVE", "CYCLE",
|
|
||||||
"DAILY", "DANCE", "DEATH", "DEBUT", "DENSE", "DEPOT", "DEPTH", "DERBY",
|
|
||||||
"DINER", "DIRTY", "DISCO", "DITCH", "DIZZY", "DOUBT", "DOUGH", "DRAFT",
|
|
||||||
"DRAIN", "DRAMA", "DRANK", "DREAM", "DRESS", "DRIFT", "DRINK", "DRIVE",
|
|
||||||
"DRONE", "DROVE", "DROWN", "DRUNK", "DYING", "EAGER", "EAGLE", "EARLY",
|
|
||||||
"EARTH", "EIGHT", "ELITE", "EMPTY", "ENEMY", "ENJOY", "ENTER", "ENTRY",
|
|
||||||
"EQUAL", "ERROR", "ESSAY", "EVENT", "EVERY", "EXACT", "EXIST", "EXTRA",
|
|
||||||
"FABLE", "FAINT", "FAITH", "FALSE", "FANCY", "FATAL", "FAULT", "FEAST",
|
|
||||||
"FENCE", "FEVER", "FIELD", "FIERY", "FIFTH", "FIFTY", "FIGHT", "FINAL",
|
|
||||||
"FIRST", "FIXED", "FLAME", "FLASH", "FLEET", "FLESH", "FLOAT", "FLOCK",
|
|
||||||
"FLOOD", "FLOOR", "FLUSH", "FOCUS", "FORCE", "FORGE", "FORTH", "FOUND",
|
|
||||||
"FRAME", "FRANK", "FRAUD", "FRESH", "FRONT", "FROST", "FRUIT", "FULLY",
|
|
||||||
"FUNNY", "GIANT", "GIVEN", "GLASS", "GLEAM", "GLOBE", "GLOOM", "GLORY",
|
|
||||||
"GLOVE", "GOING", "GRACE", "GRADE", "GRAIN", "GRAND", "GRANT", "GRAPE",
|
|
||||||
"GRASP", "GRASS", "GRAVE", "GREAT", "GREEN", "GREET", "GRIEF", "GRIND",
|
|
||||||
"GROAN", "GROOM", "GROSS", "GROUP", "GROVE", "GROWN", "GUARD", "GUESS",
|
|
||||||
"GUIDE", "GUILT", "GUISE", "HARSH", "HEART", "HEAVY", "HENCE", "HERBS",
|
|
||||||
"HINGE", "HONEY", "HONOR", "HORSE", "HOTEL", "HOUSE", "HUMAN", "HURRY",
|
|
||||||
"IDEAL", "IMAGE", "INNER", "INPUT", "ISSUE", "IVORY", "JEWEL", "JOINT",
|
|
||||||
"JUDGE", "JUICE", "JUICY", "JUMBO", "KARMA", "KNACK", "KNEEL", "KNIFE",
|
|
||||||
"KNOCK", "KNOWN", "LABEL", "LANCE", "LARGE", "LASER", "LATER", "LAUGH",
|
|
||||||
"LAYER", "LEARN", "LEASE", "LEAST", "LEGAL", "LEMON", "LEVEL", "LIGHT",
|
|
||||||
"LIMIT", "LIVER", "LOCAL", "LODGE", "LOGIC", "LOOSE", "LOVER", "LOWER",
|
|
||||||
"LUCKY", "LUNAR", "MAGIC", "MAJOR", "MAKER", "MANOR", "MAPLE", "MARCH",
|
|
||||||
"MATCH", "MAYOR", "MEDIA", "MERCY", "MERIT", "METAL", "MIGHT", "MINOR",
|
|
||||||
"MINUS", "MODEL", "MONEY", "MONTH", "MORAL", "MOTOR", "MOUNT", "MOUSE",
|
|
||||||
"MOUTH", "MOVIE", "MUSIC", "NAIVE", "NERVE", "NEVER", "NIGHT", "NINJA",
|
|
||||||
"NOBLE", "NOISE", "NORTH", "NOVEL", "NURSE", "NYMPH", "OCEAN", "OFFER",
|
|
||||||
"OFTEN", "OLIVE", "ONSET", "OPERA", "ORDER", "OTHER", "OUTER", "OWNED",
|
|
||||||
"OWNER", "OZONE", "PAINT", "PANIC", "PAPER", "PARTY", "PEACE", "PEACH",
|
|
||||||
"PEARL", "PENNY", "PHASE", "PHONE", "PHOTO", "PIANO", "PIECE", "PILOT",
|
|
||||||
"PIXEL", "PLACE", "PLAIN", "PLANE", "PLANT", "PLATE", "PLAZA", "PLEAD",
|
|
||||||
"PLUMB", "PLUMP", "POINT", "POLAR", "POPPY", "POWER", "PRESS", "PRICE",
|
|
||||||
"PRIDE", "PRIME", "PRINT", "PRIOR", "PRISM", "PROBE", "PROOF", "PROSE",
|
|
||||||
"PROUD", "PROVE", "PULSE", "PUPIL", "QUEEN", "QUERY", "QUEST", "QUEUE",
|
|
||||||
"QUIET", "QUOTA", "QUOTE", "RADAR", "RADIO", "RAISE", "RALLY", "RANGE",
|
|
||||||
"RAPID", "RATIO", "REACH", "READY", "REALM", "REBEL", "REFER", "REIGN",
|
|
||||||
"RELAX", "REPLY", "RIDER", "RIDGE", "RISKY", "RIVER", "ROBIN", "ROBOT",
|
|
||||||
"ROCKY", "ROUGH", "ROUND", "ROUTE", "ROYAL", "RULER", "RURAL", "SADLY",
|
|
||||||
"SAINT", "SALAD", "SAUCE", "SCALE", "SCENE", "SCENT", "SCOUT", "SENSE",
|
|
||||||
"SEVEN", "SHADE", "SHAFT", "SHALL", "SHAME", "SHAPE", "SHARE", "SHARK",
|
|
||||||
"SHARP", "SHEEP", "SHEER", "SHELF", "SHELL", "SHIFT", "SHINE", "SHIRT",
|
|
||||||
"SHOCK", "SHOOT", "SHORT", "SHOUT", "SIEGE", "SIGHT", "SILLY", "SINCE",
|
|
||||||
"SIXTH", "SIXTY", "SKILL", "SKULL", "SLATE", "SLAVE", "SLEEP", "SLICE",
|
|
||||||
"SLIDE", "SLOPE", "SMALL", "SMART", "SMELL", "SMILE", "SMOKE", "SNAKE",
|
|
||||||
"SOLAR", "SOLID", "SOLVE", "SORRY", "SOUND", "SOUTH", "SPACE", "SPARE",
|
|
||||||
"SPARK", "SPEAK", "SPEAR", "SPEED", "SPEND", "SPICE", "SPINE", "SPITE",
|
|
||||||
"SPLIT", "SPOKE", "SPOON", "SPORT", "SPRAY", "SQUAD", "STACK", "STAFF",
|
|
||||||
"STAGE", "STAIN", "STAIR", "STAKE", "STAND", "STARK", "STATE", "STEAM",
|
|
||||||
"STEEL", "STEEP", "STEER", "STERN", "STICK", "STIFF", "STILL", "STOCK",
|
|
||||||
"STOMP", "STONE", "STORE", "STORM", "STORY", "STRAP", "STRAW", "STRAY",
|
|
||||||
"STRIP", "STUCK", "STUDY", "STUFF", "STYLE", "SUGAR", "SUITE", "SUNNY",
|
|
||||||
"SUPER", "SURGE", "SWAMP", "SWEAR", "SWEEP", "SWEET", "SWIFT", "SWIPE",
|
|
||||||
"SWORD", "TABLE", "TASTE", "TEACH", "TEARS", "TEMPT", "TENSE", "TENTH",
|
|
||||||
"THEFT", "THEIR", "THERE", "THICK", "THING", "THINK", "THIRD", "THREE",
|
|
||||||
"THREW", "THROW", "TIGER", "TIGHT", "TIMER", "TIRED", "TITAN", "TITLE",
|
|
||||||
"TOKEN", "TOPIC", "TOTAL", "TOUCH", "TOUGH", "TOWER", "TOXIC", "TRACE",
|
|
||||||
"TRACK", "TRADE", "TRAIL", "TRAIN", "TRAIT", "TRASH", "TREND", "TRIAL",
|
|
||||||
"TRIBE", "TRICK", "TRIED", "TROOP", "TROUT", "TRUCK", "TRULY", "TRUNK",
|
|
||||||
"TRUST", "TRUTH", "TWIST", "ULTRA", "UNCLE", "UNDER", "UNION", "UNITY",
|
|
||||||
"UNTIL", "UPPER", "UPSET", "URBAN", "USAGE", "USHER", "USUAL", "UTTER",
|
|
||||||
"VAGUE", "VALID", "VALUE", "VAPOR", "VAULT", "VIGOR", "VIRAL", "VISIT",
|
|
||||||
"VITAL", "VIVID", "VOCAL", "VOICE", "VOTER", "WAGER", "WATCH", "WATER",
|
|
||||||
"WEARY", "WEAVE", "WEDGE", "WEIRD", "WHERE", "WHILE", "WHITE", "WHOLE",
|
|
||||||
"WIDER", "WITCH", "WOMAN", "WOMEN", "WORLD", "WORRY", "WORSE", "WORST",
|
|
||||||
"WORTH", "WOULD", "WRATH", "WRITE", "WRONG", "YACHT", "YIELD", "YOUNG",
|
|
||||||
"YOUTH", "ZEBRA",
|
|
||||||
];
|
|
||||||
|
|
||||||
private static async Task<string[]> GetWordPoolAsync()
|
|
||||||
{
|
|
||||||
if (_cachedWords is not null) return _cachedWords;
|
|
||||||
|
|
||||||
await _fetchLock.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_cachedWords is not null) return _cachedWords;
|
|
||||||
|
|
||||||
string json = await _http.GetStringAsync(
|
|
||||||
"https://api.datamuse.com/words?sp=?????&max=1000&md=f");
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var words = doc.RootElement.EnumerateArray()
|
|
||||||
.Select(e => e.GetProperty("word").GetString() ?? "")
|
|
||||||
.Where(w => w.Length == WordLength && w.All(char.IsLetter))
|
|
||||||
.Select(w => w.ToUpper())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (words.Length > 0)
|
|
||||||
{
|
|
||||||
_cachedWords = words;
|
|
||||||
return words;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_fetchLock.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
return WordList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<bool> IsValidWordAsync(string word)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string json = await _http.GetStringAsync(
|
|
||||||
$"https://api.datamuse.com/words?sp={word.ToLower()}&max=1");
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var arr = doc.RootElement;
|
|
||||||
if (arr.GetArrayLength() > 0)
|
|
||||||
{
|
|
||||||
string? returned = arr[0].GetProperty("word").GetString();
|
|
||||||
return string.Equals(returned, word, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns per-letter emoji feedback using standard Wordle rules
|
|
||||||
private static string[] GetFeedback(string guess, string word)
|
|
||||||
{
|
|
||||||
var result = new string[WordLength];
|
|
||||||
var wordLeft = word.ToCharArray();
|
|
||||||
var guessLeft = guess.ToCharArray();
|
|
||||||
|
|
||||||
// First pass: greens
|
|
||||||
for (int i = 0; i < WordLength; i++)
|
|
||||||
{
|
|
||||||
if (guessLeft[i] == wordLeft[i])
|
|
||||||
{
|
|
||||||
result[i] = "🟩";
|
|
||||||
wordLeft[i] = '\0';
|
|
||||||
guessLeft[i] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second pass: yellows and grays
|
|
||||||
for (int i = 0; i < WordLength; i++)
|
|
||||||
{
|
|
||||||
if (guessLeft[i] == '\0') continue;
|
|
||||||
|
|
||||||
int idx = Array.IndexOf(wordLeft, guessLeft[i]);
|
|
||||||
if (idx >= 0)
|
|
||||||
{
|
|
||||||
result[i] = "🟨";
|
|
||||||
wordLeft[idx] = '\0';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result[i] = "⬛";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildDisplay(WordleSession session)
|
|
||||||
{
|
|
||||||
var rows = new List<string>();
|
|
||||||
|
|
||||||
for (int i = 0; i < MaxGuesses; i++)
|
|
||||||
{
|
|
||||||
if (i < session.Guesses.Count)
|
|
||||||
{
|
|
||||||
string emojis = string.Join("", session.Feedback[i]);
|
|
||||||
string letters = string.Join(" ", session.Guesses[i].ToCharArray());
|
|
||||||
rows.Add($"{emojis} {letters}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rows.Add("⬜⬜⬜⬜⬜");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Join("\n",
|
|
||||||
"🟩 **WORDLE** — Guess the 5-letter word!",
|
|
||||||
"",
|
|
||||||
string.Join("\n", rows),
|
|
||||||
"",
|
|
||||||
$"Guesses: {session.Guesses.Count}/{MaxGuesses}",
|
|
||||||
"",
|
|
||||||
$"*Use `{Config.Prefix}wg <word>` to guess!*"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task ProcessGuessAsync(CommandContext ctx, Channel channel, string rawGuess)
|
|
||||||
{
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
if (!_sessions.TryGetValue(channelId, out var session)) return;
|
|
||||||
|
|
||||||
string guess = rawGuess.ToUpper();
|
|
||||||
string memberName = ctx.Member.Name ?? "Unknown";
|
|
||||||
|
|
||||||
if (guess.Length != WordLength || !guess.All(char.IsLetter))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Your guess must be exactly {WordLength} letters with no numbers or symbols.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await IsValidWordAsync(guess))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"`{guess}` isn't a valid word!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.Guesses.Contains(guess))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"`{guess}` has already been guessed!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var feedback = GetFeedback(guess, session.Word);
|
|
||||||
session.Guesses.Add(guess);
|
|
||||||
session.Feedback.Add(feedback);
|
|
||||||
session.Contributors.Add(memberName);
|
|
||||||
|
|
||||||
bool won = guess == session.Word;
|
|
||||||
bool lost = !won && session.Guesses.Count >= MaxGuesses;
|
|
||||||
|
|
||||||
if (won)
|
|
||||||
{
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
string contributorList = string.Join(", ", session.Contributors);
|
|
||||||
await RepostBoardAsync(ctx, channel, session,
|
|
||||||
BuildDisplay(session)
|
|
||||||
+ $"\n\n🎉 **{memberName} got it in {session.Guesses.Count}!**\nContributors: {contributorList}");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"🎉 **The word was `{session.Word}`!** Got it in {session.Guesses.Count}/{MaxGuesses}!");
|
|
||||||
}
|
|
||||||
else if (lost)
|
|
||||||
{
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
await RepostBoardAsync(ctx, channel, session,
|
|
||||||
BuildDisplay(session)
|
|
||||||
+ $"\n\n💀 **Game over!** The word was `{session.Word}`.");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"💀 **Game over!** The word was `{session.Word}`.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var newMsg = await RepostBoardAsync(ctx, channel, session, BuildDisplay(session));
|
|
||||||
if (newMsg is not null)
|
|
||||||
_sessions[channelId] = session with { BotMessage = newMsg };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Message?> RepostBoardAsync(CommandContext ctx, Channel channel, WordleSession session, string content)
|
|
||||||
{
|
|
||||||
if (ctx.Client.Cache.Messages.TryGet(session.BotMessage.Id, out var old) && old is not null)
|
|
||||||
try { await old.DeleteAsync(); } catch { }
|
|
||||||
else
|
|
||||||
try { await session.BotMessage.DeleteAsync(); } catch { }
|
|
||||||
|
|
||||||
var result = await MessageHelper.ReplyAsync(ctx, channel, content);
|
|
||||||
if (!result.Success || result.Data is null) return null;
|
|
||||||
return ctx.Client.Cache.Messages.TryGet(result.Data.Id, out var cached) && cached is not null ? cached : result.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
// sd/wordle end — end the current game
|
|
||||||
if (args.Length >= 1 && args[0].ToLower() == "end")
|
|
||||||
{
|
|
||||||
if (!_sessions.TryGetValue(channelId, out var session))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's no active Wordle game in this channel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isStarter = member.UserId == session.StarterId;
|
|
||||||
bool isMod = await PermissionHelper.HasPermAsync(member, channel, [ChatChannelPermissions.ManageMessages]);
|
|
||||||
|
|
||||||
if (!isStarter && !isMod)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Only the person who started the game (or a moderator) can end it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sessions.TryRemove(channelId, out _);
|
|
||||||
if (ctx.Client.Cache.Messages.TryGet(session.BotMessage.Id, out var old) && old is not null)
|
|
||||||
try { await old.DeleteAsync(); } catch { }
|
|
||||||
else
|
|
||||||
try { await session.BotMessage.DeleteAsync(); } catch { }
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"🛑 Wordle ended by {member.Name}. The word was `{session.Word}`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sd/wordle board — repost the current board
|
|
||||||
if (args.Length >= 1 && args[0].ToLower() == "board")
|
|
||||||
{
|
|
||||||
if (!_sessions.TryGetValue(channelId, out var session))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's no active Wordle game in this channel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newMsg = await RepostBoardAsync(ctx, channel, session, BuildDisplay(session));
|
|
||||||
if (newMsg is not null)
|
|
||||||
_sessions[channelId] = session with { BotMessage = newMsg };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sd/wordle — start a new game
|
|
||||||
if (_sessions.ContainsKey(channelId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "There's already an active Wordle game in this channel!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pool = await GetWordPoolAsync();
|
|
||||||
string word = pool[Random.Shared.Next(pool.Length)];
|
|
||||||
|
|
||||||
var newSession = new WordleSession(word, [], [], [], member.UserId, null!, channel, ctx.Client);
|
|
||||||
string display = BuildDisplay(newSession);
|
|
||||||
var sent = await MessageHelper.ReplyAsync(ctx, channel, display);
|
|
||||||
if (!sent.Success || sent.Data is null) return;
|
|
||||||
|
|
||||||
_sessions[channelId] = newSession with { BotMessage = sent.Data };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WordleGuess : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "wg";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Guess a word in the active Wordle game.";
|
|
||||||
public string Section => "Fun";
|
|
||||||
public string Usage => "wg <word>";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
if (!ctx.ChannelCache.TryGetValue(ctx.ChannelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (ctx.Args.Length == 0 || string.IsNullOrWhiteSpace(ctx.Args[0]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a word to guess.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Wordle.ProcessGuessAsync(ctx, channel, ctx.Args[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Client;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class BotGuide : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "botguide";
|
|
||||||
public string[] Aliases => ["bot", "bguide"];
|
|
||||||
public string Description => "Sends a link the a bot guide that SkyJoshua has made.";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "botguide";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ValourClient Client = ctx.Client;
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
PlanetMember Member = ctx.Member;
|
|
||||||
Message Message = ctx.Message;
|
|
||||||
Planet Planet = ctx.Planet;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] Args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string msg = @"Here is a link to a Bot Guide that SkyJoshua has made (WIP):
|
|
||||||
https://git.skyjoshua.xyz/SkyJoshua/Valour-Bot-Guide";
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Devcentral : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "devcentral";
|
|
||||||
public string[] Aliases => ["dev"];
|
|
||||||
public string Description => "Sends an invite link to the Dev Central Planet.";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "devcentral";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = $"you can join the Dev Central (ID: 42439954653511681) planet here: https://app.valour.gg/I/k2tz9c4i";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +1,158 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models.Messages.Embeds;
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Help : ICommand
|
public class Help : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "help";
|
public string Name => "help";
|
||||||
public string[] Aliases => ["h"];
|
public string[] Aliases => ["cmds"];
|
||||||
public string Description => "Shows all the commands and their descriptions.";
|
public string Description => "Shows all commands by category.";
|
||||||
public string Section => "Info";
|
public string Category => "Info";
|
||||||
public string Usage => "help [section] [page]";
|
public string Usage => "help";
|
||||||
private const int PageSize = 5;
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
private const int PageSize = 10;
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
var categories = CommandRegistry.Categories
|
||||||
long channelId = ctx.ChannelId;
|
.Where(c => c.Key != "template")
|
||||||
string[] args = ctx.Args;
|
.Where(c => c.Key != "dev" || PermissionHelper.IsOwner(ctx.Member))
|
||||||
PlanetMember member = ctx.Member;
|
.OrderBy(c => c.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
var categoryFirstPage = new Dictionary<string, int>();
|
||||||
|
var allChunks = new List<(string CategoryName, List<ICommand> Cmds, int ChunkIndex, int TotalChunks)>();
|
||||||
|
|
||||||
// Show all sections.
|
int nextPageIndex = 1;
|
||||||
if (args.Length == 0)
|
foreach (var (categoryName, cmds) in categories)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var ordered = cmds.OrderBy(c => c.Name).ToList();
|
||||||
sb.AppendLine("**Available Categories**");
|
var chunks = ordered
|
||||||
foreach (var section in CommandRegistry.Sections.Keys)
|
.Select((cmd, i) => (cmd, i))
|
||||||
|
.GroupBy(x => x.i / PageSize)
|
||||||
|
.Select(g => g.Select(x => x.cmd).ToList())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
categoryFirstPage[categoryName] = nextPageIndex;
|
||||||
|
for (int c = 0; c < chunks.Count; c++)
|
||||||
|
allChunks.Add((categoryName, chunks[c], c, chunks.Count));
|
||||||
|
|
||||||
|
nextPageIndex += chunks.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allCmds = allChunks
|
||||||
|
.SelectMany(chunk => chunk.Cmds)
|
||||||
|
.DistinctBy(cmd => cmd.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var cmdDetailPage = new Dictionary<string, int>();
|
||||||
|
int detailPageIndex = nextPageIndex;
|
||||||
|
foreach (var cmd in allCmds)
|
||||||
|
cmdDetailPage[cmd.Name] = detailPageIndex++;
|
||||||
|
|
||||||
|
var builder = new EmbedBuilder();
|
||||||
|
builder.embed.HideChangePageArrows = true;
|
||||||
|
|
||||||
|
// Home page
|
||||||
|
builder.AddPage("✦ Help Menu", $"Prefix: {Config.Prefix} | <> = required [] = optional");
|
||||||
|
builder.AddRow()
|
||||||
|
.AddText("Select a Category")
|
||||||
|
.WithStyles(EmbedStyles.LabelText)
|
||||||
|
.CloseRow();
|
||||||
|
|
||||||
|
builder.AddRow();
|
||||||
|
foreach (var (categoryName, cmds) in categories)
|
||||||
|
{
|
||||||
|
builder.AddButton($"{categoryName.ToTitleCase()} ({cmds.Count})")
|
||||||
|
.WithStyles(EmbedStyles.CategoryBtn)
|
||||||
|
.OnClickGoToEmbedPage(categoryFirstPage[categoryName]);
|
||||||
|
}
|
||||||
|
builder.CloseRow();
|
||||||
|
|
||||||
|
// Category pages
|
||||||
|
int embedPage = 1;
|
||||||
|
foreach (var (categoryName, cmds, chunkIndex, totalChunks) in allChunks)
|
||||||
|
{
|
||||||
|
string? footer = totalChunks > 1
|
||||||
|
? $"Page {chunkIndex + 1}/{totalChunks} | Prefix: {Config.Prefix} | <> = required [] = optional"
|
||||||
|
: $"Prefix: {Config.Prefix} | <> = required [] = optional";
|
||||||
|
|
||||||
|
builder.AddPage($"✦ {categoryName.ToTitleCase()} Commands", footer);
|
||||||
|
|
||||||
|
foreach (var cmd in cmds)
|
||||||
{
|
{
|
||||||
if (section == "template") continue;
|
builder.AddRow()
|
||||||
if (section == "dev" && !PermissionHelper.IsOwner(member)) continue;
|
.AddButton(cmd.Name.ToTitleCase())
|
||||||
if (section == "mod" && !PermissionHelper.HasPerm(member, [PlanetPermissions.Kick, PlanetPermissions.Ban, PlanetPermissions.ManageRoles])) continue;
|
.WithStyles(EmbedStyles.CommandBtn)
|
||||||
sb.AppendLine($"- `{section.ToTitleCase()}` ({CommandRegistry.Sections[section].Count})");
|
.OnClickGoToEmbedPage(cmdDetailPage[cmd.Name])
|
||||||
|
.CloseRow();
|
||||||
}
|
}
|
||||||
sb.AppendLine($"\nUse `{Config.Prefix}help <category>` to see commands in a category.");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, sb.ToString());
|
builder.AddRow()
|
||||||
return;
|
.AddButton("← Back")
|
||||||
|
.WithStyles(EmbedStyles.BackBtn)
|
||||||
|
.OnClickGoToEmbedPage(0);
|
||||||
|
|
||||||
|
if (chunkIndex > 0)
|
||||||
|
{
|
||||||
|
builder.AddButton("← Prev")
|
||||||
|
.WithStyles(EmbedStyles.NavBtn)
|
||||||
|
.OnClickGoToEmbedPage(embedPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkIndex < totalChunks - 1)
|
||||||
|
{
|
||||||
|
builder.AddButton("Next →")
|
||||||
|
.WithStyles(EmbedStyles.NavBtn)
|
||||||
|
.OnClickGoToEmbedPage(embedPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.CloseRow();
|
||||||
|
|
||||||
|
embedPage++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// section [page]
|
// Command detail pages
|
||||||
string sectionName = args[0].ToLower();
|
foreach (var cmd in allCmds)
|
||||||
if (!CommandRegistry.Sections.TryGetValue(sectionName, out var commands))
|
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Unknown category `{sectionName}`.");
|
builder.AddPage($"✦ {cmd.Name.ToTitleCase()}", $"Prefix: {Config.Prefix} | <> = required [] = optional");
|
||||||
return;
|
|
||||||
|
builder.AddRow()
|
||||||
|
.AddText("Description", cmd.Description)
|
||||||
|
.CloseRow();
|
||||||
|
|
||||||
|
if (cmd.Aliases.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AddRow()
|
||||||
|
.AddText("Aliases", string.Join(", ", cmd.Aliases))
|
||||||
|
.CloseRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(cmd.Usage))
|
||||||
|
{
|
||||||
|
builder.AddRow()
|
||||||
|
.AddText("Usage", $"{Config.Prefix}{cmd.Usage}")
|
||||||
|
.CloseRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.SubCommands.Length > 0)
|
||||||
|
{
|
||||||
|
builder.AddRow()
|
||||||
|
.AddText("Sub-commands", string.Join(", ", cmd.SubCommands.Select(s => s)))
|
||||||
|
.CloseRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddRow()
|
||||||
|
.AddButton("← Back")
|
||||||
|
.WithStyles(EmbedStyles.BackBtn)
|
||||||
|
.OnClickGoToEmbedPage(categoryFirstPage[cmd.Category.ToLower()])
|
||||||
|
.CloseRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sectionName == "dev" && !PermissionHelper.IsOwner(member))
|
await MessageHelper.ReplyAsync(ctx, null, builder.embed);
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Unknown category `{sectionName}`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sectionName == "mod" && !PermissionHelper.HasPerm(member, [PlanetPermissions.Kick, PlanetPermissions.Ban, PlanetPermissions.ManageRoles]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Unknown category `{sectionName}`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int page = 1;
|
|
||||||
if (args.Length >= 2 && int.TryParse(args[1], out int parsedPage))
|
|
||||||
{
|
|
||||||
page = parsedPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalPages = (int)Math.Ceiling(commands.Count / (double)PageSize);
|
|
||||||
page = Math.Clamp(page, 1, totalPages);
|
|
||||||
|
|
||||||
var pageCommands = commands.Skip((page - 1) * PageSize).Take(PageSize);
|
|
||||||
|
|
||||||
var sb2 = new StringBuilder();
|
|
||||||
sb2.AppendLine($"**{sectionName.ToTitleCase()} commands** (Page {page}/{totalPages}):");
|
|
||||||
foreach (var cmd in pageCommands)
|
|
||||||
{
|
|
||||||
var name = cmd.Aliases.Length > 0
|
|
||||||
? $"{cmd.Name}|{string.Join("|", cmd.Aliases)}"
|
|
||||||
: cmd.Name;
|
|
||||||
sb2.AppendLine($"`{Config.Prefix}{name}` - {cmd.Description}");
|
|
||||||
}
|
|
||||||
sb2.AppendLine($"\nUse `{Config.Prefix}help {sectionName} <page>` to see more.");
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, sb2.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +1,107 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Globalization;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
|
using Superpower.Model;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds.Styles;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds.Styles.Basic;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds.Styles.Flex;
|
||||||
using Valour.Shared.Models;
|
using Valour.Shared.Models;
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public partial class Info : ICommand
|
public class Info : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "info";
|
public string Name => "info";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
public string Description => "Shows the info about a User or the Planet.";
|
public string Description => "Shows info about a Planet or User";
|
||||||
public string Section => "Info";
|
public string Category => "Info";
|
||||||
public string Usage => "info <user|planet> [mention|memberid]";
|
public string Usage => "info <sub> [user]";
|
||||||
|
public string[] SubCommands => ["planet", "user"];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
if (ctx.Args.Length == 0)
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please specify `user` or `planet`.");
|
await MessageHelper.ReplyAsync(ctx, "Please specify `planet` or `user`.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (args[0].ToLower())
|
switch (ctx.Args[0].ToLower())
|
||||||
{
|
{
|
||||||
case "user":
|
case "user":
|
||||||
case "u":
|
case "u":
|
||||||
await HandleUserInfo(ctx, channel);
|
await HandleUserInfo(ctx);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "planet":
|
case "planet":
|
||||||
case "p":
|
case "p":
|
||||||
await HandlePlanetInfo(ctx, channel);
|
await HandlePlanetInfo(ctx);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "invalid option. Use `user` or `planet`.");
|
await MessageHelper.ReplyAsync(ctx, "Invalid Option. Use either `planet` or `user`.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Text.RegularExpressions.GeneratedRegex(@"«@m-(\d+)»")]
|
private async Task HandleUserInfo(CommandContext ctx)
|
||||||
private static partial System.Text.RegularExpressions.Regex MemberMentionRegex();
|
|
||||||
|
|
||||||
private static long? ExtractMemberMentionId(Message message)
|
|
||||||
{
|
{
|
||||||
var mention = message.Mentions?.FirstOrDefault(m => m.Type == MentionType.PlanetMember);
|
long memberId = ctx.Message.Mentions?.Any() == true
|
||||||
if (mention != null) return mention.TargetId;
|
? ctx.Message.Mentions.First().TargetId
|
||||||
|
: long.TryParse(ctx.Args.ElementAtOrDefault(1), out var parsed)
|
||||||
var match = MemberMentionRegex().Match(message.Content ?? "");
|
? parsed
|
||||||
if (match.Success && long.TryParse(match.Groups[1].Value, out long id))
|
: ctx.Member.Id;
|
||||||
return id;
|
PlanetMember member = await ctx.Planet.FetchMemberAsync(memberId);
|
||||||
|
if (member is null)
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleUserInfo(CommandContext ctx, Channel channel)
|
|
||||||
{
|
|
||||||
Message message = ctx.Message;
|
|
||||||
Planet planet = ctx.Planet;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
PlanetMember? target;
|
|
||||||
|
|
||||||
var mentionId = ExtractMemberMentionId(message);
|
|
||||||
|
|
||||||
if (mentionId.HasValue)
|
|
||||||
{
|
{
|
||||||
target = await planet.FetchMemberAsync(mentionId.Value);
|
await MessageHelper.ReplyAsync(ctx, "Could not find member.");
|
||||||
}
|
|
||||||
else if (args.Length > 1 && long.TryParse(args[1], out long memberid))
|
|
||||||
{
|
|
||||||
target = await planet.FetchMemberAsync(memberid);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
target = ctx.Member;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var b = new EmbedBuilder()
|
||||||
sb.AppendLine($"**{target.Name.Trim()}**");
|
.AddPage($"{member.Name.Trim()}'s Info")
|
||||||
sb.AppendLine($"User ID: `{target.UserId}`");
|
.WithTitleStyles(new TextColor(member.PrimaryRole.Color))
|
||||||
sb.AppendLine($"Member ID: `{target.Id}`");
|
// .AddMedia(MessageAttachmentType.Image, 64, 64, "image/webp", "avatar.webp", member.GetAvatar(AvatarFormat.Webp64))
|
||||||
sb.AppendLine($"Nickname: `{(string.IsNullOrWhiteSpace(target.Nickname) ? "None" : target.Nickname)}`");
|
// .WithStyles(
|
||||||
sb.AppendLine($"Subscription: `{(string.IsNullOrWhiteSpace(target.User.SubscriptionType) ? "None" : target.User.SubscriptionType)}`");
|
// new Position(right: new Size(Unit.Pixels, 8), top: new Size(Unit.Pixels, 8)),
|
||||||
sb.AppendLine($"Status: `{(string.IsNullOrWhiteSpace(target.Status) ? "None" : target.Status)}`");
|
// new Width(new Size(Unit.Pixels, 64)),
|
||||||
sb.AppendLine($"Primary Role: `{target.PrimaryRole?.Name ?? "None"}`");
|
// new Height(new Size(Unit.Pixels, 64)),
|
||||||
sb.AppendLine($"Roles: `{string.Join(", ", target.Roles.Select(r => r.Name))}`");
|
// new BorderRadius(new Size(Unit.Percent, 50))
|
||||||
sb.AppendLine($"Valour Join Date: `{target.User.TimeJoined} UTC`");
|
// )
|
||||||
|
.AddRow().AddText("Member ID", member.Id.ToString()).WithStyles(new TextColor("#ff9d00")).CloseRow()
|
||||||
|
.AddRow().AddText("User ID", member.UserId.ToString()).WithStyles(new TextColor("#ff9d00")).CloseRow()
|
||||||
|
.AddRow().AddText("Nickname", string.IsNullOrWhiteSpace(member.Nickname) ? "None" : member.Nickname).WithStyles(new TextColor(string.IsNullOrWhiteSpace(member.Nickname) ? "#ffed4a" : member.PrimaryRole.Color)).CloseRow()
|
||||||
|
.AddRow().AddText("Subscription", string.IsNullOrWhiteSpace(member.User.SubscriptionType) ? "None" : member.User.SubscriptionType).WithStyles(new TextColor(member.User.HasStargazer ? member.User.GetStarColor1() : "#ffed4a")).CloseRow()
|
||||||
|
.AddRow().AddText("Status", string.IsNullOrWhiteSpace(member.Status) ? "None" : member.Status).WithStyles(new TextColor("#ffed4a")).CloseRow()
|
||||||
|
.AddRow().AddText("Primary Role", member.PrimaryRole.Name).WithStyles(new TextColor(member.PrimaryRole.Color)).CloseRow()
|
||||||
|
.AddRow().AddText("Roles", string.Join(", ", member.Roles.Select(r => r.Name))).WithStyles(new TextColor(member.Roles[Random.Shared.Next(member.Roles.Count)].Color)).CloseRow()
|
||||||
|
.AddRow().AddText("Account Created", member.User.TimeJoined.ToString()).WithStyles(new TextColor("#979797")).CloseRow();
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, sb.ToString());
|
await MessageHelper.ReplyAsync(ctx, null, b.embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandlePlanetInfo(CommandContext ctx, Channel channel)
|
private async Task HandlePlanetInfo(CommandContext ctx)
|
||||||
{
|
{
|
||||||
var planet = ctx.Planet;
|
PlanetMember pOwner = await ctx.Planet.FetchMemberByUserAsync(ctx.Planet.OwnerId);
|
||||||
|
EmbedBuilder b = new EmbedBuilder()
|
||||||
|
.AddPage($"{ctx.Planet.Name}'s Info")
|
||||||
|
.AddRow().AddText("Planet ID", ctx.Planet.Id.ToString()).CloseRow()
|
||||||
|
.AddRow().AddText("Planet Description", ctx.Planet.Description).CloseRow()
|
||||||
|
.AddRow().AddText("State", ctx.Planet.Public ? ctx.Planet.Discoverable ? "Public (Discoverable)" : "Public (Not Discoverable)" : "Private").CloseRow()
|
||||||
|
.AddRow().AddText("NSFW", ctx.Planet.Nsfw.ToString()).CloseRow()
|
||||||
|
.AddRow().AddText("Owner Name (ID)", $"{pOwner.Name} ({pOwner.Id})").CloseRow()
|
||||||
|
.AddRow().AddText("Members", ctx.Planet.Members.Count.ToString())
|
||||||
|
.AddText("Channels", ctx.Planet.Channels.Count.ToString())
|
||||||
|
.AddText("Roles", ctx.Planet.Roles.Count.ToString()).CloseRow()
|
||||||
|
;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
await MessageHelper.ReplyAsync(ctx, null, b.embed);
|
||||||
sb.AppendLine($"**{planet.Name}**");
|
|
||||||
sb.AppendLine($"Planet ID: `{planet.Id}`");
|
|
||||||
sb.AppendLine($"Owner ID: `{planet.OwnerId}`");
|
|
||||||
sb.AppendLine($"Member Count: `{planet.Members?.Count ?? 0}`");
|
|
||||||
sb.AppendLine($"Channel Count: `{planet.Channels?.Count ?? 0}`");
|
|
||||||
sb.AppendLine($"Role Count: `{planet.Roles?.Count ?? 0}`");
|
|
||||||
sb.AppendLine($"Description: `{(string.IsNullOrWhiteSpace(planet.Description) ? "None" : planet.Description)}`");
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, sb.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class JoinSite : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "joinsite";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Links to a site to help your bots join a planet.";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "joinsite";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = $"You can use this website to easily add your bot to a planet: https://skyjoshua.xyz/planetjoiner";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Minecraft : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "minecraft";
|
|
||||||
public string[] Aliases => ["mc"];
|
|
||||||
public string Description => "Sends the Unofficial ValourSMP IPs";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "minecraft";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = @$"you can join the Unofficial ValourSMP Minecraft Server by using this ip:
|
|
||||||
Java: `valour.sxsc.xyz`, Bedrock: `valourbr.sxsc.xyz` Both with the default ports.
|
|
||||||
Cool features can be found here: https://sxsc.xyz/servers/valour/";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
@@ -10,22 +9,18 @@ namespace SkyBot.Commands
|
|||||||
{
|
{
|
||||||
public string Name => "ping";
|
public string Name => "ping";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
public string Description => "Shows the bot's response time.";
|
public string Description => "Displays the bots response time.";
|
||||||
public string Section => "Info";
|
public string Category => "Info";
|
||||||
public string Usage => "ping";
|
public string Usage => "ping";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
DateTime start = DateTime.UtcNow;
|
DateTime start = DateTime.UtcNow;
|
||||||
TaskResult<Message> message = await MessageHelper.ReplyAsync(ctx, channel, "🏓 Pinging...");
|
TaskResult<Message> message = await MessageHelper.ReplyAsync(ctx, "🏓 Pinging...");
|
||||||
double elapsed = (DateTime.UtcNow - start).TotalMilliseconds;
|
double elapsed = (DateTime.UtcNow - start).TotalMilliseconds;
|
||||||
|
|
||||||
await MessageHelper.EditAsync(channel, message.Data, $"🏓 Pong! `{elapsed:F0}ms`");
|
await MessageHelper.EditAsync(ctx.Channel, message.Data, $"🏓 Ping! `{elapsed:F0}ms`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
@@ -9,22 +7,14 @@ namespace SkyBot.Commands
|
|||||||
{
|
{
|
||||||
public string Name => "source";
|
public string Name => "source";
|
||||||
public string[] Aliases => ["src"];
|
public string[] Aliases => ["src"];
|
||||||
public string Description => "Shows the source code for this bot.";
|
public string Description => "";
|
||||||
public string Section => "Info";
|
public string Category => "Info";
|
||||||
public string Usage => "source";
|
public string Usage => "source";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
await MessageHelper.ReplyAsync(ctx, $"You can find my source code here: {Config.SourceLink}");
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = $"You can find my source code here: {Config.SourceLink}";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Suggest : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "suggest";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Sends a link to where you can suggest commands.";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "suggest";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = $"You can suggest a command to be added here: https://docs.google.com/spreadsheets/d/1CzcpLAuMiPL_RODrZ5x25cPj8yE-rR3mEnqrd_2Fbmk";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class SwaggerAPI : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "swagger";
|
|
||||||
public string[] Aliases => ["api"];
|
|
||||||
public string Description => "Sends a link to the Valour.gg Swagger API.";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "swagger";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = $"Here is a link to the Swagger API: https://api.valour.gg/swagger";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,28 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Uptime : ICommand
|
public class Uptime : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "uptime";
|
public string Name => "uptime";
|
||||||
public string[] Aliases => ["up"];
|
public string[] Aliases => [];
|
||||||
public string Description => "Shows how long the bot has been running.";
|
public string Description => "Displays the uptime of the bot";
|
||||||
public string Section => "Info";
|
public string Category => "Info";
|
||||||
public string Usage => "uptime";
|
public string Usage => "uptime";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
TimeSpan uptime = DateTime.UtcNow - Program.StartTime;
|
||||||
long channelId = ctx.ChannelId;
|
string formatted = uptime.TotalDays >= 1
|
||||||
|
? $"{(int)uptime.TotalDays}d {uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s"
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
: uptime.TotalHours >= 1
|
||||||
|
? $"{uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s"
|
||||||
TimeSpan uptime = DateTime.UtcNow - SkyBot.StartTime;
|
: uptime.TotalMinutes >= 1
|
||||||
string formatted = $"{(int)uptime.TotalDays}d {uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s";
|
? $"{uptime.Minutes}m {uptime.Seconds}s"
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"⏱️ Uptime: `{formatted}`");
|
: $"{uptime.Seconds}s";
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"⏱️ SkyBot Uptime: {formatted}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class UserCount : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "usercount";
|
|
||||||
public string[] Aliases => ["users"];
|
|
||||||
public string Description => "Shows the user count of Valour.";
|
|
||||||
public string Section => "Info";
|
|
||||||
public string Usage => "usercount";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
string message = @$"Current Valour user count is: {ValourUsercountHelper.ValourUsercount:N0}
|
|
||||||
You can see a graph of the user count here: /meow";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,44 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Shared;
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Version : ICommand
|
public class Version : ICommand
|
||||||
{
|
{
|
||||||
|
private static readonly HttpClient http = new();
|
||||||
|
|
||||||
public string Name => "version";
|
public string Name => "version";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => ["versions", "ver"];
|
||||||
public string Description => "Shows the current version of the Bot and Valour.";
|
public string Description => "";
|
||||||
public string Section => "Info";
|
public string Category => "Info";
|
||||||
public string Usage => "version";
|
public string Usage => "version";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
string latestSdk = "Unknown";
|
||||||
long channelId = ctx.ChannelId;
|
try
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
string message = @$"Bot Version: {typeof(Version).Assembly.GetName().Version}
|
|
||||||
Valour Version: {typeof(Channel).Assembly.GetName().Version}";
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, message);
|
NuGetVersions? response = await http.GetFromJsonAsync<NuGetVersions>("https://api.nuget.org/v3-flatcontainer/valour.sdk/index.json");
|
||||||
}
|
string? raw = response?.Versions?.LastOrDefault();
|
||||||
|
latestSdk = raw is not null ? (raw.Count(c => c == '.') == 2 ? $"{raw}.0": raw) : "Unknown";
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
string msg = @$"Bot Version: {typeof(Version).Assembly.GetName().Version}
|
||||||
|
Valour Version: {ctx.Client.PrimaryNode.GetAsync("api/version").Result.Data}
|
||||||
|
ValourSDK Version: {typeof(Channel).Assembly.GetName().Version} (latest: {latestSdk})";
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NuGetVersions
|
||||||
|
{
|
||||||
|
[JsonPropertyName("versions")]
|
||||||
|
public List<string>? Versions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Ban : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "ban";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Bans a user from the planet.";
|
|
||||||
public string Section => "Mod";
|
|
||||||
public string Usage => "ban <mention|memberid> [reason] [length (y=year, M=month, w=week, d=day, h=hour, m=minute)]";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Planet planet = ctx.Planet;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
PlanetMember bot = await planet.FetchMemberByUserAsync(ctx.Client.Me.Id);
|
|
||||||
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Ban]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"You don't have permission to use this command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PermissionHelper.HasPerm(bot, [PlanetPermissions.Ban]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"I don't have permission to ban members.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.Mentions.Any() && args.Length < 1)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please mention someone or user their id.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
long targetId;
|
|
||||||
if (message.Mentions.Any())
|
|
||||||
{
|
|
||||||
targetId = message.Mentions.First().TargetId;
|
|
||||||
}
|
|
||||||
else if (!long.TryParse(args[0], out targetId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid member ID.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PlanetMember victim = await planet.FetchMemberAsync(targetId);
|
|
||||||
|
|
||||||
if (victim == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime? expires = null;
|
|
||||||
List<string> remainingArgs = args[1..].ToList();
|
|
||||||
|
|
||||||
for (int i = 0; i < remainingArgs.Count; i++)
|
|
||||||
{
|
|
||||||
var parsed = MessageHelper.ParseDuration(remainingArgs[i]);
|
|
||||||
if (parsed != null)
|
|
||||||
{
|
|
||||||
expires = parsed;
|
|
||||||
remainingArgs.RemoveAt(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string reason = remainingArgs.Count > 0 ? string.Join(" ", remainingArgs) : "No reason provided.";
|
|
||||||
|
|
||||||
PlanetBan ban = new PlanetBan(ctx.Client)
|
|
||||||
{
|
|
||||||
PlanetId = planet.Id,
|
|
||||||
TargetId = victim.UserId,
|
|
||||||
IssuerId = ctx.Client.Me.Id,
|
|
||||||
Reason = reason,
|
|
||||||
TimeCreated = DateTime.UtcNow,
|
|
||||||
TimeExpires = expires
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TaskResult<PlanetBan> result = await ban.CreateAsync();
|
|
||||||
if (!result.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Failed to ban {victim.Name}: {result.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expires == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Permanently Banned `{victim.Name}`. Reason: `{reason}`");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Banned `{victim.Name}` until `{expires}`. Reason: `{reason}`");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class GetBans : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "bans";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Lists all bans in the planet.";
|
|
||||||
public string Section => "Mod";
|
|
||||||
public string Usage => "bans [page]";
|
|
||||||
|
|
||||||
private const int PageSize = 10;
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Planet planet = ctx.Planet;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Ban]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int page = 1;
|
|
||||||
if (args.Length > 0 && int.TryParse(args[0], out int parsedPage))
|
|
||||||
page = parsedPage;
|
|
||||||
|
|
||||||
int skip = (page - 1) * PageSize;
|
|
||||||
|
|
||||||
var queryResult = await planet.Node.PostAsyncWithResponse<QueryResponse<PlanetBan>>(
|
|
||||||
$"api/planets/{planet.Id}/bans/query",
|
|
||||||
new { skip, take = PageSize, options = new { } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!queryResult.Success || queryResult.Data?.Items == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Failed to fetch bans.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queryResult.Data.Items.Any())
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "No bans found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalPages = (int)Math.Ceiling(queryResult.Data.TotalCount / (double)PageSize);
|
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.AppendLine($"**Bans** (Page {page}/{totalPages}):");
|
|
||||||
|
|
||||||
IEnumerable<PlanetBan> activeBans = queryResult.Data.Items.Where(b => b.Permanent || b.TimeExpires > DateTime.UtcNow);
|
|
||||||
|
|
||||||
if (!activeBans.Any())
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "No active bans found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var ban in activeBans)
|
|
||||||
{
|
|
||||||
var user = await ctx.Client.UserService.FetchUserAsync(ban.TargetId);
|
|
||||||
string username = user?.NameAndTag ?? "Unknown";
|
|
||||||
string expires = ban.TimeExpires.HasValue
|
|
||||||
? $"{ban.TimeExpires}"
|
|
||||||
: "Never";
|
|
||||||
sb.AppendLine($"**{username}** `{user?.Id}` - {ban.Reason} (Expires: `{expires}`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine($"\nUse `{Config.Prefix}bans <page>` to see more.");
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, sb.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Kick : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "kick";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Kicks a user from the planet.";
|
|
||||||
public string Section => "Mod";
|
|
||||||
public string Usage => "kick <user> [reason]";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Planet planet = ctx.Planet;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
PlanetMember bot = await planet.FetchMemberByUserAsync(ctx.Client.Me.Id);
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Kick]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"You don't have permission to use this command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PermissionHelper.HasPerm(bot, [PlanetPermissions.Kick]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"I don't have permission to kick members.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.Mentions.Any() && args.Length < 2)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please mention someone or user their id.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
long targetId;
|
|
||||||
if (message.Mentions.Any())
|
|
||||||
{
|
|
||||||
targetId = message.Mentions.First().TargetId;
|
|
||||||
}
|
|
||||||
else if (!long.TryParse(args[1], out targetId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid member ID.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PlanetMember victim = await planet.FetchMemberAsync(targetId);
|
|
||||||
|
|
||||||
if (victim == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string reason = args.Length > 1 && !message.Mentions.Any()
|
|
||||||
? string.Join(" ", args[2..])
|
|
||||||
: args.Length > 1
|
|
||||||
? string.Join(" ", args[1..])
|
|
||||||
: "No reason provided.";
|
|
||||||
|
|
||||||
await victim.DeleteAsync();
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Kicked {victim.Name}. Reason: {reason}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using SkyBot.Services;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class SetWelcome : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "setwelcome";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Sets the welcome channel, message or active.";
|
|
||||||
public string Section => "Mod";
|
|
||||||
public string Usage => "set <channel|message|active [value]";
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
Planet planet = ctx.Planet;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Manage]) && !PermissionHelper.IsOwner(member))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please specify `channel` or `message`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (args[0].ToLower())
|
|
||||||
{
|
|
||||||
case "channel":
|
|
||||||
case "c":
|
|
||||||
long targetChannelId;
|
|
||||||
if (message.Mentions != null && message.Mentions.Any(m => m.Type == MentionType.Channel)) {targetChannelId = message.Mentions.First(m => m.Type == MentionType.Channel).TargetId;}
|
|
||||||
else if (args.Length > 1 && long.TryParse(args[1], out long parsedChannelId)) {targetChannelId = parsedChannelId;}
|
|
||||||
else {targetChannelId = channelId;}
|
|
||||||
|
|
||||||
if (!channelCache.ContainsKey(targetChannelId)) {await MessageHelper.ReplyAsync(ctx, channel, "Could not find that channel."); return;}
|
|
||||||
|
|
||||||
await WelcomeService.SetWelcomeChannel(planet.Id, targetChannelId);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Welcome channel set to «@c-{targetChannelId}».");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "message":
|
|
||||||
case "m":
|
|
||||||
if (args.Length < 2)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a message. Valid variables: {username} {nickname} {fulluser} {mention} {id}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
string msg = string.Join(" ", args[1..]);
|
|
||||||
await WelcomeService.SetWelcomeMessage( planet.Id, msg);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Welcome message set to: `{msg}`");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "active":
|
|
||||||
case "a":
|
|
||||||
if (args.Length < 2)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a value. Use `true`, `false`, or `toggle`.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
string value = args[1].ToLower();
|
|
||||||
if (value != "toggle" && value != "true" && value != "false")
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid value. Use `true`, `false`, `toggle`");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == "toggle")
|
|
||||||
{
|
|
||||||
var toggle = await WelcomeService.SetActive(planet.Id);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, toggle.Value ? "Welcome messages enabled." : "Welcome messages disabled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool.TryParse(value, out var active);
|
|
||||||
|
|
||||||
await WelcomeService.SetActive(planet.Id, active);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, active ? "Welcome messages enabled." : "Welcome messages disabled.");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid option. Use `channel`, `message` or `active`.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared;
|
|
||||||
using Valour.Shared.Authorization;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Unban : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "unban";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Unbans a user from the planet.";
|
|
||||||
public string Section => "Mod";
|
|
||||||
public string Usage => "unban <id>";
|
|
||||||
|
|
||||||
// public async Task Execute(CommandContext ctx)
|
|
||||||
// {
|
|
||||||
// ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
// long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
// if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
// {
|
|
||||||
// await MessageHelper.ReplyAsync(ctx, channel, "Unbanning is currently unavailable due to a Valour server bug.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
Planet planet = ctx.Planet;
|
|
||||||
Message message = ctx.Message;
|
|
||||||
string[] args = ctx.Args;
|
|
||||||
PlanetMember member = ctx.Member;
|
|
||||||
PlanetMember bot = await planet.FetchMemberByUserAsync(ctx.Client.Me.Id);
|
|
||||||
|
|
||||||
if (channelCache.TryGetValue(channelId, out var channel))
|
|
||||||
{
|
|
||||||
if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Ban]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PermissionHelper.HasPerm(bot, [PlanetPermissions.Ban]))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "I don't have permission to unban members.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Length < 1)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a user ID.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!long.TryParse(args[0], out long targetUserId))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Invalid user ID.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlanetBan? ban = null;
|
|
||||||
int skip = 0;
|
|
||||||
int take = 50;
|
|
||||||
|
|
||||||
while (ban == null)
|
|
||||||
{
|
|
||||||
var queryResult = await planet.Node.PostAsyncWithResponse<QueryResponse<PlanetBan>>(
|
|
||||||
$"api/planets/{planet.Id}/bans/query",
|
|
||||||
new {skip, take, options = new { }}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!queryResult.Success || queryResult.Data?.Items == null || !queryResult.Data.Items.Any())
|
|
||||||
break;
|
|
||||||
|
|
||||||
ban = queryResult.Data.Items.FirstOrDefault(b => b.TargetId == targetUserId && (b.Permanent || b.TimeExpires > DateTime.UtcNow));
|
|
||||||
|
|
||||||
if (queryResult.Data.Items.Count < take)
|
|
||||||
break;
|
|
||||||
|
|
||||||
skip += take;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ban == null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not find a ban for that user.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ban.TimeExpires = DateTime.UtcNow.AddSeconds(-1);
|
|
||||||
TaskResult<PlanetBan> result = await planet.Node.PutAsyncWithResponse<PlanetBan>($"api/bans/{ban.Id}", ban);
|
|
||||||
if (!result.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Failed to unban.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
User user = await ctx.Client.UserService.FetchUserAsync(targetUserId);
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Unbanned `{user?.NameAndTag ?? targetUserId.ToString()}`.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
SkyBot/Commands/Mods/Ban.cs
Normal file
53
SkyBot/Commands/Mods/Ban.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Shared.Authorization;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Ban : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "ban";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Bans a member from the planet.";
|
||||||
|
public string Category => "Mod";
|
||||||
|
public string Usage => "ban <@member> [reason]";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (!await PermissionHelper.HasPermAsync(ctx.Member, [PlanetPermissions.Ban]))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"You dont have permission to execute this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await PermissionHelper.HasPermAsync(ctx.Planet.MyMember, [PlanetPermissions.Ban]))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"I don't have permission to ban members.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Message.Mentions?.Any() != true && ctx.Args.Length < 1)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Mention a member or enter their ID to ban them.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long? targetId = ctx.Message.Mentions?.Any() == true ? ctx.Message.Mentions.First().TargetId : long.TryParse(ctx.Args.ElementAtOrDefault(0), out long parsed) ? parsed : null;
|
||||||
|
if (targetId is null) {await MessageHelper.ReplyAsync(ctx, "Could not find user."); return;};
|
||||||
|
|
||||||
|
User victim = await ctx.Client.UserService.FetchUserAsync(targetId.Value);
|
||||||
|
if (victim is null) {await MessageHelper.ReplyAsync(ctx, "Could not find user."); return;};
|
||||||
|
|
||||||
|
DateTime? expires = ctx.Args.Select(MessageHelper.ParseDuration).FirstOrDefault(x => x != null);
|
||||||
|
|
||||||
|
string reason = string.Join(" ", ctx.Args.Skip(1).Where(a => MessageHelper.ParseDuration(a) is null));
|
||||||
|
if (string.IsNullOrWhiteSpace(reason)) reason = "No reason provided";
|
||||||
|
|
||||||
|
await ctx.Planet.BanAsync(victim.Id, reason, expires);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Banned {victim.NameAndTag}. Reason: `{reason}`. Expires: `{(expires.HasValue ? expires + " UTC" : "Never")}`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
SkyBot/Commands/Mods/Kick.cs
Normal file
83
SkyBot/Commands/Mods/Kick.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds.Styles.Basic;
|
||||||
|
using Valour.Shared.Authorization;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Kick : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "Kick";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Kicks a member from the planet.";
|
||||||
|
public string Category => "Mod";
|
||||||
|
public string Usage => "kick <@member> [reason]";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (!await PermissionHelper.HasPermAsync(ctx.Member, [PlanetPermissions.Kick]))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You don't have permission to execute this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await PermissionHelper.HasPermAsync(ctx.Planet.MyMember, [PlanetPermissions.Kick]))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "I don't have permission to kick members.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Message.Mentions?.Any() != true && ctx.Args.Length < 1)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Mention a member or enter their ID to kick them.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long? targetId = ctx.Message.Mentions?.Any() == true ? ctx.Message.Mentions.First().TargetId : long.TryParse(ctx.Args.ElementAtOrDefault(0), out long parsed) ? parsed : null;
|
||||||
|
if (targetId is null) {await MessageHelper.ReplyAsync(ctx, "Could not find member."); return;};
|
||||||
|
|
||||||
|
PlanetMember offender = await ctx.Planet.FetchMemberAsync(targetId.Value);
|
||||||
|
if (offender is null) {await MessageHelper.ReplyAsync(ctx, "Could not find member."); return;};
|
||||||
|
|
||||||
|
string reason = string.Join(" ", ctx.Args.Skip(1));
|
||||||
|
if (string.IsNullOrWhiteSpace(reason)) reason = "No reason provided";
|
||||||
|
|
||||||
|
EmbedBuilder p = new EmbedBuilder()
|
||||||
|
.AddPage("Member Kick", DateTime.UtcNow.ToString())
|
||||||
|
.WithTitleStyles(new TextColor("#ff9900"))
|
||||||
|
.WithFooterStyles(new TextColor("#555555"))
|
||||||
|
.AddRow()
|
||||||
|
.AddText("Offender", offender.User.NameAndTag)
|
||||||
|
.WithStyles(new TextColor("#ff9900"))
|
||||||
|
.CloseRow()
|
||||||
|
.AddRow()
|
||||||
|
.AddText("Issuer", ctx.Member.User.NameAndTag)
|
||||||
|
.WithStyles(new TextColor("#ff9900"))
|
||||||
|
.CloseRow()
|
||||||
|
.AddText("Reason", reason)
|
||||||
|
.WithStyles(new TextColor("#ff9900"))
|
||||||
|
.CloseRow();
|
||||||
|
|
||||||
|
EmbedBuilder d = new EmbedBuilder()
|
||||||
|
.AddPage("Kicked from Planet", DateTime.UtcNow.ToString())
|
||||||
|
.WithTitleStyles(new TextColor("#ff9900"))
|
||||||
|
.WithFooterStyles(new TextColor("#555555"))
|
||||||
|
.AddRow()
|
||||||
|
.AddText("Planet (ID)", $"{ctx.Planet.Name} ({ctx.Planet.Id})")
|
||||||
|
.WithStyles(new TextColor("#ff9900"))
|
||||||
|
.CloseRow()
|
||||||
|
.AddRow()
|
||||||
|
.AddText("Reason", reason)
|
||||||
|
.WithStyles(new TextColor("#ff9900"))
|
||||||
|
.CloseRow();
|
||||||
|
|
||||||
|
await offender.User.SendDirectMessageAsync(ctx.Client, null, d.embed);
|
||||||
|
await offender.DeleteAsync();
|
||||||
|
await MessageHelper.ReplyAsync(ctx, null, p.embed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
99
SkyBot/Commands/Mods/Restrict.cs
Normal file
99
SkyBot/Commands/Mods/Restrict.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using SkyBot.Services;
|
||||||
|
using Valour.Shared.Authorization;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Restrict : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "restrict";
|
||||||
|
public string[] Aliases => ["channelrestrict", "cr"];
|
||||||
|
public string Description => "Enable or disable command categories or specific commands in this channel.";
|
||||||
|
public string Category => "Mod";
|
||||||
|
public string Usage => "restrict <disable|enable|list> <category> <all|commandname>";
|
||||||
|
public string[] SubCommands => ["disable", "enable", "list"];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
if (!PermissionHelper.IsOwner(ctx.Member) && !await PermissionHelper.HasPermAsync(ctx.Member, [ChatChannelPermissions.ManageChannel], ctx.Channel))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You need the **Manage Channel** permission to use this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string sub = ctx.Args.ElementAtOrDefault(0)?.ToLower() ?? "";
|
||||||
|
|
||||||
|
if (sub == "list")
|
||||||
|
{
|
||||||
|
var restrictions = ChannelRestrictionService.GetRestrictions(ctx.Channel.Id);
|
||||||
|
if (restrictions.Count == 0)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "No restrictions set for this channel.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = restrictions.Select(k =>
|
||||||
|
k.StartsWith("cat:") ? $"Category: **{k[4..].ToTitleCase()}**" :
|
||||||
|
k.StartsWith("cmd:") ? $"Command: `{k[4..]}`" : k);
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Restrictions in this channel:\n{string.Join("\n", lines)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub is not ("disable" or "enable"))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Usage: `{Config.Prefix}{Usage}`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? category = ctx.Args.ElementAtOrDefault(1)?.ToLower();
|
||||||
|
string? target = ctx.Args.ElementAtOrDefault(2)?.ToLower();
|
||||||
|
|
||||||
|
if (category is null)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Please specify a category. Available: {string.Join(", ", CommandRegistry.Categories.Keys)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CommandRegistry.Categories.ContainsKey(category))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Unknown category `{category}`. Available: {string.Join(", ", CommandRegistry.Categories.Keys)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool disable = sub == "disable";
|
||||||
|
|
||||||
|
if (target is null or "all")
|
||||||
|
{
|
||||||
|
string key = ChannelRestrictionService.CategoryKey(category);
|
||||||
|
bool changed = disable
|
||||||
|
? await ChannelRestrictionService.DisableAsync(ctx.Channel.Id, key)
|
||||||
|
: await ChannelRestrictionService.EnableAsync(ctx.Channel.Id, key);
|
||||||
|
|
||||||
|
if (!changed)
|
||||||
|
await MessageHelper.ReplyAsync(ctx, disable ? $"**{category.ToTitleCase()}** commands are already disabled here." : $"**{category.ToTitleCase()}** commands are already enabled here.");
|
||||||
|
else
|
||||||
|
await MessageHelper.ReplyAsync(ctx, disable ? $"Disabled all **{category.ToTitleCase()}** commands in this channel." : $"Enabled all **{category.ToTitleCase()}** commands in this channel.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!CommandRegistry.Commands.ContainsKey(target))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Unknown command `{target}`.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string key = ChannelRestrictionService.CommandKey(target);
|
||||||
|
bool changed = disable
|
||||||
|
? await ChannelRestrictionService.DisableAsync(ctx.Channel.Id, key)
|
||||||
|
: await ChannelRestrictionService.EnableAsync(ctx.Channel.Id, key);
|
||||||
|
|
||||||
|
if (!changed)
|
||||||
|
await MessageHelper.ReplyAsync(ctx, disable ? $"`{target}` is already disabled here." : $"`{target}` is already enabled here.");
|
||||||
|
else
|
||||||
|
await MessageHelper.ReplyAsync(ctx, disable ? $"Disabled `{target}` in this channel." : $"Enabled `{target}` in this channel.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Angry : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "angry";
|
|
||||||
public string[] Aliases => Array.Empty<string>();
|
|
||||||
public string Description => "Express anger with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "angry [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/angry");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch an angry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the angry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "angry.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the angry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the angry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} is angry at {target}! 😠"
|
|
||||||
: $"{sender} is angry! 😠";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "angry.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Baka : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "baka";
|
|
||||||
public string[] Aliases => ["idiot"];
|
|
||||||
public string Description => "Call someone a baka with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "baka [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/baka");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a baka gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the baka gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "baka.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the baka gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the baka gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} calls {target} a baka!"
|
|
||||||
: $"{sender} says baka!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "baka.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Bite : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "bite";
|
|
||||||
public string[] Aliases => ["bites"];
|
|
||||||
public string Description => "Bite someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "bite [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/bite");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a bite gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the bite gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "bite.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the bite gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the bite gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} bites {target}!"
|
|
||||||
: $"{sender} wants to bite someone!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "bite.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Blowkiss : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "blowkiss";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Blow a kiss with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "blowkiss [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/blowkiss");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a blowkiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the blowkiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "blowkiss.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the blowkiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the blowkiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} blows a kiss to {target}!"
|
|
||||||
: $"{sender} blows a kiss!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "blowkiss.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Blush : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "blush";
|
|
||||||
public string[] Aliases => Array.Empty<string>();
|
|
||||||
public string Description => "Blush with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "blush [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/blush");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a blush gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the blush gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "blush.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the blush gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the blush gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} is blushing at {target}!"
|
|
||||||
: $"{sender} is blushing!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "blush.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Bonk : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "bonk";
|
|
||||||
public string[] Aliases => ["bonks"];
|
|
||||||
public string Description => "Bonk someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "bonk [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/bonk");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a bonk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the bonk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "bonk.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the bonk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the bonk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} bonks {target}!"
|
|
||||||
: $"{sender} is bonking!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "bonk.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Carry : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "carry";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Carry someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "carry [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/carry");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a carry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the carry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "carry.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the carry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the carry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} carries {target}!"
|
|
||||||
: $"{sender} wants to carry someone!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "carry.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Clap : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "clap";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Clap with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "clap [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/clap");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a clap gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the clap gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "clap.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the clap gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the clap gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} claps for {target}!"
|
|
||||||
: $"{sender} is clapping!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "clap.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Cry : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "cry";
|
|
||||||
public string[] Aliases => ["cries"];
|
|
||||||
public string Description => "Cry with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "cry [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/cry");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a cry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the cry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "cry.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the cry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the cry gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} is crying because of {target}!"
|
|
||||||
: $"{sender} is crying!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "cry.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Cuddle : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "cuddle";
|
|
||||||
public string[] Aliases => ["cuddles"];
|
|
||||||
public string Description => "Cuddle someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "cuddle [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/cuddle");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a cuddle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the cuddle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "cuddle.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the cuddle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the cuddle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} cuddles with {target}!"
|
|
||||||
: $"{sender} wants cuddles!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "cuddle.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Dance : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "dance";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Dance with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "dance [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/dance");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a dance gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the dance gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "dance.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the dance gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the dance gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} dances with {target}!"
|
|
||||||
: $"{sender} is dancing!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "dance.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
163
SkyBot/Commands/RP/Emote.cs
Normal file
163
SkyBot/Commands/RP/Emote.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Shared.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Emote : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "emote";
|
||||||
|
public string[] Aliases => ["e", "action"];
|
||||||
|
public string Description => "RP emote actions.";
|
||||||
|
public string Category => "RP";
|
||||||
|
public string Usage => "emote <action> [@user]";
|
||||||
|
public string[] SubCommands => ["angry", "baka", "bite", "blowkiss", "blush", "bonk", "carry", "clap", "cry", "cuddle", "dance", "facepalm", "happy", "holdhand", "hug", "kiss", "laugh", "lurk", "nom", "nya", "pat", "poke", "pout", "punch", "run", "shocked", "sleep", "smug", "spin", "tableflip", "teehee", "tickle", "wave", "wink", "yawn"];
|
||||||
|
|
||||||
|
private static readonly HttpClient _http = new()
|
||||||
|
{
|
||||||
|
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
switch (ctx.Args.ElementAtOrDefault(0)?.ToLower())
|
||||||
|
{
|
||||||
|
case "angry": await SendAction(ctx, "angry", "{0} is angry at {1}! 😠", "{0} is angry! 😠", false); break;
|
||||||
|
case "baka": await SendAction(ctx, "baka", "{0} calls {1} a baka!", "Please mention someone who is a baka!", true); break;
|
||||||
|
case "bite": await SendAction(ctx, "bite", "{0} bites {1}!", "Please mention someone to bite!", true); break;
|
||||||
|
case "blowkiss": await SendAction(ctx, "blowkiss", "{0} blows a kiss to {1}!", "Please mention someone to blow a kiss too!", true); break;
|
||||||
|
case "blush": await SendAction(ctx, "blush", "{0} is blushing at {1}!", "{0} is blushing!", false); break;
|
||||||
|
case "bonk": await SendAction(ctx, "bonk", "{0} bonks {1}!", "Please mention someone to bonk!", true); break;
|
||||||
|
case "carry": await SendAction(ctx, "carry", "{0} carries {1}!", "Please mention someone to carry!", true); break;
|
||||||
|
case "clap": await SendAction(ctx, "clap", "{0} claps for {1}!", "{0} is clapping!", false); break;
|
||||||
|
case "cry": await SendAction(ctx, "cry", "{0} is crying because of {1}!", "{0} is crying!", false); break;
|
||||||
|
case "cuddle": await SendAction(ctx, "cuddle", "{0} cuddles with {1}!", "Please mention someone to cuddle!", true); break;
|
||||||
|
case "dance": await SendAction(ctx, "dance", "{0} dances with {1}!", "{0} is dancing!", false); break;
|
||||||
|
case "facepalm": await SendAction(ctx, "facepalm", "{0} facepalms at {1}!", "{0} facepalms!", false); break;
|
||||||
|
case "happy": await SendAction(ctx, "happy", "{0} is happy with {1}!", "{0} is happy!", false); break;
|
||||||
|
case "holdhand": await SendAction(ctx, "handhold", "{0} holds hands with {1}!", "Please mention someone to hold hands with!", true); break;
|
||||||
|
case "hug": await SendAction(ctx, "hug", "{0} hugs {1}!", "Please mention someone to hug!", true); break;
|
||||||
|
case "kiss": await SendAction(ctx, "kiss", "{0} kisses {1}!", "Please mention someone to kiss!", true); break;
|
||||||
|
case "laugh": await SendAction(ctx, "laugh", "{0} laughs at {1}!", "{0} is laughing!", false); break;
|
||||||
|
case "lurk": await SendAction(ctx, "lurk", "{0} lurks around {1}!", "{0} is lurking!", false); break;
|
||||||
|
case "nom": await SendAction(ctx, "nom", "{0} noms on {1}!", "{0} is nomming!", false); break;
|
||||||
|
case "nya": await SendAction(ctx, "nya", "{0} nyas at {1}!", "{0} nyas!", false); break;
|
||||||
|
case "pat": await SendAction(ctx, "pat", "{0} gives {1} headpats!", "Please mention someone to give heatpats too!", true); break;
|
||||||
|
case "poke": await SendAction(ctx, "poke", "{0} pokes {1}!", "Please mention someone to poke!", true); break;
|
||||||
|
case "pout": await SendAction(ctx, "pout", "{0} pouts at {1}!", "{0} is pouting!", false); break;
|
||||||
|
case "punch": await SendAction(ctx, "punch", "{0} punches {1}!", "Please mention someone to punch!", true); break;
|
||||||
|
case "run": await SendAction(ctx, "run", "{0} runs away from {1}!", "{0} runs away!", false); break;
|
||||||
|
case "shocked": await SendAction(ctx, "shocked", "{0} is shocked by {1}!", "{0} is shocked!", false); break;
|
||||||
|
case "sleep": await SendAction(ctx, "sleep", "{0} falls asleep on {1}!", "{0} is sleeping! zzz...", false); break;
|
||||||
|
case "smug": await SendAction(ctx, "smug", "{0} looks smug at {1}!", "{0} is feeling smug!", false); break;
|
||||||
|
case "spin": await SendAction(ctx, "spin", "{0} spins {1} around!", "{0} is spinning!", false); break;
|
||||||
|
case "tableflip": await SendAction(ctx, "tableflip", "{0} flips the table at {1}! (╯°□°)╯︵ ┻━┻", "{0} flips the table! (╯°□°)╯︵ ┻━┻", false); break;
|
||||||
|
case "teehee": await SendAction(ctx, "teehee", "{0} teehees at {1}!", "{0} teehees~", false); break;
|
||||||
|
case "tickle": await SendAction(ctx, "tickle", "{0} tickles {1}!", "Please mention someone to tickle!", true); break;
|
||||||
|
case "wave": await SendAction(ctx, "wave", "{0} waves at {1}!", "{0} waves!", false); break;
|
||||||
|
case "wink": await SendAction(ctx, "wink", "{0} winks at {1}! ;)", "{0} winks! ;)", false); break;
|
||||||
|
case "yawn": await SendAction(ctx, "yawn", "{0} yawns at {1}!", "{0} is yawning!", false); break;
|
||||||
|
default:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Unknown emote. Usage: `{Config.Prefix}emote <action>`\nActions: {string.Join(", ", SubCommands)}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SendAction(CommandContext ctx, string apiAction, string withTarget, string withoutTarget, bool requiresTarget = false)
|
||||||
|
{
|
||||||
|
string? target = null;
|
||||||
|
if (ctx.Message.ReplyToId is not null)
|
||||||
|
{
|
||||||
|
var replied = await ctx.Message.FetchReplyMessageAsync();
|
||||||
|
if (replied is not null)
|
||||||
|
{
|
||||||
|
var author = await replied.FetchAuthorAsync();
|
||||||
|
if (author is not null)
|
||||||
|
target = author.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target is null && ctx.Args.Length > 1)
|
||||||
|
target = string.Join(" ", ctx.Args[1..]);
|
||||||
|
|
||||||
|
if (requiresTarget && target is null)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, withoutTarget);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.Channel.SendIsTyping();
|
||||||
|
|
||||||
|
string json;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json = await _http.GetStringAsync($"https://nekos.best/api/v2/{apiAction}");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Could not fetch a {apiAction} gif. Try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
string gifUrl = doc.RootElement.GetProperty("results")[0].GetProperty("url").GetString()!;
|
||||||
|
|
||||||
|
byte[] gifBytes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Could not download the {apiAction} gif. Try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = 0, height = 0;
|
||||||
|
if (gifBytes.Length >= 10)
|
||||||
|
{
|
||||||
|
width = gifBytes[6] | (gifBytes[7] << 8);
|
||||||
|
height = gifBytes[8] | (gifBytes[9] << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
string cdnUrl;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var form = new MultipartFormDataContent();
|
||||||
|
using var fileContent = new ByteArrayContent(gifBytes);
|
||||||
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
||||||
|
form.Add(fileContent, "file", $"{apiAction}.gif");
|
||||||
|
|
||||||
|
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
||||||
|
if (!uploadResult.Success)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Could not upload the {apiAction} gif. Try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cdnUrl = uploadResult.Data!;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Could not upload the {apiAction} gif. Try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
string text = target is not null
|
||||||
|
? string.Format(withTarget, sender, target)
|
||||||
|
: string.Format(withoutTarget, sender);
|
||||||
|
|
||||||
|
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
||||||
|
{
|
||||||
|
Location = cdnUrl,
|
||||||
|
MimeType = "image/gif",
|
||||||
|
FileName = $"{apiAction}.gif",
|
||||||
|
Width = width,
|
||||||
|
Height = height
|
||||||
|
};
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, text, attachments: [attachment]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Facepalm : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "facepalm";
|
|
||||||
public string[] Aliases => Array.Empty<string>();
|
|
||||||
public string Description => "Facepalm with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "facepalm [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/facepalm");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a facepalm gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the facepalm gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "facepalm.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the facepalm gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the facepalm gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} facepalms at {target}!"
|
|
||||||
: $"{sender} facepalms!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "facepalm.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Happy : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "happy";
|
|
||||||
public string[] Aliases => Array.Empty<string>();
|
|
||||||
public string Description => "Express happiness with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "happy [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/happy");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a happy gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the happy gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "happy.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the happy gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the happy gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} is happy with {target}!"
|
|
||||||
: $"{sender} is happy!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "happy.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Holdhand : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "holdhand";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Hold hands with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "holdhand [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/handhold");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a handhold gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the handhold gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "handhold.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the handhold gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the handhold gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} holds hands with {target}!"
|
|
||||||
: $"{sender} wants to hold hands!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "handhold.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Hug : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "hug";
|
|
||||||
public string[] Aliases => ["hugs"];
|
|
||||||
public string Description => "Send a hug with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "hug [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
// Fetch a random hug gif from nekos.best
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/hug");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a hug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
// Download the gif bytes
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the hug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read GIF dimensions from header (bytes 6-9, little-endian)
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload to Valour CDN
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "hug.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the hug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the hug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the hug target: replied-to message author > args > nobody
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} hugs {target}!"
|
|
||||||
: $"{sender} wants a hug!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "hug.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Kiss : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "kiss";
|
|
||||||
public string[] Aliases => ["kisses"];
|
|
||||||
public string Description => "Kiss someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "kiss [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/kiss");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a kiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the kiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "kiss.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the kiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the kiss gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} kisses {target}!"
|
|
||||||
: $"{sender} wants a kiss!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "kiss.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Laugh : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "laugh";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Laugh with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "laugh [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/laugh");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a laugh gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the laugh gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "laugh.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the laugh gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the laugh gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} laughs at {target}!"
|
|
||||||
: $"{sender} is laughing!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "laugh.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Lurk : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "lurk";
|
|
||||||
public string[] Aliases => ["lurks"];
|
|
||||||
public string Description => "Lurk with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "lurk [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/lurk");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a lurk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the lurk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "lurk.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the lurk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the lurk gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} lurks around {target}!"
|
|
||||||
: $"{sender} is lurking!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "lurk.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +1,111 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using SkyBot.Helpers;
|
using SkyBot.Helpers;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using SkyBot.Services;
|
using SkyBot.Services;
|
||||||
using Valour.Sdk.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Marriage : ICommand
|
public class Marriage : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "marriage";
|
public string Name => "marriage";
|
||||||
public string[] Aliases => ["marry"];
|
public string[] Aliases => ["marry"];
|
||||||
public string Description => "Marriage system — propose, accept, decline, check status, or divorce.";
|
public string Description => "Marriage system — propose, check status, or divorce.";
|
||||||
public string Section => "RP";
|
public string Category => "RP";
|
||||||
public string Usage => "marriage <propose|accept|decline|status|divorce>";
|
public string Usage => "marriage <action>";
|
||||||
|
public string[] SubCommands => ["propose", "status", "divorce", "force"];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
public async Task Execute(CommandContext ctx)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
switch (ctx.Args.ElementAtOrDefault(0)?.ToLower())
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
string sub = ctx.Args.Length > 0 ? ctx.Args[0].ToLower() : "status";
|
|
||||||
|
|
||||||
switch (sub)
|
|
||||||
{
|
{
|
||||||
case "propose":
|
case "propose":
|
||||||
await HandlePropose(ctx, channel);
|
await HandlePropose(ctx);
|
||||||
break;
|
|
||||||
case "accept":
|
|
||||||
await HandleAccept(ctx, channel);
|
|
||||||
break;
|
|
||||||
case "decline":
|
|
||||||
await HandleDecline(ctx, channel);
|
|
||||||
break;
|
|
||||||
case "divorce":
|
|
||||||
await HandleDivorce(ctx, channel);
|
|
||||||
break;
|
break;
|
||||||
case "force":
|
case "force":
|
||||||
await HandleForce(ctx, channel);
|
await HandleForce(ctx);
|
||||||
|
break;
|
||||||
|
case "divorce":
|
||||||
|
await HandleDivorce(ctx);
|
||||||
break;
|
break;
|
||||||
case "status":
|
case "status":
|
||||||
await HandleStatus(ctx, channel);
|
await HandleStatus(ctx);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"{Config.Prefix}marriage <propose|accept|decline|status|divorce>");
|
await MessageHelper.ReplyAsync(ctx, $"Usage: `{Config.Prefix}marriage <propose|status|divorce>`");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandlePropose(CommandContext ctx, Channel channel)
|
private static async Task HandlePropose(CommandContext ctx)
|
||||||
{
|
{
|
||||||
long proposerId = ctx.Member.UserId;
|
long propserId = ctx.Member.UserId;
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
|
||||||
long? targetUserId = null;
|
long? targetUserId = null;
|
||||||
string? targetMention = null;
|
string? targetMention = null;
|
||||||
|
|
||||||
// Prefer reply-to for target resolution
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
if (ctx.Message.ReplyToId is not null)
|
||||||
{
|
{
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
var replied = await ctx.Message.FetchReplyMessageAsync();
|
||||||
if (replied is not null)
|
if (replied is null) return;
|
||||||
{
|
var author = await replied.FetchAuthorAsync();
|
||||||
var author = await replied.FetchAuthorAsync();
|
if (author is null) return;
|
||||||
if (author is not null)
|
targetUserId = author.Id;
|
||||||
{
|
targetMention = $"«@u-{author.Id}»";
|
||||||
targetUserId = author.Id;
|
|
||||||
targetMention = $"«@u-{author.Id}»";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to message mention
|
|
||||||
if (targetUserId is null && ctx.Message.Mentions is not null && ctx.Message.Mentions.Any())
|
if (targetUserId is null && ctx.Message.Mentions is not null && ctx.Message.Mentions.Any())
|
||||||
{
|
{
|
||||||
long memberId = ctx.Message.Mentions.First().TargetId;
|
long memberId = ctx.Message.Mentions.First().TargetId;
|
||||||
var mentioned = await ctx.Planet.FetchMemberAsync(memberId);
|
var mentioned = await ctx.Planet.FetchMemberAsync(memberId);
|
||||||
if (mentioned is not null)
|
if (mentioned is null) return;
|
||||||
{
|
targetUserId = mentioned.UserId;
|
||||||
targetUserId = mentioned.UserId;
|
targetMention = $"«@u-{mentioned.UserId}»";
|
||||||
targetMention = $"«@u-{mentioned.UserId}»";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetUserId is null)
|
if (targetUserId is null)
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
await MessageHelper.ReplyAsync(ctx, $"Please reply to someone's message or mention them. Usage: `{Config.Prefix}marriage propose <reply|@user>");
|
||||||
$"Please reply to someone's message or mention them. Usage: `{Config.Prefix}marriage propose @user`");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = MarriageService.Propose(proposerId, targetUserId.Value);
|
var result = MarriageService.Propose(propserId, targetUserId.Value);
|
||||||
|
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case MarriageService.ProposeResult.SelfPropose:
|
case MarriageService.ProposeResult.SelfPropose:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You can't propose to yourself!");
|
await MessageHelper.ReplyAsync(ctx, "You can't propose to yourself.");
|
||||||
break;
|
break;
|
||||||
case MarriageService.ProposeResult.AlreadyMarried:
|
case MarriageService.ProposeResult.AlreadyMarried:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
await MessageHelper.ReplyAsync(ctx, $"You are already married, {sender}. You'd need to `{Config.Prefix}marriage divorce` first.");
|
||||||
$"You're already married, {sender}! You'd need to `{Config.Prefix}marriage divorce` first.");
|
|
||||||
break;
|
break;
|
||||||
case MarriageService.ProposeResult.TargetAlreadyMarried:
|
case MarriageService.ProposeResult.TargetAlreadyMarried:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
await MessageHelper.ReplyAsync(ctx, $"{targetMention} is already married.");
|
||||||
$"{targetMention} is already married!");
|
|
||||||
break;
|
break;
|
||||||
case MarriageService.ProposeResult.AlreadyProposed:
|
case MarriageService.ProposeResult.AlreadyProposed:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
await MessageHelper.ReplyAsync(ctx, $"You've already proposed to {targetMention}! Waiting for thsir reponse");
|
||||||
$"You've already proposed to {targetMention}! Waiting for their response...");
|
|
||||||
break;
|
break;
|
||||||
case MarriageService.ProposeResult.Ok:
|
case MarriageService.ProposeResult.Ok:
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
await MessageHelper.ReplyAsync(ctx, $"💍 {sender} gets down on one knee and proposes to {targetMention}\n{targetMention}, type `{Config.Prefix}accept` or `{Config.Prefix}decline`. This proposal expires in 5 minutes.");
|
||||||
$"💍 {sender} gets down on one knee and proposes to {targetMention}!\n" +
|
|
||||||
$"{targetMention}, type `{Config.Prefix}marriage accept` to accept or `{Config.Prefix}marriage decline` to decline.\n" +
|
|
||||||
$"This proposal expires in 5 minutes.");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleAccept(CommandContext ctx, Channel channel)
|
private static async Task HandleDivorce(CommandContext ctx)
|
||||||
{
|
{
|
||||||
long acceptorId = ctx.Member.UserId;
|
long userId = ctx.Member.UserId;
|
||||||
string acceptorName = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
|
||||||
var (result, proposerId) = await MarriageService.AcceptAsync(acceptorId);
|
long? partnerId = MarriageService.GetMarriage(userId)?.SpouseId;
|
||||||
|
if (partnerId is null)
|
||||||
switch (result)
|
|
||||||
{
|
{
|
||||||
case MarriageService.AcceptResult.NoPendingProposal:
|
await MessageHelper.ReplyAsync(ctx, $"You're not married, {name}.");
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
return;
|
||||||
"You don't have any pending proposals to accept.");
|
|
||||||
break;
|
|
||||||
case MarriageService.AcceptResult.Expired:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"That proposal from «@u-{proposerId}» has expired.");
|
|
||||||
break;
|
|
||||||
case MarriageService.AcceptResult.AlreadyMarried:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
"One of you is already married! The proposal has been cancelled.");
|
|
||||||
break;
|
|
||||||
case MarriageService.AcceptResult.Ok:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"💒 {acceptorName} has accepted «@u-{proposerId}»'s proposal! They are now married! 🎉");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MarriageService.RequestDivorceConfirmation(userId);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Are you sure you want to divorce «@u-{partnerId}»?\nType `{Config.Prefix}confirm` within 60 seconds to confirm, or `{Config.Prefix}cancel` to cancel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleDecline(CommandContext ctx, Channel channel)
|
private static async Task HandleStatus(CommandContext ctx)
|
||||||
{
|
|
||||||
long acceptorId = ctx.Member.UserId;
|
|
||||||
string acceptorName = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
|
|
||||||
var (result, proposerId) = MarriageService.Decline(acceptorId);
|
|
||||||
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case MarriageService.DeclineResult.NoPendingProposal:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
"You don't have any pending proposals to decline.");
|
|
||||||
break;
|
|
||||||
case MarriageService.DeclineResult.Ok:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"💔 {acceptorName} has declined «@u-{proposerId}»'s proposal.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task HandleStatus(CommandContext ctx, Channel channel)
|
|
||||||
{
|
{
|
||||||
long userId = ctx.Member.UserId;
|
long userId = ctx.Member.UserId;
|
||||||
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
@@ -176,173 +114,103 @@ namespace SkyBot.Commands
|
|||||||
{
|
{
|
||||||
long memberId = ctx.Message.Mentions.First().TargetId;
|
long memberId = ctx.Message.Mentions.First().TargetId;
|
||||||
var mentioned = await ctx.Planet.FetchMemberAsync(memberId);
|
var mentioned = await ctx.Planet.FetchMemberAsync(memberId);
|
||||||
if (mentioned is not null)
|
if (mentioned is null) return;
|
||||||
{
|
userId = mentioned.UserId;
|
||||||
userId = mentioned.UserId;
|
name = mentioned.Nickname ?? mentioned.User?.Name ?? "Unknown";
|
||||||
name = mentioned.Nickname ?? mentioned.User?.Name ?? "Unknown";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long? partnerId = MarriageService.GetPartner(userId);
|
MarriageModel? marriage = MarriageService.GetMarriage(userId);
|
||||||
|
|
||||||
if (partnerId is null)
|
if (marriage is null)
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
await MessageHelper.ReplyAsync(ctx, $"{name} is not currently married.");
|
||||||
$"💔 {name} is not currently married.");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
long partnerId = marriage.SpouseId;
|
||||||
$"💑 {name} is married to «@u-{partnerId}»!");
|
DateTimeOffset dt = DateTimeOffset.FromUnixTimeMilliseconds(marriage.MarriedAt);
|
||||||
|
string marriedAt = $"{dt.OrdinalDay()} {dt:MMMM yyyy HH:mm} UTC";
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"{name} is married to «@u-{partnerId}»!\nThey got married: `{marriedAt}`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleForce(CommandContext ctx, Channel channel)
|
private static async Task HandleForce(CommandContext ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Member.UserId != Config.OwnerId)
|
if (!PermissionHelper.IsOwner(ctx.Member))
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command.");
|
await MessageHelper.ReplyAsync(ctx, "You don't have permission to use this command.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string action = ctx.Args.Length > 1 ? ctx.Args[1].ToLower() : "";
|
|
||||||
|
|
||||||
if (action is not ("marry" or "divorce"))
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"Usage: `{Config.Prefix}marriage force marry @user1 @user2` or `{Config.Prefix}marriage force divorce @user1`");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string action = ctx.Args.ElementAtOrDefault(1)?.ToLower() ?? "";
|
||||||
var mentions = ctx.Message.Mentions ?? [];
|
var mentions = ctx.Message.Mentions ?? [];
|
||||||
|
|
||||||
if (action == "marry")
|
switch (action)
|
||||||
{
|
{
|
||||||
if (mentions.Count < 2)
|
case "marry":
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
var member1 = await ctx.Planet.FetchMemberAsync(mentions[0].TargetId);
|
||||||
$"Please mention two users. Usage: `{Config.Prefix}marriage force marry @user1 @user2`");
|
var member2 = await ctx.Planet.FetchMemberAsync(mentions[1].TargetId);
|
||||||
return;
|
|
||||||
|
if (member1 is null || member2 is null)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "Could not find one or both of those members.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await MarriageService.ForceMarryAsync(member1.UserId, member2.UserId);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case MarriageService.ForceMarryResult.SameUser:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You can't marry someone to themselves!");
|
||||||
|
break;
|
||||||
|
case MarriageService.ForceMarryResult.User1AlreadyMarried:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"«@u-{member1.UserId}» is already married.");
|
||||||
|
break;
|
||||||
|
case MarriageService.ForceMarryResult.User2AlreadyMarried:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"«@u-{member2.UserId}» is already married.");
|
||||||
|
break;
|
||||||
|
case MarriageService.ForceMarryResult.Ok:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"💒 «@u-{member1.UserId}» and «@u-{member2.UserId}» are now married! 🎉");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case "divorce":
|
||||||
var member1 = await ctx.Planet.FetchMemberAsync(mentions[0].TargetId);
|
|
||||||
var member2 = await ctx.Planet.FetchMemberAsync(mentions[1].TargetId);
|
|
||||||
|
|
||||||
if (member1 is null || member2 is null)
|
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not find one or both of those members.");
|
if (mentions.Count < 1)
|
||||||
return;
|
{
|
||||||
}
|
await MessageHelper.ReplyAsync(ctx, $"Please mention a user. Usage: `{Config.Prefix}marriage force divorce @user`");
|
||||||
|
return;
|
||||||
var result = await MarriageService.ForceMarryAsync(member1.UserId, member2.UserId);
|
}
|
||||||
|
|
||||||
switch (result)
|
var member = await ctx.Planet.FetchMemberAsync(mentions[0].TargetId);
|
||||||
{
|
|
||||||
case MarriageService.ForceMarryResult.SameUser:
|
if (member is null)
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You can't marry someone to themselves!");
|
{
|
||||||
break;
|
await MessageHelper.ReplyAsync(ctx, "Could not find that member.");
|
||||||
case MarriageService.ForceMarryResult.User1AlreadyMarried:
|
return;
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
}
|
||||||
$"«@u-{member1.UserId}» is already married.");
|
|
||||||
break;
|
var (result, partnerId) = await MarriageService.ForceDivorceAsync(member.UserId);
|
||||||
case MarriageService.ForceMarryResult.User2AlreadyMarried:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
switch (result)
|
||||||
$"«@u-{member2.UserId}» is already married.");
|
{
|
||||||
break;
|
case MarriageService.DivorceResult.NotMarried:
|
||||||
case MarriageService.ForceMarryResult.Ok:
|
await MessageHelper.ReplyAsync(ctx, $"«@u-{member.UserId}» is not married.");
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
break;
|
||||||
$"💒 «@u-{member1.UserId}» and «@u-{member2.UserId}» are now married! 🎉");
|
case MarriageService.DivorceResult.Ok:
|
||||||
break;
|
await MessageHelper.ReplyAsync(ctx, $"💔 «@u-{member.UserId}» and «@u-{partnerId}» have been forcefully divorced.");
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
else // divorce
|
break;
|
||||||
{
|
|
||||||
if (mentions.Count < 1)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"Please mention a user. Usage: `{Config.Prefix}marriage force divorce @user`");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var member = await ctx.Planet.FetchMemberAsync(mentions[0].TargetId);
|
|
||||||
|
|
||||||
if (member is null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var (result, partnerId) = await MarriageService.DivorceAsync(member.UserId);
|
|
||||||
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case MarriageService.DivorceResult.NotMarried:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"«@u-{member.UserId}» is not married.");
|
|
||||||
break;
|
|
||||||
case MarriageService.DivorceResult.Ok:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"💔 «@u-{member.UserId}» and «@u-{partnerId}» have been forcefully divorced.");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Usage: `{Config.Prefix}marriage force marry @user1 @user2` or `{Config.Prefix}marriage force divorce @user`");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleDivorce(CommandContext ctx, Channel channel)
|
|
||||||
{
|
|
||||||
long userId = ctx.Member.UserId;
|
|
||||||
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string action = ctx.Args.Length > 1 ? ctx.Args[1].ToLower() : "";
|
|
||||||
|
|
||||||
if (action == "cancel")
|
|
||||||
{
|
|
||||||
if (MarriageService.CancelDivorce(userId))
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"Divorce cancelled, {name}.");
|
|
||||||
else
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "You don't have a pending divorce to cancel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action == "confirm")
|
|
||||||
{
|
|
||||||
var (result, partnerId) = await MarriageService.DivorceAsync(userId, confirmed: true);
|
|
||||||
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case MarriageService.DivorceResult.NotMarried:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"You're not married, {name}.");
|
|
||||||
break;
|
|
||||||
case MarriageService.DivorceResult.NoConfirmation:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"No pending divorce found. Run `{Config.Prefix}marriage divorce` first.");
|
|
||||||
break;
|
|
||||||
case MarriageService.DivorceResult.ConfirmationExpired:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"Your divorce confirmation expired. Run `{Config.Prefix}marriage divorce` again to start over.");
|
|
||||||
break;
|
|
||||||
case MarriageService.DivorceResult.Ok:
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"💔 {name} and «@u-{partnerId}» have divorced.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial divorce request — ask for confirmation
|
|
||||||
long? partnerId2 = MarriageService.GetPartner(userId);
|
|
||||||
if (partnerId2 is null)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, $"You're not married, {name}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool requested = MarriageService.RequestDivorceConfirmation(userId);
|
|
||||||
if (requested)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel,
|
|
||||||
$"Are you sure you want to divorce «@u-{partnerId2}»?\n" +
|
|
||||||
$"Type `{Config.Prefix}marriage divorce confirm` within 60 seconds to confirm, or `{Config.Prefix}marriage divorce cancel` to cancel.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Nom : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "nom";
|
|
||||||
public string[] Aliases => ["noms"];
|
|
||||||
public string Description => "Nom on someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "nom [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/nom");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a nom gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the nom gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "nom.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the nom gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the nom gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} noms on {target}!"
|
|
||||||
: $"{sender} is nomming!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "nom.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Nya : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "nya";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Nya with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "nya [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/nya");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a nya gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the nya gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "nya.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the nya gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the nya gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} nyas at {target}!"
|
|
||||||
: $"{sender} nyas!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "nya.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Pat : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "pat";
|
|
||||||
public string[] Aliases => ["pats"];
|
|
||||||
public string Description => "Give someone headpats with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "pat [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
// Fetch a random pat gif from nekos.best
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/pat");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a pat gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
// Download the gif bytes
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the pat gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read GIF dimensions from header (bytes 6-9, little-endian)
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload to Valour CDN
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "pat.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the pat gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the pat gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the pat target: replied-to message author > args > nobody
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} gives {target} headpats!"
|
|
||||||
: $"{sender} wants headpats!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "pat.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Poke : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "poke";
|
|
||||||
public string[] Aliases => ["pokes"];
|
|
||||||
public string Description => "Poke someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "poke [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/poke");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a poke gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the poke gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "poke.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the poke gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the poke gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} pokes {target}!"
|
|
||||||
: $"{sender} is poking around!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "poke.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Pout : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "pout";
|
|
||||||
public string[] Aliases => ["pouts"];
|
|
||||||
public string Description => "Pout with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "pout [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/pout");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a pout gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the pout gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "pout.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the pout gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the pout gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} pouts at {target}!"
|
|
||||||
: $"{sender} is pouting!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "pout.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Punch : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "punch";
|
|
||||||
public string[] Aliases => ["punches"];
|
|
||||||
public string Description => "Punch someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "punch [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/punch");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a punch gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the punch gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "punch.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the punch gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the punch gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} punches {target}!"
|
|
||||||
: $"{sender} is throwing punches!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "punch.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Run : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "run";
|
|
||||||
public string[] Aliases => ["runs"];
|
|
||||||
public string Description => "Run away with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "run [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/run");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a run gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the run gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "run.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the run gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the run gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} runs away from {target}!"
|
|
||||||
: $"{sender} runs away!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "run.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Shocked : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "shocked";
|
|
||||||
public string[] Aliases => ["shock"];
|
|
||||||
public string Description => "Express shock with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "shocked [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/shocked");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a shocked gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the shocked gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "shocked.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the shocked gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the shocked gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} is shocked by {target}!"
|
|
||||||
: $"{sender} is shocked!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "shocked.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Sleep : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "sleep";
|
|
||||||
public string[] Aliases => ["eep"];
|
|
||||||
public string Description => "Sleep with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "sleep [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/sleep");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a sleep gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the sleep gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "sleep.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the sleep gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the sleep gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} falls asleep on {target}!"
|
|
||||||
: $"{sender} is sleeping! zzz...";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "sleep.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Smug : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "smug";
|
|
||||||
public string[] Aliases => Array.Empty<string>();
|
|
||||||
public string Description => "Look smug with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "smug [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/smug");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a smug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the smug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "smug.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the smug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the smug gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} looks smug at {target}!"
|
|
||||||
: $"{sender} is feeling smug!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "smug.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Spin : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "spin";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Spin with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "spin [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/spin");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a spin gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the spin gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "spin.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the spin gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the spin gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} spins {target} around!"
|
|
||||||
: $"{sender} is spinning!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "spin.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Tableflip : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "tableflip";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Flip a table with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "tableflip [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/tableflip");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a tableflip gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the tableflip gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "tableflip.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the tableflip gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the tableflip gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} flips the table at {target}! (╯°□°)╯︵ ┻━┻"
|
|
||||||
: $"{sender} flips the table! (╯°□°)╯︵ ┻━┻";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "tableflip.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Teehee : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "teehee";
|
|
||||||
public string[] Aliases => ["hehe"];
|
|
||||||
public string Description => "Teehee with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "teehee [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/teehee");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a teehee gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the teehee gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "teehee.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the teehee gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the teehee gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} teehees at {target}!"
|
|
||||||
: $"{sender} teehees~";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "teehee.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Tickle : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "tickle";
|
|
||||||
public string[] Aliases => ["tickles"];
|
|
||||||
public string Description => "Tickle someone with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "tickle [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/tickle");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a tickle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the tickle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "tickle.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the tickle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the tickle gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} tickles {target}!"
|
|
||||||
: $"{sender} wants to tickle someone!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "tickle.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Wave : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "wave";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Wave with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "wave [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/wave");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a wave gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the wave gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "wave.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the wave gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the wave gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} waves at {target}!"
|
|
||||||
: $"{sender} waves!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "wave.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Wink : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "wink";
|
|
||||||
public string[] Aliases => [];
|
|
||||||
public string Description => "Wink with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "wink [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/wink");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a wink gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the wink gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "wink.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the wink gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the wink gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} winks at {target}! ;)"
|
|
||||||
: $"{sender} winks! ;)";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "wink.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
using SkyBot.Helpers;
|
|
||||||
using SkyBot.Models;
|
|
||||||
using Valour.Sdk.Models;
|
|
||||||
using Valour.Shared.Models;
|
|
||||||
|
|
||||||
namespace SkyBot.Commands
|
|
||||||
{
|
|
||||||
public class Yawn : ICommand
|
|
||||||
{
|
|
||||||
public string Name => "yawn";
|
|
||||||
public string[] Aliases => ["yawns"];
|
|
||||||
public string Description => "Yawn with a random gif.";
|
|
||||||
public string Section => "RP";
|
|
||||||
public string Usage => "yawn [@user]";
|
|
||||||
|
|
||||||
private static readonly HttpClient _http = new()
|
|
||||||
{
|
|
||||||
DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
|
||||||
long channelId = ctx.ChannelId;
|
|
||||||
|
|
||||||
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
|
||||||
|
|
||||||
await channel.SendIsTyping();
|
|
||||||
|
|
||||||
string json;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json = await _http.GetStringAsync("https://nekos.best/api/v2/yawn");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not fetch a yawn gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
var results = doc.RootElement.GetProperty("results");
|
|
||||||
string gifUrl = results[0].GetProperty("url").GetString()!;
|
|
||||||
|
|
||||||
byte[] gifBytes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
gifBytes = await _http.GetByteArrayAsync(gifUrl);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not download the yawn gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
if (gifBytes.Length >= 10)
|
|
||||||
{
|
|
||||||
width = gifBytes[6] | (gifBytes[7] << 8);
|
|
||||||
height = gifBytes[8] | (gifBytes[9] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cdnUrl;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
using var fileContent = new ByteArrayContent(gifBytes);
|
|
||||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif");
|
|
||||||
form.Add(fileContent, "file", "yawn.gif");
|
|
||||||
|
|
||||||
var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse<string>("upload/image", form);
|
|
||||||
if (!uploadResult.Success)
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the yawn gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cdnUrl = uploadResult.Data!;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "Could not upload the yawn gif. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? target = null;
|
|
||||||
if (ctx.Message.ReplyToId is not null)
|
|
||||||
{
|
|
||||||
var replied = await ctx.Message.FetchReplyMessageAsync();
|
|
||||||
if (replied is not null)
|
|
||||||
{
|
|
||||||
var author = await replied.FetchAuthorAsync();
|
|
||||||
if (author is not null)
|
|
||||||
target = author.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target is null && ctx.Args.Length > 0)
|
|
||||||
target = string.Join(" ", ctx.Args);
|
|
||||||
|
|
||||||
string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
|
||||||
string text = target is not null
|
|
||||||
? $"{sender} yawns at {target}!"
|
|
||||||
: $"{sender} is yawning!";
|
|
||||||
|
|
||||||
var attachment = new MessageAttachment(MessageAttachmentType.Image)
|
|
||||||
{
|
|
||||||
Location = cdnUrl,
|
|
||||||
MimeType = "image/gif",
|
|
||||||
FileName = "yawn.gif",
|
|
||||||
Width = width,
|
|
||||||
Height = height
|
|
||||||
};
|
|
||||||
|
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, text, [attachment]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
SkyBot/Commands/Utils/Accept.cs
Normal file
23
SkyBot/Commands/Utils/Accept.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Accept : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "accept";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Accepts a pending action.";
|
||||||
|
public string Category => "Utils";
|
||||||
|
public string Usage => "accept";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
foreach (var handler in PendingActionRegistry.AcceptHandlers)
|
||||||
|
if (await handler(ctx)) return;
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You have nothing pending to accept.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
SkyBot/Commands/Utils/Cancel.cs
Normal file
23
SkyBot/Commands/Utils/Cancel.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Cancel : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "cancel";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Cancels a pending action.";
|
||||||
|
public string Category => "Utils";
|
||||||
|
public string Usage => "cancel";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
foreach (var handler in PendingActionRegistry.CancelHandlers)
|
||||||
|
if (await handler(ctx)) return;
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You have nothing pending to cancel.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
SkyBot/Commands/Utils/Confirm.cs
Normal file
23
SkyBot/Commands/Utils/Confirm.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Confirm : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "confirm";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Confirms a pending action.";
|
||||||
|
public string Category => "Utils";
|
||||||
|
public string Usage => "confirm";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
foreach (var handler in PendingActionRegistry.ConfirmHandlers)
|
||||||
|
if (await handler(ctx)) return;
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You have nothing pending to confirm.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
SkyBot/Commands/Utils/Decline.cs
Normal file
23
SkyBot/Commands/Utils/Decline.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class Decline : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "decline";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Declines a pending action.";
|
||||||
|
public string Category => "Utils";
|
||||||
|
public string Usage => "decline";
|
||||||
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
foreach (var handler in PendingActionRegistry.DeclineHandlers)
|
||||||
|
if (await handler(ctx)) return;
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You have nothing pending to decline.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
namespace SkyBot
|
namespace SkyBot
|
||||||
{
|
{
|
||||||
|
|
||||||
public static class Config {
|
public static class Config {
|
||||||
public static readonly long OwnerId = 15652354820931584;
|
public static readonly long OwnerId = 15652354820931584;
|
||||||
public static readonly string Prefix = "s/";
|
public static readonly string Prefix = "s/";
|
||||||
public static readonly string SourceLink = "https://git.skyjoshua.xyz/SkyJoshua/SkyBot";
|
public static readonly string SourceLink = "https://git.skyjoshua.xyz/SkyJoshua/SkyBot";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
52
SkyBot/Helpers/EmbedStyles.cs
Normal file
52
SkyBot/Helpers/EmbedStyles.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using Valour.Sdk.Models.Messages.Embeds.Styles;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds.Styles.Basic;
|
||||||
|
|
||||||
|
namespace SkyBot.Helpers
|
||||||
|
{
|
||||||
|
public static class EmbedStyles
|
||||||
|
{
|
||||||
|
private static readonly Size Radius = new(Unit.Pixels, 8);
|
||||||
|
private static readonly Size PadV = new(Unit.Pixels, 2);
|
||||||
|
private static readonly Size PadH = new(Unit.Pixels, 6);
|
||||||
|
private static readonly Size FitContent = new(Unit.FitContent);
|
||||||
|
|
||||||
|
|
||||||
|
public static StyleBase[] LabelText => [
|
||||||
|
new TextColor("#a0a0b8"),
|
||||||
|
new FontWeight(600),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static StyleBase[] CategoryBtn => [
|
||||||
|
new BackgroundColor("#5865F2"),
|
||||||
|
new TextColor("#ffffff"),
|
||||||
|
new BorderRadius(Radius),
|
||||||
|
new Padding(left: PadH, right: PadH, top: PadV, bottom: PadV),
|
||||||
|
new FontWeight(600),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static StyleBase[] CommandBtn => [
|
||||||
|
new BackgroundColor("#2b2d3e"),
|
||||||
|
new TextColor("#e0e0f0"),
|
||||||
|
new BorderRadius(Radius),
|
||||||
|
new Padding(left: PadH, right: PadH, top: PadV, bottom: PadV),
|
||||||
|
new Width(FitContent),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static StyleBase[] NavBtn => [
|
||||||
|
new BackgroundColor("#1e1f2e"),
|
||||||
|
new TextColor("#a0a0b8"),
|
||||||
|
new BorderRadius(Radius),
|
||||||
|
new Padding(left: PadH, right: PadH, top: PadV, bottom: PadV),
|
||||||
|
new Width(FitContent),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static StyleBase[] BackBtn => [
|
||||||
|
new BackgroundColor("#5865F2"),
|
||||||
|
new TextColor("#ffffff"),
|
||||||
|
new BorderRadius(Radius),
|
||||||
|
new Padding(left: PadH, right: PadH, top: PadV, bottom: PadV),
|
||||||
|
new FontWeight(600),
|
||||||
|
new Width(FitContent),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,52 +1,61 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using SkyBot.Models;
|
using SkyBot.Models;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds;
|
||||||
using Valour.Shared;
|
using Valour.Shared;
|
||||||
|
|
||||||
namespace SkyBot.Helpers
|
namespace SkyBot.Helpers
|
||||||
{
|
{
|
||||||
public static class MessageHelper
|
public static class MessageHelper
|
||||||
{
|
{
|
||||||
|
|
||||||
public static string Mention(this PlanetMember member) => $"«@m-{member.Id}»";
|
public static string Mention(this PlanetMember member) => $"«@m-{member.Id}»";
|
||||||
public static string Mention(this User user) => $"«@u-{user.Id}»";
|
public static string Mention(this User user) => $"«@u-{user.Id}»";
|
||||||
public static string ToTitleCase(this string str) => System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str);
|
public static string ToTitleCase(this string str) => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str);
|
||||||
|
|
||||||
public static async Task<TaskResult<Message>> ReplyAsync(CommandContext ctx, Channel channel, string content)
|
|
||||||
{
|
|
||||||
long? replyToId = ctx.Message.ReplyToId.HasValue ? ctx.Message.ReplyToId : ctx.Message.Id;
|
|
||||||
|
|
||||||
var msg = new Message(ctx.Client)
|
public static string OrdinalDay(this DateTimeOffset dt)
|
||||||
|
{
|
||||||
|
string suffix = (dt.Day % 100) switch
|
||||||
|
{
|
||||||
|
11 or 12 or 13 => "th",
|
||||||
|
_ => (dt.Day % 10) switch
|
||||||
|
{
|
||||||
|
1 => "st",
|
||||||
|
2 => "nd",
|
||||||
|
3 => "rd",
|
||||||
|
_ => "th"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return $"{dt.Day}{suffix}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<TaskResult<Message>> ReplyAsync(CommandContext ctx, string? content, Embed? embed = null, bool reply = false, IEnumerable<MessageAttachment>? attachments = null)
|
||||||
|
{
|
||||||
|
long? replyToId = reply ? ctx.Message.ReplyToId : ctx.Message.Id;
|
||||||
|
|
||||||
|
Message msg = new(ctx.Client)
|
||||||
{
|
{
|
||||||
Content = content,
|
Content = content,
|
||||||
ChannelId = channel.Id,
|
ChannelId = ctx.Channel.Id,
|
||||||
PlanetId = ctx.Planet.Id,
|
PlanetId = ctx.Planet.Id,
|
||||||
AuthorUserId = ctx.Client.Me.Id,
|
AuthorUserId = ctx.Client.Me.Id,
|
||||||
AuthorMemberId = channel.Planet?.MyMember.Id,
|
AuthorMemberId = ctx.Channel.Planet?.MyMember.Id,
|
||||||
ReplyToId = replyToId,
|
ReplyToId = replyToId,
|
||||||
Fingerprint = Guid.NewGuid().ToString()
|
Fingerprint = Guid.NewGuid().ToString()
|
||||||
};
|
};
|
||||||
return await ctx.Client.MessageService.SendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<TaskResult<Message>> ReplyAsync(CommandContext ctx, Channel channel, string content, List<MessageAttachment> attachments)
|
if (embed is not null)
|
||||||
{
|
msg.SetEmbed(embed);
|
||||||
long? replyToId = ctx.Message.ReplyToId.HasValue ? ctx.Message.ReplyToId : ctx.Message.Id;
|
|
||||||
|
|
||||||
var msg = new Message(ctx.Client)
|
if (attachments is not null)
|
||||||
{
|
{
|
||||||
Content = content,
|
msg.Attachments ??= [];
|
||||||
ChannelId = channel.Id,
|
msg.Attachments.AddRange(attachments);
|
||||||
PlanetId = ctx.Planet.Id,
|
}
|
||||||
AuthorUserId = ctx.Client.Me.Id,
|
|
||||||
AuthorMemberId = channel.Planet?.MyMember.Id,
|
|
||||||
ReplyToId = replyToId,
|
|
||||||
Fingerprint = Guid.NewGuid().ToString()
|
|
||||||
};
|
|
||||||
msg.SetAttachments(attachments);
|
|
||||||
return await ctx.Client.MessageService.SendMessage(msg);
|
return await ctx.Client.MessageService.SendMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static async Task<TaskResult<Message>> EditAsync(Channel channel, Message message, string content)
|
public static async Task<TaskResult<Message>> EditAsync(Channel channel, Message message, string content)
|
||||||
{
|
{
|
||||||
message.Content = content;
|
message.Content = content;
|
||||||
@@ -71,19 +80,6 @@ namespace SkyBot.Helpers
|
|||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSingleEmoji(string input)
|
};
|
||||||
{
|
};
|
||||||
if (string.IsNullOrWhiteSpace(input)) return false;
|
|
||||||
|
|
||||||
input = input.Trim();
|
|
||||||
|
|
||||||
var enumerator = StringInfo.GetTextElementEnumerator(input);
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (enumerator.MoveNext()) count++;
|
|
||||||
|
|
||||||
return count == 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
97
SkyBot/Helpers/PendingActionRegistry.cs
Normal file
97
SkyBot/Helpers/PendingActionRegistry.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using SkyBot.Models;
|
||||||
|
using SkyBot.Services;
|
||||||
|
|
||||||
|
namespace SkyBot.Helpers
|
||||||
|
{
|
||||||
|
public static class PendingActionRegistry
|
||||||
|
{
|
||||||
|
public static readonly List<Func<CommandContext, Task<bool>>> ConfirmHandlers =
|
||||||
|
[
|
||||||
|
TryConfirmDivorce,
|
||||||
|
// Register new confirm handlers here
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<Func<CommandContext, Task<bool>>> CancelHandlers =
|
||||||
|
[
|
||||||
|
TryCancelDivorce,
|
||||||
|
// Register new cancel handlers here
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<Func<CommandContext, Task<bool>>> AcceptHandlers =
|
||||||
|
[
|
||||||
|
TryAcceptProposal,
|
||||||
|
// Register new accept handlers here
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<Func<CommandContext, Task<bool>>> DeclineHandlers =
|
||||||
|
[
|
||||||
|
TryDeclineProposal,
|
||||||
|
// Register new decline handlers here
|
||||||
|
];
|
||||||
|
|
||||||
|
private static async Task<bool> TryConfirmDivorce(CommandContext ctx)
|
||||||
|
{
|
||||||
|
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
var (result, partnerId) = await MarriageService.DivorceAsync(ctx.Member.UserId, confirmed: true);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case MarriageService.DivorceResult.Ok:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"💔 {name} and «@u-{partnerId}» have divorced.");
|
||||||
|
return true;
|
||||||
|
case MarriageService.DivorceResult.ConfirmationExpired:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Your divorce confirmation expired. Run `{Config.Prefix}marriage divorce` again to start over.");
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> TryCancelDivorce(CommandContext ctx)
|
||||||
|
{
|
||||||
|
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
|
||||||
|
if (!MarriageService.CancelDivorce(ctx.Member.UserId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"Divorce cancelled, {name}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> TryAcceptProposal(CommandContext ctx)
|
||||||
|
{
|
||||||
|
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
var (result, proposerId) = await MarriageService.AcceptAsync(ctx.Member.UserId);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case MarriageService.AcceptResult.Ok:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"💒 {name} has accepted «@u-{proposerId}»'s proposal! They are now married! 🎉");
|
||||||
|
return true;
|
||||||
|
case MarriageService.AcceptResult.Expired:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"The proposal from «@u-{proposerId}» has expired.");
|
||||||
|
return true;
|
||||||
|
case MarriageService.AcceptResult.ProposerAlreadyMarried:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"«@u-{proposerId}» is already married!");
|
||||||
|
return true;
|
||||||
|
case MarriageService.AcceptResult.AcceptorAlreadyMarried:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, "You are already married!");
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> TryDeclineProposal(CommandContext ctx)
|
||||||
|
{
|
||||||
|
string name = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown";
|
||||||
|
var (result, proposerId) = MarriageService.Decline(ctx.Member.UserId);
|
||||||
|
|
||||||
|
if (result == MarriageService.DeclineResult.NoPendingProposal)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await MessageHelper.ReplyAsync(ctx, $"💔 {name} has declined «@u-{proposerId}»'s proposal.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
SkyBot/Helpers/PendingConfirmations.cs
Normal file
29
SkyBot/Helpers/PendingConfirmations.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace SkyBot.Helpers
|
||||||
|
{
|
||||||
|
public static class PendingConfirmations
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<long, TaskCompletionSource<bool>> pending = new();
|
||||||
|
public static bool IsPending(long userId) => pending.ContainsKey(userId);
|
||||||
|
|
||||||
|
public static Task<bool> WaitAsync(long userId, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
pending[userId] = tcs;
|
||||||
|
_ = Task.Delay(timeout).ContinueWith(_ => tcs.TrySetResult(false));
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryComplete(long userId, bool confirmed)
|
||||||
|
{
|
||||||
|
if (pending.TryRemove(userId, out var tcs))
|
||||||
|
{
|
||||||
|
return tcs.TrySetResult(confirmed);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,64 +5,38 @@ namespace SkyBot.Helpers
|
|||||||
{
|
{
|
||||||
public static class PermissionHelper
|
public static class PermissionHelper
|
||||||
{
|
{
|
||||||
// Planet-level permissions
|
public static async Task<bool> HasPermAsync(PlanetMember member, Permission[] permissions, Channel? channel = null, bool requireAll = false)
|
||||||
public static bool HasPerm(PlanetMember member, PlanetPermission[] permissions, bool requireAll = false)
|
|
||||||
{
|
{
|
||||||
if (member == null) return false;
|
if (member is null) return false;
|
||||||
if (member.HasPermission(PlanetPermissions.FullControl)) return true;
|
if (member.HasPermission(PlanetPermissions.FullControl)) return true;
|
||||||
if (member.Roles.Any(r => r.IsAdmin)) return true;
|
if (member.Roles.Any(r => r.IsAdmin)) return true;
|
||||||
|
|
||||||
return requireAll
|
var results = new List<bool>();
|
||||||
? permissions.All(p => member.HasPermission(p))
|
|
||||||
: permissions.Any(p => member.HasPermission(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat channel permissions
|
foreach (var perm in permissions)
|
||||||
public static async Task<bool> HasPermAsync(PlanetMember member, Channel channel, ChatChannelPermission[] permissions, bool requireAll = false)
|
{
|
||||||
{
|
bool result = perm switch
|
||||||
if (member == null) return false;
|
{
|
||||||
if (member.HasPermission(PlanetPermissions.FullControl)) return true;
|
PlanetPermission p => member.HasPermission(p),
|
||||||
if (member.Roles.Any(r => r.IsAdmin)) return true;
|
ChatChannelPermission p => channel is not null
|
||||||
|
? await channel.HasPermissionAsync(member, p)
|
||||||
|
: throw new ArgumentNullException(nameof(channel), $"Channel is required for ChatChannelPermission '{p.Name}'"),
|
||||||
|
VoiceChannelPermission p => channel is not null
|
||||||
|
? await channel.HasPermissionAsync(member, p)
|
||||||
|
: throw new ArgumentNullException(nameof(channel), $"Channel is required for VoiceChannelPermission '{p.Name}'"),
|
||||||
|
_ => throw new ArgumentException($"Unsupported permission type: {perm.GetType().Name}")
|
||||||
|
};
|
||||||
|
|
||||||
if (requireAll)
|
results.Add(result);
|
||||||
{
|
|
||||||
foreach (var p in permissions)
|
|
||||||
if (!await channel.HasPermissionAsync(member, p)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var p in permissions)
|
|
||||||
if (await channel.HasPermissionAsync(member, p)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Voice channel permissions
|
return requireAll ? results.All(r => r) : results.Any(r => r);
|
||||||
public static async Task<bool> HasPermAsync(PlanetMember member, Channel channel, VoiceChannelPermission[] permissions, bool requireAll = false)
|
|
||||||
{
|
|
||||||
if (member == null) return false;
|
|
||||||
if (member.HasPermission(PlanetPermissions.FullControl)) return true;
|
|
||||||
if (member.Roles.Any(r => r.IsAdmin)) return true;
|
|
||||||
|
|
||||||
if (requireAll)
|
|
||||||
{
|
|
||||||
foreach (var p in permissions)
|
|
||||||
if (!await channel.HasPermissionAsync(member, p)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var p in permissions)
|
|
||||||
if (await channel.HasPermissionAsync(member, p)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsOwner(PlanetMember member)
|
public static bool IsOwner(PlanetMember member)
|
||||||
{
|
{
|
||||||
if (member == null) return false;
|
if (member is null) return false;
|
||||||
return member.UserId == Config.OwnerId;
|
return member.UserId == Config.OwnerId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
SkyBot/Helpers/PlanetHelper.cs
Normal file
33
SkyBot/Helpers/PlanetHelper.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Shared;
|
||||||
|
|
||||||
|
namespace SkyBot.Helpers
|
||||||
|
{
|
||||||
|
public static class PlanetHelper
|
||||||
|
{
|
||||||
|
public static Task<TaskResult<PlanetBan>> BanAsync(this Planet planet, long targetUserId, string reason, DateTime? expires = null)
|
||||||
|
{
|
||||||
|
var ban = new PlanetBan(planet.Client)
|
||||||
|
{
|
||||||
|
PlanetId = planet.Id,
|
||||||
|
IssuerId = planet.Client.Me.Id,
|
||||||
|
TargetId = targetUserId,
|
||||||
|
Reason = reason,
|
||||||
|
TimeCreated = DateTime.UtcNow,
|
||||||
|
TimeExpires = expires
|
||||||
|
};
|
||||||
|
return ban.CreateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<PlanetBan?> FindBanAsync(this Planet planet, long targetUserId)
|
||||||
|
{
|
||||||
|
var engine = planet.GetBanQueryEngine();
|
||||||
|
await foreach (var ban in engine)
|
||||||
|
{
|
||||||
|
if (ban.TargetId == targetUserId)
|
||||||
|
return ban;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
SkyBot/Helpers/UserHelper.cs
Normal file
29
SkyBot/Helpers/UserHelper.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using SkyBot.Models;
|
||||||
|
using Valour.Sdk.Client;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Sdk.Models.Messages.Embeds;
|
||||||
|
using Valour.Shared;
|
||||||
|
|
||||||
|
namespace SkyBot.Helpers
|
||||||
|
{
|
||||||
|
public static class UserHelper
|
||||||
|
{
|
||||||
|
public static async Task<TaskResult<Message>> SendDirectMessageAsync(this User user, ValourClient client, string? content, Embed? embed = null)
|
||||||
|
{
|
||||||
|
var channelResult = await client.PrimaryNode.GetJsonAsync<Channel>($"api/channels/direct/byUser/{user.Id}");
|
||||||
|
if (!channelResult.Success)
|
||||||
|
return new TaskResult<Message>(false, "Could not open DM channel.");
|
||||||
|
|
||||||
|
var msg = new Message(client)
|
||||||
|
{
|
||||||
|
Content = content,
|
||||||
|
ChannelId = channelResult.Data.Id,
|
||||||
|
AuthorUserId = client.Me.Id,
|
||||||
|
Fingerprint = Guid.NewGuid().ToString()
|
||||||
|
};
|
||||||
|
msg.SetEmbed(embed);
|
||||||
|
|
||||||
|
return await client.MessageService.SendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace SkyBot.Helpers
|
|
||||||
{
|
|
||||||
public static class ValourUsercountHelper {
|
|
||||||
private static readonly HttpClient _http = new HttpClient();
|
|
||||||
private static long _valourUsercount;
|
|
||||||
public static long ValourUsercount => _valourUsercount;
|
|
||||||
|
|
||||||
public static async Task UpdateUsercount()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await _http.GetStringAsync("https://api.valour.gg/api/users/count");
|
|
||||||
|
|
||||||
_valourUsercount = JsonSerializer.Deserialize<long>(response);
|
|
||||||
|
|
||||||
Console.WriteLine($"Valour user count updated: {_valourUsercount}");
|
|
||||||
} catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Failed to update valour user count: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void StartUpdater()
|
|
||||||
{
|
|
||||||
var timer = new System.Timers.Timer(300_000);
|
|
||||||
timer.Elapsed += async (_, _) => await UpdateUsercount();
|
|
||||||
timer.AutoReset = true;
|
|
||||||
timer.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,12 @@ namespace SkyBot.Models
|
|||||||
{
|
{
|
||||||
public class CommandContext
|
public class CommandContext
|
||||||
{
|
{
|
||||||
public required ValourClient Client{ get; set; }
|
public required ValourClient Client { get; set; }
|
||||||
public required ConcurrentDictionary<long, Channel> ChannelCache { get; set; }
|
public required ConcurrentDictionary<long, Channel> ChannelCache { get; set; }
|
||||||
public required PlanetMember Member { get; set; }
|
public required PlanetMember Member { get; set; }
|
||||||
public required Message Message { get; set; }
|
public required Message Message { get; set; }
|
||||||
public required Planet Planet { get; set; }
|
public required Planet Planet { get; set; }
|
||||||
public required long ChannelId { get; set; }
|
public required Channel Channel { get; set; }
|
||||||
public required string[] Args { get; set; }
|
public required string[] Args { get; set; }
|
||||||
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace SkyBot.Models
|
namespace SkyBot.Models
|
||||||
{
|
{
|
||||||
public interface ICommand
|
public interface ICommand
|
||||||
@@ -7,8 +5,9 @@ namespace SkyBot.Models
|
|||||||
string Name { get; }
|
string Name { get; }
|
||||||
string[] Aliases { get; }
|
string[] Aliases { get; }
|
||||||
string Description { get; }
|
string Description { get; }
|
||||||
string Section { get; }
|
string Category { get; }
|
||||||
string Usage { get; }
|
string Usage { get; }
|
||||||
|
string[] SubCommands { get; }
|
||||||
Task Execute(CommandContext ctx);
|
Task Execute(CommandContext ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user