this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove json , sqlitemaxxing (#20)

* fix

* fuck

* yay

authored by

April and committed by
GitHub
ab9c937b 0e091d07

+469 -31
-3
.env.example
··· 1 1 DISCORD_TOKEN=discord token 2 2 GITHUB_TOKEN=github token 3 - # you can find this file at https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre844992.32f313e49e42/packages.json.br 4 - # p.s. remeber to decompress it 5 - NIXPKGS_JSON=./nixpkgs.json
+3 -1
.gitignore
··· 2 2 vendor 3 3 result* 4 4 target 5 - /.idea 5 + /.idea 6 + nixpkgs.db 7 + nixpkgs.hash
+113 -1
Cargo.lock
··· 27 27 ] 28 28 29 29 [[package]] 30 + name = "alloc-no-stdlib" 31 + version = "2.0.4" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 34 + 35 + [[package]] 36 + name = "alloc-stdlib" 37 + version = "0.2.2" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 40 + dependencies = [ 41 + "alloc-no-stdlib", 42 + ] 43 + 44 + [[package]] 30 45 name = "allocator-api2" 31 46 version = "0.2.21" 32 47 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 193 208 version = "0.2.1" 194 209 dependencies = [ 195 210 "bottomify", 211 + "brotli", 196 212 "color-eyre", 197 213 "dotenv", 198 214 "humantime", 199 215 "kittysay", 200 216 "nixpkgs-track_lib", 217 + "once_cell", 201 218 "poise", 202 219 "rand 0.9.2", 203 220 "regex", 204 221 "reqwest 0.12.24", 222 + "rusqlite", 205 223 "serde", 206 224 "serde_json", 207 225 "serenity", 226 + "sha2", 208 227 "simd-json", 209 228 "tokio", 210 229 ] ··· 228 247 "clap 2.34.0", 229 248 "phf", 230 249 "phf_codegen", 250 + ] 251 + 252 + [[package]] 253 + name = "brotli" 254 + version = "8.0.2" 255 + source = "registry+https://github.com/rust-lang/crates.io-index" 256 + checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" 257 + dependencies = [ 258 + "alloc-no-stdlib", 259 + "alloc-stdlib", 260 + "brotli-decompressor", 261 + ] 262 + 263 + [[package]] 264 + name = "brotli-decompressor" 265 + version = "5.0.0" 266 + source = "registry+https://github.com/rust-lang/crates.io-index" 267 + checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" 268 + dependencies = [ 269 + "alloc-no-stdlib", 270 + "alloc-stdlib", 231 271 ] 232 272 233 273 [[package]] ··· 674 714 ] 675 715 676 716 [[package]] 717 + name = "fallible-iterator" 718 + version = "0.3.0" 719 + source = "registry+https://github.com/rust-lang/crates.io-index" 720 + checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 721 + 722 + [[package]] 723 + name = "fallible-streaming-iterator" 724 + version = "0.1.9" 725 + source = "registry+https://github.com/rust-lang/crates.io-index" 726 + checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 727 + 728 + [[package]] 677 729 name = "fastrand" 678 730 version = "2.3.0" 679 731 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 712 764 713 765 [[package]] 714 766 name = "foldhash" 767 + version = "0.1.5" 768 + source = "registry+https://github.com/rust-lang/crates.io-index" 769 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 770 + 771 + [[package]] 772 + name = "foldhash" 715 773 version = "0.2.0" 716 774 source = "registry+https://github.com/rust-lang/crates.io-index" 717 775 checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" ··· 938 996 939 997 [[package]] 940 998 name = "hashbrown" 999 + version = "0.15.5" 1000 + source = "registry+https://github.com/rust-lang/crates.io-index" 1001 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1002 + dependencies = [ 1003 + "foldhash 0.1.5", 1004 + ] 1005 + 1006 + [[package]] 1007 + name = "hashbrown" 941 1008 version = "0.16.1" 942 1009 source = "registry+https://github.com/rust-lang/crates.io-index" 943 1010 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 944 1011 dependencies = [ 945 1012 "allocator-api2", 946 1013 "equivalent", 947 - "foldhash", 1014 + "foldhash 0.2.0", 1015 + ] 1016 + 1017 + [[package]] 1018 + name = "hashlink" 1019 + version = "0.10.0" 1020 + source = "registry+https://github.com/rust-lang/crates.io-index" 1021 + checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 1022 + dependencies = [ 1023 + "hashbrown 0.15.5", 948 1024 ] 949 1025 950 1026 [[package]] ··· 1373 1449 checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 1374 1450 1375 1451 [[package]] 1452 + name = "libsqlite3-sys" 1453 + version = "0.35.0" 1454 + source = "registry+https://github.com/rust-lang/crates.io-index" 1455 + checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" 1456 + dependencies = [ 1457 + "cc", 1458 + "pkg-config", 1459 + "vcpkg", 1460 + ] 1461 + 1462 + [[package]] 1376 1463 name = "linux-raw-sys" 1377 1464 version = "0.11.0" 1378 1465 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2026 2113 ] 2027 2114 2028 2115 [[package]] 2116 + name = "rusqlite" 2117 + version = "0.37.0" 2118 + source = "registry+https://github.com/rust-lang/crates.io-index" 2119 + checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" 2120 + dependencies = [ 2121 + "bitflags 2.10.0", 2122 + "fallible-iterator", 2123 + "fallible-streaming-iterator", 2124 + "hashlink", 2125 + "libsqlite3-sys", 2126 + "smallvec", 2127 + ] 2128 + 2129 + [[package]] 2029 2130 name = "rustc-demangle" 2030 2131 version = "0.1.26" 2031 2132 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2328 2429 version = "0.10.6" 2329 2430 source = "registry+https://github.com/rust-lang/crates.io-index" 2330 2431 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2432 + dependencies = [ 2433 + "cfg-if", 2434 + "cpufeatures", 2435 + "digest", 2436 + ] 2437 + 2438 + [[package]] 2439 + name = "sha2" 2440 + version = "0.10.9" 2441 + source = "registry+https://github.com/rust-lang/crates.io-index" 2442 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 2331 2443 dependencies = [ 2332 2444 "cfg-if", 2333 2445 "cpufeatures",
+4
Cargo.toml
··· 23 23 serenity = "0.12.4" 24 24 poise = { git = "https://github.com/serenity-rs/poise", branch = "current" } 25 25 simd-json = { version = "0.17.0", features = ["serde"] } 26 + rusqlite = { version = "0.37.0", features = ["bundled"] } 27 + once_cell = "1.21.3" 28 + brotli = "8.0.2" 29 + sha2 = "0.10.9" 26 30 27 31 [dependencies.serde] 28 32 version = "1.0.228"
+73 -25
src/commands/nix/nixpkg.rs
··· 1 - use std::fs; 2 - 1 + use once_cell::sync::Lazy; 2 + use rusqlite::{Connection, params}; 3 + use std::sync::Mutex; 3 4 use crate::types::Context; 4 - use color_eyre::eyre::Result; 5 + use color_eyre::eyre::{Result, eyre}; 5 6 use poise::{serenity_prelude::CreateEmbed, CreateReply}; 6 7 7 - #[derive(serde::Deserialize)] 8 + static DB: Lazy<Mutex<Connection>> = Lazy::new(|| { 9 + let db_path = std::env::var("NIXPKGS_DB") 10 + .unwrap_or_else(|_| "nixpkgs.db".to_string()); 11 + Mutex::new(Connection::open(db_path).expect("Failed to open database")) 12 + }); 13 + 14 + #[derive(Debug)] 8 15 struct Package { 9 16 pname: String, 10 17 version: String, 11 18 meta: PackageMeta, 12 19 } 13 20 14 - #[derive(serde::Deserialize)] 21 + #[derive(Debug)] 15 22 struct PackageMeta { 16 23 description: String, 17 24 homepage: Option<String>, ··· 23 30 unfree: bool, 24 31 } 25 32 26 - #[derive(serde::Deserialize)] 33 + #[derive(Debug)] 27 34 struct License { 28 - #[serde(rename = "spdxId")] 29 35 spdx_id: String, 30 36 } 31 37 32 - #[derive(serde::Deserialize)] 38 + #[derive(Debug)] 33 39 struct Maintainers { 34 40 name: String, 35 41 github: String, ··· 46 52 #[description = "package name"] package: String, 47 53 ) -> Result<()> { 48 54 ctx.defer().await?; 49 - 50 - let nixpkgs_json = 51 - std::env::var("NIXPKGS_JSON").expect("NIXPKGS_JSON environment variable must be set"); 52 - 53 - let mut data = fs::read(nixpkgs_json)?; 54 - let pkgs: serde_json::Value = simd_json::serde::from_slice(&mut data)?; 55 - let pkg: Package = serde_json::from_value(pkgs["packages"][package].clone())?; 56 - 55 + 56 + let (mut pkg, maintainers) = { 57 + let db = DB.lock().unwrap(); 58 + 59 + let mut stmt = db.prepare( 60 + "SELECT pname, version, description, homepage, license_spdx_id, 61 + position, broken, insecure, unfree 62 + FROM packages WHERE package_name = ?1" 63 + )?; 64 + 65 + let pkg = stmt.query_row(params![&package], |row| { 66 + Ok(Package { 67 + pname: row.get(0)?, 68 + version: row.get(1)?, 69 + meta: PackageMeta { 70 + description: row.get(2)?, 71 + homepage: row.get(3)?, 72 + license: License { 73 + spdx_id: row.get::<_, Option<String>>(4)?.unwrap_or_else(|| "Unknown".to_string()), 74 + }, 75 + position: row.get::<_, Option<String>>(5)?.unwrap_or_else(|| "unknown".to_string()), 76 + broken: row.get::<_, i32>(6)? != 0, 77 + insecure: row.get::<_, i32>(7)? != 0, 78 + unfree: row.get::<_, i32>(8)? != 0, 79 + maintainers: Vec::new(), 80 + }, 81 + }) 82 + }).map_err(|_| eyre!("Package not found"))?; 83 + 84 + let mut maint_stmt = db.prepare( 85 + "SELECT name, github FROM maintainers WHERE package_name = ?1" 86 + )?; 87 + 88 + let maintainers: Vec<Maintainers> = maint_stmt 89 + .query_map(params![&package], |row| { 90 + Ok(Maintainers { 91 + name: row.get::<_, Option<String>>(0)?.unwrap_or_else(|| "Unknown".to_string()), 92 + github: row.get::<_, Option<String>>(1)?.unwrap_or_else(|| "".to_string()), 93 + }) 94 + })? 95 + .filter_map(Result::ok) 96 + .collect(); 97 + 98 + (pkg, maintainers) 99 + }; 100 + 101 + pkg.meta.maintainers = maintainers; 102 + 57 103 let file = pkg.meta.position.split(':').next().unwrap_or("unknown"); 58 104 59 105 let embed = CreateEmbed::new() 60 106 .title(format!("{} {}", pkg.pname, pkg.version)) 61 - .url(format!( 62 - "https://github.com/nixos/nixpkgs/blob/master/{file}" 63 - )) 107 + .url(format!("https://github.com/nixos/nixpkgs/blob/master/{file}")) 64 108 .description(pkg.meta.description) 65 109 .field( 66 110 "Homepage", ··· 73 117 .field("broken", pkg.meta.broken.to_string(), true) 74 118 .field( 75 119 "maintainers", 76 - pkg.meta 77 - .maintainers 78 - .iter() 79 - .map(|m| format!("[{}](https://github.com/{})", m.name, m.github)) 80 - .collect::<Vec<String>>() 81 - .join(", "), 120 + if pkg.meta.maintainers.is_empty() { 121 + "None".to_string() 122 + } else { 123 + pkg.meta.maintainers 124 + .iter() 125 + .filter(|m| !m.github.is_empty()) 126 + .map(|m| format!("[{}](https://github.com/{})", m.name, m.github)) 127 + .collect::<Vec<String>>() 128 + .join(", ") 129 + }, 82 130 false, 83 131 ) 84 132 .color(0x00DE_A586);
+276 -1
src/main.rs
··· 4 4 mod types; 5 5 6 6 use dotenv::dotenv; 7 - use std::{env, sync::Arc}; 7 + use std::{env, sync::Arc, path::Path}; 8 8 9 9 use color_eyre::eyre::Result; 10 10 use poise::serenity_prelude::{ActivityData, ClientBuilder, GatewayIntents}; 11 + use sha2::{Sha256, Digest}; 12 + 13 + #[derive(Debug)] 14 + struct NixpkgsRelease { 15 + url: String, 16 + hash: String, 17 + } 18 + 19 + async fn get_latest_nixpkgs_release() -> Result<NixpkgsRelease> { 20 + let base_url = env::var("NIXPKGS_CHANNEL") 21 + .unwrap_or_else(|_| "https://channels.nixos.org/nixpkgs-unstable".to_string()); 22 + 23 + let response = reqwest::get(&base_url).await?; 24 + let html = response.text().await?; 25 + 26 + let url_regex = regex::Regex::new(r#"<a href='([^']+/packages\.json\.br)'>packages\.json\.br</a>"#)?; 27 + let hash_regex = regex::Regex::new(r#"packages\.json\.br</a></td><td align='right'>\d+</td><td><tt>([a-f0-9]{64})</tt>"#)?; 28 + 29 + let url = url_regex.captures(&html) 30 + .and_then(|cap| cap.get(1)) 31 + .map(|m| { 32 + let path = m.as_str(); 33 + if path.starts_with("http") { 34 + path.to_string() 35 + } else if path.starts_with('/') { 36 + format!("https://releases.nixos.org{}", path) 37 + } else { 38 + format!("https://releases.nixos.org/{}", path) 39 + } 40 + }) 41 + .ok_or_else(|| color_eyre::eyre::eyre!("Could not find packages.json.br URL"))?; 42 + 43 + let hash = hash_regex.captures(&html) 44 + .and_then(|cap| cap.get(1)) 45 + .map(|m| m.as_str().to_string()) 46 + .ok_or_else(|| color_eyre::eyre::eyre!("Could not find packages.json.br hash"))?; 47 + 48 + Ok(NixpkgsRelease { url, hash }) 49 + } 50 + 51 + fn get_stored_hash() -> Option<String> { 52 + let hash_path = env::var("NIXPKGS_HASH_FILE").unwrap_or_else(|_| "nixpkgs.hash".to_string()); 53 + std::fs::read_to_string(hash_path).ok() 54 + } 55 + 56 + fn store_hash(hash: &str) -> Result<()> { 57 + let hash_path = env::var("NIXPKGS_HASH_FILE").unwrap_or_else(|_| "nixpkgs.hash".to_string()); 58 + std::fs::write(hash_path, hash)?; 59 + Ok(()) 60 + } 61 + 62 + async fn ensure_nixpkgs_database() -> Result<()> { 63 + let db_path = env::var("NIXPKGS_DB").unwrap_or_else(|_| "nixpkgs.db".to_string()); 64 + 65 + println!("Checking for nixpkgs updates..."); 66 + let release = get_latest_nixpkgs_release().await?; 67 + let stored_hash = get_stored_hash(); 68 + 69 + if Path::new(&db_path).exists() && stored_hash.as_deref() == Some(&release.hash) { 70 + println!("nixpkgs database is up to date"); 71 + return Ok(()); 72 + } 73 + 74 + if Path::new(&db_path).exists() { 75 + println!("New nixpkgs release detected, updating database..."); 76 + std::fs::remove_file(&db_path)?; 77 + } else { 78 + println!("nixpkgs database not found, building..."); 79 + } 80 + 81 + println!("Downloading from {}...", release.url); 82 + let response = reqwest::get(&release.url).await?; 83 + let compressed = response.bytes().await?; 84 + 85 + let mut hasher = Sha256::new(); 86 + hasher.update(&compressed); 87 + let computed_hash = format!("{:x}", hasher.finalize()); 88 + 89 + if computed_hash != release.hash { 90 + return Err(color_eyre::eyre::eyre!( 91 + "Hash mismatch! Expected {}, got {}", 92 + release.hash, 93 + computed_hash 94 + )); 95 + } 96 + 97 + println!("Hash verified, decompressing..."); 98 + let mut decompressed = Vec::new(); 99 + let mut decoder = brotli::Decompressor::new(compressed.as_ref(), 4096); 100 + std::io::copy(&mut decoder, &mut decompressed)?; 101 + 102 + println!("Parsing JSON..."); 103 + let json_data: serde_json::Value = serde_json::from_slice(&decompressed)?; 104 + 105 + let packages = json_data["packages"].as_object().ok_or_else(|| { 106 + color_eyre::eyre::eyre!("Invalid packages.json format") 107 + })?; 108 + 109 + println!("Creating database with {} packages...", packages.len()); 110 + 111 + let mut conn = rusqlite::Connection::open(&db_path)?; 112 + 113 + conn.execute( 114 + "CREATE TABLE packages ( 115 + package_name TEXT PRIMARY KEY, 116 + pname TEXT, 117 + version TEXT, 118 + name TEXT, 119 + system TEXT, 120 + output_name TEXT, 121 + available INTEGER, 122 + broken INTEGER, 123 + description TEXT, 124 + homepage TEXT, 125 + insecure INTEGER, 126 + unfree INTEGER, 127 + unsupported INTEGER, 128 + position TEXT, 129 + long_description TEXT, 130 + main_program TEXT, 131 + license_spdx_id TEXT, 132 + license_full_name TEXT, 133 + license_free INTEGER, 134 + license_url TEXT 135 + )", 136 + [], 137 + )?; 138 + 139 + conn.execute( 140 + "CREATE TABLE maintainers ( 141 + id INTEGER PRIMARY KEY AUTOINCREMENT, 142 + package_name TEXT, 143 + name TEXT, 144 + email TEXT, 145 + github TEXT, 146 + github_id INTEGER, 147 + matrix TEXT, 148 + FOREIGN KEY (package_name) REFERENCES packages(package_name) 149 + )", 150 + [], 151 + )?; 152 + 153 + conn.execute("CREATE INDEX idx_package_name ON packages(package_name)", [])?; 154 + conn.execute("CREATE INDEX idx_pname ON packages(pname)", [])?; 155 + conn.execute("CREATE INDEX idx_maintainers_package ON maintainers(package_name)", [])?; 156 + 157 + let total = packages.len(); 158 + let mut count = 0; 159 + let batch_size = 1000; 160 + 161 + let mut package_batch = Vec::new(); 162 + let mut maintainer_batch = Vec::new(); 163 + 164 + for (pkg_name, pkg_data) in packages { 165 + let meta = &pkg_data["meta"]; 166 + let license_data = &meta["license"]; 167 + 168 + let license_spdx = match license_data { 169 + serde_json::Value::Object(obj) => obj.get("spdxId").and_then(|v| v.as_str()).map(|s| s.to_string()), 170 + serde_json::Value::Array(arr) => { 171 + let ids: Vec<&str> = arr.iter() 172 + .filter_map(|v| v.get("spdxId")) 173 + .filter_map(|v| v.as_str()) 174 + .collect(); 175 + if ids.is_empty() { None } else { Some(ids.join(", ")) } 176 + } 177 + serde_json::Value::String(s) => Some(s.clone()), 178 + _ => None, 179 + }; 180 + 181 + let homepage = meta.get("homepage") 182 + .and_then(|h| match h { 183 + serde_json::Value::String(s) => Some(s.as_str()), 184 + serde_json::Value::Array(arr) => arr.first().and_then(|v| v.as_str()), 185 + _ => None, 186 + }); 187 + 188 + package_batch.push(( 189 + pkg_name.clone(), 190 + pkg_data.get("pname").and_then(|v| v.as_str()).map(|s| s.to_string()), 191 + pkg_data.get("version").and_then(|v| v.as_str()).map(|s| s.to_string()), 192 + pkg_data.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()), 193 + pkg_data.get("system").and_then(|v| v.as_str()).map(|s| s.to_string()), 194 + pkg_data.get("outputName").and_then(|v| v.as_str()).map(|s| s.to_string()), 195 + meta.get("available").and_then(|v| v.as_bool()).unwrap_or(false) as i32, 196 + meta.get("broken").and_then(|v| v.as_bool()).unwrap_or(false) as i32, 197 + meta.get("description").and_then(|v| v.as_str()).map(|s| s.to_string()), 198 + homepage.map(|s| s.to_string()), 199 + meta.get("insecure").and_then(|v| v.as_bool()).unwrap_or(false) as i32, 200 + meta.get("unfree").and_then(|v| v.as_bool()).unwrap_or(false) as i32, 201 + meta.get("unsupported").and_then(|v| v.as_bool()).unwrap_or(false) as i32, 202 + meta.get("position").and_then(|v| v.as_str()).map(|s| s.to_string()), 203 + meta.get("longDescription").and_then(|v| v.as_str()).map(|s| s.to_string()), 204 + meta.get("mainProgram").and_then(|v| v.as_str()).map(|s| s.to_string()), 205 + license_spdx, 206 + None::<String>, 207 + 0, 208 + None::<String>, 209 + )); 210 + 211 + if let Some(maintainers) = meta.get("maintainers").and_then(|v| v.as_array()) { 212 + for m in maintainers { 213 + if let Some(obj) = m.as_object() { 214 + maintainer_batch.push(( 215 + pkg_name.clone(), 216 + obj.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()), 217 + obj.get("email").and_then(|v| v.as_str()).map(|s| s.to_string()), 218 + obj.get("github").and_then(|v| v.as_str()).map(|s| s.to_string()), 219 + obj.get("githubId").and_then(|v| v.as_i64()), 220 + obj.get("matrix").and_then(|v| v.as_str()).map(|s| s.to_string()), 221 + )); 222 + } 223 + } 224 + } 225 + 226 + count += 1; 227 + 228 + if package_batch.len() >= batch_size { 229 + let tx = conn.transaction()?; 230 + { 231 + let mut stmt = tx.prepare_cached("INSERT INTO packages VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")?; 232 + for p in &package_batch { 233 + stmt.execute(rusqlite::params![p.0, p.1, p.2, p.3, p.4, p.5, p.6, p.7, p.8, p.9, p.10, p.11, p.12, p.13, p.14, p.15, p.16, p.17, p.18, p.19])?; 234 + } 235 + } 236 + { 237 + let mut stmt = tx.prepare_cached("INSERT INTO maintainers (package_name, name, email, github, github_id, matrix) VALUES (?, ?, ?, ?, ?, ?)")?; 238 + for m in &maintainer_batch { 239 + stmt.execute(rusqlite::params![m.0, m.1, m.2, m.3, m.4, m.5])?; 240 + } 241 + } 242 + tx.commit()?; 243 + 244 + println!("Progress: {}/{} ({:.1}%)", count, total, (count as f64 / total as f64) * 100.0); 245 + package_batch.clear(); 246 + maintainer_batch.clear(); 247 + } 248 + } 249 + 250 + if !package_batch.is_empty() { 251 + let tx = conn.transaction()?; 252 + { 253 + let mut stmt = tx.prepare_cached("INSERT INTO packages VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")?; 254 + for p in &package_batch { 255 + stmt.execute(rusqlite::params![p.0, p.1, p.2, p.3, p.4, p.5, p.6, p.7, p.8, p.9, p.10, p.11, p.12, p.13, p.14, p.15, p.16, p.17, p.18, p.19])?; 256 + } 257 + } 258 + { 259 + let mut stmt = tx.prepare_cached("INSERT INTO maintainers (package_name, name, email, github, github_id, matrix) VALUES (?, ?, ?, ?, ?, ?)")?; 260 + for m in &maintainer_batch { 261 + stmt.execute(rusqlite::params![m.0, m.1, m.2, m.3, m.4, m.5])?; 262 + } 263 + } 264 + tx.commit()?; 265 + } 266 + 267 + println!("Vacuuming..."); 268 + conn.execute("VACUUM", [])?; 269 + 270 + store_hash(&release.hash)?; 271 + 272 + println!("Database created successfully: {db_path}"); 273 + Ok(()) 274 + } 11 275 12 276 #[tokio::main] 13 277 async fn main() -> Result<()> { ··· 16 280 17 281 // Enable color_eyre beacuse error handling ig 18 282 color_eyre::install()?; 283 + ensure_nixpkgs_database().await?; 19 284 20 285 // Configure the client with your Discord bot token in the environment. 21 286 let token = env::var("DISCORD_TOKEN").expect("Expected DISCORD_TOKEN to be set"); ··· 75 340 http_server::start_http_server(ctx_clone, data_clone, 3000).await 76 341 { 77 342 eprintln!("HTTP server error: {e}"); 343 + } 344 + }); 345 + 346 + tokio::spawn(async { 347 + let mut interval = tokio::time::interval(std::time::Duration::from_secs(3600)); 348 + loop { 349 + interval.tick().await; 350 + if let Err(e) = ensure_nixpkgs_database().await { 351 + eprintln!("Failed to update nixpkgs database: {}", e); 352 + } 78 353 } 79 354 }); 80 355