Skip to content

Commit

Permalink
fix(twitter): split into two messages to embed everything
Browse files Browse the repository at this point in the history
  • Loading branch information
uku3lig committed Dec 26, 2024
1 parent 9287707 commit 2333877
Showing 1 changed file with 86 additions and 69 deletions.
155 changes: 86 additions & 69 deletions src/twitter.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use anyhow::Result;
use axum::extract::Json;
use regex::Regex;
use reqwest::header::{ACCEPT, AUTHORIZATION};
use reqwest::{
header::{ACCEPT, AUTHORIZATION, LOCATION},
redirect::Policy,
Client,
};
use serde::Deserialize;
use serde_json::json;

use crate::RouteResponse;

#[derive(Deserialize)]
pub struct ProcessData {
tweet_url: String,
Expand All @@ -24,49 +26,78 @@ pub struct ProcessData {
enum CobaltResponse {
Error,
Picker { picker: Vec<PickerObj> },
Redirect,
Tunnel,
Redirect { url: String },
Tunnel { url: String },
}

#[derive(Debug, Deserialize)]
struct PickerObj {
url: String,
}

pub async fn webhook(Json(data): Json<ProcessData>) -> RouteResponse<()> {
struct Webhook {
url: String,
name: String,
avatar_url: String,
}

impl Webhook {
async fn send(&self, message: &str) {
let params = json!({
"content": message,
"username": self.name,
"avatar_url": self.avatar_url
});

let res = crate::CLIENT
.post(&self.url)
.json(&params)
.send()
.await
.and_then(reqwest::Response::error_for_status);

if let Err(e) = res {
tracing::warn!("Could not send webhook message: {e}");
}
}
}

pub async fn webhook(Json(data): Json<ProcessData>) {
tokio::spawn(async move {
let mut message = format!("New tweet by {}: ", data.tweet_author);
let mut message = format!("New tweet by {}: {}", data.tweet_author, data.tweet_url);

let cobalt = fetch_cobalt(&data.cobalt_url, &data.cobalt_key, &data.tweet_url).await;
let cobalt = match cobalt {
let webhook = Webhook {
url: data.webhook_url,
name: data.tweet_author,
avatar_url: data.webhook_avatar,
};

let tco_urls = match resolve_tco_urls(&data.tweet_body).await {
Ok(res) => res,
Err(e) => {
tracing::warn!("Could not fetch cobalt data for {}: {e}", &data.tweet_url);
tracing::warn!("Could not fetch t.co urls for {}: {e}", &data.tweet_url);
return;
}
};

message.push_str(&cobalt);
message.push_str(&resolve_tco_urls(&data.tweet_body));

let webhook_res = send_webhook(
&data.webhook_url,
&message,
&data.tweet_author,
&data.webhook_avatar,
)
.await;
message.push_str(&tco_urls);
webhook.send(&message).await;

if let Err(e) = webhook_res {
tracing::warn!("Could not send webhook message: {e}");
}
let attachments = fetch_cobalt(&data.cobalt_url, &data.cobalt_key, &data.tweet_url).await;
match attachments {
Ok(Some(attachments)) => webhook.send(&attachments).await,
Ok(None) => {}
Err(e) => tracing::warn!("Could not fetch cobalt data for {}: {e}", &data.tweet_url),
};
});

Ok(())
}

/// fetch attachment urls, or replace link with fxtwitter if there are none
async fn fetch_cobalt(cobalt_url: &str, cobalt_key: &str, tweet_url: &str) -> Result<String> {
/// fetch attachment urls
async fn fetch_cobalt(
cobalt_url: &str,
cobalt_key: &str,
tweet_url: &str,
) -> Result<Option<String>> {
let request = crate::CLIENT
.post(cobalt_url)
.json(&json!({ "url": tweet_url }))
Expand All @@ -80,57 +111,43 @@ async fn fetch_cobalt(cobalt_url: &str, cobalt_key: &str, tweet_url: &str) -> Re
.json::<CobaltResponse>()
.await?;

let out = match response {
CobaltResponse::Picker { picker } => {
let urls = picker
.iter()
.enumerate()
.map(|(idx, p)| format!("[{}]({})", idx + 1, p.url))
.collect::<Vec<_>>();

format!("{tweet_url} (attachments: {})", urls.join(" "))
}
_ => tweet_url.replace("x.com", "fxtwitter.com"),
let urls = match response {
CobaltResponse::Redirect { url } | CobaltResponse::Tunnel { url } => vec![url],
CobaltResponse::Picker { picker } => picker.into_iter().map(|p| p.url).collect(),
CobaltResponse::Error => return Ok(None),
};

Ok(out)
let formatted = urls
.into_iter()
.enumerate()
.map(|(idx, url)| format!("[{}]({url})", idx + 1))
.collect::<Vec<_>>();

Ok(Some(format!("attachments: {}", formatted.join(" "))))
}

fn resolve_tco_urls(tweet_body: &str) -> String {
async fn resolve_tco_urls(tweet_body: &str) -> Result<String> {
let tco_regex = Regex::new(r"https://t\.co/\S+").unwrap();
let tco_client = Client::builder().redirect(Policy::none()).build().unwrap();

let tco_urls = tco_regex
.captures_iter(tweet_body)
.map(|c| c.extract())
.map(|(url, [])| url)
.enumerate()
.map(|(idx, url)| format!("[{}]({url})", idx + 1))
.collect::<Vec<_>>();

if tco_urls.is_empty() {
String::new()
} else {
format!(" (urls: {})", tco_urls.join(" "))
}
}
.map(|(url, [])| url);

async fn send_webhook(
webhook_url: &str,
message: &str,
webhook_name: &str,
webhook_avatar: &str,
) -> Result<()> {
let params = json!({
"content": message,
"username": webhook_name,
"avatar_url": webhook_avatar
});
let mut results = Vec::new();
for (idx, url) in tco_urls.enumerate() {
let response = tco_client.get(url).send().await?;

crate::CLIENT
.post(webhook_url)
.json(&params)
.send()
.await?
.error_for_status()?;
if let Some(r_url) = response.headers().get(LOCATION) {
let value = r_url.to_str()?;
results.push(format!("[{}]({value})", idx + 1));
}
}

Ok(())
if results.is_empty() {
Ok(String::new())
} else {
Ok(format!(" (urls: {})", results.join(" ")))
}
}

0 comments on commit 2333877

Please sign in to comment.