[no description]
much more reliable
bendn 4 weeks ago
parent fbdb0a6 · commit 75d4d38
-rw-r--r--rustfmt.toml7
-rw-r--r--src/main.rs309
-rw-r--r--src/tenor.rs3
3 files changed, 203 insertions, 116 deletions
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..13d8a30
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,7 @@
+max_width = 80
+format_strings = true
+group_imports = "StdExternalCrate"
+imports_granularity = "Module"
+match_arm_blocks = false
+style_edition = "2024"
+use_small_heuristics = "Max"
diff --git a/src/main.rs b/src/main.rs
index 6440132..77a5fe5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,18 +1,23 @@
-#![feature(impl_trait_in_fn_trait_return, try_blocks, try_blocks_heterogeneous)]
+#![allow(incomplete_features)]
+#![feature(
+ impl_trait_in_fn_trait_return,
+ try_blocks,
+ try_blocks_heterogeneous,
+ deref_patterns
+)]
mod tenor;
+use std::borrow::Cow;
+use std::mem::transmute;
+use std::sync::{Arc, LazyLock};
+
use anyhow::Result;
-use fimg::Image;
-use fimg::WritePng;
-use fimg::scale::Lanczos3;
-use fimg::scale::Nearest;
+use fimg::scale::{Lanczos3, Nearest};
+use fimg::{Image, WritePng};
use gif::Frame;
use regex::Regex;
use serenity::all::*;
-use std::sync::Arc;
-use std::sync::LazyLock;
-
#[tokio::main]
async fn main() {
spawn().await;
@@ -53,11 +58,32 @@ pub async fn spawn() {
FullEvent::Ready { .. } => {
println!("bot ready");
}
- FullEvent::Message { new_message } => {
- if let Err(error) = handle_message(c, new_message).await {
- eprintln!("{}", error);
- }
- }
+ FullEvent::Message { new_message } =>
+ match handle_message(c, new_message).await {
+ Ok(Some(x)) => {
+ let (a, b) = futures::future::join(
+ new_message.delete(c),
+ new_message.channel_id.send_message(
+ c,
+ CreateMessage::new()
+ .content(format!(
+ "<@{}>",
+ new_message.author.id
+ ))
+ .add_file(x),
+ ),
+ )
+ .await;
+ if let Err(e) = a {
+ eprintln!("FAIL {e}");
+ }
+ if let Err(e) = b {
+ eprintln!("FAIL {e}");
+ }
+ }
+ Ok(None) => {}
+ Err(e) => eprintln!("FAIL {e}"),
+ },
_ => {}
};
Ok(())
@@ -72,7 +98,7 @@ pub async fn spawn() {
},
..Default::default()
})
- .setup(|ctx, _ready, _| {
+ .setup(|_, _, _| {
Box::pin(async move {
println!("registered");
Ok(())
@@ -91,97 +117,70 @@ pub async fn spawn() {
.unwrap();
}
-async fn handle_message(c: &poise::serenity_prelude::Context, new_message: &Message) -> Result<()> {
- static TENOR: LazyLock<Regex> =
- LazyLock::new(|| Regex::new("https?://tenor.com/view/.+-[0-9]+").unwrap());
- static DISCORD: LazyLock<Regex> = LazyLock::new(|| {
- Regex::new(
- r"https:\/\/(?:cdn|media)\.discordapp\.(?:com|net)\/attachments\/\d+\/\d+\/(?<name>[\w-]+)\.(?<ext>\w+)(?:\?ex=[0-9a-f]+&is=[0-9a-f]+&hm=[0-9a-f]+)?(?:&animated=true)?(?:&width=\d+)?(?:&height=\d+)?&?",
- )
- .unwrap()
- });
-
+static TENOR: LazyLock<Regex> =
+ LazyLock::new(|| Regex::new("https?://tenor.com/view/.+-[0-9]+").unwrap());
+static DISCORD: LazyLock<Regex> = LazyLock::new(|| {
+ Regex::new(
+ r"https:\/\/(?:cdn|media)\.discordapp\.(?:com|net)\/attachments\/\d+\/\d+\/(?<name>[\w-]+)\.(?<ext>\w+)(?<tracking>\?[a-z]+=[0-9a-f]+(?:&[a-z]+=[0-9a-f]+)*)?",
+ )
+ .unwrap()
+});
+async fn handle_message(
+ c: &poise::serenity_prelude::Context,
+ new_message: &Message,
+) -> Result<Option<CreateAttachment>> {
if new_message.author.bot {
- return Ok(());
+ return Ok(None);
}
- if let Some(x) = TENOR.find(&new_message.content) {
- let x = x.as_str();
- let (r, e) = tenor::download_url(&x).await?;
- if r.media_formats.gif.dims[1] > 160 {
- let r = try bikeshed anyhow::Result<()> {
- let gif = e().await?;
- let (dec, vec) = regif(&gif)?;
- let vec = vec(dec)?;
-
- new_message
- .channel_id
- .send_message(
- c,
- CreateMessage::new()
- .files([CreateAttachment::bytes(vec, format!("g{}.gif", r.h1_title))])
- .content(format!("<@{}>", new_message.author.id)),
- )
- .await?;
- };
- new_message.delete(c).await?;
- r?;
- }
- } else if let Some(x) = DISCORD.captures(&new_message.content)
- && let Some(url) = x.get(0)
- && let Some(name) = x.name("name")
- && let Some(x) = x.name("ext")
- {
- let dat = reqwest::get(url.as_str()).await?.bytes().await?;
- if x.as_str() == "gif" {
- let (dec, vec) = regif(&dat)?;
- if dec.height() > 160 {
- new_message
- .channel_id
- .send_message(
- c,
- CreateMessage::new()
- .files([CreateAttachment::bytes(
- vec(dec)?,
- format!("{}.gif", name.as_str()),
- )])
- .content(format!("<@{}>", new_message.author.id)),
- )
- .await?;
- new_message.delete(c).await?;
- }
- } else {
- let i = if let Some(f) = image::ImageFormat::from_extension(x.as_str()) {
- image::load_from_memory_with_format(&*dat, f)?.to_rgba8()
- } else {
- let Ok(i) = image::load_from_memory(&*dat) else {
- return Ok(());
- };
- i.to_rgba8()
- };
- if i.height() < 160 {
- return Ok(());
+ let mut new_message = Cow::Borrowed(new_message);
+ if let Some(x) = DISCORD.captures(&new_message.content) {
+ if x.name("tracking").is_none() {
+ if new_message.embeds.is_empty() {
+ new_message = Cow::Owned(
+ c.http
+ .get_message(new_message.channel_id, new_message.id)
+ .await?,
+ );
+ if new_message.embeds.is_empty() {
+ tokio::time::sleep(tokio::time::Duration::from_secs(10))
+ .await;
+ new_message = Cow::Owned(
+ c.http
+ .get_message(new_message.channel_id, new_message.id)
+ .await?,
+ );
+ }
}
- let i = Image::<_, 4>::build(i.width(), i.height())
- .buf(i.into_vec())
- .boxed();
- let mut to = Vec::with_capacity(1 << 10);
- resize4(i).write(&mut to)?;
- new_message
- .channel_id
- .send_message(
- c,
- CreateMessage::new()
- .files([CreateAttachment::bytes(
- to,
- format!("{}.png", name.as_str()),
- )])
- .content(format!("<@{}>", new_message.author.id)),
- )
- .await?;
- new_message.delete(c).await?;
+ } else if let Some(url) = x.get(0)
+ && let Some(name) = x.name("name")
+ && let Some(ext) = x.name("ext")
+ {
+ return rediscord(url.into(), ext.into(), name.into()).await;
}
}
- Ok(())
+ if let Some(x) = retenor(&new_message.content).await? {
+ return Ok(Some(x));
+ }
+
+ for embed in new_message.embeds.clone() {
+ let Some(u) = embed.url else { continue };
+ if let Some(x) = embed.provider
+ && let Some("Tenor") = x.name
+ && let Some(x) = retenor(&u).await?
+ {
+ return Ok(Some(x));
+ } else if let Some(x) =
+ embed.image.or(embed.thumbnail.map(|x| unsafe { transmute(x) }))
+ && x.height.is_none_or(|x| x > 160)
+ && let Some(cap) = DISCORD.captures(&x.url)
+ && let Some(name) = cap.name("name")
+ && let Some(ext) = cap.name("ext")
+ {
+ return rediscord(&x.url, ext.as_str(), name.as_str()).await;
+ }
+ }
+
+ Ok(None)
}
fn regif(
@@ -199,30 +198,46 @@ fn regif(
println!("resizing gif...");
let now = std::time::Instant::now();
let p = decoder.global_palette();
- let nw = ((decoder.width() as f32 / decoder.height() as f32) * 152.0).round() as u16;
- let mut encoder = gif::Encoder::new(&mut vec, nw, 152, p.unwrap_or(&[])).unwrap();
+ let nw = ((decoder.width() as f32 / decoder.height() as f32) * 152.0)
+ .round() as u16;
+ let mut encoder =
+ gif::Encoder::new(&mut vec, nw, 152, p.unwrap_or(&[])).unwrap();
encoder.set_repeat(decoder.repeat())?;
- let bg = decoder.bg_color().unwrap_or(0);
- let mut pf = Image::<Vec<u8>, 1>::build(decoder.width() as _, decoder.height() as _).buf(
- vec![bg as u8; decoder.width() as usize * decoder.height() as usize],
- );
+ let bg = decoder.bg_color().map(|x| x as u8); // fuck bg color
+ let mut pf = Image::<Vec<u8>, 1>::build(
+ decoder.width() as _,
+ decoder.height() as _,
+ )
+ .buf(vec![
+ bg.unwrap_or(0);
+ decoder.width() as usize * decoder.height() as usize
+ ]);
// let mut first = true;
for frame in decoder.into_iter() {
let frame = frame?;
- // if take(&mut first) && let Some(x) = frame.transparent{
- // pf = Image::<Vec<u8>, 1>::build(pf.width(), pf.height())
- // .buf(vec![x as u8; pf.width() as usize * pf.height() as usize]);
+ // if std::mem::take(&mut first)
+ // && let Some(x) = frame.transparent
+ // {
+ // pf = Image::<Vec<u8>, 1>::build(pf.width(), pf.height())
+ // .buf(vec![
+ // x as u8;
+ // pf.width() as usize * pf.height() as usize
+ // ]);
// }
let _pf = pf.clone();
match frame.dispose {
gif::DisposalMethod::Background => {
pf = Image::<Vec<u8>, 1>::build(pf.width(), pf.height())
- .buf(vec![bg as u8; pf.width() as usize * pf.height() as usize]);
+ .buf(vec![
+ frame.transparent.or(bg).unwrap_or(0);
+ pf.width() as usize * pf.height() as usize
+ ]);
}
_ => {}
}
- let x_ = Image::<_, 1>::build(frame.width as _, frame.height as _).buf(&*frame.buffer);
+ let x_ = Image::<_, 1>::build(frame.width as _, frame.height as _)
+ .buf(&*frame.buffer);
pf.as_mut().clipping_overlay_at(
&x_,
@@ -256,9 +271,10 @@ fn resize4(mut x: Image<Box<[u8]>, 4>) -> Image<Box<[u8]>, 4> {
x.scale::<Lanczos3>(nw, 152)
}
-impl<T: AsMut<[u8]> + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAtClipping<Image<U, 1>> for Image<T, 1> {
+impl<T: AsMut<[u8]> + AsRef<[u8]>, U: AsRef<[u8]>>
+ OverlayAtClipping<Image<U, 1>> for Image<T, 1>
+{
#[inline]
- #[cfg_attr(debug_assertions, track_caller)]
fn clipping_overlay_at(
&mut self,
with: &Image<U, 1>,
@@ -268,7 +284,6 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAtClipping<Image<U, 1>
) -> &mut Self {
for j in 0..with.height() {
for i in 0..with.width() {
- // SAFETY: i, j is in bounds.
if let Some([their_px]) = with.get_pixel(i, j)
&& let Some([our_px]) = self.get_pixel_mut(i + x, j + y)
&& t != Some(*their_px)
@@ -282,5 +297,69 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAtClipping<Image<U, 1>
}
trait OverlayAtClipping<W> {
- fn clipping_overlay_at(&mut self, with: &W, x: u32, y: u32, t: Option<u8>) -> &mut Self;
+ fn clipping_overlay_at(
+ &mut self,
+ with: &W,
+ x: u32,
+ y: u32,
+ t: Option<u8>,
+ ) -> &mut Self;
+}
+
+async fn retenor(u: &str) -> anyhow::Result<Option<CreateAttachment>> {
+ if let Some(x) = TENOR.captures(&u) {
+ let (r, e) = tenor::download_url(&x.get(0).unwrap().as_str()).await?;
+ if r.media_formats.gif.dims[1] > 160 {
+ let r = try bikeshed anyhow::Result<CreateAttachment> {
+ let gif = e().await?;
+ let (dec, vec) = regif(&gif)?;
+ let vec = vec(dec)?;
+ CreateAttachment::bytes(vec, format!("g{}.gif", r.h1_title))
+ };
+ return r.map(Some);
+ }
+ }
+ Ok(None)
+}
+
+async fn rediscord(
+ u: &str,
+ ext: &str,
+ name: &str,
+) -> anyhow::Result<Option<CreateAttachment>> {
+ println!("GET {u:?}");
+ let rq = reqwest::get(u).await?.error_for_status()?;
+ let dat = rq.bytes().await?;
+ if &*dat == b"This content is no longer available" {
+ eprintln!("FAIL funny gif");
+ return Ok(None);
+ }
+ if ext == "gif" {
+ let (dec, vec) = regif(&dat)?;
+ if dec.height() > 160 {
+ return Ok(Some(CreateAttachment::bytes(
+ vec(dec)?,
+ format!("{name}.gif"),
+ )));
+ }
+ } else {
+ let i = if let Some(f) = image::ImageFormat::from_extension(ext) {
+ image::load_from_memory_with_format(&*dat, f)?.to_rgba8()
+ } else {
+ let Ok(i) = image::load_from_memory(&*dat) else {
+ return Ok(None);
+ };
+ i.to_rgba8()
+ };
+ if i.height() < 160 {
+ return Ok(None);
+ }
+ let i = Image::<_, 4>::build(i.width(), i.height())
+ .buf(i.into_vec())
+ .boxed();
+ let mut to = Vec::with_capacity(1 << 10);
+ resize4(i).write(&mut to)?;
+ return Ok(Some(CreateAttachment::bytes(to, format!("{name}.png"))));
+ }
+ Ok(None)
}
diff --git a/src/tenor.rs b/src/tenor.rs
index 0cf79af..aad5933 100644
--- a/src/tenor.rs
+++ b/src/tenor.rs
@@ -136,7 +136,8 @@ pub async fn download_url(
PurpleResult,
impl FnOnce() -> impl Future<Output = anyhow::Result<Vec<u8>>>,
)> {
- let response = get(dbg!(url)).await.context("h")?;
+ println!("GET {url}");
+ let response = get(url).await.context("h")?;
let html_content = response.text().await.context("ah")?;
let document = Html::parse_document(&html_content);
let selector = Selector::parse("#store-cache").unwrap();