Sleazy Fork is available in English.

Twitter Image to SauceNAO

Adds a button to convert Twitter image URLs to SauceNAO search URLs. Instantly searches for single image tweets, and shows a dropdown for multiple images.

질문, 리뷰하거나, 이 스크립트를 신고하세요.
  1. // ==UserScript==
  2. // @name Twitter Image to SauceNAO
  3. // @namespace http://yourwebsite.com
  4. // @version 3.2
  5. // @description Adds a button to convert Twitter image URLs to SauceNAO search URLs. Instantly searches for single image tweets, and shows a dropdown for multiple images.
  6. // @author FunkyJustin
  7. // @match https://twitter.com/*
  8. // @match https://x.com/*
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. function convertToSauceNAOUrl(imageUrl) {
  17. const encodedUrl = encodeURIComponent(imageUrl);
  18. return `https://saucenao.com/search.php?url=${encodedUrl}`;
  19. }
  20.  
  21. function createSauceNAODropdown(imageUrls, button) {
  22. const dropdown = document.createElement('div');
  23. dropdown.className = 'saucenao-dropdown';
  24. dropdown.style.position = 'absolute';
  25. dropdown.style.backgroundColor = 'white';
  26. dropdown.style.border = '1px solid #ccc';
  27. dropdown.style.zIndex = '10000';
  28. dropdown.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)';
  29. dropdown.style.maxHeight = '200px';
  30. dropdown.style.overflowY = 'auto';
  31. dropdown.style.padding = '4px';
  32. dropdown.style.fontSize = '14px';
  33.  
  34. imageUrls.forEach((imageUrl, index) => {
  35. const menuItem = document.createElement('div');
  36. menuItem.style.padding = '8px';
  37. menuItem.style.cursor = 'pointer';
  38. menuItem.style.color = '#000';
  39. menuItem.innerText = `Search Image ${index + 1}`;
  40.  
  41. menuItem.addEventListener('click', () => {
  42. const saucenaoUrl = convertToSauceNAOUrl(imageUrl);
  43. window.open(saucenaoUrl, '_blank');
  44. dropdown.remove();
  45. });
  46.  
  47. dropdown.appendChild(menuItem);
  48. });
  49.  
  50. const searchAllItem = document.createElement('div');
  51. searchAllItem.style.padding = '8px';
  52. searchAllItem.style.cursor = 'pointer';
  53. searchAllItem.style.color = '#000';
  54. searchAllItem.innerText = 'Search All Images';
  55.  
  56. searchAllItem.addEventListener('click', () => {
  57. imageUrls.forEach(imageUrl => {
  58. const saucenaoUrl = convertToSauceNAOUrl(imageUrl);
  59. window.open(saucenaoUrl, '_blank');
  60. });
  61. dropdown.remove();
  62. });
  63.  
  64. dropdown.appendChild(searchAllItem);
  65.  
  66. button.parentNode.style.position = 'relative';
  67. button.parentNode.appendChild(dropdown);
  68.  
  69. const buttonRect = button.getBoundingClientRect();
  70. dropdown.style.bottom = `${button.offsetTop}px`;
  71. dropdown.style.left = `${button.offsetLeft}px`;
  72.  
  73. document.addEventListener('click', (event) => {
  74. if (!dropdown.contains(event.target) && event.target !== button) {
  75. dropdown.remove();
  76. }
  77. }, { once: true });
  78.  
  79. return dropdown;
  80. }
  81.  
  82. function addSauceNAOBtn() {
  83. const articles = document.querySelectorAll('article');
  84.  
  85. articles.forEach(article => {
  86. const images = article.querySelectorAll('img[src*="pbs.twimg.com/media"]');
  87. const imageUrls = Array.from(images).map(img => img.src);
  88.  
  89. if (imageUrls.length > 0) {
  90. const container = article.querySelector('div[role="group"]');
  91.  
  92. if (container) {
  93. const existingButton = container.querySelector('.saucenao-button');
  94. if (!existingButton) {
  95. const button = document.createElement('div');
  96. button.className = 'saucenao-button';
  97. button.style.display = 'inline-block';
  98. button.style.marginLeft = '8px';
  99. button.style.cursor = 'pointer';
  100. button.style.color = 'rgb(29, 155, 240)';
  101.  
  102. button.innerText = 'SauceNAO';
  103.  
  104. const dropdownIcon = document.createElement('span');
  105. dropdownIcon.innerText = ' ▼'; // Dropdown icon
  106. dropdownIcon.style.display = imageUrls.length > 1 ? 'inline' : 'none'; // Show only if multiple images
  107.  
  108. button.appendChild(dropdownIcon); // Add the icon to the button
  109.  
  110. button.addEventListener('click', (event) => {
  111. event.stopPropagation();
  112. event.preventDefault();
  113.  
  114. if (imageUrls.length === 1) {
  115. const saucenaoUrl = convertToSauceNAOUrl(imageUrls[0]);
  116. window.open(saucenaoUrl, '_blank');
  117. } else {
  118. document.querySelectorAll('.saucenao-dropdown').forEach(el => el.remove());
  119. createSauceNAODropdown(imageUrls, button);
  120. }
  121. });
  122.  
  123. container.appendChild(button);
  124. }
  125. }
  126. }
  127. });
  128. }
  129.  
  130. const observer = new MutationObserver((mutations) => {
  131. mutations.forEach(() => {
  132. addSauceNAOBtn();
  133. });
  134. });
  135.  
  136. observer.observe(document.body, {
  137. childList: true,
  138. subtree: true
  139. });
  140.  
  141. addSauceNAOBtn();
  142. })();