style: use tabs over spaces
This commit is contained in:
parent
f2979d4cde
commit
f0550dd429
41 changed files with 1112 additions and 1109 deletions
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hard_tabs = true
|
66
build.rs
66
build.rs
|
@ -8,15 +8,15 @@ include!("src/tags.rs");
|
||||||
/// generate the ChoiceParameter enum and tag data we will use in the `tag` command
|
/// generate the ChoiceParameter enum and tag data we will use in the `tag` command
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
let generated = Path::new(&out_dir).join("generated.rs");
|
let generated = Path::new(&out_dir).join("generated.rs");
|
||||||
|
|
||||||
let tag_files: Vec<String> = fs::read_dir(TAG_DIR)
|
let tag_files: Vec<String> = fs::read_dir(TAG_DIR)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|f| f.unwrap().file_name().to_string_lossy().to_string())
|
.map(|f| f.unwrap().file_name().to_string_lossy().to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let tags: Vec<Tag> = tag_files
|
let tags: Vec<Tag> = tag_files
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
|
@ -45,23 +45,23 @@ fn main() {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let formatted_names: Vec<String> = tags
|
let formatted_names: Vec<String> = tags
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| t.file_name.replace(".md", "").replace('-', "_"))
|
.map(|t| t.file_name.replace(".md", "").replace('-', "_"))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let tag_choice = format!(
|
let tag_choice = format!(
|
||||||
r#"
|
r#"
|
||||||
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
|
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
|
||||||
#[derive(Clone, Debug, poise::ChoiceParameter)]
|
#[derive(Clone, Debug, poise::ChoiceParameter)]
|
||||||
pub enum TagChoice {{
|
pub enum TagChoice {{
|
||||||
{}
|
{}
|
||||||
}}"#,
|
}}"#,
|
||||||
formatted_names.join(",\n")
|
formatted_names.join(",\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
let to_str = format!(
|
let to_str = format!(
|
||||||
r#"
|
r#"
|
||||||
impl TagChoice {{
|
impl TagChoice {{
|
||||||
fn as_str(&self) -> &str {{
|
fn as_str(&self) -> &str {{
|
||||||
match &self {{
|
match &self {{
|
||||||
|
@ -70,22 +70,22 @@ fn main() {
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
"#,
|
"#,
|
||||||
formatted_names
|
formatted_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|n| {
|
.map(|n| {
|
||||||
let file_name = n.replace('_', "-") + ".md";
|
let file_name = n.replace('_', "-") + ".md";
|
||||||
format!("Self::{n} => \"{file_name}\",")
|
format!("Self::{n} => \"{file_name}\",")
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
let contents = Vec::from([tag_choice, to_str]).join("\n\n");
|
let contents = Vec::from([tag_choice, to_str]).join("\n\n");
|
||||||
|
|
||||||
fs::write(generated, contents).unwrap();
|
fs::write(generated, contents).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"cargo:rustc-env=TAGS={}",
|
"cargo:rustc-env=TAGS={}",
|
||||||
// make sure we can deserialize with env! at runtime
|
// make sure we can deserialize with env! at runtime
|
||||||
serde_json::to_string(&tags).unwrap()
|
serde_json::to_string(&tags).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,15 @@ use reqwest::StatusCode;
|
||||||
const DADJOKE: &str = "https://icanhazdadjoke.com";
|
const DADJOKE: &str = "https://icanhazdadjoke.com";
|
||||||
|
|
||||||
pub async fn get_joke() -> Result<String> {
|
pub async fn get_joke() -> Result<String> {
|
||||||
let req = REQWEST_CLIENT.get(DADJOKE).build()?;
|
let req = REQWEST_CLIENT.get(DADJOKE).build()?;
|
||||||
|
|
||||||
debug!("Making request to {}", req.url());
|
debug!("Making request to {}", req.url());
|
||||||
let resp = REQWEST_CLIENT.execute(req).await?;
|
let resp = REQWEST_CLIENT.execute(req).await?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
Ok(resp.text().await?)
|
Ok(resp.text().await?)
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!("Couldn't get a joke!"))
|
Err(eyre!("Couldn't get a joke!"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ pub mod prism_meta;
|
||||||
pub mod rory;
|
pub mod rory;
|
||||||
|
|
||||||
pub static USER_AGENT: Lazy<String> = Lazy::new(|| {
|
pub static USER_AGENT: Lazy<String> = Lazy::new(|| {
|
||||||
let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development");
|
let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development");
|
||||||
|
|
||||||
format!("refraction/{version}")
|
format!("refraction/{version}")
|
||||||
});
|
});
|
||||||
|
|
||||||
pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
||||||
reqwest::Client::builder()
|
reqwest::Client::builder()
|
||||||
.user_agent(USER_AGENT.to_string())
|
.user_agent(USER_AGENT.to_string())
|
||||||
.build()
|
.build()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,30 +8,32 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PluralKitMessage {
|
pub struct PluralKitMessage {
|
||||||
pub sender: String,
|
pub sender: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PLURAL_KIT: &str = "https://api.pluralkit.me/v2";
|
const PLURAL_KIT: &str = "https://api.pluralkit.me/v2";
|
||||||
const MESSAGES_ENDPOINT: &str = "/messages";
|
const MESSAGES_ENDPOINT: &str = "/messages";
|
||||||
|
|
||||||
pub async fn get_sender(message_id: MessageId) -> Result<UserId> {
|
pub async fn get_sender(message_id: MessageId) -> Result<UserId> {
|
||||||
let req = REQWEST_CLIENT
|
let req = REQWEST_CLIENT
|
||||||
.get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}"))
|
.get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}"))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
debug!("Making request to {}", req.url());
|
debug!("Making request to {}", req.url());
|
||||||
let resp = REQWEST_CLIENT.execute(req).await?;
|
let resp = REQWEST_CLIENT.execute(req).await?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
let data = resp.json::<PluralKitMessage>().await?;
|
let data = resp.json::<PluralKitMessage>().await?;
|
||||||
let id: u64 = data.sender.parse().wrap_err_with(|| format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}"))?;
|
let id: u64 = data.sender.parse().wrap_err_with(|| {
|
||||||
let sender = UserId::from(id);
|
format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}")
|
||||||
|
})?;
|
||||||
|
let sender = UserId::from(id);
|
||||||
|
|
||||||
Ok(sender)
|
Ok(sender)
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!(
|
Err(eyre!(
|
||||||
"Failed to get PluralKit message information from {PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id} with {status}",
|
"Failed to get PluralKit message information from {PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id} with {status}",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,39 +8,39 @@ use serde::{Deserialize, Serialize};
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MinecraftPackageJson {
|
pub struct MinecraftPackageJson {
|
||||||
pub format_version: u8,
|
pub format_version: u8,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub recommended: Vec<String>,
|
pub recommended: Vec<String>,
|
||||||
pub uid: String,
|
pub uid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRISM_META: &str = "https://meta.prismlauncher.org/v1";
|
const PRISM_META: &str = "https://meta.prismlauncher.org/v1";
|
||||||
const MINECRAFT_PACKAGEJSON_ENDPOINT: &str = "/net.minecraft/package.json";
|
const MINECRAFT_PACKAGEJSON_ENDPOINT: &str = "/net.minecraft/package.json";
|
||||||
|
|
||||||
pub async fn get_latest_minecraft_version() -> Result<String> {
|
pub async fn get_latest_minecraft_version() -> Result<String> {
|
||||||
let req = REQWEST_CLIENT
|
let req = REQWEST_CLIENT
|
||||||
.get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}"))
|
.get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}"))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
debug!("Making request to {}", req.url());
|
debug!("Making request to {}", req.url());
|
||||||
let resp = REQWEST_CLIENT.execute(req).await?;
|
let resp = REQWEST_CLIENT.execute(req).await?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
let data = resp
|
let data = resp
|
||||||
.json::<MinecraftPackageJson>()
|
.json::<MinecraftPackageJson>()
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't parse Minecraft versions!")?;
|
.wrap_err_with(|| "Couldn't parse Minecraft versions!")?;
|
||||||
|
|
||||||
let version = data
|
let version = data
|
||||||
.recommended
|
.recommended
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| eyre!("Couldn't find latest version of Minecraft!"))?;
|
.ok_or_else(|| eyre!("Couldn't find latest version of Minecraft!"))?;
|
||||||
|
|
||||||
Ok(version.clone())
|
Ok(version.clone())
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!(
|
Err(eyre!(
|
||||||
"Failed to get latest Minecraft version from {PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT} with {status}",
|
"Failed to get latest Minecraft version from {PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT} with {status}",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,40 +7,40 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct RoryResponse {
|
pub struct RoryResponse {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RORY: &str = "https://rory.cat";
|
const RORY: &str = "https://rory.cat";
|
||||||
const ENDPOINT: &str = "/purr";
|
const ENDPOINT: &str = "/purr";
|
||||||
|
|
||||||
pub async fn get_rory(id: Option<u64>) -> Result<RoryResponse> {
|
pub async fn get_rory(id: Option<u64>) -> Result<RoryResponse> {
|
||||||
let target = id.map(|id| id.to_string()).unwrap_or_default();
|
let target = id.map(|id| id.to_string()).unwrap_or_default();
|
||||||
|
|
||||||
let req = REQWEST_CLIENT
|
let req = REQWEST_CLIENT
|
||||||
.get(format!("{RORY}{ENDPOINT}/{target}"))
|
.get(format!("{RORY}{ENDPOINT}/{target}"))
|
||||||
.build()
|
.build()
|
||||||
.wrap_err_with(|| "Couldn't build reqwest client!")?;
|
.wrap_err_with(|| "Couldn't build reqwest client!")?;
|
||||||
|
|
||||||
debug!("Making request to {}", req.url());
|
debug!("Making request to {}", req.url());
|
||||||
let resp = REQWEST_CLIENT
|
let resp = REQWEST_CLIENT
|
||||||
.execute(req)
|
.execute(req)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't make request for rory!")?;
|
.wrap_err_with(|| "Couldn't make request for rory!")?;
|
||||||
|
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
let data = resp
|
let data = resp
|
||||||
.json::<RoryResponse>()
|
.json::<RoryResponse>()
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't parse the rory response!")?;
|
.wrap_err_with(|| "Couldn't parse the rory response!")?;
|
||||||
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!(
|
Err(eyre!(
|
||||||
"Failed to get rory from {RORY}{ENDPOINT}/{target} with {status}",
|
"Failed to get rory from {RORY}{ENDPOINT}/{target} with {status}",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ use color_eyre::eyre::Result;
|
||||||
/// It's a joke
|
/// It's a joke
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn joke(ctx: Context<'_>) -> Result<()> {
|
pub async fn joke(ctx: Context<'_>) -> Result<()> {
|
||||||
let joke = dadjoke::get_joke().await?;
|
let joke = dadjoke::get_joke().await?;
|
||||||
|
|
||||||
ctx.reply(joke).await?;
|
ctx.reply(joke).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,22 +5,22 @@ use color_eyre::eyre::{eyre, Result};
|
||||||
/// Returns the number of members in the server
|
/// Returns the number of members in the server
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn members(ctx: Context<'_>) -> Result<()> {
|
pub async fn members(ctx: Context<'_>) -> Result<()> {
|
||||||
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?;
|
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?;
|
||||||
|
|
||||||
let count = guild.member_count;
|
let count = guild.member_count;
|
||||||
let online = if let Some(count) = guild.approximate_presence_count {
|
let online = if let Some(count) = guild.approximate_presence_count {
|
||||||
count.to_string()
|
count.to_string()
|
||||||
} else {
|
} else {
|
||||||
"Undefined".to_string()
|
"Undefined".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.send(|m| {
|
ctx.send(|m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
e.title(format!("{count} total members!"))
|
e.title(format!("{count} total members!"))
|
||||||
.description(format!("{online} online members"))
|
.description(format!("{online} online members"))
|
||||||
.color(consts::COLORS["blue"])
|
.color(consts::COLORS["blue"])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,6 @@ use color_eyre::eyre::Result;
|
||||||
/// Replies with pong!
|
/// Replies with pong!
|
||||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||||
pub async fn ping(ctx: Context<'_>) -> Result<()> {
|
pub async fn ping(ctx: Context<'_>) -> Result<()> {
|
||||||
ctx.reply("Pong!").await?;
|
ctx.reply("Pong!").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,24 +6,24 @@ use color_eyre::eyre::Result;
|
||||||
/// Gets a Rory photo!
|
/// Gets a Rory photo!
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn rory(
|
pub async fn rory(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "specify a Rory ID"] id: Option<u64>,
|
#[description = "specify a Rory ID"] id: Option<u64>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let rory = get_rory(id).await?;
|
let rory = get_rory(id).await?;
|
||||||
|
|
||||||
ctx.send(|m| {
|
ctx.send(|m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
if let Some(error) = rory.error {
|
if let Some(error) = rory.error {
|
||||||
e.title("Error!").description(error)
|
e.title("Error!").description(error)
|
||||||
} else {
|
} else {
|
||||||
e.title("Rory :3")
|
e.title("Rory :3")
|
||||||
.url(&rory.url)
|
.url(&rory.url)
|
||||||
.image(rory.url)
|
.image(rory.url)
|
||||||
.footer(|f| f.text(format!("ID {}", rory.id)))
|
.footer(|f| f.text(format!("ID {}", rory.id)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,48 +4,48 @@ use color_eyre::eyre::{eyre, Result};
|
||||||
|
|
||||||
/// Say something through the bot
|
/// Say something through the bot
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
prefix_command,
|
prefix_command,
|
||||||
ephemeral,
|
ephemeral,
|
||||||
default_member_permissions = "MODERATE_MEMBERS",
|
default_member_permissions = "MODERATE_MEMBERS",
|
||||||
required_permissions = "MODERATE_MEMBERS"
|
required_permissions = "MODERATE_MEMBERS"
|
||||||
)]
|
)]
|
||||||
pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> {
|
pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> {
|
||||||
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?;
|
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?;
|
||||||
let channel = ctx
|
let channel = ctx
|
||||||
.guild_channel()
|
.guild_channel()
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| eyre!("Couldn't get channel!"))?;
|
.ok_or_else(|| eyre!("Couldn't get channel!"))?;
|
||||||
|
|
||||||
ctx.defer_ephemeral().await?;
|
ctx.defer_ephemeral().await?;
|
||||||
channel.say(ctx, &content).await?;
|
channel.say(ctx, &content).await?;
|
||||||
ctx.say("I said what you said!").await?;
|
ctx.say("I said what you said!").await?;
|
||||||
|
|
||||||
if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id {
|
if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id {
|
||||||
let log_channel = guild
|
let log_channel = guild
|
||||||
.channels
|
.channels
|
||||||
.iter()
|
.iter()
|
||||||
.find(|c| c.0 == &channel_id)
|
.find(|c| c.0 == &channel_id)
|
||||||
.ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?;
|
.ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?;
|
||||||
|
|
||||||
log_channel
|
log_channel
|
||||||
.1
|
.1
|
||||||
.clone()
|
.clone()
|
||||||
.guild()
|
.guild()
|
||||||
.ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))?
|
.ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))?
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
e.title("Say command used!")
|
e.title("Say command used!")
|
||||||
.description(content)
|
.description(content)
|
||||||
.author(|a| {
|
.author(|a| {
|
||||||
a.name(ctx.author().tag()).icon_url(
|
a.name(ctx.author().tag()).icon_url(
|
||||||
ctx.author().avatar_url().unwrap_or("undefined".to_string()),
|
ctx.author().avatar_url().unwrap_or("undefined".to_string()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,27 @@ use color_eyre::eyre::{Context as _, Result};
|
||||||
/// Returns GitHub stargazer count
|
/// Returns GitHub stargazer count
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn stars(ctx: Context<'_>) -> Result<()> {
|
pub async fn stars(ctx: Context<'_>) -> Result<()> {
|
||||||
let prismlauncher = ctx
|
let prismlauncher = ctx
|
||||||
.data()
|
.data()
|
||||||
.octocrab
|
.octocrab
|
||||||
.repos("PrismLauncher", "PrismLauncher")
|
.repos("PrismLauncher", "PrismLauncher")
|
||||||
.get()
|
.get()
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?;
|
.wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?;
|
||||||
|
|
||||||
let count = if let Some(count) = prismlauncher.stargazers_count {
|
let count = if let Some(count) = prismlauncher.stargazers_count {
|
||||||
count.to_string()
|
count.to_string()
|
||||||
} else {
|
} else {
|
||||||
"undefined".to_string()
|
"undefined".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.send(|m| {
|
ctx.send(|m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
e.title(format!("⭐ {count} total stars!"))
|
e.title(format!("⭐ {count} total stars!"))
|
||||||
.color(COLORS["yellow"])
|
.color(COLORS["yellow"])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,48 +13,48 @@ static TAGS: Lazy<Vec<Tag>> = Lazy::new(|| serde_json::from_str(env!("TAGS")).un
|
||||||
/// Send a tag
|
/// Send a tag
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn tag(
|
pub async fn tag(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "the copypasta you want to send"] name: TagChoice,
|
#[description = "the copypasta you want to send"] name: TagChoice,
|
||||||
user: Option<User>,
|
user: Option<User>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let tag_file = name.as_str();
|
let tag_file = name.as_str();
|
||||||
let tag = TAGS
|
let tag = TAGS
|
||||||
.iter()
|
.iter()
|
||||||
.find(|t| t.file_name == tag_file)
|
.find(|t| t.file_name == tag_file)
|
||||||
.ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?;
|
.ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?;
|
||||||
|
|
||||||
let frontmatter = &tag.frontmatter;
|
let frontmatter = &tag.frontmatter;
|
||||||
|
|
||||||
ctx.send(|m| {
|
ctx.send(|m| {
|
||||||
if let Some(user) = user {
|
if let Some(user) = user {
|
||||||
m.content(format!("<@{}>", user.id));
|
m.content(format!("<@{}>", user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
e.title(&frontmatter.title);
|
e.title(&frontmatter.title);
|
||||||
e.description(&tag.content);
|
e.description(&tag.content);
|
||||||
|
|
||||||
if let Some(color) = &frontmatter.color {
|
if let Some(color) = &frontmatter.color {
|
||||||
let color = *consts::COLORS
|
let color = *consts::COLORS
|
||||||
.get(color.as_str())
|
.get(color.as_str())
|
||||||
.unwrap_or(&Color::default());
|
.unwrap_or(&Color::default());
|
||||||
e.color(color);
|
e.color(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(image) = &frontmatter.image {
|
if let Some(image) = &frontmatter.image {
|
||||||
e.image(image);
|
e.image(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fields) = &frontmatter.fields {
|
if let Some(fields) = &frontmatter.fields {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
e.field(&field.name, &field.value, field.inline);
|
e.field(&field.name, &field.value, field.inline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e
|
e
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ use poise::Command;
|
||||||
mod general;
|
mod general;
|
||||||
|
|
||||||
pub fn to_global_commands() -> Vec<Command<Data, Report>> {
|
pub fn to_global_commands() -> Vec<Command<Data, Report>> {
|
||||||
vec![
|
vec![
|
||||||
general::joke(),
|
general::joke(),
|
||||||
general::members(),
|
general::members(),
|
||||||
general::ping(),
|
general::ping(),
|
||||||
general::rory(),
|
general::rory(),
|
||||||
general::say(),
|
general::say(),
|
||||||
general::stars(),
|
general::stars(),
|
||||||
general::tag(),
|
general::tag(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,81 +7,81 @@ use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RefractionOAuth2 {
|
pub struct RefractionOAuth2 {
|
||||||
pub redirect_uri: Url,
|
pub redirect_uri: Url,
|
||||||
pub scope: String,
|
pub scope: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct RefractionChannels {
|
pub struct RefractionChannels {
|
||||||
pub say_log_channel_id: Option<ChannelId>,
|
pub say_log_channel_id: Option<ChannelId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct DiscordConfig {
|
pub struct DiscordConfig {
|
||||||
pub client_id: ApplicationId,
|
pub client_id: ApplicationId,
|
||||||
pub client_secret: String,
|
pub client_secret: String,
|
||||||
pub bot_token: String,
|
pub bot_token: String,
|
||||||
pub oauth2: RefractionOAuth2,
|
pub oauth2: RefractionOAuth2,
|
||||||
pub channels: RefractionChannels,
|
pub channels: RefractionChannels,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RefractionOAuth2 {
|
impl Default for RefractionOAuth2 {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scope: "identify connections role_connections.write".to_string(),
|
scope: "identify connections role_connections.write".to_string(),
|
||||||
redirect_uri: Url::parse("https://google.com").unwrap(),
|
redirect_uri: Url::parse("https://google.com").unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RefractionOAuth2 {
|
impl RefractionOAuth2 {
|
||||||
pub fn new_from_env() -> Result<Self> {
|
pub fn new_from_env() -> Result<Self> {
|
||||||
let unparsed = format!("{}/oauth2/callback", required_var!("PUBLIC_URI"));
|
let unparsed = format!("{}/oauth2/callback", required_var!("PUBLIC_URI"));
|
||||||
let redirect_uri = Url::parse(&unparsed)?;
|
let redirect_uri = Url::parse(&unparsed)?;
|
||||||
|
|
||||||
debug!("OAuth2 Redirect URI is {redirect_uri}");
|
debug!("OAuth2 Redirect URI is {redirect_uri}");
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RefractionChannels {
|
impl RefractionChannels {
|
||||||
pub fn new_from_env() -> Result<Self> {
|
pub fn new_from_env() -> Result<Self> {
|
||||||
let unparsed = std::env::var("DISCORD_SAY_LOG_CHANNELID");
|
let unparsed = std::env::var("DISCORD_SAY_LOG_CHANNELID");
|
||||||
if let Ok(unparsed) = unparsed {
|
if let Ok(unparsed) = unparsed {
|
||||||
let id = unparsed.parse::<u64>()?;
|
let id = unparsed.parse::<u64>()?;
|
||||||
let channel_id = ChannelId::from(id);
|
let channel_id = ChannelId::from(id);
|
||||||
|
|
||||||
debug!("Log channel is {id}");
|
debug!("Log channel is {id}");
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
say_log_channel_id: Some(channel_id),
|
say_log_channel_id: Some(channel_id),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server.");
|
warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server.");
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
say_log_channel_id: None,
|
say_log_channel_id: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiscordConfig {
|
impl DiscordConfig {
|
||||||
pub fn new_from_env() -> Result<Self> {
|
pub fn new_from_env() -> Result<Self> {
|
||||||
let unparsed_client = required_var!("DISCORD_CLIENT_ID").parse::<u64>()?;
|
let unparsed_client = required_var!("DISCORD_CLIENT_ID").parse::<u64>()?;
|
||||||
let client_id = ApplicationId::from(unparsed_client);
|
let client_id = ApplicationId::from(unparsed_client);
|
||||||
let client_secret = required_var!("DISCORD_CLIENT_SECRET");
|
let client_secret = required_var!("DISCORD_CLIENT_SECRET");
|
||||||
let bot_token = required_var!("DISCORD_BOT_TOKEN");
|
let bot_token = required_var!("DISCORD_BOT_TOKEN");
|
||||||
let oauth2 = RefractionOAuth2::new_from_env()?;
|
let oauth2 = RefractionOAuth2::new_from_env()?;
|
||||||
let channels = RefractionChannels::new_from_env()?;
|
let channels = RefractionChannels::new_from_env()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client_id,
|
client_id,
|
||||||
client_secret,
|
client_secret,
|
||||||
bot_token,
|
bot_token,
|
||||||
oauth2,
|
oauth2,
|
||||||
channels,
|
channels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,62 +4,62 @@ use crate::required_var;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RefractionRepo {
|
pub struct RefractionRepo {
|
||||||
pub owner: String,
|
pub owner: String,
|
||||||
pub repo: String,
|
pub repo: String,
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GithubConfig {
|
pub struct GithubConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub repos: Vec<RefractionRepo>,
|
pub repos: Vec<RefractionRepo>,
|
||||||
pub cache_sec: u16,
|
pub cache_sec: u16,
|
||||||
pub update_cron_job: String,
|
pub update_cron_job: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GithubConfig {
|
impl Default for GithubConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let owner = "PrismLauncher".to_string();
|
let owner = "PrismLauncher".to_string();
|
||||||
let repos = Vec::<RefractionRepo>::from([
|
let repos = Vec::<RefractionRepo>::from([
|
||||||
RefractionRepo {
|
RefractionRepo {
|
||||||
owner: owner.clone(),
|
owner: owner.clone(),
|
||||||
repo: "PrismLauncher".to_string(),
|
repo: "PrismLauncher".to_string(),
|
||||||
key: "launcher".to_string(),
|
key: "launcher".to_string(),
|
||||||
name: "Launcher contributor".to_string(),
|
name: "Launcher contributor".to_string(),
|
||||||
},
|
},
|
||||||
RefractionRepo {
|
RefractionRepo {
|
||||||
owner: owner.clone(),
|
owner: owner.clone(),
|
||||||
repo: "prismlauncher.org".to_string(),
|
repo: "prismlauncher.org".to_string(),
|
||||||
|
|
||||||
key: "website".to_string(),
|
key: "website".to_string(),
|
||||||
name: "Web developer".to_string(),
|
name: "Web developer".to_string(),
|
||||||
},
|
},
|
||||||
RefractionRepo {
|
RefractionRepo {
|
||||||
owner: owner.clone(),
|
owner: owner.clone(),
|
||||||
repo: "Translations".to_string(),
|
repo: "Translations".to_string(),
|
||||||
|
|
||||||
key: "translations".to_string(),
|
key: "translations".to_string(),
|
||||||
name: "Translator".to_string(),
|
name: "Translator".to_string(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
repos,
|
repos,
|
||||||
cache_sec: 3600,
|
cache_sec: 3600,
|
||||||
update_cron_job: "0 */10 * * * *".to_string(), // every 10 minutes
|
update_cron_job: "0 */10 * * * *".to_string(), // every 10 minutes
|
||||||
token: String::default(),
|
token: String::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GithubConfig {
|
impl GithubConfig {
|
||||||
pub fn new_from_env() -> Result<Self> {
|
pub fn new_from_env() -> Result<Self> {
|
||||||
let token = required_var!("GITHUB_TOKEN");
|
let token = required_var!("GITHUB_TOKEN");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
token,
|
token,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,32 +8,32 @@ pub use github::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub discord: DiscordConfig,
|
pub discord: DiscordConfig,
|
||||||
pub github: GithubConfig,
|
pub github: GithubConfig,
|
||||||
pub http_port: u16,
|
pub http_port: u16,
|
||||||
pub redis_url: String,
|
pub redis_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
discord: DiscordConfig::default(),
|
discord: DiscordConfig::default(),
|
||||||
github: GithubConfig::default(),
|
github: GithubConfig::default(),
|
||||||
http_port: 3000,
|
http_port: 3000,
|
||||||
redis_url: "redis://localhost:6379".to_string(),
|
redis_url: "redis://localhost:6379".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new_from_env() -> Result<Self> {
|
pub fn new_from_env() -> Result<Self> {
|
||||||
let discord = DiscordConfig::new_from_env()?;
|
let discord = DiscordConfig::new_from_env()?;
|
||||||
let github = GithubConfig::new_from_env()?;
|
let github = GithubConfig::new_from_env()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
discord,
|
discord,
|
||||||
github,
|
github,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,31 +4,31 @@ use once_cell::sync::Lazy;
|
||||||
use poise::serenity_prelude::Color;
|
use poise::serenity_prelude::Color;
|
||||||
|
|
||||||
pub static COLORS: Lazy<HashMap<&str, Color>> = Lazy::new(|| {
|
pub static COLORS: Lazy<HashMap<&str, Color>> = Lazy::new(|| {
|
||||||
HashMap::from([
|
HashMap::from([
|
||||||
("red", Color::from((239, 68, 68))),
|
("red", Color::from((239, 68, 68))),
|
||||||
("green", Color::from((34, 197, 94))),
|
("green", Color::from((34, 197, 94))),
|
||||||
("blue", Color::from((96, 165, 250))),
|
("blue", Color::from((96, 165, 250))),
|
||||||
("yellow", Color::from((253, 224, 71))),
|
("yellow", Color::from((253, 224, 71))),
|
||||||
("orange", Color::from((251, 146, 60))),
|
("orange", Color::from((251, 146, 60))),
|
||||||
// TODO purple & pink :D
|
// TODO purple & pink :D
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
pub const ETA_MESSAGES: [&str; 16] = [
|
pub const ETA_MESSAGES: [&str; 16] = [
|
||||||
"Sometime",
|
"Sometime",
|
||||||
"Some day",
|
"Some day",
|
||||||
"Not far",
|
"Not far",
|
||||||
"The future",
|
"The future",
|
||||||
"Never",
|
"Never",
|
||||||
"Perhaps tomorrow?",
|
"Perhaps tomorrow?",
|
||||||
"There are no ETAs",
|
"There are no ETAs",
|
||||||
"No",
|
"No",
|
||||||
"Nah",
|
"Nah",
|
||||||
"Yes",
|
"Yes",
|
||||||
"Yas",
|
"Yas",
|
||||||
"Next month",
|
"Next month",
|
||||||
"Next year",
|
"Next year",
|
||||||
"Next week",
|
"Next week",
|
||||||
"In Prism Launcher 2.0.0",
|
"In Prism Launcher 2.0.0",
|
||||||
"At the appropriate juncture, in due course, in the fullness of time",
|
"At the appropriate juncture, in due course, in the fullness of time",
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,43 +7,43 @@ use poise::serenity_prelude::Timestamp;
|
||||||
use poise::FrameworkError;
|
use poise::FrameworkError;
|
||||||
|
|
||||||
pub async fn handle(error: FrameworkError<'_, Data, Report>) {
|
pub async fn handle(error: FrameworkError<'_, Data, Report>) {
|
||||||
match error {
|
match error {
|
||||||
FrameworkError::Setup {
|
FrameworkError::Setup {
|
||||||
error, framework, ..
|
error, framework, ..
|
||||||
} => {
|
} => {
|
||||||
error!("Error setting up client! Bailing out");
|
error!("Error setting up client! Bailing out");
|
||||||
framework.shard_manager().lock().await.shutdown_all().await;
|
framework.shard_manager().lock().await.shutdown_all().await;
|
||||||
|
|
||||||
panic!("{error}")
|
panic!("{error}")
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameworkError::Command { error, ctx } => {
|
FrameworkError::Command { error, ctx } => {
|
||||||
error!("Error in command {}:\n{error:?}", ctx.command().name);
|
error!("Error in command {}:\n{error:?}", ctx.command().name);
|
||||||
ctx.send(|c| {
|
ctx.send(|c| {
|
||||||
c.embed(|e| {
|
c.embed(|e| {
|
||||||
e.title("Something went wrong!")
|
e.title("Something went wrong!")
|
||||||
.description("oopsie")
|
.description("oopsie")
|
||||||
.timestamp(Timestamp::now())
|
.timestamp(Timestamp::now())
|
||||||
.color(COLORS["red"])
|
.color(COLORS["red"])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameworkError::EventHandler {
|
FrameworkError::EventHandler {
|
||||||
error,
|
error,
|
||||||
ctx: _,
|
ctx: _,
|
||||||
event,
|
event,
|
||||||
framework: _,
|
framework: _,
|
||||||
} => {
|
} => {
|
||||||
error!("Error while handling event {}:\n{error:?}", event.name());
|
error!("Error while handling event {}:\n{error:?}", event.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
error => {
|
error => {
|
||||||
if let Err(e) = poise::builtins::on_error(error).await {
|
if let Err(e) = poise::builtins::on_error(error).await {
|
||||||
error!("Unhandled error occured:\n{e:#?}");
|
error!("Unhandled error occured:\n{e:#?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,164 +7,164 @@ use regex::Regex;
|
||||||
pub type Issue = Option<(String, String)>;
|
pub type Issue = Option<(String, String)>;
|
||||||
|
|
||||||
pub async fn find_issues(log: &str, data: &Data) -> Result<Vec<(String, String)>> {
|
pub async fn find_issues(log: &str, data: &Data) -> Result<Vec<(String, String)>> {
|
||||||
let issues = [
|
let issues = [
|
||||||
fabric_internal,
|
fabric_internal,
|
||||||
flatpak_nvidia,
|
flatpak_nvidia,
|
||||||
forge_java,
|
forge_java,
|
||||||
intel_hd,
|
intel_hd,
|
||||||
java_option,
|
java_option,
|
||||||
lwjgl_2_java_9,
|
lwjgl_2_java_9,
|
||||||
macos_ns,
|
macos_ns,
|
||||||
oom,
|
oom,
|
||||||
optinotfine,
|
optinotfine,
|
||||||
pre_1_12_native_transport_java_9,
|
pre_1_12_native_transport_java_9,
|
||||||
wrong_java,
|
wrong_java,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut res: Vec<(String, String)> = issues.iter().filter_map(|issue| issue(log)).collect();
|
let mut res: Vec<(String, String)> = issues.iter().filter_map(|issue| issue(log)).collect();
|
||||||
|
|
||||||
if let Some(issues) = outdated_launcher(log, data).await? {
|
if let Some(issues) = outdated_launcher(log, data).await? {
|
||||||
res.push(issues)
|
res.push(issues)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fabric_internal(log: &str) -> Issue {
|
fn fabric_internal(log: &str) -> Issue {
|
||||||
const CLASS_NOT_FOUND: &str = "Caused by: java.lang.ClassNotFoundException: ";
|
const CLASS_NOT_FOUND: &str = "Caused by: java.lang.ClassNotFoundException: ";
|
||||||
|
|
||||||
let issue = (
|
let issue = (
|
||||||
"Fabric Internal Access".to_string(),
|
"Fabric Internal Access".to_string(),
|
||||||
"The mod you are using is using fabric internals that are not meant \
|
"The mod you are using is using fabric internals that are not meant \
|
||||||
to be used by anything but the loader itself.
|
to be used by anything but the loader itself.
|
||||||
Those mods break both on Quilt and with fabric updates.
|
Those mods break both on Quilt and with fabric updates.
|
||||||
If you're using fabric, downgrade your fabric loader could work, \
|
If you're using fabric, downgrade your fabric loader could work, \
|
||||||
on Quilt you can try updating to the latest beta version, \
|
on Quilt you can try updating to the latest beta version, \
|
||||||
but there's nothing much to do unless the mod author stops using them."
|
but there's nothing much to do unless the mod author stops using them."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let errors = [
|
let errors = [
|
||||||
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.impl"),
|
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.impl"),
|
||||||
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.mixin"),
|
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.mixin"),
|
||||||
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.impl"),
|
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.impl"),
|
||||||
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.mixin"),
|
&format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.mixin"),
|
||||||
"org.quiltmc.loader.impl.FormattedException: java.lang.NoSuchMethodError:",
|
"org.quiltmc.loader.impl.FormattedException: java.lang.NoSuchMethodError:",
|
||||||
];
|
];
|
||||||
|
|
||||||
let found = errors.iter().any(|e| log.contains(e));
|
let found = errors.iter().any(|e| log.contains(e));
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatpak_nvidia(log: &str) -> Issue {
|
fn flatpak_nvidia(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Outdated Nvidia Flatpak Driver".to_string(),
|
"Outdated Nvidia Flatpak Driver".to_string(),
|
||||||
"The Nvidia driver for flatpak is outdated.
|
"The Nvidia driver for flatpak is outdated.
|
||||||
Please run `flatpak update` to fix this issue. \
|
Please run `flatpak update` to fix this issue. \
|
||||||
If that does not solve it, \
|
If that does not solve it, \
|
||||||
please wait until the driver is added to Flathub and run it again."
|
please wait until the driver is added to Flathub and run it again."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains("org.lwjgl.LWJGLException: Could not choose GLX13 config")
|
let found = log.contains("org.lwjgl.LWJGLException: Could not choose GLX13 config")
|
||||||
|| log.contains("GLFW error 65545: GLX: Failed to find a suitable GLXFBConfig");
|
|| log.contains("GLFW error 65545: GLX: Failed to find a suitable GLXFBConfig");
|
||||||
|
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forge_java(log: &str) -> Issue {
|
fn forge_java(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Forge Java Bug".to_string(),
|
"Forge Java Bug".to_string(),
|
||||||
"Old versions of Forge crash with Java 8u321+.
|
"Old versions of Forge crash with Java 8u321+.
|
||||||
To fix this, update forge to the latest version via the Versions tab
|
To fix this, update forge to the latest version via the Versions tab
|
||||||
(right click on Forge, click Change Version, and choose the latest one)
|
(right click on Forge, click Change Version, and choose the latest one)
|
||||||
Alternatively, you can download 8u312 or lower. \
|
Alternatively, you can download 8u312 or lower. \
|
||||||
See [archive](https://github.com/adoptium/temurin8-binaries/releases/tag/jdk8u312-b07)"
|
See [archive](https://github.com/adoptium/temurin8-binaries/releases/tag/jdk8u312-b07)"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.<init>(Ljava/util/jar/Manifest;)V");
|
let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.<init>(Ljava/util/jar/Manifest;)V");
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intel_hd(log: &str) -> Issue {
|
fn intel_hd(log: &str) -> Issue {
|
||||||
let issue =
|
let issue =
|
||||||
(
|
(
|
||||||
"Intel HD Windows 10".to_string(),
|
"Intel HD Windows 10".to_string(),
|
||||||
"Your drivers don't support windows 10 officially
|
"Your drivers don't support windows 10 officially
|
||||||
See https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info".to_string()
|
See https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info".to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.<init>(Ljava/util/jar/Manifest;)V");
|
let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.<init>(Ljava/util/jar/Manifest;)V");
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn java_option(log: &str) -> Issue {
|
fn java_option(log: &str) -> Issue {
|
||||||
static VM_OPTION_REGEX: Lazy<Regex> =
|
static VM_OPTION_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap());
|
Lazy::new(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap());
|
||||||
static OPTION_REGEX: Lazy<Regex> =
|
static OPTION_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap());
|
Lazy::new(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap());
|
||||||
|
|
||||||
if let Some(captures) = VM_OPTION_REGEX.captures(log) {
|
if let Some(captures) = VM_OPTION_REGEX.captures(log) {
|
||||||
let title = if &captures[1] == "UseShenandoahGC" {
|
let title = if &captures[1] == "UseShenandoahGC" {
|
||||||
"Wrong Java Arguments"
|
"Wrong Java Arguments"
|
||||||
} else {
|
} else {
|
||||||
"Java 8 and below don't support ShenandoahGC"
|
"Java 8 and below don't support ShenandoahGC"
|
||||||
};
|
};
|
||||||
return Some((
|
return Some((
|
||||||
title.to_string(),
|
title.to_string(),
|
||||||
format!("Remove `-XX:{}` from your Java arguments", &captures[1]),
|
format!("Remove `-XX:{}` from your Java arguments", &captures[1]),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(captures) = OPTION_REGEX.captures(log) {
|
if let Some(captures) = OPTION_REGEX.captures(log) {
|
||||||
return Some((
|
return Some((
|
||||||
"Wrong Java Arguments".to_string(),
|
"Wrong Java Arguments".to_string(),
|
||||||
format!("Remove `{}` from your Java arguments", &captures[1]),
|
format!("Remove `{}` from your Java arguments", &captures[1]),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lwjgl_2_java_9(log: &str) -> Issue {
|
fn lwjgl_2_java_9(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Linux: crash with pre-1.13 and Java 9+".to_string(),
|
"Linux: crash with pre-1.13 and Java 9+".to_string(),
|
||||||
"Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \
|
"Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \
|
||||||
Switching to Java 8 or below will fix your issue.
|
Switching to Java 8 or below will fix your issue.
|
||||||
Alternatively, you can use [Temurin](https://adoptium.net/temurin/releases). \
|
Alternatively, you can use [Temurin](https://adoptium.net/temurin/releases). \
|
||||||
However, multiplayer will not work in versions from 1.8 to 1.11.
|
However, multiplayer will not work in versions from 1.8 to 1.11.
|
||||||
For more information, type `/tag java`."
|
For more information, type `/tag java`."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!");
|
let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!");
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn macos_ns(log: &str) -> Issue {
|
fn macos_ns(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"MacOS NSInternalInconsistencyException".to_string(),
|
"MacOS NSInternalInconsistencyException".to_string(),
|
||||||
"You need to downgrade your Java 8 version. See https://prismlauncher.org/wiki/getting-started/installing-java/#older-minecraft-on-macos".to_string()
|
"You need to downgrade your Java 8 version. See https://prismlauncher.org/wiki/getting-started/installing-java/#older-minecraft-on-macos".to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let found =
|
let found =
|
||||||
log.contains("Terminating app due to uncaught exception 'NSInternalInconsistencyException");
|
log.contains("Terminating app due to uncaught exception 'NSInternalInconsistencyException");
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn oom(log: &str) -> Issue {
|
fn oom(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Out of Memory".to_string(),
|
"Out of Memory".to_string(),
|
||||||
"Allocating more RAM to your instance could help prevent this crash.".to_string(),
|
"Allocating more RAM to your instance could help prevent this crash.".to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains("java.lang.OutOfMemoryError");
|
let found = log.contains("java.lang.OutOfMemoryError");
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optinotfine(log: &str) -> Issue {
|
fn optinotfine(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Potential OptiFine Incompatibilities".to_string(),
|
"Potential OptiFine Incompatibilities".to_string(),
|
||||||
"OptiFine is known to cause problems when paired with other mods. \
|
"OptiFine is known to cause problems when paired with other mods. \
|
||||||
Try to disable OptiFine and see if the issue persists.
|
Try to disable OptiFine and see if the issue persists.
|
||||||
|
@ -172,50 +172,50 @@ fn optinotfine(log: &str) -> Issue {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains("[✔] OptiFine_") || log.contains("[✔] optifabric-");
|
let found = log.contains("[✔] OptiFine_") || log.contains("[✔] optifabric-");
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn outdated_launcher(log: &str, data: &Data) -> Result<Issue> {
|
async fn outdated_launcher(log: &str, data: &Data) -> Result<Issue> {
|
||||||
static OUTDATED_LAUNCHER_REGEX: Lazy<Regex> =
|
static OUTDATED_LAUNCHER_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap());
|
Lazy::new(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap());
|
||||||
|
|
||||||
let Some(captures) = OUTDATED_LAUNCHER_REGEX.captures(log) else {
|
let Some(captures) = OUTDATED_LAUNCHER_REGEX.captures(log) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let version_from_log = captures[0].replace("Prism Launcher version: ", "");
|
let version_from_log = captures[0].replace("Prism Launcher version: ", "");
|
||||||
|
|
||||||
let storage = &data.storage;
|
let storage = &data.storage;
|
||||||
let latest_version = if storage.launcher_version_is_cached().await? {
|
let latest_version = if storage.launcher_version_is_cached().await? {
|
||||||
storage.get_launcher_version().await?
|
storage.get_launcher_version().await?
|
||||||
} else {
|
} else {
|
||||||
let version = data
|
let version = data
|
||||||
.octocrab
|
.octocrab
|
||||||
.repos("PrismLauncher", "PrismLauncher")
|
.repos("PrismLauncher", "PrismLauncher")
|
||||||
.releases()
|
.releases()
|
||||||
.get_latest()
|
.get_latest()
|
||||||
.await?
|
.await?
|
||||||
.tag_name;
|
.tag_name;
|
||||||
|
|
||||||
storage.cache_launcher_version(&version).await?;
|
storage.cache_launcher_version(&version).await?;
|
||||||
version
|
version
|
||||||
};
|
};
|
||||||
|
|
||||||
if version_from_log < latest_version {
|
if version_from_log < latest_version {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Outdated Prism Launcher".to_string(),
|
"Outdated Prism Launcher".to_string(),
|
||||||
format!("Your installed version is {version_from_log}, while the newest version is {latest_version}.\nPlease update, for more info see https://prismlauncher.org/download/")
|
format!("Your installed version is {version_from_log}, while the newest version is {latest_version}.\nPlease update, for more info see https://prismlauncher.org/download/")
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Some(issue))
|
Ok(Some(issue))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pre_1_12_native_transport_java_9(log: &str) -> Issue {
|
fn pre_1_12_native_transport_java_9(log: &str) -> Issue {
|
||||||
let issue = (
|
let issue = (
|
||||||
"Linux: broken multiplayer with 1.8-1.11 and Java 9+".to_string(),
|
"Linux: broken multiplayer with 1.8-1.11 and Java 9+".to_string(),
|
||||||
"These versions of Minecraft use an outdated version of Netty which does not properly support Java 9.
|
"These versions of Minecraft use an outdated version of Netty which does not properly support Java 9.
|
||||||
|
|
||||||
|
@ -229,33 +229,33 @@ which is why the issue was not present."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let found = log.contains(
|
let found = log.contains(
|
||||||
"java.lang.RuntimeException: Unable to access address of buffer\n\tat io.netty.channel.epoll"
|
"java.lang.RuntimeException: Unable to access address of buffer\n\tat io.netty.channel.epoll"
|
||||||
);
|
);
|
||||||
|
|
||||||
found.then_some(issue)
|
found.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrong_java(log: &str) -> Issue {
|
fn wrong_java(log: &str) -> Issue {
|
||||||
static SWITCH_VERSION_REGEX: Lazy<Regex> = Lazy::new(|| {
|
static SWITCH_VERSION_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(
|
Regex::new(
|
||||||
r"(?m)Please switch to one of the following Java versions for this instance:[\r\n]+(Java version [\d.]+)",
|
r"(?m)Please switch to one of the following Java versions for this instance:[\r\n]+(Java version [\d.]+)",
|
||||||
).unwrap()
|
).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(captures) = SWITCH_VERSION_REGEX.captures(log) {
|
if let Some(captures) = SWITCH_VERSION_REGEX.captures(log) {
|
||||||
let versions = captures[1].split('\n').collect::<Vec<&str>>().join(", ");
|
let versions = captures[1].split('\n').collect::<Vec<&str>>().join(", ");
|
||||||
return Some((
|
return Some((
|
||||||
"Wrong Java Version".to_string(),
|
"Wrong Java Version".to_string(),
|
||||||
format!("Please switch to one of the following: `{versions}`\nFor more information, type `/tag java`"),
|
format!("Please switch to one of the following: `{versions}`\nFor more information, type `/tag java`"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let issue = (
|
let issue = (
|
||||||
"Java compatibility check skipped".to_string(),
|
"Java compatibility check skipped".to_string(),
|
||||||
"The Java major version may not work with your Minecraft instance. Please switch to a compatible version".to_string()
|
"The Java major version may not work with your Minecraft instance. Please switch to a compatible version".to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
log.contains("Java major version is incompatible. Things might break.")
|
log.contains("Java major version is incompatible. Things might break.")
|
||||||
.then_some(issue)
|
.then_some(issue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,57 +12,57 @@ use issues::find_issues;
|
||||||
use providers::find_log;
|
use providers::find_log;
|
||||||
|
|
||||||
pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> {
|
pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> {
|
||||||
let channel = message.channel_id;
|
let channel = message.channel_id;
|
||||||
|
|
||||||
let log = find_log(message).await;
|
let log = find_log(message).await;
|
||||||
|
|
||||||
if log.is_err() {
|
if log.is_err() {
|
||||||
channel
|
channel
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.reference_message(message)
|
m.reference_message(message)
|
||||||
.allowed_mentions(|am| am.replied_user(true))
|
.allowed_mentions(|am| am.replied_user(true))
|
||||||
.embed(|e| {
|
.embed(|e| {
|
||||||
e.title("Analyze failed!")
|
e.title("Analyze failed!")
|
||||||
.description("Couldn't download log")
|
.description("Couldn't download log")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(log) = log? else {
|
let Some(log) = log? else {
|
||||||
debug!("No log found in message! Skipping analysis");
|
debug!("No log found in message! Skipping analysis");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let issues = find_issues(&log, data).await?;
|
let issues = find_issues(&log, data).await?;
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.reference_message(message)
|
m.reference_message(message)
|
||||||
.allowed_mentions(|am| am.replied_user(true))
|
.allowed_mentions(|am| am.replied_user(true))
|
||||||
.embed(|e| {
|
.embed(|e| {
|
||||||
e.title("Log analysis");
|
e.title("Log analysis");
|
||||||
|
|
||||||
if issues.is_empty() {
|
if issues.is_empty() {
|
||||||
e.color(COLORS["green"]).field(
|
e.color(COLORS["green"]).field(
|
||||||
"Analyze failed!",
|
"Analyze failed!",
|
||||||
"No issues found automatically",
|
"No issues found automatically",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
e.color(COLORS["red"]);
|
e.color(COLORS["red"]);
|
||||||
|
|
||||||
for (title, description) in issues {
|
for (title, description) in issues {
|
||||||
e.field(title, description, false);
|
e.field(title, description, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e
|
e
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,17 @@ use reqwest::StatusCode;
|
||||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap());
|
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap());
|
||||||
|
|
||||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||||
let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else {
|
let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = REQWEST_CLIENT.get(url).build()?;
|
let request = REQWEST_CLIENT.get(url).build()?;
|
||||||
let response = REQWEST_CLIENT.execute(request).await?;
|
let response = REQWEST_CLIENT.execute(request).await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
Ok(Some(response.text().await?))
|
Ok(Some(response.text().await?))
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!("Failed to fetch paste from {url} with {status}",))
|
Err(eyre!("Failed to fetch paste from {url} with {status}",))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,17 @@ use color_eyre::eyre::Result;
|
||||||
use poise::serenity_prelude::Message;
|
use poise::serenity_prelude::Message;
|
||||||
|
|
||||||
pub async fn find(message: &Message) -> Result<Option<String>> {
|
pub async fn find(message: &Message) -> Result<Option<String>> {
|
||||||
// find first uploaded text file
|
// find first uploaded text file
|
||||||
if let Some(attachment) = message.attachments.iter().find(|a| {
|
if let Some(attachment) = message.attachments.iter().find(|a| {
|
||||||
a.content_type
|
a.content_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|ct| ct.starts_with("text/").then_some(true))
|
.and_then(|ct| ct.starts_with("text/").then_some(true))
|
||||||
.is_some()
|
.is_some()
|
||||||
}) {
|
}) {
|
||||||
let bytes = attachment.download().await?;
|
let bytes = attachment.download().await?;
|
||||||
let res = String::from_utf8(bytes)?;
|
let res = String::from_utf8(bytes)?;
|
||||||
Ok(Some(res))
|
Ok(Some(res))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,21 @@ use regex::Regex;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
static REGEX: Lazy<Regex> =
|
static REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap());
|
Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap());
|
||||||
|
|
||||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||||
let Some(captures) = REGEX.captures(content) else {
|
let Some(captures) = REGEX.captures(content) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!("https://hst.sh/raw/{}", &captures[1]);
|
let url = format!("https://hst.sh/raw/{}", &captures[1]);
|
||||||
let request = REQWEST_CLIENT.get(&url).build()?;
|
let request = REQWEST_CLIENT.get(&url).build()?;
|
||||||
let response = REQWEST_CLIENT.execute(request).await?;
|
let response = REQWEST_CLIENT.execute(request).await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
Ok(Some(response.text().await?))
|
Ok(Some(response.text().await?))
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!("Failed to fetch paste from {url} with {status}"))
|
Err(eyre!("Failed to fetch paste from {url} with {status}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,18 @@ use reqwest::StatusCode;
|
||||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap());
|
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap());
|
||||||
|
|
||||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||||
let Some(captures) = REGEX.captures(content) else {
|
let Some(captures) = REGEX.captures(content) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]);
|
let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]);
|
||||||
let request = REQWEST_CLIENT.get(&url).build()?;
|
let request = REQWEST_CLIENT.get(&url).build()?;
|
||||||
let response = REQWEST_CLIENT.execute(request).await?;
|
let response = REQWEST_CLIENT.execute(request).await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
Ok(Some(response.text().await?))
|
Ok(Some(response.text().await?))
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!("Failed to fetch log from {url} with {status}"))
|
Err(eyre!("Failed to fetch log from {url} with {status}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,22 @@ mod pastebin;
|
||||||
pub type LogProvider = Result<Option<String>>;
|
pub type LogProvider = Result<Option<String>>;
|
||||||
|
|
||||||
pub async fn find_log(message: &Message) -> LogProvider {
|
pub async fn find_log(message: &Message) -> LogProvider {
|
||||||
macro_rules! provider_impl {
|
macro_rules! provider_impl {
|
||||||
($provider:ident) => {
|
($provider:ident) => {
|
||||||
if let Some(content) = $provider::find(&message.content).await? {
|
if let Some(content) = $provider::find(&message.content).await? {
|
||||||
return Ok(Some(content));
|
return Ok(Some(content));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
provider_impl!(_0x0);
|
provider_impl!(_0x0);
|
||||||
provider_impl!(mclogs);
|
provider_impl!(mclogs);
|
||||||
provider_impl!(haste);
|
provider_impl!(haste);
|
||||||
provider_impl!(paste_gg);
|
provider_impl!(paste_gg);
|
||||||
provider_impl!(pastebin);
|
provider_impl!(pastebin);
|
||||||
|
|
||||||
if let Some(content) = attachment::find(message).await? {
|
if let Some(content) = attachment::find(message).await? {
|
||||||
return Ok(Some(content));
|
return Ok(Some(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,59 +12,59 @@ static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
struct PasteResponse {
|
struct PasteResponse {
|
||||||
status: String,
|
status: String,
|
||||||
result: Option<Vec<PasteResult>>,
|
result: Option<Vec<PasteResult>>,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
struct PasteResult {
|
struct PasteResult {
|
||||||
id: String,
|
id: String,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
visibility: Option<String>,
|
visibility: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||||
let Some(captures) = REGEX.captures(content) else {
|
let Some(captures) = REGEX.captures(content) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let paste_id = &captures[1];
|
let paste_id = &captures[1];
|
||||||
let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files");
|
let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files");
|
||||||
|
|
||||||
let resp = REQWEST_CLIENT
|
let resp = REQWEST_CLIENT
|
||||||
.execute(REQWEST_CLIENT.get(&files_url).build()?)
|
.execute(REQWEST_CLIENT.get(&files_url).build()?)
|
||||||
.await?;
|
.await?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if resp.status() != StatusCode::OK {
|
if resp.status() != StatusCode::OK {
|
||||||
return Err(eyre!(
|
return Err(eyre!(
|
||||||
"Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!"
|
"Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let paste_files: PasteResponse = resp.json().await?;
|
let paste_files: PasteResponse = resp.json().await?;
|
||||||
let file_id = &paste_files
|
let file_id = &paste_files
|
||||||
.result
|
.result
|
||||||
.ok_or_else(|| eyre!("Couldn't find any files associated with paste {paste_id}!"))?[0]
|
.ok_or_else(|| eyre!("Couldn't find any files associated with paste {paste_id}!"))?[0]
|
||||||
.id;
|
.id;
|
||||||
|
|
||||||
let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw");
|
let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw");
|
||||||
|
|
||||||
let resp = REQWEST_CLIENT
|
let resp = REQWEST_CLIENT
|
||||||
.execute(REQWEST_CLIENT.get(&raw_url).build()?)
|
.execute(REQWEST_CLIENT.get(&raw_url).build()?)
|
||||||
.await?;
|
.await?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if status != StatusCode::OK {
|
if status != StatusCode::OK {
|
||||||
return Err(eyre!(
|
return Err(eyre!(
|
||||||
"Couldn't get file {file_id} from paste {paste_id} with status {status}!"
|
"Couldn't get file {file_id} from paste {paste_id} with status {status}!"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = resp.text().await?;
|
let text = resp.text().await?;
|
||||||
|
|
||||||
Ok(Some(text))
|
Ok(Some(text))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,21 @@ use regex::Regex;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
static REGEX: Lazy<Regex> =
|
static REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap());
|
Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap());
|
||||||
|
|
||||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||||
let Some(captures) = REGEX.captures(content) else {
|
let Some(captures) = REGEX.captures(content) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!("https://pastebin.com/raw/{}", &captures[1]);
|
let url = format!("https://pastebin.com/raw/{}", &captures[1]);
|
||||||
let request = REQWEST_CLIENT.get(&url).build()?;
|
let request = REQWEST_CLIENT.get(&url).build()?;
|
||||||
let response = REQWEST_CLIENT.execute(request).await?;
|
let response = REQWEST_CLIENT.execute(request).await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
if let StatusCode::OK = status {
|
if let StatusCode::OK = status {
|
||||||
Ok(Some(response.text().await?))
|
Ok(Some(response.text().await?))
|
||||||
} else {
|
} else {
|
||||||
Err(eyre!("Failed to fetch paste from {url} with {status}"))
|
Err(eyre!("Failed to fetch paste from {url} with {status}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,24 @@ use color_eyre::eyre::{Context as _, Result};
|
||||||
use poise::serenity_prelude::{Context, InteractionType, Reaction};
|
use poise::serenity_prelude::{Context, InteractionType, Reaction};
|
||||||
|
|
||||||
pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> {
|
pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> {
|
||||||
let user = reaction
|
let user = reaction
|
||||||
.user(ctx)
|
.user(ctx)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't fetch user from reaction!")?;
|
.wrap_err_with(|| "Couldn't fetch user from reaction!")?;
|
||||||
|
|
||||||
let message = reaction
|
let message = reaction
|
||||||
.message(ctx)
|
.message(ctx)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't fetch message from reaction!")?;
|
.wrap_err_with(|| "Couldn't fetch message from reaction!")?;
|
||||||
|
|
||||||
if let Some(interaction) = &message.interaction {
|
if let Some(interaction) = &message.interaction {
|
||||||
if interaction.kind == InteractionType::ApplicationCommand
|
if interaction.kind == InteractionType::ApplicationCommand
|
||||||
&& interaction.user == user
|
&& interaction.user == user
|
||||||
&& reaction.emoji.unicode_eq("❌")
|
&& reaction.emoji.unicode_eq("❌")
|
||||||
{
|
{
|
||||||
message.delete(ctx).await?;
|
message.delete(ctx).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,15 @@ use regex::Regex;
|
||||||
static ETA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\beta\b").unwrap());
|
static ETA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\beta\b").unwrap());
|
||||||
|
|
||||||
pub async fn handle(ctx: &Context, message: &Message) -> Result<()> {
|
pub async fn handle(ctx: &Context, message: &Message) -> Result<()> {
|
||||||
if !ETA_REGEX.is_match(&message.content) {
|
if !ETA_REGEX.is_match(&message.content) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = format!(
|
let response = format!(
|
||||||
"{} <:pofat:1031701005559144458>",
|
"{} <:pofat:1031701005559144458>",
|
||||||
utils::random_choice(consts::ETA_MESSAGES)?
|
utils::random_choice(consts::ETA_MESSAGES)?
|
||||||
);
|
);
|
||||||
|
|
||||||
message.reply(ctx, response).await?;
|
message.reply(ctx, response).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,25 +4,25 @@ use poise::serenity_prelude::{Context, Message};
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
|
||||||
pub async fn handle(ctx: &Context, message: &Message) -> Result<()> {
|
pub async fn handle(ctx: &Context, message: &Message) -> Result<()> {
|
||||||
let embeds = utils::resolve_message(ctx, message).await?;
|
let embeds = utils::resolve_message(ctx, message).await?;
|
||||||
|
|
||||||
// TOOD getchoo: actually reply to user
|
// TOOD getchoo: actually reply to user
|
||||||
// ...not sure why Message doesn't give me a builder in reply() or equivalents
|
// ...not sure why Message doesn't give me a builder in reply() or equivalents
|
||||||
let our_channel = message
|
let our_channel = message
|
||||||
.channel(ctx)
|
.channel(ctx)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Couldn't get channel from message!")?
|
.wrap_err_with(|| "Couldn't get channel from message!")?
|
||||||
.guild()
|
.guild()
|
||||||
.ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?;
|
.ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?;
|
||||||
|
|
||||||
if !embeds.is_empty() {
|
if !embeds.is_empty() {
|
||||||
our_channel
|
our_channel
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.set_embeds(embeds)
|
m.set_embeds(embeds)
|
||||||
.allowed_mentions(|am| am.replied_user(false))
|
.allowed_mentions(|am| am.replied_user(false))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,53 +13,53 @@ pub mod pluralkit;
|
||||||
mod support_onboard;
|
mod support_onboard;
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
event: &Event<'_>,
|
event: &Event<'_>,
|
||||||
_framework: FrameworkContext<'_, Data, Report>,
|
_framework: FrameworkContext<'_, Data, Report>,
|
||||||
data: &Data,
|
data: &Data,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match event {
|
match event {
|
||||||
Event::Ready { data_about_bot } => {
|
Event::Ready { data_about_bot } => {
|
||||||
info!("Logged in as {}!", data_about_bot.user.name);
|
info!("Logged in as {}!", data_about_bot.user.name);
|
||||||
|
|
||||||
let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?;
|
let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?;
|
||||||
let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version));
|
let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version));
|
||||||
|
|
||||||
info!("Setting presence to activity {activity:#?}");
|
info!("Setting presence to activity {activity:#?}");
|
||||||
ctx.set_presence(Some(activity), OnlineStatus::Online).await;
|
ctx.set_presence(Some(activity), OnlineStatus::Online).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Message { new_message } => {
|
Event::Message { new_message } => {
|
||||||
// ignore new messages from bots
|
// ignore new messages from bots
|
||||||
// NOTE: the webhook_id check allows us to still respond to PK users
|
// NOTE: the webhook_id check allows us to still respond to PK users
|
||||||
if new_message.author.bot && new_message.webhook_id.is_none() {
|
if new_message.author.bot && new_message.webhook_id.is_none() {
|
||||||
debug!("Ignoring message {} from bot", new_message.id);
|
debug!("Ignoring message {} from bot", new_message.id);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect PK users first to make sure we don't respond to unproxied messages
|
// detect PK users first to make sure we don't respond to unproxied messages
|
||||||
pluralkit::handle(ctx, new_message, data).await?;
|
pluralkit::handle(ctx, new_message, data).await?;
|
||||||
|
|
||||||
if data.storage.is_user_plural(new_message.author.id).await?
|
if data.storage.is_user_plural(new_message.author.id).await?
|
||||||
&& pluralkit::is_message_proxied(new_message).await?
|
&& pluralkit::is_message_proxied(new_message).await?
|
||||||
{
|
{
|
||||||
debug!("Not replying to unproxied PluralKit message");
|
debug!("Not replying to unproxied PluralKit message");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
eta::handle(ctx, new_message).await?;
|
eta::handle(ctx, new_message).await?;
|
||||||
expand_link::handle(ctx, new_message).await?;
|
expand_link::handle(ctx, new_message).await?;
|
||||||
analyze_logs::handle(ctx, new_message, data).await?;
|
analyze_logs::handle(ctx, new_message, data).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::ReactionAdd { add_reaction } => {
|
Event::ReactionAdd { add_reaction } => {
|
||||||
delete_on_reaction::handle(ctx, add_reaction).await?
|
delete_on_reaction::handle(ctx, add_reaction).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?,
|
Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?,
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,34 +9,34 @@ use tokio::time::sleep;
|
||||||
const PK_DELAY_SEC: Duration = Duration::from_secs(1000);
|
const PK_DELAY_SEC: Duration = Duration::from_secs(1000);
|
||||||
|
|
||||||
pub async fn is_message_proxied(message: &Message) -> Result<bool> {
|
pub async fn is_message_proxied(message: &Message) -> Result<bool> {
|
||||||
debug!(
|
debug!(
|
||||||
"Waiting on PluralKit API for {} seconds",
|
"Waiting on PluralKit API for {} seconds",
|
||||||
PK_DELAY_SEC.as_secs()
|
PK_DELAY_SEC.as_secs()
|
||||||
);
|
);
|
||||||
sleep(PK_DELAY_SEC).await;
|
sleep(PK_DELAY_SEC).await;
|
||||||
|
|
||||||
let proxied = api::pluralkit::get_sender(message.id).await.is_ok();
|
let proxied = api::pluralkit::get_sender(message.id).await.is_ok();
|
||||||
|
|
||||||
Ok(proxied)
|
Ok(proxied)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle(_ctx: &Context, msg: &Message, data: &Data) -> Result<()> {
|
pub async fn handle(_ctx: &Context, msg: &Message, data: &Data) -> Result<()> {
|
||||||
if msg.webhook_id.is_some() {
|
if msg.webhook_id.is_some() {
|
||||||
debug!(
|
debug!(
|
||||||
"Message {} has a webhook ID. Checking if it was sent through PluralKit",
|
"Message {} has a webhook ID. Checking if it was sent through PluralKit",
|
||||||
msg.id
|
msg.id
|
||||||
);
|
);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Waiting on PluralKit API for {} seconds",
|
"Waiting on PluralKit API for {} seconds",
|
||||||
PK_DELAY_SEC.as_secs()
|
PK_DELAY_SEC.as_secs()
|
||||||
);
|
);
|
||||||
sleep(PK_DELAY_SEC).await;
|
sleep(PK_DELAY_SEC).await;
|
||||||
|
|
||||||
if let Ok(sender) = api::pluralkit::get_sender(msg.id).await {
|
if let Ok(sender) = api::pluralkit::get_sender(msg.id).await {
|
||||||
data.storage.store_user_plurality(sender).await?;
|
data.storage.store_user_plurality(sender).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,41 +3,41 @@ use log::*;
|
||||||
use poise::serenity_prelude::{ChannelType, Context, GuildChannel};
|
use poise::serenity_prelude::{ChannelType, Context, GuildChannel};
|
||||||
|
|
||||||
pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> {
|
pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> {
|
||||||
if thread.kind != ChannelType::PublicThread {
|
if thread.kind != ChannelType::PublicThread {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent_id = thread
|
let parent_id = thread
|
||||||
.parent_id
|
.parent_id
|
||||||
.ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?;
|
.ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?;
|
||||||
|
|
||||||
let parent_channel = ctx
|
let parent_channel = ctx
|
||||||
.cache
|
.cache
|
||||||
.guild_channel(parent_id)
|
.guild_channel(parent_id)
|
||||||
.ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?;
|
.ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?;
|
||||||
|
|
||||||
if parent_channel.name != "support" {
|
if parent_channel.name != "support" {
|
||||||
debug!("Not posting onboarding message to threads outside of support");
|
debug!("Not posting onboarding message to threads outside of support");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let owner = thread
|
let owner = thread
|
||||||
.owner_id
|
.owner_id
|
||||||
.ok_or_else(|| eyre!("Couldn't get owner of thread!"))?;
|
.ok_or_else(|| eyre!("Couldn't get owner of thread!"))?;
|
||||||
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"<@{}> We've received your support ticket! {} {}",
|
"<@{}> We've received your support ticket! {} {}",
|
||||||
owner,
|
owner,
|
||||||
"Please upload your logs and post the link here if possible (run `tag log` to find out how).",
|
"Please upload your logs and post the link here if possible (run `tag log` to find out how).",
|
||||||
"Please don't ping people for support questions, unless you have their permission."
|
"Please don't ping people for support questions, unless you have their permission."
|
||||||
);
|
);
|
||||||
|
|
||||||
thread
|
thread
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.content(msg)
|
m.content(msg)
|
||||||
.allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner])))
|
.allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner])))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
150
src/main.rs
150
src/main.rs
|
@ -7,7 +7,7 @@ use color_eyre::owo_colors::OwoColorize;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use poise::{
|
use poise::{
|
||||||
serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions,
|
serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serenity::ShardManager;
|
use serenity::ShardManager;
|
||||||
|
@ -34,108 +34,108 @@ type Context<'a> = poise::Context<'a, Data, Report>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
config: Config,
|
config: Config,
|
||||||
storage: Storage,
|
storage: Storage,
|
||||||
octocrab: Arc<octocrab::Octocrab>,
|
octocrab: Arc<octocrab::Octocrab>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let config = Config::new_from_env()?;
|
let config = Config::new_from_env()?;
|
||||||
let storage = Storage::new(&config.redis_url)?;
|
let storage = Storage::new(&config.redis_url)?;
|
||||||
let octocrab = octocrab::instance();
|
let octocrab = octocrab::instance();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
config,
|
||||||
storage,
|
storage,
|
||||||
octocrab,
|
octocrab,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(
|
async fn setup(
|
||||||
ctx: &serenity::Context,
|
ctx: &serenity::Context,
|
||||||
_ready: &serenity::Ready,
|
_ready: &serenity::Ready,
|
||||||
framework: &Framework<Data, Report>,
|
framework: &Framework<Data, Report>,
|
||||||
) -> Result<Data> {
|
) -> Result<Data> {
|
||||||
let data = Data::new()?;
|
let data = Data::new()?;
|
||||||
|
|
||||||
// test redis connection
|
// test redis connection
|
||||||
let mut client = data.storage.client.clone();
|
let mut client = data.storage.client.clone();
|
||||||
|
|
||||||
if !client.check_connection() {
|
if !client.check_connection() {
|
||||||
return Err(eyre!(
|
return Err(eyre!(
|
||||||
"Couldn't connect to storage! Is your daemon running?"
|
"Couldn't connect to storage! Is your daemon running?"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
info!("Registered global commands!");
|
info!("Registered global commands!");
|
||||||
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_shutdown(shard_manager: Arc<Mutex<ShardManager>>, reason: &str) {
|
async fn handle_shutdown(shard_manager: Arc<Mutex<ShardManager>>, reason: &str) {
|
||||||
warn!("{reason}! Shutting down bot...");
|
warn!("{reason}! Shutting down bot...");
|
||||||
shard_manager.lock().await.shutdown_all().await;
|
shard_manager.lock().await.shutdown_all().await;
|
||||||
println!("{}", "Everything is shutdown. Goodbye!".green())
|
println!("{}", "Everything is shutdown. Goodbye!".green())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let token = std::env::var("DISCORD_BOT_TOKEN")
|
let token = std::env::var("DISCORD_BOT_TOKEN")
|
||||||
.wrap_err_with(|| "Couldn't find bot token in environment!")?;
|
.wrap_err_with(|| "Couldn't find bot token in environment!")?;
|
||||||
|
|
||||||
let intents =
|
let intents =
|
||||||
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT;
|
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT;
|
||||||
|
|
||||||
let options = FrameworkOptions {
|
let options = FrameworkOptions {
|
||||||
commands: commands::to_global_commands(),
|
commands: commands::to_global_commands(),
|
||||||
|
|
||||||
on_error: |error| Box::pin(handlers::handle_error(error)),
|
on_error: |error| Box::pin(handlers::handle_error(error)),
|
||||||
|
|
||||||
command_check: Some(|ctx| {
|
command_check: Some(|ctx| {
|
||||||
Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) })
|
Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) })
|
||||||
}),
|
}),
|
||||||
|
|
||||||
event_handler: |ctx, event, framework, data| {
|
event_handler: |ctx, event, framework, data| {
|
||||||
Box::pin(handlers::handle_event(ctx, event, framework, data))
|
Box::pin(handlers::handle_event(ctx, event, framework, data))
|
||||||
},
|
},
|
||||||
|
|
||||||
prefix_options: PrefixFrameworkOptions {
|
prefix_options: PrefixFrameworkOptions {
|
||||||
prefix: Some("r".into()),
|
prefix: Some("r".into()),
|
||||||
edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))),
|
edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let framework = Framework::builder()
|
let framework = Framework::builder()
|
||||||
.token(token)
|
.token(token)
|
||||||
.intents(intents)
|
.intents(intents)
|
||||||
.options(options)
|
.options(options)
|
||||||
.setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework)))
|
.setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework)))
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| "Failed to build framework!")?;
|
.wrap_err_with(|| "Failed to build framework!")?;
|
||||||
|
|
||||||
let shard_manager = framework.shard_manager().clone();
|
let shard_manager = framework.shard_manager().clone();
|
||||||
let mut sigterm = signal(SignalKind::terminate())?;
|
let mut sigterm = signal(SignalKind::terminate())?;
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
result = framework.start() => result.map_err(Report::from),
|
result = framework.start() => result.map_err(Report::from),
|
||||||
_ = sigterm.recv() => {
|
_ = sigterm.recv() => {
|
||||||
handle_shutdown(shard_manager, "Recieved SIGTERM").await;
|
handle_shutdown(shard_manager, "Recieved SIGTERM").await;
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
_ = ctrl_c() => {
|
_ = ctrl_c() => {
|
||||||
handle_shutdown(shard_manager, "Interrupted").await;
|
handle_shutdown(shard_manager, "Interrupted").await;
|
||||||
std::process::exit(130);
|
std::process::exit(130);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,105 +10,105 @@ const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1";
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Storage {
|
pub struct Storage {
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage {
|
impl Storage {
|
||||||
pub fn new(redis_url: &str) -> Result<Self> {
|
pub fn new(redis_url: &str) -> Result<Self> {
|
||||||
let client = Client::open(redis_url)?;
|
let client = Client::open(redis_url)?;
|
||||||
|
|
||||||
Ok(Self { client })
|
Ok(Self { client })
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
these are mainly light abstractions to avoid the `let mut con`
|
these are mainly light abstractions to avoid the `let mut con`
|
||||||
boilerplate, as well as not require the caller to format the
|
boilerplate, as well as not require the caller to format the
|
||||||
strings for keys
|
strings for keys
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async fn get_key<T>(&self, key: &str) -> Result<T>
|
async fn get_key<T>(&self, key: &str) -> Result<T>
|
||||||
where
|
where
|
||||||
T: FromRedisValue,
|
T: FromRedisValue,
|
||||||
{
|
{
|
||||||
debug!("Getting key {key}");
|
debug!("Getting key {key}");
|
||||||
|
|
||||||
let mut con = self.client.get_async_connection().await?;
|
let mut con = self.client.get_async_connection().await?;
|
||||||
let res: T = con.get(key).await?;
|
let res: T = con.get(key).await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_key<'a>(
|
async fn set_key<'a>(
|
||||||
&self,
|
&self,
|
||||||
key: &str,
|
key: &str,
|
||||||
value: impl ToRedisArgs + Debug + Send + Sync + 'a,
|
value: impl ToRedisArgs + Debug + Send + Sync + 'a,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug!("Creating key {key}:\n{value:#?}");
|
debug!("Creating key {key}:\n{value:#?}");
|
||||||
|
|
||||||
let mut con = self.client.get_async_connection().await?;
|
let mut con = self.client.get_async_connection().await?;
|
||||||
con.set(key, value).await?;
|
con.set(key, value).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn key_exists(&self, key: &str) -> Result<bool> {
|
async fn key_exists(&self, key: &str) -> Result<bool> {
|
||||||
debug!("Checking if key {key} exists");
|
debug!("Checking if key {key} exists");
|
||||||
|
|
||||||
let mut con = self.client.get_async_connection().await?;
|
let mut con = self.client.get_async_connection().await?;
|
||||||
let exists: u64 = con.exists(key).await?;
|
let exists: u64 = con.exists(key).await?;
|
||||||
|
|
||||||
Ok(exists > 0)
|
Ok(exists > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_key(&self, key: &str) -> Result<()> {
|
async fn delete_key(&self, key: &str) -> Result<()> {
|
||||||
debug!("Deleting key {key}");
|
debug!("Deleting key {key}");
|
||||||
|
|
||||||
let mut con = self.client.get_async_connection().await?;
|
let mut con = self.client.get_async_connection().await?;
|
||||||
con.del(key).await?;
|
con.del(key).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> {
|
async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> {
|
||||||
debug!("Expiring key {key} in {expire_seconds}");
|
debug!("Expiring key {key} in {expire_seconds}");
|
||||||
|
|
||||||
let mut con = self.client.get_async_connection().await?;
|
let mut con = self.client.get_async_connection().await?;
|
||||||
con.expire(key, expire_seconds).await?;
|
con.expire(key, expire_seconds).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> {
|
pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> {
|
||||||
info!("Marking {sender} as a PluralKit user");
|
info!("Marking {sender} as a PluralKit user");
|
||||||
let key = format!("{PK_KEY}:{sender}");
|
let key = format!("{PK_KEY}:{sender}");
|
||||||
|
|
||||||
// Just store some value. We only care about the presence of this key
|
// Just store some value. We only care about the presence of this key
|
||||||
self.set_key(&key, 0).await?;
|
self.set_key(&key, 0).await?;
|
||||||
self.expire_key(&key, 7 * 24 * 60 * 60).await?;
|
self.expire_key(&key, 7 * 24 * 60 * 60).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_user_plural(&self, user_id: UserId) -> Result<bool> {
|
pub async fn is_user_plural(&self, user_id: UserId) -> Result<bool> {
|
||||||
let key = format!("{PK_KEY}:{user_id}");
|
let key = format!("{PK_KEY}:{user_id}");
|
||||||
self.key_exists(&key).await
|
self.key_exists(&key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cache_launcher_version(&self, version: &str) -> Result<()> {
|
pub async fn cache_launcher_version(&self, version: &str) -> Result<()> {
|
||||||
self.set_key(LAUNCHER_VERSION_KEY, version).await?;
|
self.set_key(LAUNCHER_VERSION_KEY, version).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_launcher_version(&self) -> Result<String> {
|
pub async fn get_launcher_version(&self) -> Result<String> {
|
||||||
let res = self.get_key(LAUNCHER_VERSION_KEY).await?;
|
let res = self.get_key(LAUNCHER_VERSION_KEY).await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn launcher_version_is_cached(&self) -> Result<bool> {
|
pub async fn launcher_version_is_cached(&self) -> Result<bool> {
|
||||||
let res = self.key_exists(LAUNCHER_VERSION_KEY).await?;
|
let res = self.key_exists(LAUNCHER_VERSION_KEY).await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/tags.rs
14
src/tags.rs
|
@ -6,15 +6,15 @@ pub const TAG_DIR: &str = "tags";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct TagFrontmatter {
|
pub struct TagFrontmatter {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
pub image: Option<String>,
|
pub image: Option<String>,
|
||||||
pub fields: Option<Vec<EmbedField>>,
|
pub fields: Option<Vec<EmbedField>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub frontmatter: TagFrontmatter,
|
pub frontmatter: TagFrontmatter,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! required_var {
|
macro_rules! required_var {
|
||||||
($name: expr) => {
|
($name: expr) => {
|
||||||
std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))?
|
std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ pub use resolve_message::resolve as resolve_message;
|
||||||
* chooses a random element from an array
|
* chooses a random element from an array
|
||||||
*/
|
*/
|
||||||
pub fn random_choice<const N: usize>(arr: [&str; N]) -> Result<String> {
|
pub fn random_choice<const N: usize>(arr: [&str; N]) -> Result<String> {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let resp = arr
|
let resp = arr
|
||||||
.choose(&mut rng)
|
.choose(&mut rng)
|
||||||
.ok_or_else(|| eyre!("Couldn't choose random object from array:\n{arr:#?}!"))?;
|
.ok_or_else(|| eyre!("Couldn't choose random object from array:\n{arr:#?}!"))?;
|
||||||
|
|
||||||
Ok((*resp).to_string())
|
Ok((*resp).to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,120 +5,120 @@ use poise::serenity_prelude::{ChannelType, Colour, Context, CreateEmbed, Message
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
static MESSAGE_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
static MESSAGE_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?<serverId>\d+)\/(?<channelId>\d+)\/(?<messageId>\d+)/g;").unwrap()
|
Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?<serverId>\d+)\/(?<channelId>\d+)\/(?<messageId>\d+)/g;").unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn find_first_image(msg: &Message) -> Option<String> {
|
pub fn find_first_image(msg: &Message) -> Option<String> {
|
||||||
msg.attachments
|
msg.attachments
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| {
|
.find(|a| {
|
||||||
a.content_type
|
a.content_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&"".to_string())
|
.unwrap_or(&"".to_string())
|
||||||
.starts_with("image/")
|
.starts_with("image/")
|
||||||
})
|
})
|
||||||
.map(|res| res.url.clone())
|
.map(|res| res.url.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve(ctx: &Context, msg: &Message) -> Result<Vec<CreateEmbed>> {
|
pub async fn resolve(ctx: &Context, msg: &Message) -> Result<Vec<CreateEmbed>> {
|
||||||
let matches = MESSAGE_PATTERN.captures_iter(&msg.content);
|
let matches = MESSAGE_PATTERN.captures_iter(&msg.content);
|
||||||
let mut embeds: Vec<CreateEmbed> = vec![];
|
let mut embeds: Vec<CreateEmbed> = vec![];
|
||||||
|
|
||||||
for captured in matches.take(3) {
|
for captured in matches.take(3) {
|
||||||
// don't leak messages from other servers
|
// don't leak messages from other servers
|
||||||
if let Some(server_id) = captured.get(0) {
|
if let Some(server_id) = captured.get(0) {
|
||||||
let other_server: u64 = server_id.as_str().parse().unwrap_or_default();
|
let other_server: u64 = server_id.as_str().parse().unwrap_or_default();
|
||||||
let current_id = msg.guild_id.unwrap_or_default();
|
let current_id = msg.guild_id.unwrap_or_default();
|
||||||
|
|
||||||
if &other_server != current_id.as_u64() {
|
if &other_server != current_id.as_u64() {
|
||||||
debug!("Not resolving message of other guild.");
|
debug!("Not resolving message of other guild.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("Couldn't find server_id from Discord link! Not resolving message to be safe");
|
warn!("Couldn't find server_id from Discord link! Not resolving message to be safe");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(channel_id) = captured.get(1) {
|
if let Some(channel_id) = captured.get(1) {
|
||||||
let parsed: u64 = channel_id.as_str().parse().unwrap_or_default();
|
let parsed: u64 = channel_id.as_str().parse().unwrap_or_default();
|
||||||
let req_channel = ctx
|
let req_channel = ctx
|
||||||
.cache
|
.cache
|
||||||
.channel(parsed)
|
.channel(parsed)
|
||||||
.ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))?
|
.ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))?
|
||||||
.guild()
|
.guild()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!")
|
eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !req_channel.is_text_based() {
|
if !req_channel.is_text_based() {
|
||||||
debug!("Not resolving message is non-text-based channel.");
|
debug!("Not resolving message is non-text-based channel.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if req_channel.kind == ChannelType::PrivateThread {
|
if req_channel.kind == ChannelType::PrivateThread {
|
||||||
if let Some(id) = req_channel.parent_id {
|
if let Some(id) = req_channel.parent_id {
|
||||||
let parent = ctx.cache.guild_channel(id).ok_or_else(|| {
|
let parent = ctx.cache.guild_channel(id).ok_or_else(|| {
|
||||||
eyre!("Couldn't get parent channel {id} for thread {req_channel}!")
|
eyre!("Couldn't get parent channel {id} for thread {req_channel}!")
|
||||||
})?;
|
})?;
|
||||||
let parent_members = parent.members(ctx).await.unwrap_or_default();
|
let parent_members = parent.members(ctx).await.unwrap_or_default();
|
||||||
|
|
||||||
if !parent_members.iter().any(|m| m.user.id == msg.author.id) {
|
if !parent_members.iter().any(|m| m.user.id == msg.author.id) {
|
||||||
debug!("Not resolving message for user not a part of a private thread.");
|
debug!("Not resolving message for user not a part of a private thread.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if req_channel
|
} else if req_channel
|
||||||
.members(ctx)
|
.members(ctx)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
.any(|m| m.user.id == msg.author.id)
|
.any(|m| m.user.id == msg.author.id)
|
||||||
{
|
{
|
||||||
debug!("Not resolving for message for user not a part of a channel");
|
debug!("Not resolving for message for user not a part of a channel");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message_id: u64 = captured
|
let message_id: u64 = captured
|
||||||
.get(2)
|
.get(2)
|
||||||
.ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))?
|
.ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))?
|
||||||
.as_str()
|
.as_str()
|
||||||
.parse()
|
.parse()
|
||||||
.wrap_err_with(|| {
|
.wrap_err_with(|| {
|
||||||
eyre!("Couldn't parse message_id from Discord regex as a MessageId!")
|
eyre!("Couldn't parse message_id from Discord regex as a MessageId!")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let original_message = req_channel.message(ctx, message_id).await?;
|
let original_message = req_channel.message(ctx, message_id).await?;
|
||||||
let mut embed = CreateEmbed::default();
|
let mut embed = CreateEmbed::default();
|
||||||
embed
|
embed
|
||||||
.author(|a| {
|
.author(|a| {
|
||||||
a.name(original_message.author.tag())
|
a.name(original_message.author.tag())
|
||||||
.icon_url(original_message.author.default_avatar_url())
|
.icon_url(original_message.author.default_avatar_url())
|
||||||
})
|
})
|
||||||
.color(Colour::BLITZ_BLUE)
|
.color(Colour::BLITZ_BLUE)
|
||||||
.timestamp(original_message.timestamp)
|
.timestamp(original_message.timestamp)
|
||||||
.footer(|f| f.text(format!("#{}", req_channel.name)))
|
.footer(|f| f.text(format!("#{}", req_channel.name)))
|
||||||
.description(format!(
|
.description(format!(
|
||||||
"{}\n\n[Jump to original message]({})",
|
"{}\n\n[Jump to original message]({})",
|
||||||
original_message.content,
|
original_message.content,
|
||||||
original_message.link()
|
original_message.link()
|
||||||
));
|
));
|
||||||
|
|
||||||
if !original_message.attachments.is_empty() {
|
if !original_message.attachments.is_empty() {
|
||||||
embed.fields(original_message.attachments.iter().map(|a| {
|
embed.fields(original_message.attachments.iter().map(|a| {
|
||||||
(
|
(
|
||||||
"Attachments".to_string(),
|
"Attachments".to_string(),
|
||||||
format!("[{}]({})", a.filename, a.url),
|
format!("[{}]({})", a.filename, a.url),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if let Some(image) = find_first_image(msg) {
|
if let Some(image) = find_first_image(msg) {
|
||||||
embed.image(image);
|
embed.image(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
embeds.push(embed);
|
embeds.push(embed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(embeds)
|
Ok(embeds)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue