/ 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  })();