Add mclo.gs upload prompt
This commit is contained in:
parent
c4da3b5f00
commit
dcc08ee4ad
9 changed files with 343 additions and 80 deletions
35
src/api/mclogs.rs
Normal file
35
src/api/mclogs.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use eyre::{eyre, OptionExt, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{HttpClient, HttpClientExt};
|
||||||
|
|
||||||
|
const MCLOGS: &str = "https://api.mclo.gs/1";
|
||||||
|
const UPLOAD: &str = "/log";
|
||||||
|
const RAW: &str = "/raw";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PostLogResponse {
|
||||||
|
pub success: bool,
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub raw: Option<String>,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn upload_log(http: &HttpClient, content: &str) -> Result<PostLogResponse> {
|
||||||
|
let url = format!("{MCLOGS}{UPLOAD}");
|
||||||
|
let request = http
|
||||||
|
.post(url)
|
||||||
|
.form(&HashMap::from([("content", content)]))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
Ok(http.execute(request).await?.json().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn raw_log(http: &HttpClient, id: &str) -> Result<String> {
|
||||||
|
let url = format!("{MCLOGS}{RAW}/{id}");
|
||||||
|
|
||||||
|
Ok(http.get_request(&url).await?.text().await?)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ use reqwest::Response;
|
||||||
|
|
||||||
pub mod dadjoke;
|
pub mod dadjoke;
|
||||||
pub mod github;
|
pub mod github;
|
||||||
|
pub mod mclogs;
|
||||||
pub mod paste_gg;
|
pub mod paste_gg;
|
||||||
pub mod pluralkit;
|
pub mod pluralkit;
|
||||||
pub mod prism_meta;
|
pub mod prism_meta;
|
||||||
|
|
65
src/handlers/event/analyze_logs/heuristics.rs
Normal file
65
src/handlers/event/analyze_logs/heuristics.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use log::trace;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
pub fn looks_like_launcher_log(log: &str) -> bool {
|
||||||
|
static QT_LOG_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
||||||
|
trace!("Guessing whether log is launcher log");
|
||||||
|
|
||||||
|
let qt_log = QT_LOG_REGEX.get_or_init(|| Regex::new(r"\d\.\d{3} [CDFIW] \|").unwrap());
|
||||||
|
qt_log.is_match(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn looks_like_mc_log(log: &str) -> bool {
|
||||||
|
static LOG4J_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
||||||
|
trace!("Guessing whether log is Minecraft log");
|
||||||
|
|
||||||
|
if log.contains("Minecraft process ID: ") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// present in almost every Minecraft version
|
||||||
|
if log.contains("Setting user: ") || log.contains("Minecraft Version: ") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if log.contains("Exception in thread ")
|
||||||
|
|| log.contains("Exception: ")
|
||||||
|
|| log.contains("Error: ")
|
||||||
|
|| log.contains("Throwable: ")
|
||||||
|
|| log.contains("Caused by: ")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if log.contains("org.prismlauncher.EntryPoint.main(EntryPoint.java")
|
||||||
|
|| log.contains("java.lang.Thread.run(Thread.java")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let log4j = LOG4J_REGEX
|
||||||
|
.get_or_init(|| Regex::new(r"\[\d{2}:\d{2}:\d{2}\] \[.+?/(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)\] ").unwrap());
|
||||||
|
|
||||||
|
if log4j.is_match(&log) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if log.contains("[INFO]")
|
||||||
|
|| log.contains("[CONFIG]")
|
||||||
|
|| log.contains("[FINE]")
|
||||||
|
|| log.contains("[FINER]")
|
||||||
|
|| log.contains("[FINEST]")
|
||||||
|
|| log.contains("[SEVERE]")
|
||||||
|
|| log.contains("[STDERR]")
|
||||||
|
|| log.contains("[WARNING]")
|
||||||
|
|| log.contains("[DEBUG]")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
|
@ -1,17 +1,34 @@
|
||||||
use crate::{consts::Colors, Data};
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
use eyre::Result;
|
sync::{Mutex, OnceLock},
|
||||||
use log::{debug, trace};
|
|
||||||
use poise::serenity_prelude::{
|
|
||||||
Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::{mclogs, HttpClientExt},
|
||||||
|
consts::Colors,
|
||||||
|
utils::first_text_attachment,
|
||||||
|
Data,
|
||||||
|
};
|
||||||
|
|
||||||
|
use color_eyre::owo_colors::OwoColorize;
|
||||||
|
use eyre::{eyre, OptionExt, Result};
|
||||||
|
use log::{debug, trace};
|
||||||
|
use poise::serenity_prelude::{
|
||||||
|
ButtonStyle, ComponentInteraction, Context, CreateAllowedMentions, CreateButton, CreateEmbed,
|
||||||
|
CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, EditMessage,
|
||||||
|
Message, MessageId, MessageType,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod heuristics;
|
||||||
mod issues;
|
mod issues;
|
||||||
mod providers;
|
mod providers;
|
||||||
|
|
||||||
use providers::find_log;
|
use providers::find_log;
|
||||||
|
|
||||||
pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> {
|
const BUTTON_UPLOAD_YES: &str = "log-upload-yes";
|
||||||
|
const BUTTON_UPLOAD_NO: &str = "log-upload-no";
|
||||||
|
|
||||||
|
pub async fn handle_message(ctx: &Context, message: &Message, data: &Data) -> Result<()> {
|
||||||
trace!(
|
trace!(
|
||||||
"Checking message {} from {} for logs",
|
"Checking message {} from {} for logs",
|
||||||
message.id,
|
message.id,
|
||||||
|
@ -19,9 +36,7 @@ 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(&data.http_client, message).await;
|
let Ok(log) = find_log(&data.http_client, message).await else {
|
||||||
|
|
||||||
if log.is_err() {
|
|
||||||
let embed = CreateEmbed::new()
|
let embed = CreateEmbed::new()
|
||||||
.title("Analysis failed!")
|
.title("Analysis failed!")
|
||||||
.description("Couldn't download log");
|
.description("Couldn't download log");
|
||||||
|
@ -33,43 +48,216 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()>
|
||||||
|
|
||||||
channel.send_message(ctx, our_message).await?;
|
channel.send_message(ctx, our_message).await?;
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(log) = log? else {
|
|
||||||
debug!("No log found in message! Skipping analysis");
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let log = log.replace("\r\n", "\n");
|
let attachment = first_text_attachment(message);
|
||||||
|
|
||||||
|
let log = match log {
|
||||||
|
Some(log) => log,
|
||||||
|
None => match attachment {
|
||||||
|
Some(attachment) => {
|
||||||
|
data.http_client
|
||||||
|
.get_request(&attachment.url)
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!("No log found in message! Skipping analysis");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let issues = issues::find(&log, data).await?;
|
let issues = issues::find(&log, data).await?;
|
||||||
|
let launcher_log = heuristics::looks_like_launcher_log(&log);
|
||||||
|
let mc_log = !launcher_log && heuristics::looks_like_mc_log(&log);
|
||||||
|
|
||||||
let embed = {
|
debug!("Heuristics: mc_log = {mc_log}, launcher_log = {launcher_log}");
|
||||||
let mut e = CreateEmbed::new().title("Log analysis");
|
|
||||||
|
|
||||||
if issues.is_empty() {
|
let show_analysis = !issues.is_empty() || mc_log;
|
||||||
e = e
|
let show_upload_prompt = attachment.is_some() && (mc_log || launcher_log);
|
||||||
.color(Colors::Green)
|
|
||||||
.description("The automatic check didn't reveal any issues, but it's possible that some issues went undetected. Please wait for a volunteer to assist you.");
|
|
||||||
} else {
|
|
||||||
e = e.color(Colors::Red);
|
|
||||||
|
|
||||||
for (title, description) in issues {
|
if !show_analysis && !show_upload_prompt {
|
||||||
e = e.field(title, description, false);
|
debug!("Found log but there is nothing to respond with");
|
||||||
}
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
e
|
|
||||||
};
|
|
||||||
|
|
||||||
let allowed_mentions = CreateAllowedMentions::new().replied_user(true);
|
let allowed_mentions = CreateAllowedMentions::new().replied_user(true);
|
||||||
let message = CreateMessage::new()
|
|
||||||
|
let mut message = CreateMessage::new()
|
||||||
.reference_message(message)
|
.reference_message(message)
|
||||||
.allowed_mentions(allowed_mentions)
|
.allowed_mentions(allowed_mentions);
|
||||||
.embed(embed);
|
|
||||||
|
if show_analysis {
|
||||||
|
message = message.add_embed({
|
||||||
|
let mut e = CreateEmbed::new().title("Log analysis");
|
||||||
|
|
||||||
|
if issues.is_empty() {
|
||||||
|
e = e
|
||||||
|
.color(Colors::Green)
|
||||||
|
.description("The automatic check didn't reveal any issues, but it's possible that some issues went undetected. Please wait for a volunteer to assist you.");
|
||||||
|
} else {
|
||||||
|
e = e.color(Colors::Red);
|
||||||
|
|
||||||
|
for (title, description) in issues {
|
||||||
|
e = e.field(title, description, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_upload_prompt {
|
||||||
|
message = message.add_embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title("Upload log?")
|
||||||
|
.color(Colors::Blue)
|
||||||
|
.description("Discord attachments make it difficult for volunteers to view logs. Would you like me to upload your log to [mclo.gs](https://mclo.gs/)?"),
|
||||||
|
);
|
||||||
|
message = message
|
||||||
|
.button(
|
||||||
|
CreateButton::new(BUTTON_UPLOAD_NO)
|
||||||
|
.style(ButtonStyle::Secondary)
|
||||||
|
.label("No"),
|
||||||
|
)
|
||||||
|
.button(
|
||||||
|
CreateButton::new(BUTTON_UPLOAD_YES)
|
||||||
|
.style(ButtonStyle::Primary)
|
||||||
|
.label("Yes"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
channel.send_message(ctx, message).await?;
|
channel.send_message(ctx, message).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_component_interaction(
|
||||||
|
ctx: &Context,
|
||||||
|
interaction: &ComponentInteraction,
|
||||||
|
data: &Data,
|
||||||
|
) -> Result<()> {
|
||||||
|
if interaction.message.kind != MessageType::InlineReply {
|
||||||
|
debug!("Ignoring component interaction on message which is not a reply");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let yes = interaction.data.custom_id == BUTTON_UPLOAD_YES;
|
||||||
|
|
||||||
|
if !yes && interaction.data.custom_id != BUTTON_UPLOAD_NO {
|
||||||
|
debug!(
|
||||||
|
"Ignoring component interaction without ID {BUTTON_UPLOAD_YES} or {BUTTON_UPLOAD_NO}"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: what is this called? need it to automatically be removed when going out of scope
|
||||||
|
// static ACTIVE_MUTEX_LOCK: OnceLock<Mutex<HashSet<MessageId>>> = OnceLock::new();
|
||||||
|
// let active_mutex = ACTIVE_MUTEX_LOCK.get_or_init(|| Mutex::new(HashSet::new()));
|
||||||
|
//
|
||||||
|
// if !active_mutex.lock().unwrap().insert(interaction.message.id) {
|
||||||
|
// debug!(
|
||||||
|
// "Already handling upload button {}; returning",
|
||||||
|
// interaction.message.id
|
||||||
|
// );
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut embeds: Vec<CreateEmbed> = interaction
|
||||||
|
.message
|
||||||
|
.embeds
|
||||||
|
.iter()
|
||||||
|
.map(|embed| CreateEmbed::from(embed.to_owned()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if yes {
|
||||||
|
// for some reason Discord never sends us the referenced message, only its id
|
||||||
|
let message_reference = interaction
|
||||||
|
.message
|
||||||
|
.message_reference
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_eyre("Missing message reference")?;
|
||||||
|
let referenced_message = ctx
|
||||||
|
.http
|
||||||
|
.get_message(
|
||||||
|
message_reference.channel_id,
|
||||||
|
message_reference
|
||||||
|
.message_id
|
||||||
|
.ok_or_eyre("Reference missing message ID")?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let first_attachment = first_text_attachment(&referenced_message)
|
||||||
|
.ok_or_eyre("Log attachment disappeared (should not be possible)")?;
|
||||||
|
let body = data
|
||||||
|
.http_client
|
||||||
|
.get_request(&first_attachment.url)
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let response = mclogs::upload_log(&data.http_client, &body).await?;
|
||||||
|
|
||||||
|
if !response.success {
|
||||||
|
let error = response
|
||||||
|
.error
|
||||||
|
.ok_or_else(|| eyre!("mclo.gs gave us an error but with no message!"))?;
|
||||||
|
|
||||||
|
interaction
|
||||||
|
.create_response(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponse::Message(
|
||||||
|
CreateInteractionResponseMessage::new()
|
||||||
|
.ephemeral(true)
|
||||||
|
.embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title("Upload failed")
|
||||||
|
.color(Colors::Red)
|
||||||
|
.description(&error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// active_mutex.lock().unwrap().remove(&interaction.message.id);
|
||||||
|
return Err(eyre!("Failed to upload log: {}", &error));
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = &response
|
||||||
|
.url
|
||||||
|
.ok_or_eyre("Missing URL in mclo.gs response!")?;
|
||||||
|
|
||||||
|
let length = embeds.len();
|
||||||
|
|
||||||
|
embeds[length - 1] = CreateEmbed::new()
|
||||||
|
.title("Uploaded log")
|
||||||
|
.color(Colors::Blue)
|
||||||
|
.description(url);
|
||||||
|
} else {
|
||||||
|
embeds.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
interaction
|
||||||
|
.create_response(ctx, CreateInteractionResponse::Acknowledge)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if embeds.len() == 0 {
|
||||||
|
interaction.message.delete(ctx).await?;
|
||||||
|
} else {
|
||||||
|
ctx.http
|
||||||
|
.edit_message(
|
||||||
|
interaction.channel_id,
|
||||||
|
interaction.message.id,
|
||||||
|
&EditMessage::new().embeds(embeds).components(vec![]),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// active_mutex.lock().unwrap().remove(&interaction.message.id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
use crate::api::{HttpClient, HttpClientExt};
|
|
||||||
|
|
||||||
use eyre::Result;
|
|
||||||
use log::trace;
|
|
||||||
use poise::serenity_prelude::Message;
|
|
||||||
|
|
||||||
pub struct Attachment;
|
|
||||||
|
|
||||||
impl super::LogProvider for Attachment {
|
|
||||||
async fn find_match(&self, message: &Message) -> Option<String> {
|
|
||||||
trace!("Checking if message {} has text attachments", message.id);
|
|
||||||
|
|
||||||
message
|
|
||||||
.attachments
|
|
||||||
.iter()
|
|
||||||
.filter_map(|a| {
|
|
||||||
a.content_type
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|ct| ct.starts_with("text/").then_some(a.url.clone()))
|
|
||||||
})
|
|
||||||
.nth(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch(&self, http: &HttpClient, content: &str) -> Result<String> {
|
|
||||||
let attachment = http.get_request(content).await?.bytes().await?.to_vec();
|
|
||||||
let log = String::from_utf8(attachment)?;
|
|
||||||
|
|
||||||
Ok(log)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::api::{HttpClient, HttpClientExt};
|
use crate::api::{mclogs::raw_log, HttpClient, HttpClientExt};
|
||||||
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
@ -7,9 +7,6 @@ use log::trace;
|
||||||
use poise::serenity_prelude::Message;
|
use poise::serenity_prelude::Message;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
const MCLOGS: &str = "https://api.mclo.gs/1";
|
|
||||||
const RAW: &str = "/raw";
|
|
||||||
|
|
||||||
pub struct MCLogs;
|
pub struct MCLogs;
|
||||||
|
|
||||||
impl super::LogProvider for MCLogs {
|
impl super::LogProvider for MCLogs {
|
||||||
|
@ -22,9 +19,6 @@ impl super::LogProvider for MCLogs {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch(&self, http: &HttpClient, content: &str) -> Result<String> {
|
async fn fetch(&self, http: &HttpClient, content: &str) -> Result<String> {
|
||||||
let url = format!("{MCLOGS}{RAW}/{content}");
|
Ok(raw_log(&http, content).await?)
|
||||||
let log = http.get_request(&url).await?.text().await?;
|
|
||||||
|
|
||||||
Ok(log)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,11 @@ use poise::serenity_prelude::Message;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
_0x0::_0x0 as _0x0st, attachment::Attachment, haste::Haste, mclogs::MCLogs, paste_gg::PasteGG,
|
_0x0::_0x0 as _0x0st, haste::Haste, mclogs::MCLogs, paste_gg::PasteGG, pastebin::PasteBin,
|
||||||
pastebin::PasteBin,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "0x0.rs"]
|
#[path = "0x0.rs"]
|
||||||
mod _0x0;
|
mod _0x0;
|
||||||
mod attachment;
|
|
||||||
mod haste;
|
mod haste;
|
||||||
mod mclogs;
|
mod mclogs;
|
||||||
mod paste_gg;
|
mod paste_gg;
|
||||||
|
@ -35,7 +33,6 @@ fn get_first_capture(regex: &Regex, string: &str) -> Option<String> {
|
||||||
#[enum_dispatch(LogProvider)]
|
#[enum_dispatch(LogProvider)]
|
||||||
enum Provider {
|
enum Provider {
|
||||||
_0x0st,
|
_0x0st,
|
||||||
Attachment,
|
|
||||||
Haste,
|
Haste,
|
||||||
MCLogs,
|
MCLogs,
|
||||||
PasteGG,
|
PasteGG,
|
||||||
|
@ -44,9 +41,8 @@ enum Provider {
|
||||||
|
|
||||||
impl Provider {
|
impl Provider {
|
||||||
pub fn iterator() -> Iter<'static, Provider> {
|
pub fn iterator() -> Iter<'static, Provider> {
|
||||||
static PROVIDERS: [Provider; 6] = [
|
static PROVIDERS: [Provider; 5] = [
|
||||||
Provider::_0x0st(_0x0st),
|
Provider::_0x0st(_0x0st),
|
||||||
Provider::Attachment(Attachment),
|
|
||||||
Provider::Haste(Haste),
|
Provider::Haste(Haste),
|
||||||
Provider::MCLogs(MCLogs),
|
Provider::MCLogs(MCLogs),
|
||||||
Provider::PasteBin(PasteBin),
|
Provider::PasteBin(PasteBin),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{api, Data, Error};
|
use crate::{api, Data, Error};
|
||||||
|
|
||||||
|
use analyze_logs::handle_component_interaction;
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace};
|
||||||
use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus};
|
use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus};
|
||||||
use poise::FrameworkContext;
|
use poise::FrameworkContext;
|
||||||
|
@ -33,6 +34,7 @@ pub async fn handle(
|
||||||
FullEvent::InteractionCreate { interaction } => {
|
FullEvent::InteractionCreate { interaction } => {
|
||||||
if let Some(component_interaction) = interaction.as_message_component() {
|
if let Some(component_interaction) = interaction.as_message_component() {
|
||||||
give_role::handle(ctx, component_interaction).await?;
|
give_role::handle(ctx, component_interaction).await?;
|
||||||
|
handle_component_interaction(ctx, component_interaction, data).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ pub async fn handle(
|
||||||
|
|
||||||
eta::handle(ctx, new_message).await?;
|
eta::handle(ctx, new_message).await?;
|
||||||
expand_link::handle(ctx, &data.http_client, new_message).await?;
|
expand_link::handle(ctx, &data.http_client, new_message).await?;
|
||||||
analyze_logs::handle(ctx, new_message, data).await?;
|
analyze_logs::handle_message(ctx, new_message, data).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
FullEvent::ReactionAdd { add_reaction } => {
|
FullEvent::ReactionAdd { add_reaction } => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use poise::serenity_prelude::{CreateEmbedAuthor, User};
|
use poise::serenity_prelude::{Attachment, CreateEmbedAuthor, Message, User};
|
||||||
|
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
|
|
||||||
|
@ -15,3 +15,15 @@ pub fn semver_split(version: &str) -> Vec<u32> {
|
||||||
.filter_map(|s| s.parse().ok())
|
.filter_map(|s| s.parse().ok())
|
||||||
.collect::<Vec<u32>>()
|
.collect::<Vec<u32>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn first_text_attachment(message: &Message) -> Option<&Attachment> {
|
||||||
|
message
|
||||||
|
.attachments
|
||||||
|
.iter()
|
||||||
|
.filter(|a| {
|
||||||
|
a.content_type
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|content_type| content_type.starts_with("text/"))
|
||||||
|
})
|
||||||
|
.nth(0)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue