/ facecheck-url-extractor-desktop&mobileV2.user.js
facecheck-url-extractor-desktop&mobileV2.user.js
1 // ==UserScript== 2 // @name FaceCheck URL Extractor with Ratings (Mobile and Desktop) 3 // @namespace http://tampermonkey.net/ 4 // @version 3.0.0 5 // @description Extracts image URLs and ratings from FaceCheck results for both mobile and desktop with automatic overlay on mobile 6 // @author vin31_ modified by Nthompson096, perplexity.ai and 0wn3dg0d 7 // @match https://facecheck.id/* 8 // @grant none 9 // ==/UserScript== 10 11 (function() { 12 'use strict'; 13 14 // Detect mobile device 15 const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 16 17 // Function to get cookie value by name 18 const getCookie = (name) => { 19 const cookies = document.cookie.split(';').map(cookie => cookie.trim()); 20 const targetCookie = cookies.find(cookie => cookie.startsWith(`${name}=`)); 21 return targetCookie ? targetCookie.split('=')[1] : null; 22 }; 23 24 // Determine the theme based on the cookie 25 const theme = getCookie('theme') || 'dark'; // Default to dark theme if cookie is not set 26 27 // SHARED FUNCTIONS 28 29 // Helper function to determine rating and color based on confidence score 30 const getRating = (confidence) => { 31 if (confidence >= 90) return { rating: 'Certain Match', color: isMobile ? 'green' : '#4caf50' }; 32 if (confidence >= 83) return { rating: 'Confident Match', color: isMobile ? 'yellow' : '#ffeb3b' }; 33 if (confidence >= 70) return { rating: 'Uncertain Match', color: isMobile ? 'orange' : '#ff9800' }; 34 if (confidence >= 50) return { rating: 'Weak Match', color: isMobile ? 'red' : '#f44336' }; 35 return { rating: 'No Match', color: isMobile ? 'white' : '#9e9e9e' }; 36 }; 37 38 // Helper to check if on results page 39 const isResultsPage = () => /https:\/\/facecheck\.id\/(?:[a-z]{2})?\#.+/.test(window.location.href); 40 41 // Shared URL extraction function (works for both mobile and desktop) 42 const extractUrls = (fimg) => { 43 const parentAnchor = fimg.closest('a'); 44 const groupId = parentAnchor ? parentAnchor.getAttribute('data-grp') : null; 45 const results = []; 46 47 // If it's a group, collect all elements of the group 48 if (groupId) { 49 const groupElements = document.querySelectorAll(`a[data-grp="${groupId}"]`); 50 groupElements.forEach(groupElement => { 51 const groupFimg = groupElement.querySelector('.facediv') || groupElement.querySelector('[id^="fimg"]'); 52 if (!groupFimg) return; 53 54 const result = extractSingleUrl(groupFimg); 55 if (result) results.push(result); 56 }); 57 } else { 58 // If it's a standalone element 59 const result = extractSingleUrl(fimg); 60 if (result) results.push(result); 61 } 62 63 return results.sort((a, b) => b.confidence - a.confidence); 64 }; 65 66 // Extract URL from a single image element 67 const extractSingleUrl = (fimg) => { 68 const bgImage = window.getComputedStyle(fimg).backgroundImage; 69 const base64Match = bgImage.match(/base64,(.*)"/); 70 const urlMatch = base64Match ? atob(base64Match[1]).match(/https?:\/\/[^\s"]+/) : null; 71 if (!urlMatch) return null; 72 73 const domain = new URL(urlMatch[0]).hostname.replace('www.', ''); 74 const distSpan = fimg.parentElement.querySelector('.dist'); 75 const confidence = distSpan ? parseInt(distSpan.textContent) : 0; 76 const { rating, color } = getRating(confidence); 77 78 return { url: urlMatch[0], domain, confidence, rating, color }; 79 }; 80 81 // MOBILE FUNCTIONALITY 82 if (isMobile) { 83 // Mobile-specific styles for overlays - ENLARGED VERSION 84 const mobileStyles = ` 85 .mobile-overlay { 86 position: absolute; 87 bottom: 0; 88 left: 0; 89 right: 0; 90 background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 50%, transparent 100%); 91 color: white; 92 padding: 12px 8px 8px 8px; 93 font-size: 14px; /* Increased from 11px */ 94 line-height: 1.4; /* Increased from 1.2 */ 95 z-index: 1000; 96 border-radius: 0 0 8px 8px; 97 pointer-events: none; 98 transform: translateY(100%); 99 transition: transform 0.3s ease; 100 } 101 .mobile-overlay.visible { 102 transform: translateY(0); 103 } 104 .mobile-overlay a { 105 color: #00FFFF; 106 text-decoration: none; 107 display: block; 108 margin-bottom: 4px; /* Increased from 2px */ 109 font-weight: bold; 110 pointer-events: all; 111 padding: 6px 8px; /* Increased from 2px 4px */ 112 border-radius: 4px; /* Increased from 3px */ 113 background: rgba(0,0,0,0.8); 114 font-size: 14px; /* Added explicit font size */ 115 } 116 .mobile-overlay a:active { 117 background: rgba(0,255,255,0.2); 118 } 119 .mobile-overlay .rating { 120 font-size: 12px; /* Increased from 10px */ 121 font-weight: normal; 122 } 123 .fimg-container { 124 position: relative; 125 overflow: hidden; 126 } 127 .mobile-info-panel { 128 position: fixed; 129 bottom: 10px; 130 left: 10px; 131 right: 10px; 132 background: rgba(0,0,0,0.95); 133 color: white; 134 padding: 15px; 135 border-radius: 8px; 136 z-index: 9999; 137 font-size: 16px; /* Increased from 14px */ 138 line-height: 1.5; /* Increased from 1.4 */ 139 max-height: 70vh; 140 overflow-y: auto; 141 transform: translateY(120%); 142 transition: transform 0.3s ease; 143 border: 1px solid rgba(0,255,255,0.3); 144 box-shadow: 0 4px 20px rgba(0,0,0,0.5); 145 } 146 .mobile-info-panel.visible { 147 transform: translateY(0); 148 } 149 .mobile-info-panel .close-btn { 150 position: absolute; 151 top: 8px; /* Increased from 5px */ 152 right: 12px; /* Increased from 10px */ 153 background: none; 154 border: none; 155 color: #00FFFF; 156 font-size: 20px; /* Increased from 16px */ 157 cursor: pointer; 158 padding: 0; 159 width: 24px; /* Increased from 20px */ 160 height: 24px; /* Increased from 20px */ 161 } 162 .mobile-info-panel a { 163 color: #00FFFF; 164 text-decoration: none; 165 display: block; 166 margin: 12px 0; /* Increased from 8px 0 */ 167 padding: 10px 12px; /* Increased from 8px 10px */ 168 background: rgba(0,255,255,0.1); 169 border-radius: 6px; 170 border: 1px solid rgba(0,255,255,0.2); 171 word-break: break-all; 172 font-size: 16px; /* Explicit font size */ 173 } 174 .mobile-info-panel a:active { 175 background: rgba(0,255,255,0.3); 176 } 177 .mobile-info-panel .url-item { 178 margin-bottom: 16px; /* Increased from 12px */ 179 } 180 .mobile-info-panel .confidence { 181 font-size: 14px; /* Increased from 12px */ 182 margin-top: 6px; /* Increased from 2px */ 183 } 184 .mobile-overlay .click-hint { 185 font-size: 12px; /* Increased from 10px */ 186 color: #aaa; 187 margin-top: 4px; /* Increased from 2px */ 188 font-style: italic; 189 } 190 `; 191 192 // Inject mobile styles 193 const mobileStyleSheet = document.createElement("style"); 194 mobileStyleSheet.type = "text/css"; 195 mobileStyleSheet.innerText = mobileStyles; 196 document.head.appendChild(mobileStyleSheet); 197 198 // Create overlay for mobile images 199 const createMobileOverlay = (fimg, results) => { 200 // Make sure the parent container has relative positioning 201 const container = fimg.parentElement; 202 if (!container.classList.contains('fimg-container')) { 203 container.classList.add('fimg-container'); 204 } 205 206 const overlay = document.createElement("div"); 207 overlay.classList.add("mobile-overlay"); 208 209 // Show domain, confidence, and click hint 210 const topResult = results[0]; 211 overlay.innerHTML = ` 212 <div style="color:${topResult.color}; pointer-events: none;"> 213 ${topResult.domain} (${topResult.confidence}%) 214 </div> 215 <div class="click-hint" style="pointer-events: none;">Tap for more URLs</div> 216 `; 217 218 container.appendChild(overlay); 219 220 // Show overlay with animation after a short delay 221 setTimeout(() => { 222 overlay.classList.add("visible"); 223 }, 100); 224 225 return overlay; 226 }; 227 228 // Create floating info panel that shows when tapping on overlay info 229 const createInfoPanel = () => { 230 const panel = document.createElement("div"); 231 panel.classList.add("mobile-info-panel"); 232 panel.innerHTML = ` 233 <button class="close-btn">×</button> 234 <div id="panel-content"></div> 235 `; 236 document.body.appendChild(panel); 237 238 // Close button functionality 239 panel.querySelector('.close-btn').addEventListener('click', (e) => { 240 e.stopPropagation(); 241 panel.classList.remove('visible'); 242 }); 243 244 // Close when clicking outside 245 document.addEventListener('click', (e) => { 246 if (!panel.contains(e.target) && panel.classList.contains('visible')) { 247 panel.classList.remove('visible'); 248 } 249 }); 250 251 return panel; 252 }; 253 254 const infoPanel = createInfoPanel(); 255 256 // Add click handler to overlays to show detailed info 257 const addOverlayClickHandler = (overlay, results) => { 258 overlay.style.pointerEvents = 'all'; 259 overlay.style.cursor = 'pointer'; 260 261 overlay.addEventListener('click', (e) => { 262 e.preventDefault(); 263 e.stopPropagation(); 264 265 const content = results.map((result, index) => ` 266 <div class="url-item"> 267 <a href="${result.url}" target="_blank"> 268 ${index + 1}. ${result.domain} 269 </a> 270 <div class="confidence" style="color:${result.color};"> 271 ${result.confidence}% - ${result.rating} 272 </div> 273 <a href="${result.url}" target="_blank" style="font-size:12px;"> 274 ${result.url} 275 </a> 276 </div> 277 `).join(''); 278 279 infoPanel.querySelector('#panel-content').innerHTML = content; 280 infoPanel.classList.add('visible'); 281 }); 282 }; 283 284 // Process all mobile images 285 const processMobileImages = () => { 286 const fimgElements = document.querySelectorAll('[id^="fimg"]'); 287 288 fimgElements.forEach(fimg => { 289 // Skip if already processed 290 if (fimg.parentElement.querySelector('.mobile-overlay')) return; 291 292 const results = extractUrls(fimg); 293 if (results.length > 0) { 294 const overlay = createMobileOverlay(fimg, results); 295 addOverlayClickHandler(overlay, results); 296 } 297 }); 298 }; 299 300 // Start processing mobile images 301 const mobileCheckInterval = setInterval(() => { 302 if (isResultsPage() && document.querySelector('[id^="fimg"]')) { 303 processMobileImages(); 304 // Continue checking for new images that might load dynamically 305 setTimeout(() => { 306 processMobileImages(); 307 }, 2000); 308 } 309 }, 1000); 310 } else { 311 // DESKTOP FUNCTIONALITY 312 313 // CSS Variables for easy theme management 314 const desktopStyles = ` 315 :root { 316 --popup-bg: ${theme === 'light' ? '#ffffff' : '#1e1e1e'}; 317 --popup-color: ${theme === 'light' ? '#007acc' : '#00ffff'}; 318 --popup-opacity: 0.95; 319 --popup-border: 1px solid ${theme === 'light' ? 'rgba(0, 122, 204, 0.2)' : 'rgba(0, 255, 255, 0.2)'}; 320 --popup-shadow: 0 4px 12px ${theme === 'light' ? 'rgba(0, 0, 0, 0.1)' : 'rgba(0, 0, 0, 0.3)'}; 321 --popup-radius: 12px; 322 --popup-padding: 16px; 323 --popup-width: 320px; 324 --popup-max-height: 400px; 325 --popup-transition: opacity 0.3s ease, transform 0.3s ease; 326 } 327 .popup { 328 position: fixed; 329 background: var(--popup-bg); 330 color: var(--popup-color); 331 opacity: 0; 332 border: var(--popup-border); 333 box-shadow: var(--popup-shadow); 334 border-radius: var(--popup-radius); 335 padding: var(--popup-padding); 336 width: var(--popup-width); 337 max-height: var(--popup-max-height); 338 overflow-y: auto; 339 pointer-events: auto; 340 transition: var(--popup-transition); 341 transform: translateY(-10px); 342 backdrop-filter: blur(10px); 343 z-index: 9999; 344 } 345 .popup.visible { 346 opacity: var(--popup-opacity); 347 transform: translateY(0); 348 } 349 .popup ul { 350 list-style: none; 351 padding: 0; 352 margin: 0; 353 } 354 .popup li { 355 margin: 8px 0; 356 } 357 .popup a { 358 color: var(--popup-color); 359 text-decoration: none; 360 transition: color 0.2s ease; 361 } 362 .popup a:hover { 363 color: #ff6f61; 364 } 365 `; 366 367 // Inject desktop styles 368 const desktopStyleSheet = document.createElement("style"); 369 desktopStyleSheet.type = "text/css"; 370 desktopStyleSheet.innerText = desktopStyles; 371 document.head.appendChild(desktopStyleSheet); 372 373 // Create and style the popup window 374 const createPopup = () => { 375 const popup = document.createElement("div"); 376 popup.classList.add("popup"); 377 document.body.appendChild(popup); 378 return popup; 379 }; 380 381 // Function to display results in the popup window 382 const displayResultsDesktop = (results, popup, fimg) => { 383 const rect = fimg.getBoundingClientRect(); 384 popup.style.left = `${rect.right - 155}px`; 385 popup.style.top = `${rect.top}px`; 386 387 const resultsList = results.map(result => ` 388 <li> 389 <a href="${result.url}" target="_blank"> 390 ${result.domain} 391 </a> 392 <span style="color:${result.color};">(${result.confidence}% - ${result.rating})</span> 393 </li> 394 `).join(''); 395 396 popup.innerHTML = `<ul>${resultsList}</ul>`; 397 popup.classList.add('visible'); 398 }; 399 400 // Create the popup window 401 const popup = createPopup(); 402 403 // Track which elements have listeners attached 404 const processedFimgs = new WeakSet(); 405 let hoverTimeout; 406 let isPopupHovered = false; 407 408 // Add event listeners for all fimg elements 409 const addHoverListeners = () => { 410 const fimgElements = document.querySelectorAll('[id^="fimg"]'); 411 412 fimgElements.forEach(fimg => { 413 if (processedFimgs.has(fimg)) return; 414 processedFimgs.add(fimg); 415 416 fimg.addEventListener('mouseenter', () => { 417 if (isPopupHovered) return; 418 clearTimeout(hoverTimeout); 419 const results = extractUrls(fimg); 420 if (results.length > 0) { 421 displayResultsDesktop(results, popup, fimg); 422 } 423 }); 424 425 fimg.addEventListener('mouseleave', () => { 426 if (isPopupHovered) return; 427 hoverTimeout = setTimeout(() => { 428 popup.classList.remove('visible'); 429 }, 300); 430 }); 431 }); 432 433 // Event handler for the popup 434 popup.addEventListener('mouseenter', () => { 435 isPopupHovered = true; 436 clearTimeout(hoverTimeout); 437 }); 438 439 popup.addEventListener('mouseleave', () => { 440 isPopupHovered = false; 441 popup.classList.remove('visible'); 442 }); 443 }; 444 445 // Start adding event listeners after the page loads 446 const desktopCheckInterval = setInterval(() => { 447 if (isResultsPage() && document.querySelector('[id^="fimg"]')) { 448 addHoverListeners(); 449 } 450 }, 1000); 451 } 452 453 })();