From a11bc181431e2abb17578ac8210bdc49cfe10c67 Mon Sep 17 00:00:00 2001 From: pandadev <70103896+0PandaDEV@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:22:21 +0200 Subject: [PATCH] fixed favicon support --- app.vue | 43 ++++++-- src-tauri/Cargo.lock | 217 ++++++++++++++++++++++++++++++++++++- src-tauri/Cargo.toml | 6 +- src-tauri/src/clipboard.rs | 96 ++++++++++++---- src-tauri/src/database.rs | 3 +- src-tauri/src/main.rs | 14 +-- 6 files changed, 336 insertions(+), 43 deletions(-) diff --git a/app.vue b/app.vue index 7d0b524..560c294 100644 --- a/app.vue +++ b/app.vue @@ -32,7 +32,7 @@ :class="['result clothoid-corner', { 'selected': isSelected(groupIndex, index) }]" @click="selectItem(groupIndex, index)" :ref="el => { if (isSelected(groupIndex, index)) selectedElement = el }"> - Favicon + Favicon Image {{ truncateContent(item.content) }} @@ -178,8 +178,12 @@ const truncateContent = (content) => { }; const isUrl = (str) => { - const urlPattern = /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/; - return urlPattern.test(str); + try { + new URL(str); + return true; + } catch { + return false; + } }; const isYoutubeWatchUrl = (url) => { @@ -191,19 +195,32 @@ const getYoutubeThumbnail = (url) => { return `https://img.youtube.com/vi/${videoId}/0.jpg`; }; -const getFavicon = (url) => { - const domain = url.replace(/^(https?:\/\/)?(www\.)?/, '').split('/')[0]; - return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`; +const getFaviconFromDb = (favicon) => { + return `data:image/png;base64,${favicon}`; }; const refreshHistory = async () => { - const rawHistory = await db.value.select('SELECT * FROM history ORDER BY timestamp DESC'); - history.value = rawHistory.map(item => { + history.value = []; + await loadMoreHistory(); +}; + +const loadMoreHistory = async () => { + const lastTimestamp = history.value.length > 0 ? history.value[history.value.length - 1].timestamp : '9999-12-31T23:59:59Z'; + const batchSize = 100; + + const rawHistory = await db.value.select( + 'SELECT * FROM history WHERE timestamp < ? ORDER BY timestamp DESC LIMIT ?', + [lastTimestamp, batchSize] + ); + + const newItems = rawHistory.map(item => { if (item.type === 'image' && !item.content.startsWith('data:image')) { return { ...item, content: `data:image/png;base64,${item.content}` }; } return item; }); + + history.value = [...history.value, ...newItems]; }; onMounted(async () => { @@ -234,6 +251,13 @@ onMounted(async () => { await listen('tauri://blur', hideApp); await listen('tauri://focus', focusSearchInput); focusSearchInput(); + + const resultsElement = resultsContainer.value.$el; + resultsElement.addEventListener('scroll', () => { + if (resultsElement.scrollTop + resultsElement.clientHeight >= resultsElement.scrollHeight - 100) { + loadMoreHistory(); + } + }); }); const hideApp = async () => { @@ -242,6 +266,7 @@ const hideApp = async () => { }; const showApp = async () => { + history.value = []; await refreshHistory(); await app.show(); await window.getCurrent().show(); @@ -295,4 +320,4 @@ watch([selectedGroupIndex, selectedItemIndex], scrollToSelectedItem); + \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 6400856..cebe294 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -160,6 +160,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "auto-launch" version = "0.5.0" @@ -987,6 +993,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1594,6 +1609,25 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -1746,6 +1780,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1756,6 +1791,39 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -2537,6 +2605,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -3146,12 +3223,15 @@ version = "0.1.0" dependencies = [ "arboard", "base64 0.22.1", - "clipboard-win", "image 0.25.1", + "log", "rand 0.8.5", "rdev", + "regex", + "reqwest", "serde", "serde_json", + "simplelog", "sqlx", "tauri", "tauri-build", @@ -3162,6 +3242,7 @@ dependencies = [ "tauri-plugin-sql", "tauri-plugin-window-state", "tokio", + "url", ] [[package]] @@ -3448,25 +3529,34 @@ checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", + "tokio-native-tls", "tokio-util", "tower-service", "url", @@ -3486,6 +3576,21 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rsa" version = "0.9.6" @@ -3534,6 +3639,46 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -3866,6 +4011,17 @@ dependencies = [ "quote", ] +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -4286,6 +4442,27 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys 0.8.6", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -4693,6 +4870,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thin-slice" version = "0.1.1" @@ -4748,7 +4934,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa 1.0.11", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -4816,6 +5004,27 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -5106,6 +5315,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7b06e2d..fd6f731 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,4 +26,8 @@ rand = "0.8" base64 = "0.22.1" arboard = "3.4.0" image = "0.25.1" -clipboard-win = "5.3.1" +reqwest = { version = "0.12.5", features = ["blocking"] } +url = "2.5.2" +log = "0.4" +simplelog = "0.12.2" +regex = "1" \ No newline at end of file diff --git a/src-tauri/src/clipboard.rs b/src-tauri/src/clipboard.rs index ca57960..a2df6ef 100644 --- a/src-tauri/src/clipboard.rs +++ b/src-tauri/src/clipboard.rs @@ -1,6 +1,5 @@ use base64::engine::general_purpose::STANDARD; use base64::Engine; -use clipboard_win::{formats, get_clipboard, is_format_avail}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rdev::{listen, simulate, EventType, Key}; @@ -10,6 +9,10 @@ use std::thread; use std::time::Duration; use tauri::Manager; use tokio::runtime::Runtime; +use url::Url; +use reqwest::Client; +use arboard::Clipboard; +use regex::Regex; #[tauri::command] pub fn simulate_paste() { @@ -30,6 +33,7 @@ pub fn simulate_paste() { pub fn setup(app_handle: tauri::AppHandle) { let (tx, rx) = mpsc::channel(); + let mut is_processing = false; std::thread::spawn(move || { listen(move |event| match event.event_type { @@ -37,34 +41,31 @@ pub fn setup(app_handle: tauri::AppHandle) { let _ = tx.send(true); } EventType::KeyRelease(Key::KeyC) => { - if rx.try_recv().is_ok() { + if rx.try_recv().is_ok() && !is_processing { + is_processing = true; let pool = app_handle.state::(); let rt = app_handle.state::(); - if let Ok(content) = get_clipboard(formats::Unicode) { + let mut clipboard = Clipboard::new().unwrap(); + + if let Ok(content) = clipboard.get_text() { rt.block_on(async { insert_content_if_not_exists(&pool, "text", content).await; }); } - if is_format_avail(formats::Bitmap.into()) { - match get_clipboard(formats::Bitmap) { - Ok(image) => { - rt.block_on(async { - let base64_image = STANDARD.encode(&image); - insert_content_if_not_exists(&pool, "image", base64_image) - .await; - }); - } - Err(e) => { - println!("Error reading image from clipboard: {:?}", e); - } - } - } else { - println!("No image format available in clipboard"); + if let Ok(image) = clipboard.get_image() { + rt.block_on(async { + let base64_image = STANDARD.encode(&image.bytes); + insert_content_if_not_exists(&pool, "image", base64_image).await; + }); } + is_processing = false; } } + EventType::KeyRelease(Key::ControlLeft | Key::ControlRight) => { + is_processing = false; + } _ => {} }) .unwrap(); @@ -72,27 +73,74 @@ pub fn setup(app_handle: tauri::AppHandle) { } async fn insert_content_if_not_exists(pool: &SqlitePool, content_type: &str, content: String) { - let exists: bool = sqlx::query_scalar( - "SELECT EXISTS(SELECT 1 FROM history WHERE content_type = ? AND content = ?)", + let last_content: Option = sqlx::query_scalar( + "SELECT content FROM history WHERE content_type = ? ORDER BY timestamp DESC LIMIT 1", ) .bind(content_type) - .bind(&content) .fetch_one(pool) .await - .unwrap_or(false); + .unwrap_or(None); - if !exists { + if last_content.as_deref() != Some(&content) { let id: String = thread_rng() .sample_iter(&Alphanumeric) .take(16) .map(char::from) .collect(); - let _ = sqlx::query("INSERT INTO history (id, content_type, content) VALUES (?, ?, ?)") + let url_regex = Regex::new(r"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)").unwrap(); + let favicon_base64 = if content_type == "text" { + if let Some(url_match) = url_regex.find(&content) { + let url_str = url_match.as_str(); + match Url::parse(url_str) { + Ok(url) => { + match fetch_favicon_as_base64(url).await { + Ok(Some(favicon)) => { + println!("Favicon fetched successfully."); + Some(favicon) + }, + Ok(None) => { + println!("No favicon found."); + None + }, + Err(e) => { + println!("Failed to fetch favicon: {}", e); + None + } + } + }, + Err(e) => { + println!("Failed to parse URL: {}", e); + None + } + } + } else { + None + } + } else { + None + }; + + let _ = sqlx::query("INSERT INTO history (id, content_type, content, favicon) VALUES (?, ?, ?, ?)") .bind(id) .bind(content_type) .bind(content) + .bind(favicon_base64) .execute(pool) .await; } } + +async fn fetch_favicon_as_base64(url: Url) -> Result, reqwest::Error> { + println!("Checking for favicon at URL: {}", url.origin().ascii_serialization()); + let client = Client::new(); + let favicon_url = format!("https://icon.horse/icon/{}", url.host_str().unwrap()); + let response = client.get(&favicon_url).send().await?; + + if response.status().is_success() { + let bytes = response.bytes().await?; + Ok(Some(STANDARD.encode(&bytes))) + } else { + Ok(None) + } +} \ No newline at end of file diff --git a/src-tauri/src/database.rs b/src-tauri/src/database.rs index b15844f..6080687 100644 --- a/src-tauri/src/database.rs +++ b/src-tauri/src/database.rs @@ -32,6 +32,7 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box> { id TEXT PRIMARY KEY, content_type TEXT NOT NULL, content TEXT NOT NULL, + favicon TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )" ) @@ -45,7 +46,7 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box> { .take(16) .map(char::from) .collect(); - sqlx::query("INSERT INTO history (id, content_type, content) VALUES (?, ?, ?)") + sqlx::query("INSERT INTO history (id, content_type, content, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP)") .bind(id) .bind("text") .bind("Welcome to your clipboard history!") diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2187fd7..4321e6d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -50,15 +50,15 @@ fn main() { if let Some(window) = app.get_window("main") { let _ = window.restore_state(StateFlags::POSITION); center_window_on_current_monitor(&window); - window.show().unwrap(); + window.hide().unwrap(); } - #[cfg(dev)] - { - let window = app.get_webview_window("main").unwrap(); - window.open_devtools(); - window.close_devtools(); - } + // #[cfg(dev)] + // { + // let window = app.get_webview_window("main").unwrap(); + // window.open_devtools(); + // window.close_devtools(); + // } Ok(()) })