analyze_logs: introduce LogProvider trait
This commit is contained in:
parent
b63ecde6b4
commit
827b5a4bd7
12 changed files with 270 additions and 164 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -465,6 +465,18 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
|
@ -1390,9 +1402,9 @@ dependencies = [
|
|||
name = "refraction"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"color-eyre",
|
||||
"dotenvy",
|
||||
"enum_dispatch",
|
||||
"env_logger",
|
||||
"eyre",
|
||||
"gray_matter",
|
||||
|
|
|
@ -15,9 +15,9 @@ serde = "1.0.196"
|
|||
serde_json = "1.0.112"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.77"
|
||||
color-eyre = "0.6.2"
|
||||
dotenvy = "0.15.7"
|
||||
enum_dispatch = "0.3.12"
|
||||
env_logger = "0.11.1"
|
||||
eyre = "0.6.11"
|
||||
log = "0.4.20"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
pub mod dadjoke;
|
||||
pub mod paste_gg;
|
||||
pub mod pluralkit;
|
||||
pub mod prism_meta;
|
||||
pub mod rory;
|
||||
|
|
55
src/api/paste_gg.rs
Normal file
55
src/api/paste_gg.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use crate::{api::REQWEST_CLIENT, utils};
|
||||
|
||||
use eyre::{eyre, OptionExt, Result};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const PASTE_GG: &str = "https://api.paste.gg/v1";
|
||||
const PASTES: &str = "/pastes";
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
enum Status {
|
||||
#[serde(rename = "success")]
|
||||
Success,
|
||||
#[serde(rename = "error")]
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Response<T> {
|
||||
status: Status,
|
||||
pub result: Option<Vec<T>>,
|
||||
error: Option<String>,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Files {
|
||||
pub id: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_files(id: &str) -> Result<Response<Files>> {
|
||||
let url = format!("{PASTE_GG}{PASTES}/{id}/files");
|
||||
debug!("Making request to {url}");
|
||||
let resp = REQWEST_CLIENT.get(url).send().await?;
|
||||
resp.error_for_status_ref()?;
|
||||
let resp: Response<Files> = resp.json().await?;
|
||||
|
||||
if resp.status == Status::Error {
|
||||
let message = resp
|
||||
.error
|
||||
.ok_or_eyre("Paste.gg gave us an error but with no message!")?;
|
||||
|
||||
Err(eyre!(message))
|
||||
} else {
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_raw_file(paste_id: &str, file_id: &str) -> eyre::Result<String> {
|
||||
let url = format!("{PASTE_GG}{PASTES}/{paste_id}/files/{file_id}/raw");
|
||||
let text = utils::text_from_url(&url).await?;
|
||||
|
||||
Ok(text)
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
use crate::api::REQWEST_CLIENT;
|
||||
use crate::utils;
|
||||
|
||||
use eyre::{eyre, Result};
|
||||
use eyre::Result;
|
||||
use log::trace;
|
||||
use once_cell::sync::Lazy;
|
||||
use poise::serenity_prelude::Message;
|
||||
use regex::Regex;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap());
|
||||
pub struct _0x0;
|
||||
|
||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||
trace!("Checking if {content} is a 0x0 paste");
|
||||
impl super::LogProvider for _0x0 {
|
||||
async fn find_match(&self, message: &Message) -> Option<String> {
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap());
|
||||
|
||||
let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else {
|
||||
return Ok(None);
|
||||
};
|
||||
trace!("Checking if message {} is a 0x0 paste", message.id);
|
||||
REGEX
|
||||
.find_iter(&message.content)
|
||||
.map(|m| m.as_str().to_string())
|
||||
.nth(0)
|
||||
}
|
||||
|
||||
let request = REQWEST_CLIENT.get(url).build()?;
|
||||
let response = REQWEST_CLIENT.execute(request).await?;
|
||||
let status = response.status();
|
||||
async fn fetch(&self, content: &str) -> Result<String> {
|
||||
let log = utils::text_from_url(content).await?;
|
||||
|
||||
if let StatusCode::OK = status {
|
||||
Ok(Some(response.text().await?))
|
||||
} else {
|
||||
Err(eyre!("Failed to fetch paste from {url} with {status}",))
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,28 @@ use eyre::Result;
|
|||
use log::trace;
|
||||
use poise::serenity_prelude::Message;
|
||||
|
||||
pub async fn find(message: &Message) -> Result<Option<String>> {
|
||||
trace!("Checking for text attachments in message {}", message.id);
|
||||
use crate::utils;
|
||||
|
||||
// find first uploaded text file
|
||||
if let Some(attachment) = message.attachments.iter().find(|a| {
|
||||
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(true))
|
||||
.is_some()
|
||||
}) {
|
||||
let bytes = attachment.download().await?;
|
||||
let res = String::from_utf8(bytes)?;
|
||||
Ok(Some(res))
|
||||
} else {
|
||||
Ok(None)
|
||||
.and_then(|ct| ct.starts_with("text/").then_some(a.url.clone()))
|
||||
})
|
||||
.nth(0)
|
||||
}
|
||||
|
||||
async fn fetch(&self, content: &str) -> Result<String> {
|
||||
let attachment = utils::bytes_from_url(content).await?;
|
||||
let log = String::from_utf8(attachment)?;
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
use crate::api::REQWEST_CLIENT;
|
||||
use crate::utils;
|
||||
|
||||
use eyre::{eyre, Result};
|
||||
use eyre::Result;
|
||||
use log::trace;
|
||||
use once_cell::sync::Lazy;
|
||||
use poise::serenity_prelude::Message;
|
||||
use regex::Regex;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
static REGEX: Lazy<Regex> =
|
||||
const HASTE: &str = "https://hst.sh";
|
||||
const RAW: &str = "/raw";
|
||||
|
||||
pub struct Haste;
|
||||
|
||||
impl super::LogProvider for Haste {
|
||||
async fn find_match(&self, message: &Message) -> Option<String> {
|
||||
static REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap());
|
||||
|
||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||
trace!("Checking if {content} is a haste log");
|
||||
trace!("Checking if message {} is a hst.sh paste", message.id);
|
||||
super::get_first_capture(®EX, &message.content)
|
||||
}
|
||||
|
||||
let Some(captures) = REGEX.captures(content) else {
|
||||
return Ok(None);
|
||||
};
|
||||
async fn fetch(&self, content: &str) -> Result<String> {
|
||||
let url = format!("{HASTE}{RAW}/{content}");
|
||||
let log = utils::text_from_url(&url).await?;
|
||||
|
||||
let url = format!("https://hst.sh/raw/{}", &captures[1]);
|
||||
let request = REQWEST_CLIENT.get(&url).build()?;
|
||||
let response = REQWEST_CLIENT.execute(request).await?;
|
||||
let status = response.status();
|
||||
|
||||
if let StatusCode::OK = status {
|
||||
Ok(Some(response.text().await?))
|
||||
} else {
|
||||
Err(eyre!("Failed to fetch paste from {url} with {status}"))
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
use crate::api::REQWEST_CLIENT;
|
||||
use crate::utils;
|
||||
|
||||
use eyre::{eyre, Result};
|
||||
use eyre::Result;
|
||||
use log::trace;
|
||||
use once_cell::sync::Lazy;
|
||||
use poise::serenity_prelude::Message;
|
||||
use regex::Regex;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap());
|
||||
const MCLOGS: &str = "https://api.mclo.gs/1";
|
||||
const RAW: &str = "/raw";
|
||||
|
||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||
trace!("Checking if {content} is an mclo.gs paste");
|
||||
pub struct MCLogs;
|
||||
|
||||
let Some(captures) = REGEX.captures(content) else {
|
||||
return Ok(None);
|
||||
};
|
||||
impl super::LogProvider for MCLogs {
|
||||
async fn find_match(&self, message: &Message) -> Option<String> {
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap());
|
||||
|
||||
let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]);
|
||||
let request = REQWEST_CLIENT.get(&url).build()?;
|
||||
let response = REQWEST_CLIENT.execute(request).await?;
|
||||
let status = response.status();
|
||||
trace!("Checking if message {} is an mclo.gs paste", message.id);
|
||||
super::get_first_capture(®EX, &message.content)
|
||||
}
|
||||
|
||||
if let StatusCode::OK = status {
|
||||
Ok(Some(response.text().await?))
|
||||
} else {
|
||||
Err(eyre!("Failed to fetch log from {url} with {status}"))
|
||||
async fn fetch(&self, content: &str) -> Result<String> {
|
||||
let url = format!("{MCLOGS}{RAW}/{content}");
|
||||
let log = utils::text_from_url(&url).await?;
|
||||
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
use std::slice::Iter;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use eyre::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use poise::serenity_prelude::Message;
|
||||
use regex::Regex;
|
||||
|
||||
use self::{
|
||||
_0x0::_0x0 as _0x0st, attachment::Attachment, haste::Haste, mclogs::MCLogs, paste_gg::PasteGG,
|
||||
pastebin::PasteBin,
|
||||
};
|
||||
|
||||
#[path = "0x0.rs"]
|
||||
mod _0x0;
|
||||
|
@ -9,25 +19,52 @@ mod mclogs;
|
|||
mod paste_gg;
|
||||
mod pastebin;
|
||||
|
||||
pub type LogProvider = Result<Option<String>>;
|
||||
|
||||
pub async fn find_log(message: &Message) -> LogProvider {
|
||||
macro_rules! provider_impl {
|
||||
($provider:ident) => {
|
||||
if let Some(content) = $provider::find(&message.content).await? {
|
||||
return Ok(Some(content));
|
||||
}
|
||||
};
|
||||
}
|
||||
provider_impl!(_0x0);
|
||||
provider_impl!(mclogs);
|
||||
provider_impl!(haste);
|
||||
provider_impl!(paste_gg);
|
||||
provider_impl!(pastebin);
|
||||
|
||||
if let Some(content) = attachment::find(message).await? {
|
||||
return Ok(Some(content));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
#[enum_dispatch]
|
||||
pub trait LogProvider {
|
||||
async fn find_match(&self, message: &Message) -> Option<String>;
|
||||
async fn fetch(&self, content: &str) -> Result<String>;
|
||||
}
|
||||
|
||||
fn get_first_capture(regex: &Lazy<Regex>, string: &str) -> Option<String> {
|
||||
regex
|
||||
.captures_iter(string)
|
||||
.filter_map(|c| c.get(1).map(|c| c.as_str().to_string()))
|
||||
.nth(1)
|
||||
}
|
||||
|
||||
#[enum_dispatch(LogProvider)]
|
||||
enum Provider {
|
||||
_0x0st,
|
||||
Attachment,
|
||||
Haste,
|
||||
MCLogs,
|
||||
PasteGG,
|
||||
PasteBin,
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
pub fn interator() -> Iter<'static, Provider> {
|
||||
static PROVIDERS: [Provider; 6] = [
|
||||
Provider::_0x0st(_0x0st),
|
||||
Provider::Attachment(Attachment),
|
||||
Provider::Haste(Haste),
|
||||
Provider::MCLogs(MCLogs),
|
||||
Provider::PasteBin(PasteBin),
|
||||
Provider::PasteGG(PasteGG),
|
||||
];
|
||||
PROVIDERS.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_log(message: &Message) -> Result<Option<String>> {
|
||||
let providers = Provider::interator();
|
||||
|
||||
for provider in providers {
|
||||
if let Some(found) = provider.find_match(message).await {
|
||||
let log = provider.fetch(&found).await?;
|
||||
return Ok(Some(log));
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -1,72 +1,36 @@
|
|||
use crate::api::REQWEST_CLIENT;
|
||||
use crate::api::paste_gg;
|
||||
|
||||
use eyre::{eyre, OptionExt, Result};
|
||||
use eyre::{OptionExt, Result};
|
||||
use log::trace;
|
||||
use once_cell::sync::Lazy;
|
||||
use poise::serenity_prelude::Message;
|
||||
use regex::Regex;
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const PASTE_GG: &str = "https://api.paste.gg/v1";
|
||||
const PASTES_ENDPOINT: &str = "/pastes";
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap());
|
||||
pub struct PasteGG;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct PasteResponse {
|
||||
status: String,
|
||||
result: Option<Vec<PasteResult>>,
|
||||
error: Option<String>,
|
||||
message: Option<String>,
|
||||
}
|
||||
impl super::LogProvider for PasteGG {
|
||||
async fn find_match(&self, message: &Message) -> Option<String> {
|
||||
static REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap());
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct PasteResult {
|
||||
id: String,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
visibility: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||
trace!("Checking if {content} is a paste.gg log");
|
||||
let Some(captures) = REGEX.captures(content) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let paste_id = &captures[1];
|
||||
let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files");
|
||||
|
||||
let resp = REQWEST_CLIENT
|
||||
.execute(REQWEST_CLIENT.get(&files_url).build()?)
|
||||
.await?;
|
||||
let status = resp.status();
|
||||
|
||||
if resp.status() != StatusCode::OK {
|
||||
return Err(eyre!(
|
||||
"Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!"
|
||||
));
|
||||
trace!("Checking if message {} is a paste.gg paste", message.id);
|
||||
super::get_first_capture(®EX, &message.content)
|
||||
}
|
||||
|
||||
let paste_files: PasteResponse = resp.json().await?;
|
||||
let file_id = &paste_files
|
||||
async fn fetch(&self, content: &str) -> Result<String> {
|
||||
let files = paste_gg::get_files(content).await?;
|
||||
let result = files
|
||||
.result
|
||||
.ok_or_eyre("Couldn't find any files associated with paste {paste_id}!")?[0]
|
||||
.id;
|
||||
.ok_or_eyre("Got an empty result from paste.gg!")?;
|
||||
|
||||
let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw");
|
||||
let file_id = result
|
||||
.iter()
|
||||
.map(|f| f.id.as_str())
|
||||
.nth(0)
|
||||
.ok_or_eyre("Couldn't get file id from empty paste.gg response!")?;
|
||||
|
||||
let resp = REQWEST_CLIENT
|
||||
.execute(REQWEST_CLIENT.get(&raw_url).build()?)
|
||||
.await?;
|
||||
let status = resp.status();
|
||||
let log = paste_gg::get_raw_file(content, file_id).await?;
|
||||
|
||||
if status != StatusCode::OK {
|
||||
return Err(eyre!(
|
||||
"Couldn't get file {file_id} from paste {paste_id} with status {status}!"
|
||||
));
|
||||
Ok(log)
|
||||
}
|
||||
|
||||
let text = resp.text().await?;
|
||||
|
||||
Ok(Some(text))
|
||||
}
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
use crate::api::REQWEST_CLIENT;
|
||||
use crate::utils;
|
||||
|
||||
use eyre::{eyre, Result};
|
||||
use eyre::Result;
|
||||
use log::trace;
|
||||
use once_cell::sync::Lazy;
|
||||
use poise::serenity_prelude::Message;
|
||||
use regex::Regex;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
static REGEX: Lazy<Regex> =
|
||||
const PASTEBIN: &str = "https://pastebin.com";
|
||||
const RAW: &str = "/raw";
|
||||
|
||||
pub struct PasteBin;
|
||||
|
||||
impl super::LogProvider for PasteBin {
|
||||
async fn find_match(&self, message: &Message) -> Option<String> {
|
||||
static REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap());
|
||||
|
||||
pub async fn find(content: &str) -> Result<Option<String>> {
|
||||
trace!("Checking if {content} is a pastebin log");
|
||||
let Some(captures) = REGEX.captures(content) else {
|
||||
return Ok(None);
|
||||
};
|
||||
trace!("Checking if message {} is a pastebin paste", message.id);
|
||||
super::get_first_capture(®EX, &message.content)
|
||||
}
|
||||
|
||||
let url = format!("https://pastebin.com/raw/{}", &captures[1]);
|
||||
let request = REQWEST_CLIENT.get(&url).build()?;
|
||||
let response = REQWEST_CLIENT.execute(request).await?;
|
||||
let status = response.status();
|
||||
async fn fetch(&self, content: &str) -> Result<String> {
|
||||
let url = format!("{PASTEBIN}{RAW}/{content}");
|
||||
let log = utils::text_from_url(&url).await?;
|
||||
|
||||
if let StatusCode::OK = status {
|
||||
Ok(Some(response.text().await?))
|
||||
} else {
|
||||
Err(eyre!("Failed to fetch paste from {url} with {status}"))
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,29 @@
|
|||
use crate::api::REQWEST_CLIENT;
|
||||
|
||||
use eyre::Result;
|
||||
use log::debug;
|
||||
use reqwest::Response;
|
||||
|
||||
pub mod resolve_message;
|
||||
|
||||
pub async fn get_url(url: &str) -> Result<Response> {
|
||||
debug!("Making request to {url}");
|
||||
let resp = REQWEST_CLIENT.get(url).send().await?;
|
||||
resp.error_for_status_ref()?;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn text_from_url(url: &str) -> Result<String> {
|
||||
let resp = get_url(url).await?;
|
||||
|
||||
let text = resp.text().await?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
pub async fn bytes_from_url(url: &str) -> Result<Vec<u8>> {
|
||||
let resp = get_url(url).await?;
|
||||
|
||||
let bytes = resp.bytes().await?;
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue