1use colored::*;
2use futures_util::sink::SinkExt;
3use poem::web::websocket::{Message, WebSocketStream};
4use reqwest::Client;
5use serde::{Deserialize, Serialize};
6use std::error::Error;
7use std::fs::File;
8use std::fs::OpenOptions;
9use std::io::Write;
10use std::sync::Arc;
11use std::time::Duration;
12use tokio::sync::Mutex;
13use tokio::time::sleep;
14use url::Url;
15
16const DEFAULT_DATA_URL: &str =
17 "https://raw.githubusercontent.com/WebBreacher/WhatsMyName/main/wmn-data.json";
18
19#[derive(Serialize, Deserialize, Debug, Clone)]
20pub struct ProgressUpdate {
21 site: String,
22 status: String,
23 url: String,
24 logo_url: String,
25 error: Option<String>,
26 is_taken: bool,
27 completed: usize,
28 total: usize,
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone)]
32pub struct SiteData {
33 name: String,
34 uri_check: String,
35 e_code: u16,
36 e_string: String,
37 m_string: String,
38 m_code: u16,
39 known: Vec<String>,
40 cat: String,
41}
42
43#[derive(Serialize, Deserialize, Debug)]
44pub struct SitesFile {
45 license: Vec<String>,
46 authors: Vec<String>,
47 categories: Vec<String>,
48 pub sites: Vec<SiteData>,
49}
50
51#[derive(Serialize, Deserialize, Debug, Clone)]
52pub struct CheckResult {
53 site: String,
54 status: String,
55 url: String,
56 logo_url: String,
57 error: Option<String>,
58}
59
60#[derive(Serialize, Deserialize, Debug)]
61struct Report {
62 username: String,
63 generated_at: String,
64 results: Vec<CheckResult>,
65}
66
67pub fn get_site_logo(domain_name: &str) -> String {
68 match domain_name {
69 "t.me" => "https://logo.clearbit.com/telegram.org".to_string(),
70 "giters.com" => "https://giters.com/images/favicon.svg".to_string(),
71 "ko-fi.com" => {
72 "https://storage.ko-fi.com/cdn/brandasset/kofi_s_logo_nolabel.png".to_string()
73 }
74 _ => format!("https://logo.clearbit.com/{}", domain_name),
75 }
76}
77
78pub fn extract_domain(url_str: &str) -> Option<String> {
79 if let Ok(url) = Url::parse(url_str) {
80 if let Some(host) = url.host_str() {
81 let parts: Vec<&str> = host.split('.').collect();
83 if parts.len() >= 2 {
84 return Some(format!(
86 "{}.{}",
87 parts[parts.len() - 2],
88 parts[parts.len() - 1]
89 ));
90 } else {
91 return Some(host.to_string());
92 }
93 }
94 }
95 None
96}
97
98pub async fn download_sites_data(client: &Client, output_file: &str) -> Result<(), Box<dyn Error>> {
99 println!("Downloading sites data from {}...", DEFAULT_DATA_URL);
100
101 let response = client.get(DEFAULT_DATA_URL).send().await?;
102
103 if response.status().is_success() {
104 let data = response.text().await?;
105 let mut file = OpenOptions::new()
106 .write(true)
107 .create(true)
108 .truncate(true)
109 .open(output_file)?;
110
111 file.write_all(data.as_bytes())?;
112 println!("Successfully downloaded sites data to {}", output_file);
113 Ok(())
114 } else {
115 Err(format!("Failed to download data: HTTP {}", response.status()).into())
116 }
117}
118
119use futures_util::stream::SplitSink;
120
121pub async fn check_username_from_webserver(
122 client: &Client,
123 username: &str,
124 sites_data: &[SiteData],
125 _threads: usize, ws_sink: Option<Arc<Mutex<SplitSink<WebSocketStream, Message>>>>,
127) -> Vec<CheckResult> {
128 println!("Checking availability for username: {}\n", username);
129
130 let total = sites_data.len();
131 let mut all_results = Vec::new();
132 let mut completed = 0;
133
134 for site in sites_data {
135 let uri_string = site.uri_check.replace("{account}", username);
136 let domain = extract_domain(&uri_string).unwrap_or_else(|| "unknown.com".to_string());
137 let logo_url = get_site_logo(&domain);
138
139 let (is_taken, status, error) = match check_site(client, site, &uri_string).await {
140 Ok((is_taken, status)) => (is_taken, status, None),
141 Err(e) => (false, "Error".to_string(), Some(e.to_string())),
142 };
143
144 completed += 1;
145
146 if let Some(ws_sink) = &ws_sink {
147 let update = ProgressUpdate {
148 site: site.name.clone(),
149 status: status.clone(),
150 url: uri_string.clone(),
151 logo_url: logo_url.clone(),
152 error: error.clone(),
153 is_taken,
154 completed,
155 total,
156 };
157 if let Ok(json) = serde_json::to_string(&update) {
158 let mut sink = ws_sink.lock().await;
159 if sink.send(Message::Text(json.into())).await.is_err() {
160 eprintln!("Failed to send WebSocket message");
161 }
162 }
163 }
164
165 let color = if is_taken { "red" } else { "green" };
166 println!("{} {} - {}", status.color(color), site.name, uri_string);
167
168 all_results.push(CheckResult {
169 site: site.name.clone(),
170 status,
171 url: uri_string,
172 logo_url,
173 error,
174 });
175
176 sleep(Duration::from_millis(100)).await;
177 }
178
179 if let Some(ws_sink) = &ws_sink {
180 let completion_msg = serde_json::json!({
181 "completed": true,
182 "total": total
183 })
184 .to_string();
185 let mut sink = ws_sink.lock().await;
186 if sink
187 .send(Message::Text(completion_msg.into()))
188 .await
189 .is_err()
190 {
191 eprintln!("Failed to send completion message");
192 }
193 }
194
195 all_results
196}
197
198pub async fn check_username(
199 client: &Client,
200 username: &str,
201 sites_data: &[SiteData],
202 threads: usize,
203) -> Vec<CheckResult> {
204 println!("Checking availability for username: {}\n", username);
205
206 let client = client.clone();
208
209 let concurrent_limit = threads;
211
212 let mut all_results = Vec::new();
214
215 for chunk in sites_data.chunks(concurrent_limit) {
217 let mut tasks = Vec::new();
218
219 for site in chunk {
221 let site = site.clone();
222 let client = client.clone();
223 let username = username.to_string();
224
225 let task = tokio::spawn(async move {
227 let uri_string = site.uri_check.replace("{account}", &username);
228 let domain =
229 extract_domain(&uri_string).unwrap_or_else(|| "unknown.com".to_string());
230 let logo_url = get_site_logo(&domain);
231
232 match check_site(&client, &site, &uri_string).await {
233 Ok((is_taken, status)) => (
234 site.name.clone(),
235 status,
236 uri_string,
237 logo_url,
238 None,
239 is_taken,
240 ),
241 Err(e) => (
242 site.name.clone(),
243 "Error".to_string(),
244 uri_string,
245 logo_url,
246 Some(e.to_string()),
247 false,
248 ),
249 }
250 });
251
252 tasks.push(task);
253 }
254
255 for task in tasks {
257 if let Ok((site_name, status, url, logo_url, error, is_taken)) = task.await {
258 let color = if is_taken { "red" } else { "green" };
259 if let Some(err) = &error {
260 println!("{} {} - {}", "Error".color("yellow"), site_name, err);
261 } else {
262 println!("{} {} - {}", status.color(color), site_name, url);
263 }
264
265 all_results.push(CheckResult {
266 site: site_name,
267 status,
268 url,
269 logo_url,
270 error,
271 });
272 }
273 }
274
275 sleep(Duration::from_millis(200)).await;
277 }
278
279 all_results
280}
281
282async fn check_site(
283 client: &Client,
284 site: &SiteData,
285 uri: &str,
286) -> Result<(bool, String), Box<dyn Error>> {
287 let response = client
288 .get(uri)
289 .timeout(Duration::from_secs(10))
290 .send()
291 .await?;
292
293 let status = response.status();
294 let body = response.text().await?;
295
296 let is_taken = if status.as_u16() == site.e_code && body.contains(&site.e_string) {
297 true
298 } else if status.as_u16() == site.m_code && body.contains(&site.m_string) {
299 false
301 } else {
302 false
304 };
305
306 let status_text = if is_taken {
307 "Taken".to_string()
308 } else {
309 "Available".to_string()
310 };
311
312 Ok((is_taken, status_text))
315}
316
317pub fn save_txt_report(username: &str, results: &[CheckResult]) -> Result<(), Box<dyn Error>> {
318 let filename = format!("{}_report.txt", username);
319 let mut file = OpenOptions::new()
320 .write(true)
321 .create(true)
322 .truncate(true)
323 .open(filename)?;
324
325 writeln!(file, "Username availability report for: {}", username)?;
326 writeln!(file, "Generated on: {}", chrono::Local::now())?;
327 writeln!(file, "{}", "-".repeat(80))?;
328
329 for result in results {
330 writeln!(file, "{}: {}", result.site, result.status)?;
331 writeln!(file, "URL: {}", result.url)?;
332 writeln!(file, "Logo: {}", result.logo_url)?;
333 if let Some(error) = &result.error {
334 writeln!(file, "Error: {}", error)?;
335 }
336 writeln!(file, "{}", "-".repeat(40))?;
337 }
338
339 Ok(())
340}
341
342pub fn save_json_report(username: &str, results: &[CheckResult]) -> Result<(), Box<dyn Error>> {
343 let filename = format!("{}_report.json", username);
344 let report = Report {
345 username: username.to_string(),
346 generated_at: chrono::Local::now().to_string(),
347 results: results.to_vec(),
348 };
349
350 let file = File::create(filename)?;
351 serde_json::to_writer_pretty(file, &report)?;
352
353 Ok(())
354}