inital (feature: post likes)
This commit is contained in:
commit
3bdb2ebef5
24 files changed
+3004
No files matched your search
Generated
+1443
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "extgk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
js-sys = "0.3.70"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.6"
|
||||
urlencoding = "2.1"
|
||||
|
||||
# rand only works for WASM if JS support is enabled in a dependency
|
||||
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
|
||||
rand = "0.8"
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. It is slower than the default
|
||||
# allocator, however.
|
||||
wee_alloc = { version = "0.4.5", optional = true }
|
||||
reqwest = { version = "0.12.7", features = ["multipart"] }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.70"
|
||||
features = [
|
||||
"console",
|
||||
"Blob",
|
||||
"WorkerGlobalScope",
|
||||
"Window",
|
||||
"KeyEvent",
|
||||
"Event",
|
||||
'Headers',
|
||||
'Request',
|
||||
'RequestInit',
|
||||
'RequestMode',
|
||||
'Response',
|
||||
'Storage',
|
||||
]
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
#![feature(let_chains)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::{ Deserialize, Serialize };
|
||||
use serde_json::json;
|
||||
use wasm_bindgen::{ prelude::wasm_bindgen, JsValue };
|
||||
|
||||
use reqwest::{ Client, Url, multipart };
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace=console)]
|
||||
fn log (s: &str);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch, js_namespace = ["chrome", "storage", "local"], js_name = set)]
|
||||
async fn storage_set (data: &JsValue) -> Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch, js_namespace = ["chrome", "storage", "local"], js_name = get)]
|
||||
async fn storage_get (data: &JsValue) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn post (link: &str) {
|
||||
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct LocalStorage {
|
||||
pub token: String,
|
||||
pub group: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub header: String,
|
||||
#[serde(default = "default_domain")]
|
||||
pub domain: String,
|
||||
#[serde(default)]
|
||||
pub blacklist: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub delay: u8,
|
||||
|
||||
#[serde(default)]
|
||||
pub posts: bool,
|
||||
#[serde(default)]
|
||||
pub likes: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub private: bool,
|
||||
#[serde(default)]
|
||||
pub screenshot: bool,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct LikedTweet {
|
||||
pub id: String,
|
||||
pub by: TwitterUser,
|
||||
pub me: Option<TwitterUser>,
|
||||
|
||||
#[serde(default)]
|
||||
pub is_private: bool,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TwitterUser {
|
||||
pub name: Option<String>,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl TwitterUser {
|
||||
fn format (&self) -> String {
|
||||
match &self.name {
|
||||
Some(name) => format!("{name} ({})", self.username.trim_start_matches("@")),
|
||||
None => format!("{}", self.username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_domain () -> String {
|
||||
String::from("x.com")
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub fn escape (mut line: String) -> String {
|
||||
const chars: [char; 21] = [ '\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '<', '&', '#', '+', '-', '=', '|', '{', '}', '.', '!' ];
|
||||
for char in chars {
|
||||
line = line.replace(char, &format!("\\{}", char));
|
||||
}
|
||||
line
|
||||
}
|
||||
|
||||
pub fn format_telegram_api_url (token: &str) -> String {
|
||||
format!("https://api.telegram.org/bot{token}")
|
||||
}
|
||||
|
||||
pub fn format_tweet_url (domain: &str, username: &str, id: &str) -> String {
|
||||
format!("https://{domain}/{username}/status/{id}")
|
||||
}
|
||||
|
||||
pub fn format_caption (url: &str, header: &str, by: &TwitterUser, me: &Option<TwitterUser>) -> String {
|
||||
let by = by.format();
|
||||
let me = me.as_ref().map(TwitterUser::format);
|
||||
|
||||
// @TODO: Edge case of where header is set but we can't retrieve Me
|
||||
|
||||
let header = escape(header
|
||||
.replace("{me}", &me.unwrap_or_default())
|
||||
.replace("{notme}", &by));
|
||||
|
||||
format!("{}{header}{}{}",
|
||||
if !header.is_empty() { "`" } else { Default::default() },
|
||||
if !header.is_empty() { "`\r\n\r\n" } else { Default::default() },
|
||||
escape(url.to_owned())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! log {
|
||||
($data: expr) => {
|
||||
log(&format!("{:?}", $data))
|
||||
};
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn like (tweet: JsValue, image: Option<Vec<u8>>) -> Option<u8> {
|
||||
let store = storage_get(&JsValue::null()).await.ok()?;
|
||||
let store: LocalStorage = serde_wasm_bindgen::from_value(store).ok()?;
|
||||
|
||||
if !store.likes { return None; }
|
||||
|
||||
let blacklist = store.blacklist.split(",").map(str::trim).collect::<HashSet<&str>>();
|
||||
|
||||
let tweet: LikedTweet = serde_wasm_bindgen::from_value(tweet).ok()?;
|
||||
|
||||
if !store.private && tweet.is_private { return None; }
|
||||
if blacklist.contains(tweet.by.username.as_str()) { return None; }
|
||||
|
||||
let domain = if store.domain.is_empty() { default_domain() } else { store.domain };
|
||||
let group = store.group;
|
||||
let token = store.token;
|
||||
let url = format_tweet_url(&domain, &tweet.by.username, &tweet.id);
|
||||
|
||||
let caption = format_caption(&url, &store.header, &tweet.by, &tweet.me);
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let link_preview_options = json!({
|
||||
"is_disabled": false,
|
||||
"url": url,
|
||||
"prefer_large_media": true,
|
||||
"show_above_text": false,
|
||||
}).to_string();
|
||||
|
||||
let (form, endpoint) = if let Some(image) = image && store.screenshot {
|
||||
let endpoint = format!("{}/sendPhoto", format_telegram_api_url(&token));
|
||||
let part = multipart::Part::bytes(image).file_name("test.png").mime_str("image/png").ok()?;
|
||||
|
||||
|
||||
(reqwest::multipart::Form::new()
|
||||
.text("chat_id", group)
|
||||
.text("caption", caption)
|
||||
.text("parse_mode", "MarkdownV2")
|
||||
.text("link_preview_options", link_preview_options)
|
||||
.part("photo", part),
|
||||
endpoint)
|
||||
|
||||
} else {
|
||||
let endpoint = format!("{}/sendMessage", format_telegram_api_url(&token));
|
||||
|
||||
(reqwest::multipart::Form::new()
|
||||
.text("chat_id", group)
|
||||
.text("link_preview_options", link_preview_options)
|
||||
.text("parse_mode", "MarkdownV2")
|
||||
.text("text", caption),
|
||||
endpoint)
|
||||
};
|
||||
|
||||
let endpoint = Url::parse(&endpoint).ok()?;
|
||||
let _resp = client
|
||||
.post(endpoint)
|
||||
.multipart(form)
|
||||
.send().await.ok()?;
|
||||
|
||||
log!(_resp);
|
||||
|
||||
Some(0)
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user