Compare commits
No commits in common. "f5768998b99f2a54d64d9d42891de4ea847d00bd" and "4b03762390d019359794dff0503b39faabe8c437" have entirely different histories.
f5768998b9
...
4b03762390
26 changed files with 335 additions and 684 deletions
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "cargo" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
22
.github/workflows/build.yml
vendored
Normal file
22
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release --verbose
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
13
.idea/ZMP-bot.iml
generated
Normal file
13
.idea/ZMP-bot.iml
generated
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="EMPTY_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/commands/T/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/src/commands/T/target" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="RsMainFunctionNotFound" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/../zmp-bot/.idea/ZMP-bot.iml" filepath="$PROJECT_DIR$/../zmp-bot/.idea/ZMP-bot.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/../zmp-bot" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -1,4 +1,4 @@
|
||||||
max_width = 100
|
max_width = 140
|
||||||
use_small_heuristics = "Default"
|
use_small_heuristics = "Default"
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
format_strings = true
|
format_strings = true
|
58
Cargo.lock
generated
58
Cargo.lock
generated
|
@ -398,20 +398,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dashmap"
|
|
||||||
version = "6.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"hashbrown 0.14.5",
|
|
||||||
"lock_api",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
@ -609,7 +595,6 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
@ -731,18 +716,6 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getset"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eded738faa0e88d3abc9d1a13cb11adc2073c400969eeb8793cf7132589959fc"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.90",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
|
@ -1341,7 +1314,7 @@ checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"dashmap 5.5.3",
|
"dashmap",
|
||||||
"skeptic",
|
"skeptic",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tagptr",
|
"tagptr",
|
||||||
|
@ -1646,28 +1619,6 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr2"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error2"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.90",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.92"
|
version = "1.0.92"
|
||||||
|
@ -2138,7 +2089,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"command_attr",
|
"command_attr",
|
||||||
"dashmap 5.5.3",
|
"dashmap",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
|
@ -2890,7 +2841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "549e54551d85ba6718a95333d9bc4367f69793d7aba638de30f8d25a1f554a1d"
|
checksum = "549e54551d85ba6718a95333d9bc4367f69793d7aba638de30f8d25a1f554a1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap 5.5.3",
|
"dashmap",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"mini-moka",
|
"mini-moka",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
@ -3467,9 +3418,6 @@ dependencies = [
|
||||||
name = "zmp-bot"
|
name = "zmp-bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dashmap 6.1.0",
|
|
||||||
"futures",
|
|
||||||
"getset",
|
|
||||||
"poise",
|
"poise",
|
||||||
"reqwest 0.12.9",
|
"reqwest 0.12.9",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -13,6 +13,3 @@ reqwest = { version = "0.12.9", features = ["json"] }
|
||||||
tokio = { version = "1.42.0", features = ["rt-multi-thread"] }
|
tokio = { version = "1.42.0", features = ["rt-multi-thread"] }
|
||||||
tracing = { version = "0.1.41" }
|
tracing = { version = "0.1.41" }
|
||||||
sqlx = { version = "0.8.2", features = ["sqlite", "sqlx-sqlite", "runtime-tokio"]}
|
sqlx = { version = "0.8.2", features = ["sqlite", "sqlx-sqlite", "runtime-tokio"]}
|
||||||
futures = "0.3.31"
|
|
||||||
dashmap = "6.1.0"
|
|
||||||
getset = "0.1.4"
|
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use reqwest::Client;
|
use reqwest::{Client, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serenity::all::ButtonStyle;
|
use serenity::all::ButtonStyle;
|
||||||
use serenity::all::{
|
use serenity::all::{ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateMessage, ReactionType, User};
|
||||||
ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateMessage, ReactionType,
|
|
||||||
User,
|
|
||||||
};
|
|
||||||
use sqlx::{query_as, Pool, Sqlite};
|
use sqlx::{query_as, Pool, Sqlite};
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
use crate::commands::command_helper::cooldown;
|
use crate::commands::command_helper::cooldown;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::error::Error::Other;
|
use crate::error::Error::Other;
|
||||||
use crate::Caches;
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -20,69 +16,69 @@ struct Links {
|
||||||
#[serde(rename = "DISCORD")]
|
#[serde(rename = "DISCORD")]
|
||||||
pub discord: Option<String>,
|
pub discord: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SocialMedia {
|
struct SocialMedia {
|
||||||
pub links: Option<Links>,
|
pub links: Option<Links>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct HypixelPlayer {
|
struct HypixelPlayer {
|
||||||
#[serde(rename = "socialMedia")]
|
#[serde(rename = "socialMedia")]
|
||||||
pub social_media: Option<SocialMedia>,
|
pub social_media: Option<SocialMedia>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct HypixelResponse {
|
struct HypixelResponse {
|
||||||
#[serde(rename = "player")]
|
#[serde(rename = "player")]
|
||||||
pub player: HypixelPlayer,
|
pub player: HypixelPlayer,
|
||||||
}
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
#[derive(PartialEq, sqlx::FromRow)]
|
struct MojangPlayer {
|
||||||
pub(crate) struct Uuid {
|
pub id: String,
|
||||||
pub(crate) uuid: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, sqlx::FromRow)]
|
||||||
|
struct Uuid {
|
||||||
|
uuid: String,
|
||||||
|
}
|
||||||
impl Uuid {
|
impl Uuid {
|
||||||
fn get(&self) -> &str {
|
fn get(&self) -> &str {
|
||||||
self.uuid.as_str()
|
self.uuid.as_str()
|
||||||
}
|
}
|
||||||
async fn for_ign(ign: &String, cli: &Client, c: &Caches) -> Result<Self, Error> {
|
async fn for_ign(ign: &str) -> Result<Self, Error> {
|
||||||
let uuid = crate::data::mojang::uuid(c, cli, ign.clone()).await?;
|
let url: String = format!("https://api.mojang.com/users/profiles/minecraft/{ign}");
|
||||||
Ok(Self { uuid })
|
let response_400: Response = reqwest::get(url).await?.error_for_status()?;
|
||||||
|
let deserialized = response_400.json::<MojangPlayer>().await?;
|
||||||
|
let uuid = Uuid { uuid: deserialized.id };
|
||||||
|
Ok(uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ign(&self, c: &Caches, cli: &Client) -> Result<String, Error> {
|
async fn ign(&self) -> Result<String, Error> {
|
||||||
let ign = crate::data::mojang::name(c, cli, self.uuid.clone()).await?;
|
let url: String = format!("https://sessionserver.mojang.com/session/minecraft/profile/{}", self.uuid);
|
||||||
Ok(ign)
|
let response_400: Response = reqwest::get(url).await?.error_for_status()?;
|
||||||
|
let deserialized = response_400.json::<MojangPlayer>().await?;
|
||||||
|
Ok(deserialized.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
struct DiscordId {
|
struct DiscordId {
|
||||||
id: u64,
|
id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Uuid {
|
impl Uuid {
|
||||||
async fn has_discord_user(&self, user: &User, client: &Client) -> Result<bool, Error> {
|
async fn has_discord_user(&self, user: &User, client: &Client) -> Result<bool, Error> {
|
||||||
let url: String = format!("https://api.hypixel.net/v2/player?uuid={}", self.uuid);
|
let url: String = format!("https://api.hypixel.net/v2/player?uuid={}", self.uuid);
|
||||||
let res: HypixelResponse = client
|
let response_400: Response = client.get(url).send().await?.error_for_status()?;
|
||||||
.get(url)
|
let deserialized = response_400.json::<HypixelResponse>().await?;
|
||||||
.send()
|
let matches = deserialized
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.json::<HypixelResponse>()
|
|
||||||
.await?;
|
|
||||||
let matches = res
|
|
||||||
.player
|
.player
|
||||||
.social_media
|
.social_media
|
||||||
.and_then(|sm| sm.links)
|
.map(|sm| sm.links)
|
||||||
.and_then(|l| l.discord)
|
.flatten()
|
||||||
.ok_or(Other(
|
.map(|l| l.discord)
|
||||||
"The Hypixel profile has no Discord account linked. Please follow the steps in \
|
.flatten()
|
||||||
<#1256219552568840263>"
|
.ok_or(Other(format!(
|
||||||
.to_string(),
|
"The Hypixel profile has no Discord account linked. Please follow the steps in {}",
|
||||||
))?
|
ChannelId::new(1256219552568840263_u64)
|
||||||
|
)))?
|
||||||
== user.name;
|
== user.name;
|
||||||
Ok(matches)
|
Ok(matches)
|
||||||
}
|
}
|
||||||
|
@ -100,13 +96,11 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
struct Link {
|
||||||
pub(crate) struct Link {
|
|
||||||
link_id: u16,
|
link_id: u16,
|
||||||
discord_ids: Vec<DiscordId>,
|
discord_ids: Vec<DiscordId>,
|
||||||
pub(crate) minecraft_accounts: Vec<Uuid>,
|
minecraft_accounts: Vec<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Link {
|
impl Link {
|
||||||
fn new(link_id: u16) -> Self {
|
fn new(link_id: u16) -> Self {
|
||||||
Link {
|
Link {
|
||||||
|
@ -117,40 +111,34 @@ impl Link {
|
||||||
}
|
}
|
||||||
async fn minecraft(mut self, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
async fn minecraft(mut self, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
||||||
let link_id: i16 = self.link_id.cast_signed();
|
let link_id: i16 = self.link_id.cast_signed();
|
||||||
self.minecraft_accounts = query_as(
|
self.minecraft_accounts =
|
||||||
format!(
|
query_as(format!("SELECT minecraft_uuid AS uuid FROM minecraft_links WHERE link_id = {link_id};").as_str())
|
||||||
"SELECT minecraft_uuid AS uuid FROM minecraft_links WHERE link_id = {link_id};"
|
.fetch_all(pool)
|
||||||
)
|
.await?;
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
async fn discord(mut self, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
async fn discord(mut self, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
||||||
let link_id: i16 = self.link_id.cast_signed();
|
let link_id: i16 = self.link_id.cast_signed();
|
||||||
self.discord_ids = query_as(
|
self.discord_ids = query_as(format!("SELECT discord_id FROM discord_links WHERE link_id = {link_id};").as_str())
|
||||||
format!("SELECT discord_id FROM discord_links WHERE link_id = {link_id};").as_str(),
|
.fetch_all(pool)
|
||||||
)
|
.await?;
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[poise::command(
|
#[poise::command(slash_command, subcommands("add", "list"))]
|
||||||
slash_command,
|
|
||||||
subcommands("add", "list"),
|
|
||||||
install_context = "User|Guild",
|
|
||||||
interaction_context = "Guild|BotDm|PrivateChannel"
|
|
||||||
)]
|
|
||||||
pub(crate) async fn account(_ctx: Context<'_>) -> Result<(), Error> {
|
pub(crate) async fn account(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
// root of slash-commands is not invokable.
|
// root of slash-commands is not invokable.
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, ephemeral = "false")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
install_context = "User|Guild",
|
||||||
|
interaction_context = "Guild|BotDm|PrivateChannel",
|
||||||
|
ephemeral = "false"
|
||||||
|
)]
|
||||||
/// Verify a Minecraft account on the Zombies MultiPlayer Discord.
|
/// Verify a Minecraft account on the Zombies MultiPlayer Discord.
|
||||||
pub(crate) async fn add(
|
pub(crate) async fn add<'a>(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Minecraft username"]
|
#[description = "Minecraft username"]
|
||||||
#[min_length = 2]
|
#[min_length = 2]
|
||||||
|
@ -160,79 +148,42 @@ pub(crate) async fn add(
|
||||||
#[description = "admin-only"] force: Option<bool>,
|
#[description = "admin-only"] force: Option<bool>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
ctx.defer().await?;
|
ctx.defer().await?;
|
||||||
let force: bool =
|
let force: bool = force.unwrap_or(false) && ctx.framework().options.owners.contains(&ctx.author().id) && {
|
||||||
force.unwrap_or(false) && ctx.framework().options.owners.contains(&ctx.author().id) && {
|
let _ = user.as_ref().ok_or(Other(
|
||||||
let _ = user.as_ref().ok_or(Other(
|
"Warning: attempted to run forced account add without specifying a target Discord account.".to_string(),
|
||||||
"Warning: attempted to run forced account add without specifying a target Discord \
|
))?;
|
||||||
account."
|
true
|
||||||
.to_string(),
|
};
|
||||||
))?;
|
|
||||||
true
|
|
||||||
};
|
|
||||||
let user: User = user.unwrap_or(ctx.author().clone());
|
let user: User = user.unwrap_or(ctx.author().clone());
|
||||||
let uuid: Uuid = Uuid::for_ign(&ign, &ctx.data().clients.general, &ctx.data().caches).await?;
|
let uuid: Uuid = Uuid::for_ign(ign.as_str()).await?;
|
||||||
match force
|
match force || uuid.has_discord_user(&user, &ctx.data().hypixel_api_client).await? {
|
||||||
|| uuid
|
|
||||||
.has_discord_user(&user, &ctx.data().clients.hypixel_api_client)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
true => {
|
true => {
|
||||||
let pool = &ctx.data().sqlite_pool;
|
let pool = &ctx.data().sqlite_pool;
|
||||||
let status: &str = match link_id_from_minecraft(pool, uuid.get()).await {
|
let status: &str = match link_id_from_minecraft(pool, uuid.get()).await {
|
||||||
None => match link_id_from_discord(pool, user.id.get()).await {
|
None => match link_id_from_discord(pool, user.id.get()).await {
|
||||||
None => {
|
None => {
|
||||||
let id = new_link_id(pool).await?;
|
let id = new_link_id(pool).await?;
|
||||||
sqlx::query(
|
sqlx::query(format!("INSERT INTO discord_links VALUES ({}, {});", id.inner, user.id.get()).as_str())
|
||||||
format!(
|
.execute(pool)
|
||||||
"INSERT INTO discord_links VALUES ({}, {});",
|
.await?;
|
||||||
id.inner,
|
sqlx::query(format!("INSERT INTO minecraft_links VALUES ({}, \"{}\");", id.inner, uuid.get()).as_str())
|
||||||
user.id.get()
|
.execute(pool)
|
||||||
)
|
.await?;
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"INSERT INTO minecraft_links VALUES ({}, \"{}\");",
|
|
||||||
id.inner,
|
|
||||||
uuid.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Linked your Discord and Minecraft account."
|
"Linked your Discord and Minecraft account."
|
||||||
}
|
}
|
||||||
Some(dc_id) => {
|
Some(dc_id) => {
|
||||||
sqlx::query(
|
sqlx::query(format!("INSERT INTO minecraft_links VALUES ({}, \"{}\");", dc_id.inner, uuid.get()).as_str())
|
||||||
format!(
|
.execute(pool)
|
||||||
"INSERT INTO minecraft_links VALUES ({}, \"{}\");",
|
.await?;
|
||||||
dc_id.inner,
|
"Your Discord account has previously had an account linked. Added the new link."
|
||||||
uuid.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Your Discord account has previously had an account linked. Added the new \
|
|
||||||
link."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(mc_id) => match link_id_from_discord(pool, user.id.get()).await {
|
Some(mc_id) => match link_id_from_discord(pool, user.id.get()).await {
|
||||||
None => {
|
None => {
|
||||||
sqlx::query(
|
sqlx::query(format!("INSERT INTO discord_links VALUES ({}, {});", mc_id.inner, user.id.get()).as_str())
|
||||||
format!(
|
.execute(pool)
|
||||||
"INSERT INTO discord_links VALUES ({}, {});",
|
.await?;
|
||||||
mc_id.inner,
|
"Your Minecraft account has previously had an account linked. Added the new link."
|
||||||
user.id.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Your Minecraft account has previously had an account linked. Added the \
|
|
||||||
new link."
|
|
||||||
}
|
}
|
||||||
Some(dc_id) => {
|
Some(dc_id) => {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
|
@ -253,24 +204,17 @@ pub(crate) async fn add(
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
"Both your Discord and Minecraft account had linked accounts. Merged all \
|
"Both your Discord and Minecraft account had linked accounts. Merged all account links."
|
||||||
account links."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let s = format!(
|
let s = format!("Verification request for <@{}> with IGN `{}`", user.id.get(), ign);
|
||||||
"Verification request for <@{}> with IGN `{}`",
|
|
||||||
user.id.get(),
|
|
||||||
ign
|
|
||||||
);
|
|
||||||
ChannelId::new(1257776992497959075)
|
ChannelId::new(1257776992497959075)
|
||||||
.send_message(
|
.send_message(
|
||||||
ctx,
|
ctx,
|
||||||
CreateMessage::new()
|
CreateMessage::new()
|
||||||
.content(s)
|
.content(s)
|
||||||
.allowed_mentions(
|
.allowed_mentions(CreateAllowedMentions::new().empty_roles().all_users(true))
|
||||||
CreateAllowedMentions::new().empty_roles().all_users(true),
|
|
||||||
)
|
|
||||||
.components(vec![CreateActionRow::Buttons(vec![
|
.components(vec![CreateActionRow::Buttons(vec![
|
||||||
CreateButton::new("accept_verification")
|
CreateButton::new("accept_verification")
|
||||||
.emoji(ReactionType::from('✅'))
|
.emoji(ReactionType::from('✅'))
|
||||||
|
@ -288,8 +232,8 @@ pub(crate) async fn add(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
false => Err(Error::Other(format!(
|
false => Err(Error::Other(format!(
|
||||||
"The Discord account linked on Hypixel does not match the specified discord \
|
"The Discord account linked on Hypixel does not match the specified discord account.\nPlease set your linked Discord account \
|
||||||
account.\nPlease set your linked Discord account on Hypixel to `{}`.",
|
on Hypixel to `{}`.",
|
||||||
user.name
|
user.name
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
|
@ -297,6 +241,8 @@ pub(crate) async fn add(
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
|
install_context = "User|Guild",
|
||||||
|
interaction_context = "Guild|BotDm|PrivateChannel",
|
||||||
ephemeral = "true",
|
ephemeral = "true",
|
||||||
context_menu_command = "Account list"
|
context_menu_command = "Account list"
|
||||||
)]
|
)]
|
||||||
|
@ -305,8 +251,7 @@ pub(crate) async fn list(ctx: Context<'_>, user: User) -> Result<(), Error> {
|
||||||
ctx.defer().await?;
|
ctx.defer().await?;
|
||||||
cooldown(&ctx, 600, 300)?;
|
cooldown(&ctx, 600, 300)?;
|
||||||
let pool: &Pool<Sqlite> = &ctx.data().sqlite_pool;
|
let pool: &Pool<Sqlite> = &ctx.data().sqlite_pool;
|
||||||
let s: String =
|
let s: String = list_string(pool, &user).await?;
|
||||||
list_string(pool, &user, &ctx.data().caches, &ctx.data().clients.general).await?;
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
CreateReply::default()
|
CreateReply::default()
|
||||||
.content(s)
|
.content(s)
|
||||||
|
@ -316,33 +261,19 @@ pub(crate) async fn list(ctx: Context<'_>, user: User) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_link(user: &User, pool: &Pool<Sqlite>) -> Result<Link, Error> {
|
pub(crate) async fn list_string(pool: &Pool<Sqlite>, user: &User) -> Result<String, Error> {
|
||||||
let link_id: u16 = link_id_from_discord(pool, user.id.get())
|
let link_id: u16 = link_id_from_discord(pool, user.id.get())
|
||||||
.await
|
.await
|
||||||
.expect("This user has no linked accounts")
|
.expect("This user has no linked accounts")
|
||||||
.into();
|
.into();
|
||||||
let link = Link::new(link_id)
|
let link: Link = Link::new(link_id).minecraft(pool).await?.discord(pool).await?;
|
||||||
.minecraft(pool)
|
|
||||||
.await?
|
|
||||||
.discord(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn list_string(
|
|
||||||
pool: &Pool<Sqlite>,
|
|
||||||
user: &User,
|
|
||||||
c: &Caches,
|
|
||||||
cli: &Client,
|
|
||||||
) -> Result<String, Error> {
|
|
||||||
let link = get_link(user, pool).await?;
|
|
||||||
let mut discord_list = String::from("### Discord:");
|
let mut discord_list = String::from("### Discord:");
|
||||||
for dc in link.discord_ids {
|
for dc in link.discord_ids {
|
||||||
discord_list.push_str(format!("\n- <@{}>", dc.id).as_str());
|
discord_list.push_str(format!("\n- <@{}>", dc.id).as_str());
|
||||||
}
|
}
|
||||||
let mut minecraft_list = String::from("### Minecraft:");
|
let mut minecraft_list = String::from("### Minecraft:");
|
||||||
for mc in link.minecraft_accounts {
|
for mc in link.minecraft_accounts {
|
||||||
minecraft_list.push_str(format!("\n- `{}`", mc.ign(c, cli).await?).as_str());
|
minecraft_list.push_str(format!("\n- `{}`", mc.ign().await?).as_str());
|
||||||
}
|
}
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"## Account list for member #{}:\n{}\n{}",
|
"## Account list for member #{}:\n{}\n{}",
|
||||||
|
@ -358,16 +289,10 @@ pub(crate) async fn remove(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn link_id_from_minecraft(pool: &Pool<Sqlite>, minecraft_uuid: &str) -> Option<LinkId> {
|
async fn link_id_from_minecraft(pool: &Pool<Sqlite>, minecraft_uuid: &str) -> Option<LinkId> {
|
||||||
query_as(
|
query_as(format!("SELECT link_id FROM minecraft_links WHERE minecraft_uuid = \"{minecraft_uuid}\" LIMIT 1;").as_str())
|
||||||
format!(
|
.fetch_optional(pool)
|
||||||
"SELECT link_id FROM minecraft_links WHERE minecraft_uuid = \"{minecraft_uuid}\" \
|
.await
|
||||||
LIMIT 1;"
|
.expect("Database error: fetching link id by uuid")
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await
|
|
||||||
.expect("Database error: fetching link id by uuid")
|
|
||||||
}
|
}
|
||||||
async fn link_id_from_discord(pool: &Pool<Sqlite>, snowflake: u64) -> Option<LinkId> {
|
async fn link_id_from_discord(pool: &Pool<Sqlite>, snowflake: u64) -> Option<LinkId> {
|
||||||
query_as(
|
query_as(
|
||||||
|
|
27
src/commands/bots.rs
Normal file
27
src/commands/bots.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use std::string::String;
|
||||||
|
|
||||||
|
use poise::CreateReply;
|
||||||
|
|
||||||
|
use crate::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
owners_only,
|
||||||
|
install_context = "User",
|
||||||
|
interaction_context = "Guild|BotDm|PrivateChannel",
|
||||||
|
ephemeral = "false",
|
||||||
|
)]
|
||||||
|
/// Change how many helpstart bots are online, to limit usage of helpstart pings.
|
||||||
|
pub(crate) async fn bots(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[min = 0_u8]
|
||||||
|
#[description = "default: 0"]
|
||||||
|
bots: u8,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.defer_ephemeral().await?;
|
||||||
|
*ctx.data().bots.write().await = bots;
|
||||||
|
let content = format!("{} bots are now registered as available", bots).to_string();
|
||||||
|
ctx.send(CreateReply::default().content(content)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
pub(crate) fn cooldown(ctx: &Context, user: u64, guild: u64) -> Result<(), Error> {
|
pub(crate) fn cooldown(ctx: &Context, user: u64, guild: u64) -> Result<(), Error> {
|
||||||
let mut cooldown_tracker = ctx.command().cooldowns.lock().unwrap();
|
let mut cooldown_tracker = ctx.command().cooldowns.lock().unwrap();
|
||||||
|
|
|
@ -1,136 +1,45 @@
|
||||||
use crate::data::helpstart_api::fetch_all;
|
|
||||||
use crate::data::helpstart_api::ListType::*;
|
|
||||||
use crate::data::mojang::name;
|
|
||||||
use crate::error::Error;
|
|
||||||
use crate::Context;
|
|
||||||
use poise::serenity_prelude::ButtonStyle;
|
|
||||||
use poise::serenity_prelude::ComponentInteractionCollector;
|
|
||||||
use poise::serenity_prelude::CreateActionRow;
|
|
||||||
use poise::serenity_prelude::CreateButton;
|
|
||||||
use poise::serenity_prelude::CreateEmbed;
|
|
||||||
use poise::serenity_prelude::CreateInteractionResponse;
|
|
||||||
use poise::serenity_prelude::CreateInteractionResponseMessage;
|
|
||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use serenity::all::CreateAllowedMentions;
|
||||||
|
|
||||||
|
use crate::commands::command_helper;
|
||||||
|
use crate::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
install_context = "Guild|User",
|
install_context = "Guild",
|
||||||
interaction_context = "Guild|BotDm|PrivateChannel",
|
interaction_context = "Guild",
|
||||||
ephemeral = "true"
|
ephemeral = "false",
|
||||||
)]
|
)]
|
||||||
// Check for bots available to you.
|
/// Ping the @helpstart to fill a queue.
|
||||||
pub(crate) async fn helpstart(ctx: Context<'_>, user: Option<String>) -> Result<(), Error> {
|
pub(crate) async fn helpstart(
|
||||||
ctx.defer_ephemeral().await?;
|
ctx: Context<'_>,
|
||||||
let links = super::accountv2::get_link(ctx.author(), &ctx.data().sqlite_pool).await?;
|
#[min = 1_u8]
|
||||||
let mc_accounts = match user {
|
#[max = 3_u8]
|
||||||
None => {
|
#[description = "amount of players in your party, DO NOT include bots"]
|
||||||
futures::future::try_join_all(
|
#[rename = "current"]
|
||||||
links
|
current_players: u8,
|
||||||
.minecraft_accounts
|
) -> Result<(), Error> {
|
||||||
.into_iter()
|
let needed_players = 4 - current_players;
|
||||||
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.uuid))
|
let bots = *ctx.data().bots.read().await;
|
||||||
.collect::<Vec<_>>(),
|
let g = ctx.guild_id().unwrap().get();
|
||||||
)
|
let mut reply = CreateReply::default();
|
||||||
.await?
|
let ping = match g {
|
||||||
}
|
1256217633959841853_u64 => 1257411572092113017_u64,
|
||||||
Some(name) => vec![name],
|
_ => 0_u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
let bots = fetch_all(&ctx.data().clients.local_api_client).await?;
|
reply = if bots >= needed_players {
|
||||||
let usable = bots
|
reply
|
||||||
.iter()
|
.content("Bots available. Please use <@424767825001971715> in the bot-commands channel instead.")
|
||||||
.filter(|b| match b.list_type() {
|
|
||||||
Whitelist => b.list().iter().any(|w| mc_accounts.contains(w)),
|
|
||||||
Blacklist => mc_accounts.iter().any(|m| !b.list().contains(m)),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let s: String = usable
|
|
||||||
.iter()
|
|
||||||
.map(|b| b.username().as_str())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
let ready: String = usable
|
|
||||||
.iter()
|
|
||||||
.filter_map(|b| {
|
|
||||||
if !*b.in_party()
|
|
||||||
&& *b.last_updated()
|
|
||||||
> (SystemTime::now().duration_since(UNIX_EPOCH).ok()? - Duration::from_secs(5))
|
|
||||||
.as_secs_f64()
|
|
||||||
{
|
|
||||||
Some(b.username().as_str())
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"{}, {}",
|
|
||||||
*b.last_updated(),
|
|
||||||
(SystemTime::now().duration_since(UNIX_EPOCH).ok()? - Duration::from_secs(5))
|
|
||||||
.as_secs_f64()
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ");
|
|
||||||
let bid = ctx.id();
|
|
||||||
let components = vec![CreateActionRow::Buttons(vec![CreateButton::new(
|
|
||||||
bid.to_string(),
|
|
||||||
)
|
|
||||||
.style(ButtonStyle::Primary)
|
|
||||||
.label("📜")])];
|
|
||||||
|
|
||||||
let reply = CreateReply::default()
|
|
||||||
.content(format!(
|
|
||||||
"Bots that are ready for use: {ready}\nBots you can use: {s}\nTotal registered bots: \
|
|
||||||
{}",
|
|
||||||
bots.len()
|
|
||||||
))
|
|
||||||
.components(components)
|
|
||||||
.ephemeral(true);
|
|
||||||
let m = ctx.send(reply).await?;
|
|
||||||
|
|
||||||
while let Some(i) = ComponentInteractionCollector::new(ctx)
|
|
||||||
.author_id(ctx.author().id)
|
|
||||||
.channel_id(ctx.channel_id())
|
|
||||||
.timeout(std::time::Duration::from_secs(60))
|
|
||||||
.filter(move |i| i.data.custom_id == bid.to_string())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
let embed = CreateEmbed::new()
|
|
||||||
.fields(bots.iter().filter_map(|b| {
|
|
||||||
if b.note().trim().is_empty() || usable.iter().any(|&u| std::ptr::eq(u, b)) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((b.username(), b.note(), true))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.title("Notes")
|
|
||||||
.description(
|
|
||||||
"Below is the note of each bot that you cannot use. It might help you get \
|
|
||||||
whitelisted.",
|
|
||||||
);
|
|
||||||
i.create_response(
|
|
||||||
ctx,
|
|
||||||
CreateInteractionResponse::Message(
|
|
||||||
CreateInteractionResponseMessage::new()
|
|
||||||
.embed(embed)
|
|
||||||
.ephemeral(true),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
m.edit(
|
|
||||||
ctx,
|
|
||||||
CreateReply::default()
|
|
||||||
.content(format!(
|
|
||||||
"Bots that are ready for use: {ready}\nBots you can use: {s}\nTotal registered \
|
|
||||||
bots: {}",
|
|
||||||
bots.len()
|
|
||||||
))
|
|
||||||
.ephemeral(true)
|
.ephemeral(true)
|
||||||
.components(vec![]),
|
} else {
|
||||||
)
|
command_helper::cooldown(&ctx, 1200, 600)?;
|
||||||
.await?;
|
reply
|
||||||
|
.content(format!("## <@&{ping}>\nneed: {}", needed_players - bots))
|
||||||
|
.ephemeral(false)
|
||||||
|
.allowed_mentions(CreateAllowedMentions::new().roles(vec![ping]))
|
||||||
|
};
|
||||||
|
ctx.send(reply).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::commands::command_helper::cooldown;
|
||||||
use crate::commands::lfg::Difficulty::Normal;
|
use crate::commands::lfg::Difficulty::Normal;
|
||||||
use crate::commands::lfg::Map::*;
|
use crate::commands::lfg::Map::*;
|
||||||
use crate::commands::lfg::Mode::*;
|
use crate::commands::lfg::Mode::*;
|
||||||
use crate::error::Error;
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
#[derive(Debug, poise::ChoiceParameter, PartialEq)]
|
#[derive(Debug, poise::ChoiceParameter, PartialEq)]
|
||||||
pub enum Map {
|
pub enum Map {
|
||||||
|
@ -47,7 +47,7 @@ pub enum Difficulty {
|
||||||
slash_command,
|
slash_command,
|
||||||
install_context = "Guild",
|
install_context = "Guild",
|
||||||
interaction_context = "Guild",
|
interaction_context = "Guild",
|
||||||
ephemeral = "false"
|
ephemeral = "false",
|
||||||
)]
|
)]
|
||||||
/// Find a team for Hypixel Zombies.
|
/// Find a team for Hypixel Zombies.
|
||||||
pub(crate) async fn lfg(
|
pub(crate) async fn lfg(
|
||||||
|
@ -102,7 +102,7 @@ pub(crate) async fn lfg(
|
||||||
AlienArcadium => Normal,
|
AlienArcadium => Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reply_content: String = format!("## <@&{ping}> {current}/{desired} {map_name}",);
|
let mut reply_content: String = format!("## <@&{ping}> {current}/{desired} {map_name}", );
|
||||||
match difficulty {
|
match difficulty {
|
||||||
Normal => {}
|
Normal => {}
|
||||||
Difficulty::Hard | Difficulty::Rip => {
|
Difficulty::Hard | Difficulty::Rip => {
|
||||||
|
@ -171,39 +171,19 @@ pub(crate) async fn expert(
|
||||||
let (ping, allowed_roles): (u64, Vec<u64>) = match mode {
|
let (ping, allowed_roles): (u64, Vec<u64>) = match mode {
|
||||||
ExpertMap::Speedrun => (
|
ExpertMap::Speedrun => (
|
||||||
1295322375637958716,
|
1295322375637958716,
|
||||||
ROLE_LIST
|
ROLE_LIST.iter().skip(2).map(|tier| [tier[4], tier[5]]).flatten().collect(),
|
||||||
.iter()
|
|
||||||
.skip(2)
|
|
||||||
.map(|tier| [tier[4], tier[5]])
|
|
||||||
.flatten()
|
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
ExpertMap::DeadEnd => (
|
ExpertMap::DeadEnd => (
|
||||||
1295321319344177172,
|
1295321319344177172,
|
||||||
ROLE_LIST
|
ROLE_LIST.iter().skip(2).map(|tier| [tier[1], tier[5]]).flatten().collect(),
|
||||||
.iter()
|
|
||||||
.skip(2)
|
|
||||||
.map(|tier| [tier[1], tier[5]])
|
|
||||||
.flatten()
|
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
ExpertMap::BadBlood => (
|
ExpertMap::BadBlood => (
|
||||||
1295322259631640607,
|
1295322259631640607,
|
||||||
ROLE_LIST
|
ROLE_LIST.iter().skip(2).map(|tier| [tier[2], tier[5]]).flatten().collect(),
|
||||||
.iter()
|
|
||||||
.skip(2)
|
|
||||||
.map(|tier| [tier[2], tier[5]])
|
|
||||||
.flatten()
|
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
ExpertMap::AlienArcadium => (
|
ExpertMap::AlienArcadium => (
|
||||||
1295322327910842441,
|
1295322327910842441,
|
||||||
ROLE_LIST
|
ROLE_LIST.iter().skip(2).map(|tier| [tier[3], tier[5]]).flatten().collect(),
|
||||||
.iter()
|
|
||||||
.skip(2)
|
|
||||||
.map(|tier| [tier[3], tier[5]])
|
|
||||||
.flatten()
|
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let is_expert: bool = ctx
|
let is_expert: bool = ctx
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub(crate) mod accountv2;
|
pub(crate) mod accountv2;
|
||||||
|
pub(crate) mod bots;
|
||||||
pub(crate) mod command_helper;
|
pub(crate) mod command_helper;
|
||||||
pub(crate) mod helpstart;
|
pub(crate) mod helpstart;
|
||||||
pub(crate) mod lfg;
|
pub(crate) mod lfg;
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
use crate::error::Error;
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
const XD: &str = "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\\
|
const XD: &str = "⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⡿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⡿⠿⠿⠿⠿⠿⠿⠿⢿⡿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⡿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⡿⠿⠿⠿⠿⠿⠿⠿⢿⡿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\\
|
⣿⣿⣿⣧⣄⡀⠀⠀⠀⢀⣠⣼⣿⣿⣿⣿⣧⣄⡀⠀⠀⠀⣀⣤⣼⣷⣦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠻⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣼⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣶⣦⣄⡀⠀⠀⠙⢿⣿⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⣧⣄⡀⠀⠀⠀⢀⣠⣼⣿⣿⣿⣿⣧⣄⡀⠀⠀⠀⣀⣤⣼⣷⣦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢿⣿⣿⣿⣿⣿⣿\\
|
⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠙⣿⣿⣿⣿⠋⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠈⢿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠈⢿⡿⠁⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⢸⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠻⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣼⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣶⣦⣄⡀⠀⠀⠙⢿⣿⣿⣿⣿\\
|
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡗⠀⠀⠀⠐⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠙⣿⣿⣿⣿⠋⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠈⢿⣿⣿⣿\\
|
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⣠⡀⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⣰⣿⣷⡄⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⣾⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠈⢿⡿⠁⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⢸⣿⣿⣿\\
|
⣿⣿⣿⣿⣿⣿⣿⡟⠁⠀⢀⣼⣿⣿⣿⣿⣆⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⢀⣼⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⠋⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠙⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⠿⠛⠁⠀⠀⣠⣾⣿⣿⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⣿⣿⣿\\
|
⣿⣿⣿⠿⠛⠁⠀⠀⠀⠙⠻⣿⣿⣿⣿⣿⡿⠟⠋⠀⠀⠀⠈⠛⠻⡿⠟⠛⠁⠀⠀⠈⠉⠉⠉⠉⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣶⣶⣶⣶⣶⣶⣶⣶⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n\
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡗⠀⠀⠀⠐⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⣿⣿⣿\\
|
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n";
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⣠⡀⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿\\
|
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⣰⣿⣷⡄⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⣾⣿⣿⣿\\
|
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⡟⠁⠀⢀⣼⣿⣿⣿⣿⣆⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⢀⣼⣿⣿⣿⣿\\
|
|
||||||
n⣿⣿⣿⣿⣿⣿⠋⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠙⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⠿⠛⠁⠀⠀⣠⣾⣿⣿⣿⣿⣿\\
|
|
||||||
n⣿⣿⣿⠿⠛⠁⠀⠀⠀⠙⠻⣿⣿⣿⣿⣿⡿⠟⠋⠀⠀⠀⠈⠛⠻⡿⠟⠛⠁⠀⠀⠈⠉⠉⠉⠉⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿\\
|
|
||||||
n⣿⣿⣿⣶⣶⣶⣶⣶⣶⣶⣶⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\\
|
|
||||||
n⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n";
|
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
owners_only,
|
owners_only,
|
||||||
install_context = "User|Guild",
|
install_context = "User|Guild",
|
||||||
interaction_context = "Guild|BotDm|PrivateChannel",
|
interaction_context = "Guild|BotDm|PrivateChannel",
|
||||||
ephemeral = "false"
|
ephemeral = "false",
|
||||||
)]
|
)]
|
||||||
/// Useless command to check if the bot is online.
|
/// Useless command to check if the bot is online.
|
||||||
pub(crate) async fn xd(ctx: Context<'_>) -> Result<(), Error> {
|
pub(crate) async fn xd(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
use crate::Error;
|
|
||||||
use getset::Getters;
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Getters)]
|
|
||||||
#[getset(get = "pub(crate)")]
|
|
||||||
pub(crate) struct Bot {
|
|
||||||
username: String,
|
|
||||||
list_type: ListType,
|
|
||||||
list: Vec<String>,
|
|
||||||
strict: bool,
|
|
||||||
/* we don't care abt lobby data
|
|
||||||
* lobby_name: String,
|
|
||||||
* lobby_number: u8,
|
|
||||||
*/
|
|
||||||
in_party: bool,
|
|
||||||
/* we don't care what script the bot is running
|
|
||||||
* client_gui_version: String,
|
|
||||||
* client_version: String,
|
|
||||||
*/
|
|
||||||
last_updated: f64,
|
|
||||||
last_updated_utc: String, //TODO: DateTime
|
|
||||||
note: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub(crate) enum ListType {
|
|
||||||
Whitelist,
|
|
||||||
Blacklist,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn fetch_all(client: &Client) -> Result<Vec<Bot>, Error> {
|
|
||||||
let url = "http://localhost:6969/list";
|
|
||||||
let response: Vec<Bot> = client
|
|
||||||
.get(url)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub(crate) mod account_links;
|
|
||||||
pub(crate) mod helpstart_api;
|
|
||||||
pub(crate) mod mojang;
|
|
|
@ -1,67 +0,0 @@
|
||||||
use crate::Caches;
|
|
||||||
use crate::Error;
|
|
||||||
use reqwest::Client;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
const TTL: Duration = Duration::from_days(1);
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
struct Response {
|
|
||||||
#[serde(rename = "id")]
|
|
||||||
uuid: String,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// :trollface:
|
|
||||||
macro_rules! cache_hit_handler {
|
|
||||||
($name:ident, $url:expr) => {
|
|
||||||
macro_rules! inner1 {
|
|
||||||
($c:expr, $input:expr) => {
|
|
||||||
$c.$name.get($input).and_then(|a| {
|
|
||||||
if a.1 > Instant::now() + TTL {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(a.0.clone())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! inner2 {
|
|
||||||
($cli:expr, $input:expr, $c:expr) => {{
|
|
||||||
let a = $cli
|
|
||||||
.get(format!($url, $input.as_str()))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.json::<Response>()
|
|
||||||
.await?
|
|
||||||
.$name;
|
|
||||||
let _old = $c.$name.insert($input, (a.clone(), Instant::now()));
|
|
||||||
a
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn $name<'a>(
|
|
||||||
c: &'a Caches,
|
|
||||||
cli: &'a Client,
|
|
||||||
input: String,
|
|
||||||
) -> Result<String, Error> {
|
|
||||||
match inner1!(c, &input) {
|
|
||||||
None => {
|
|
||||||
let updated = inner2!(cli, input, c);
|
|
||||||
Ok(updated)
|
|
||||||
}
|
|
||||||
Some(hit) => Ok(hit),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_hit_handler!(
|
|
||||||
name,
|
|
||||||
"https://api.minecraftservices.com/minecraft/profile/lookup/{}"
|
|
||||||
);
|
|
||||||
|
|
||||||
cache_hit_handler!(uuid, "https://api.mojang.com/users/profiles/minecraft/{}");
|
|
44
src/error.rs
44
src/error.rs
|
@ -1,5 +1,5 @@
|
||||||
use poise::{CreateReply, FrameworkError};
|
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
use poise::{CreateReply, FrameworkError};
|
||||||
|
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ macro_rules! reply_fail_handler {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Sqlx(sqlx::Error),
|
SqlxError(sqlx::Error),
|
||||||
Api(reqwest::Error),
|
ApiError(reqwest::Error),
|
||||||
Serenity(serenity::Error),
|
SerenityError(serenity::Error),
|
||||||
OnCooldown(std::time::Duration),
|
OnCooldown(std::time::Duration),
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,10 @@ pub enum Error {
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
match self {
|
match self {
|
||||||
Error::Sqlx(e) => write!(f, "SQLx Error: {}", e),
|
Error::SqlxError(e) => write!(f, "SQLx Error: {}", e),
|
||||||
Error::Api(e) => write!(f, "HTTPS Error:\n{}", e),
|
Error::ApiError(e) => write!(f, "HTTPS Error (Hypixel / Mojang API):\n{}", e),
|
||||||
Error::Serenity(e) => write!(f, "Discord Error:\n {}", e),
|
Error::SerenityError(e) => write!(f, "Discord Error:\n {}", e),
|
||||||
Error::OnCooldown(d) => write!(
|
Error::OnCooldown(d) => write!(f, "This command is on cooldown. {}s remaining.", d.as_secs()),
|
||||||
f,
|
|
||||||
"This command is on cooldown. {}s remaining.",
|
|
||||||
d.as_secs()
|
|
||||||
),
|
|
||||||
Error::Other(s) => write!(f, "{}", s),
|
Error::Other(s) => write!(f, "{}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,39 +34,33 @@ impl Display for Error {
|
||||||
|
|
||||||
impl From<sqlx::Error> for Error {
|
impl From<sqlx::Error> for Error {
|
||||||
fn from(error: sqlx::Error) -> Self {
|
fn from(error: sqlx::Error) -> Self {
|
||||||
Error::Sqlx(error)
|
Error::SqlxError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwest::Error> for Error {
|
impl From<reqwest::Error> for Error {
|
||||||
fn from(error: reqwest::Error) -> Self {
|
fn from(error: reqwest::Error) -> Self {
|
||||||
Error::Api(error)
|
Error::ApiError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serenity::Error> for Error {
|
impl From<serenity::Error> for Error {
|
||||||
fn from(error: serenity::Error) -> Self {
|
fn from(error: serenity::Error) -> Self {
|
||||||
Error::Serenity(error)
|
Error::SerenityError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn handle_error(error: FrameworkError<'_, Data, Error>) {
|
pub(crate) async fn handle_error<'a>(error: FrameworkError<'a, Data, Error>) {
|
||||||
match error {
|
match error {
|
||||||
FrameworkError::Command { error, ctx, .. } => {
|
FrameworkError::Command { error, ctx, .. } => {
|
||||||
reply_fail_handler!(ctx.send(
|
reply_fail_handler!(ctx.send(CreateReply::default().content(error.to_string()).ephemeral(true)))
|
||||||
CreateReply::default()
|
},
|
||||||
.content(error.to_string())
|
FrameworkError::CommandStructureMismatch { description, ctx, .. } => {
|
||||||
.ephemeral(true)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FrameworkError::CommandStructureMismatch {
|
|
||||||
description, ctx, ..
|
|
||||||
} => {
|
|
||||||
reply_fail_handler!(ctx.send(
|
reply_fail_handler!(ctx.send(
|
||||||
CreateReply::default()
|
CreateReply::default()
|
||||||
.content(format!(
|
.content(format!(
|
||||||
"# Command arguments did not match. The command probably has been updated \
|
"# Command arguments did not match. The command probably has been updated recently. Try reloading Discord. \
|
||||||
recently. Try reloading Discord. Description:\n{}",
|
Description:\n{}",
|
||||||
description
|
description
|
||||||
))
|
))
|
||||||
.ephemeral(true)
|
.ephemeral(true)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use serenity::all::ButtonStyle::Success;
|
use serenity::all::ButtonStyle::Success;
|
||||||
|
use serenity::all::ComponentInteractionDataKind;
|
||||||
use serenity::all::Context;
|
use serenity::all::Context;
|
||||||
use serenity::all::CreateActionRow;
|
use serenity::all::CreateActionRow;
|
||||||
use serenity::all::CreateButton;
|
use serenity::all::CreateButton;
|
||||||
|
@ -11,16 +12,11 @@ use serenity::all::Interaction;
|
||||||
use serenity::all::ReactionType;
|
use serenity::all::ReactionType;
|
||||||
use serenity::all::RoleId;
|
use serenity::all::RoleId;
|
||||||
use serenity::all::{ButtonStyle, ComponentInteraction};
|
use serenity::all::{ButtonStyle, ComponentInteraction};
|
||||||
use serenity::all::{ComponentInteractionDataKind, CreateInteractionResponse};
|
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
pub(crate) async fn component(
|
pub(crate) async fn component(ctx: &Context, interaction: &Interaction, data: &Data) -> Result<(), Error> {
|
||||||
ctx: &Context,
|
|
||||||
interaction: &Interaction,
|
|
||||||
data: &Data,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let component = interaction.clone().message_component().unwrap();
|
let component = interaction.clone().message_component().unwrap();
|
||||||
match component.data.kind {
|
match component.data.kind {
|
||||||
ComponentInteractionDataKind::Button => button(ctx, component, data).await,
|
ComponentInteractionDataKind::Button => button(ctx, component, data).await,
|
||||||
|
@ -28,33 +24,24 @@ pub(crate) async fn component(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn button(
|
async fn button(ctx: &Context, mut interaction: ComponentInteraction, data: &Data) -> Result<(), Error> {
|
||||||
ctx: &Context,
|
let m = &interaction.message;
|
||||||
mut interaction: ComponentInteraction,
|
let u = m.mentions.first().expect("Message did not mention a user.");
|
||||||
data: &Data,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let u = interaction
|
|
||||||
.message
|
|
||||||
.mentions
|
|
||||||
.first()
|
|
||||||
.expect("Message did not mention a user.")
|
|
||||||
.id;
|
|
||||||
match interaction.data.custom_id.as_str() {
|
match interaction.data.custom_id.as_str() {
|
||||||
"accept_verification" => {
|
"accept_verification" => {
|
||||||
let member = interaction
|
let member = m
|
||||||
.message
|
|
||||||
.guild_id
|
.guild_id
|
||||||
.unwrap_or(GuildId::new(1256217633959841853_u64))
|
.unwrap_or(GuildId::new(1256217633959841853_u64))
|
||||||
.member(ctx, u)
|
.member(ctx, u.id)
|
||||||
.await?;
|
.await?;
|
||||||
let (_, _, _dm, _) = futures::try_join!(
|
member.add_role(ctx, RoleId::new(1256218805911425066_u64)).await?;
|
||||||
member.add_role(ctx, RoleId::new(1256218805911425066_u64)),
|
member.remove_role(ctx, RoleId::new(1256253358701023232_u64)).await?;
|
||||||
member.remove_role(ctx, RoleId::new(1256253358701023232_u64)),
|
let _dm = u
|
||||||
u.direct_message(
|
.direct_message(ctx, CreateMessage::new().content("Your verified minecraft account was approved."))
|
||||||
ctx,
|
.await?;
|
||||||
CreateMessage::new().content("Your verified minecraft account was approved.")
|
interaction
|
||||||
),
|
.message
|
||||||
interaction.message.edit(
|
.edit(
|
||||||
ctx,
|
ctx,
|
||||||
EditMessage::new().components(vec![CreateActionRow::Buttons(vec![
|
EditMessage::new().components(vec![CreateActionRow::Buttons(vec![
|
||||||
CreateButton::new("accept_verification")
|
CreateButton::new("accept_verification")
|
||||||
|
@ -70,19 +57,16 @@ async fn button(
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
])]),
|
])]),
|
||||||
)
|
)
|
||||||
)?;
|
|
||||||
interaction
|
|
||||||
.create_response(ctx, CreateInteractionResponse::Acknowledge)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"deny_verification" => {
|
"deny_verification" => {
|
||||||
let (_dm, _) = futures::try_join!(
|
let _dm = u
|
||||||
u.direct_message(
|
.direct_message(ctx, CreateMessage::new().content("Your verified minecraft account was denied."))
|
||||||
ctx,
|
.await?;
|
||||||
CreateMessage::new().content("Your verified minecraft account was denied.")
|
interaction
|
||||||
),
|
.message
|
||||||
interaction.message.edit(
|
.edit(
|
||||||
ctx,
|
ctx,
|
||||||
EditMessage::new().components(vec![CreateActionRow::Buttons(vec![
|
EditMessage::new().components(vec![CreateActionRow::Buttons(vec![
|
||||||
CreateButton::new("accept_verification")
|
CreateButton::new("accept_verification")
|
||||||
|
@ -98,30 +82,15 @@ async fn button(
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
])]),
|
])]),
|
||||||
)
|
)
|
||||||
)?;
|
|
||||||
interaction
|
|
||||||
.create_response(ctx, CreateInteractionResponse::Acknowledge)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"list_accounts" => {
|
"list_accounts" => {
|
||||||
let user = interaction.message.mentions.first().unwrap();
|
let user = interaction.message.mentions.first().unwrap();
|
||||||
let s: String = crate::commands::accountv2::list_string(
|
let s: String = crate::commands::accountv2::list_string(&data.sqlite_pool, user).await?;
|
||||||
&data.sqlite_pool,
|
|
||||||
user,
|
|
||||||
&data.caches,
|
|
||||||
&data.clients.general,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
interaction
|
interaction
|
||||||
.create_response(
|
.create_response(ctx, Message(CreateInteractionResponseMessage::new().content(s).ephemeral(true)))
|
||||||
ctx,
|
|
||||||
Message(
|
|
||||||
CreateInteractionResponseMessage::new()
|
|
||||||
.content(s)
|
|
||||||
.ephemeral(true),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,7 @@ use crate::error::Error;
|
||||||
pub(crate) async fn on_create(ctx: &Context, thread: &GuildChannel) -> Result<(), Error> {
|
pub(crate) async fn on_create(ctx: &Context, thread: &GuildChannel) -> Result<(), Error> {
|
||||||
match thread.parent_id.map(|parent| parent.get()) {
|
match thread.parent_id.map(|parent| parent.get()) {
|
||||||
Some(1295108216388325386) => {
|
Some(1295108216388325386) => {
|
||||||
thread
|
thread.id.edit_thread(ctx, EditThread::new().rate_limit_per_user(60_u16)).await?;
|
||||||
.id
|
|
||||||
.edit_thread(ctx, EditThread::new().rate_limit_per_user(60_u16))
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Some(_) => Ok(()),
|
Some(_) => Ok(()),
|
||||||
|
|
122
src/main.rs
122
src/main.rs
|
@ -1,92 +1,44 @@
|
||||||
#![feature(integer_sign_cast)]
|
#![feature(integer_sign_cast)]
|
||||||
#![feature(duration_constructors)]
|
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
|
use serenity::{FullEvent, model::id::UserId};
|
||||||
use serenity::all::{ActivityData, InteractionType, RoleId};
|
use serenity::all::{ActivityData, InteractionType, RoleId};
|
||||||
use serenity::prelude::GatewayIntents;
|
use serenity::prelude::GatewayIntents;
|
||||||
use serenity::{model::id::UserId, FullEvent};
|
|
||||||
use sqlx::Sqlite;
|
use sqlx::Sqlite;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod data;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
|
||||||
struct Caches {
|
|
||||||
name: DashMap<String, (String, std::time::Instant)>,
|
|
||||||
uuid: DashMap<String, (String, std::time::Instant)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Caches {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: DashMap::new(),
|
|
||||||
uuid: DashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ApiClients {
|
|
||||||
hypixel_api_client: reqwest::Client,
|
|
||||||
local_api_client: reqwest::Client,
|
|
||||||
general: reqwest::Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ApiClients {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
hypixel_api_client: {
|
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
|
||||||
headers.insert(
|
|
||||||
"API-Key",
|
|
||||||
reqwest::header::HeaderValue::try_from(
|
|
||||||
std::env::var("HYPIXEL_API_KEY").unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
reqwest::ClientBuilder::new()
|
|
||||||
.default_headers(headers)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
local_api_client: reqwest::ClientBuilder::new()
|
|
||||||
.danger_accept_invalid_hostnames(true)
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
general: reqwest::ClientBuilder::default().build().unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
|
bots: Arc<RwLock<u8>>,
|
||||||
sqlite_pool: sqlx::Pool<Sqlite>,
|
sqlite_pool: sqlx::Pool<Sqlite>,
|
||||||
clients: ApiClients,
|
hypixel_api_client: reqwest::Client,
|
||||||
caches: Caches,
|
} // User data, which is stored and accessible in all command invocations
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Data {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
sqlite_pool: sqlx::sqlite::SqlitePoolOptions::new()
|
|
||||||
.idle_timeout(Duration::from_secs(10))
|
|
||||||
.connect_lazy("sqlite:accounts.db")
|
|
||||||
.unwrap(),
|
|
||||||
caches: Caches::default(),
|
|
||||||
clients: ApiClients::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
let sqlite_pool = sqlx::sqlite::SqlitePoolOptions::new()
|
||||||
|
.idle_timeout(Duration::from_secs(10))
|
||||||
|
.connect_lazy("sqlite:accounts.db")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let hypixel_api: String = std::env::var("HYPIXEL_API_KEY").unwrap();
|
||||||
|
let hypixel_api_client = {
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
headers.insert("API-Key", reqwest::header::HeaderValue::try_from(hypixel_api).unwrap());
|
||||||
|
reqwest::ClientBuilder::new().default_headers(headers).build().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
let options = poise::FrameworkOptions {
|
let options = poise::FrameworkOptions {
|
||||||
commands: vec![
|
commands: vec![
|
||||||
commands::lfg::lfg(),
|
commands::lfg::lfg(),
|
||||||
|
@ -94,6 +46,7 @@ async fn main() {
|
||||||
commands::lfg::other(),
|
commands::lfg::other(),
|
||||||
commands::xd::xd(),
|
commands::xd::xd(),
|
||||||
commands::helpstart::helpstart(),
|
commands::helpstart::helpstart(),
|
||||||
|
commands::bots::bots(),
|
||||||
commands::accountv2::account(),
|
commands::accountv2::account(),
|
||||||
],
|
],
|
||||||
manual_cooldowns: true,
|
manual_cooldowns: true,
|
||||||
|
@ -106,15 +59,8 @@ async fn main() {
|
||||||
error::handle_error(error).await;
|
error::handle_error(error).await;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
owners: {
|
owners: { HashSet::from([UserId::new(449579075531440128_u64), UserId::new(659112817508745216_u64)]) },
|
||||||
HashSet::from([
|
event_handler: |ctx, event, framework, data| Box::pin(event_handler(ctx, event, framework, data)),
|
||||||
UserId::new(449579075531440128_u64),
|
|
||||||
UserId::new(659112817508745216_u64),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
event_handler: |ctx, event, framework, data| {
|
|
||||||
Box::pin(event_handler(ctx, event, framework, data))
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,15 +69,17 @@ async fn main() {
|
||||||
.setup(move |ctx, _ready, framework| {
|
.setup(move |ctx, _ready, framework| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
Ok(Data::default())
|
Ok(Data {
|
||||||
|
bots: Arc::new(RwLock::new(0)),
|
||||||
|
sqlite_pool,
|
||||||
|
hypixel_api_client,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let token = std::env::var("DISCORD_TOKEN").unwrap();
|
let token = std::env::var("DISCORD_TOKEN").unwrap();
|
||||||
let intents = GatewayIntents::non_privileged()
|
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
||||||
| GatewayIntents::MESSAGE_CONTENT
|
|
||||||
| GatewayIntents::GUILD_MEMBERS;
|
|
||||||
let client = serenity::ClientBuilder::new(token, intents)
|
let client = serenity::ClientBuilder::new(token, intents)
|
||||||
.framework(framework)
|
.framework(framework)
|
||||||
.activity(ActivityData::custom("NPC moment..."))
|
.activity(ActivityData::custom("NPC moment..."))
|
||||||
|
@ -148,27 +96,23 @@ async fn event_handler(
|
||||||
match event {
|
match event {
|
||||||
FullEvent::Ready { data_about_bot, .. } => {
|
FullEvent::Ready { data_about_bot, .. } => {
|
||||||
println!("Logged in as '{}'!", data_about_bot.user.name);
|
println!("Logged in as '{}'!", data_about_bot.user.name);
|
||||||
}
|
},
|
||||||
FullEvent::GuildMemberAddition { new_member } => {
|
FullEvent::GuildMemberAddition { new_member } => {
|
||||||
if new_member.guild_id.get() == 1256217633959841853_u64 {
|
if new_member.guild_id.get() == 1256217633959841853_u64 {
|
||||||
new_member
|
new_member.add_role(ctx, RoleId::new(1256253358701023232_u64)).await?;
|
||||||
.add_role(ctx, RoleId::new(1256253358701023232_u64))
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FullEvent::InteractionCreate { interaction } => {
|
FullEvent::InteractionCreate { interaction } => {
|
||||||
if interaction.application_id().get() == 1165594074473037824
|
if interaction.application_id().get() == 1165594074473037824 && interaction.kind() == InteractionType::Component {
|
||||||
&& interaction.kind() == InteractionType::Component
|
|
||||||
{
|
|
||||||
handlers::bot_interaction::component(ctx, interaction, data).await?;
|
handlers::bot_interaction::component(ctx, interaction, data).await?;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
FullEvent::Message { new_message } => {
|
FullEvent::Message { new_message } => {
|
||||||
handlers::message::on_create(ctx, new_message).await?;
|
handlers::message::on_create(ctx, new_message).await?;
|
||||||
}
|
},
|
||||||
FullEvent::ThreadCreate { thread } => {
|
FullEvent::ThreadCreate { thread } => {
|
||||||
handlers::thread::on_create(ctx, thread).await?;
|
handlers::thread::on_create(ctx, thread).await?;
|
||||||
}
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Reference in a new issue