feat: embed building
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.showUnlinkedFileNotification": false
|
||||||
|
}
|
1286
Cargo.lock
generated
1286
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,9 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ecstasy = "0.3.4"
|
log = "0.4.20"
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
|
reqwest = { version = "0.11.24", features = ["blocking", "json"] }
|
||||||
serenity = "0.12.0"
|
serenity = "0.12.0"
|
||||||
|
simple_logger = { version = "4.3.3", features = ["threads"] }
|
||||||
tokio = { version = "1.36.0", features = ["rt-multi-thread"] }
|
tokio = { version = "1.36.0", features = ["rt-multi-thread"] }
|
||||||
|
78
src/apis/api.rs
Normal file
78
src/apis/api.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use super::gelbooru::GelbooruApi;
|
||||||
|
use poise::serenity_prelude as serenity;
|
||||||
|
use ::serenity::all::Timestamp;
|
||||||
|
|
||||||
|
|
||||||
|
pub trait SauceApi {
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
fn color(&self) -> (u8, u8, u8);
|
||||||
|
fn logo(&self) -> String;
|
||||||
|
fn into_sauce(&self) -> SauceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SauceData {
|
||||||
|
pub title: String, // Embed (title)
|
||||||
|
pub url: String, // Embed (Post link)
|
||||||
|
pub image_url: String, // Embed (image)
|
||||||
|
pub source_url: String, // Embed (Author link)
|
||||||
|
pub rating: String, // Embed (footer)
|
||||||
|
pub tags: Option<Vec<String>>, // Embed (Tags formatted in 3 columns)
|
||||||
|
pub characters: Option<Vec<String>>, // Embed
|
||||||
|
pub artists: Option<Vec<String>>, // Embed
|
||||||
|
pub parodies: Option<Vec<String>>, // Embed
|
||||||
|
pub color: (u8, u8, u8), // Embed (color)
|
||||||
|
pub timestamp: Timestamp, // Embed (timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sauce {
|
||||||
|
pub api: Box<dyn SauceApi>,
|
||||||
|
pub data: SauceData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sauce {
|
||||||
|
pub fn new(url: &str) -> Self {
|
||||||
|
let api = Box::new(GelbooruApi::new());
|
||||||
|
let data = api.into_sauce();
|
||||||
|
Self { api, data }
|
||||||
|
} // TODO: this is most likely async
|
||||||
|
|
||||||
|
pub fn to_embed(&self) -> serenity::CreateEmbed {
|
||||||
|
let author = serenity::CreateEmbedAuthor::new("Author")
|
||||||
|
.name(self.data.artists.clone().unwrap().join(" & "))
|
||||||
|
.url(self.data.source_url.clone());
|
||||||
|
|
||||||
|
// divide tags into 3 columns
|
||||||
|
let tags = self.data.tags.clone().unwrap(); // TODO: filter out non-image descriptive tags
|
||||||
|
let column_1 = Self::tag_column(tags.clone(), 0);
|
||||||
|
let column_2 = Self::tag_column(tags.clone(), 1);
|
||||||
|
let column_3 = Self::tag_column(tags.clone(), 2);
|
||||||
|
|
||||||
|
let characters = self.data.characters.clone().unwrap().join("\n");
|
||||||
|
|
||||||
|
let footer = serenity::CreateEmbedFooter::new(self.data.rating.clone());
|
||||||
|
|
||||||
|
serenity::CreateEmbed::new()
|
||||||
|
.color(self.data.color.clone())
|
||||||
|
.author(author)
|
||||||
|
.title(self.data.title.clone()) // TODO: For posts without a title, use the first character tag in a short sentence that describes the image depending on the rating (e.g. "Link, Zelda and Riju" for safe posts, "Link and Zelda (explicit)" for explicit posts)
|
||||||
|
.url(self.data.url.clone())
|
||||||
|
.image(self.data.image_url.clone()) // TODO: We can't use that for videos, we need to make a button to link to the video "View video" and show the thumbnail
|
||||||
|
.fields(vec![
|
||||||
|
("Tags", column_1, true),
|
||||||
|
("", column_2, true),
|
||||||
|
("", column_3, true),
|
||||||
|
("Characters", characters, false)
|
||||||
|
])
|
||||||
|
.footer(footer)
|
||||||
|
.timestamp(self.data.timestamp.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag_column(tags: Vec<String>, column: i32) -> String {
|
||||||
|
tags.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| i % 3 == column as usize)
|
||||||
|
.map(|(_, tag)| tag.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
}
|
119
src/apis/gelbooru.rs
Normal file
119
src/apis/gelbooru.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
use crate::apis::api::SauceApi;
|
||||||
|
use crate::apis::api::SauceData;
|
||||||
|
use serenity::all::Timestamp;
|
||||||
|
|
||||||
|
pub struct GelbooruApi;
|
||||||
|
impl GelbooruApi {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boxed(self) -> Box<dyn SauceApi> {
|
||||||
|
Box::new(Self::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SauceApi for GelbooruApi {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Gelbooru"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(&self) -> (u8, u8, u8) {
|
||||||
|
(37, 150, 190) // Gelbooru blue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logo(&self) -> String {
|
||||||
|
String::from("https://gelbooru.com/favicon.ico") // TODO: find high-res logo
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_sauce(&self) -> SauceData {
|
||||||
|
SauceData {
|
||||||
|
title: String::from("Gelbooru"),
|
||||||
|
url: String::from("https://gelbooru.com/"),
|
||||||
|
image_url: String::from("https://gelbooru.com/"),
|
||||||
|
source_url: String::from("https://gelbooru.com/"),
|
||||||
|
rating: String::from("Rating"),
|
||||||
|
tags: None,
|
||||||
|
characters: None,
|
||||||
|
artists: None,
|
||||||
|
parodies: None,
|
||||||
|
color: self.color(),
|
||||||
|
timestamp: Timestamp::from_millis(0 as i64).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum GelbooruRating {
|
||||||
|
Safe,
|
||||||
|
Questionable,
|
||||||
|
Explicit,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum GelbooruPostStatus {
|
||||||
|
Active,
|
||||||
|
Flagged,
|
||||||
|
Pending,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum GelbooruTagType {
|
||||||
|
General,
|
||||||
|
Artist,
|
||||||
|
Faults,
|
||||||
|
Copyright,
|
||||||
|
Character,
|
||||||
|
Meta,
|
||||||
|
Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GelbooruTag {
|
||||||
|
pub id: i64,
|
||||||
|
pub name: String,
|
||||||
|
pub count: i64,
|
||||||
|
pub type_: GelbooruTagType,
|
||||||
|
pub ambiguous: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GelbooruTag {
|
||||||
|
pub fn new(id: i64, name: String, count: i64, type_: GelbooruTagType, ambiguous: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
count,
|
||||||
|
type_,
|
||||||
|
ambiguous,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GelbooruPost {
|
||||||
|
pub id: i64,
|
||||||
|
pub created_at: String,
|
||||||
|
pub score: i64,
|
||||||
|
pub width: i64,
|
||||||
|
pub height: i64,
|
||||||
|
pub md5: String,
|
||||||
|
//directory
|
||||||
|
pub file_name: String, //image field
|
||||||
|
pub rating: GelbooruRating,
|
||||||
|
pub source_url: String,
|
||||||
|
pub change: Option<i64>,
|
||||||
|
pub owner: String,
|
||||||
|
pub creator_id: i64,
|
||||||
|
//parent_id
|
||||||
|
//sample
|
||||||
|
pub preview_height: i64,
|
||||||
|
pub preview_width: i64,
|
||||||
|
pub tags: Vec<GelbooruTag>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub has_notes: bool,
|
||||||
|
pub has_comments: bool,
|
||||||
|
pub file_url: String,
|
||||||
|
pub preview_url: String,
|
||||||
|
pub sample_url: Option<String>,
|
||||||
|
pub sample_height: Option<i64>,
|
||||||
|
pub sample_width: Option<i64>,
|
||||||
|
pub status: GelbooruPostStatus,
|
||||||
|
pub post_locked: bool,
|
||||||
|
//has_children
|
||||||
|
}
|
2
src/apis/mod.rs
Normal file
2
src/apis/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod api;
|
||||||
|
pub mod gelbooru;
|
@@ -10,7 +10,7 @@ pub async fn sc_event_handler(
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match event {
|
match event {
|
||||||
serenity::FullEvent::Ready { .. }=> {
|
serenity::FullEvent::Ready { .. }=> {
|
||||||
println!("Bot is ready!");
|
log::info!("Connected to Discord");
|
||||||
}
|
}
|
||||||
serenity::FullEvent::Message { new_message } => message_handler(ctx, new_message, data)?,
|
serenity::FullEvent::Message { new_message } => message_handler(ctx, new_message, data)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -23,6 +23,6 @@ fn message_handler(
|
|||||||
msg: &serenity::Message,
|
msg: &serenity::Message,
|
||||||
_data: &Data,
|
_data: &Data,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
println!("Received message: {:?}", msg.content);
|
log::info!("Received message: {:?}", msg.content);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
12
src/main.rs
12
src/main.rs
@@ -1,10 +1,13 @@
|
|||||||
|
use log::LevelFilter;
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
|
use simple_logger::SimpleLogger;
|
||||||
|
|
||||||
pub struct Data {} // User data, which is stored and accessible in all command invocations
|
pub struct Data {} // User data, which is stored and accessible in all command invocations
|
||||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
pub mod event_handlers;
|
pub mod event_handlers;
|
||||||
|
pub mod apis;
|
||||||
|
|
||||||
/// Ping command with latency measurement
|
/// Ping command with latency measurement
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
@@ -18,6 +21,11 @@ async fn ping(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
SimpleLogger::new()
|
||||||
|
.with_level(LevelFilter::Off)
|
||||||
|
.with_module_level("sauce_connoisseur", LevelFilter::Info)
|
||||||
|
.init()
|
||||||
|
.unwrap();
|
||||||
let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN");
|
let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN");
|
||||||
let intents =
|
let intents =
|
||||||
serenity::GatewayIntents::non_privileged().union(serenity::GatewayIntents::MESSAGE_CONTENT);
|
serenity::GatewayIntents::non_privileged().union(serenity::GatewayIntents::MESSAGE_CONTENT);
|
||||||
@@ -30,7 +38,9 @@ async fn main() {
|
|||||||
},
|
},
|
||||||
commands: vec![ping()],
|
commands: vec![ping()],
|
||||||
event_handler: |ctx, event, framework, data| {
|
event_handler: |ctx, event, framework, data| {
|
||||||
Box::pin(event_handlers::sc_event_handler(ctx, event, framework, data))
|
Box::pin(event_handlers::sc_event_handler(
|
||||||
|
ctx, event, framework, data,
|
||||||
|
))
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user