Image Search Script

长按滑鼠右键,快速呼叫图片搜寻选单,提供简洁流畅的使用体验。

当前为 2024-11-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Image Search Script
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.5
  5. // @description Quickly access an intuitive and visually pleasing image search menu with a long right-click on any image.
  6. // @description:zh-TW 長按滑鼠右鍵,快速呼叫圖片搜尋選單,提供簡潔流暢的使用體驗。
  7. // @description:zh-CN 长按滑鼠右键,快速呼叫图片搜寻选单,提供简洁流畅的使用体验。
  8. // @author Pixmi
  9. // @homepage https://github.com/Pixmi/image-search-script
  10. // @supportURL https://github.com/Pixmi/image-search-script/issues
  11. // @icon https://raw.githubusercontent.com/Pixmi/image-search-script/refs/heads/main/icon.svg
  12. // @match *://*/*
  13. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_addStyle
  17. // @grant GM_registerMenuCommand
  18. // @grant GM_xmlhttpRequest
  19. // @connect ascii2d.net
  20. // @license GPL-3.0
  21. // @run-at document-body
  22. // @noframes
  23. // ==/UserScript==
  24.  
  25. GM_addStyle(`
  26. #image-search-menu {
  27. background-color: rgba(0, 0, 0, .75);
  28. color: rgb(255, 255, 255);
  29. display: none;
  30. flex-direction: column;
  31. font-size: 16px;
  32. opacity: 0;
  33. width: unset;
  34. min-width: 150px;
  35. height: unset;
  36. min-height: unset;
  37. transition: opacity .5s;
  38. position: fixed;
  39. top: unset;
  40. left: unset;
  41. z-index: 9999;
  42. }
  43. #image-search-menu.show {
  44. display: flex;
  45. opacity: 1;
  46. }
  47. .image-search-option {
  48. cursor: pointer;
  49. display: block;
  50. padding: 5px 10px;
  51. }
  52. .image-search-option + .image-search-option {
  53. border-top: 1px solid rgba(255, 255, 255, .5);
  54. }
  55. .image-search-option:hover {
  56. background-color: rgba(255, 255, 255, .3);
  57. }
  58. iframe#image-search-setting {
  59. width: 300px !important;
  60. height: 300px !important;
  61. }
  62. `);
  63.  
  64. const searchOptions = new Map([
  65. {
  66. label: 'Google Lens',
  67. key: 'GOOGLE_LENS',
  68. url: 'https://lens.google.com/uploadbyurl?url=%s'
  69. }, {
  70. label: 'SauceNAO',
  71. key: 'SAUCENAO',
  72. url: 'https://saucenao.com/search.php?db=999&url=%s'
  73. }, {
  74. label: 'Ascii2D',
  75. key: 'ASCII2D',
  76. url: ''
  77. }, {
  78. label: 'IQDB',
  79. key: 'IQDB',
  80. url: 'https://iqdb.org/?url=%s'
  81. }, {
  82. label: 'TinEye',
  83. key: 'TINEYE',
  84. url: 'https://www.tineye.com/search?url=%s'
  85. }, {
  86. label: 'Baidu',
  87. key: 'BAIDU',
  88. url: 'https://image.baidu.com/n/pc_search?queryImageUrl=%s'
  89. }, {
  90. label: 'Bing',
  91. key: 'BING',
  92. url: 'https://www.bing.com/images/searchbyimage?FORM=IRSBIQ&cbir=sbi&imgurl=%s'
  93. }
  94. ].map(item => [item.key, item]));
  95.  
  96. (function () {
  97. 'use strict';
  98.  
  99. document.addEventListener('mousedown', (event) => {
  100. searchMenu.holding = false;
  101. if (event.button === 2 && event.target.nodeName === 'IMG') {
  102. searchMenu.timer = setTimeout(() => {
  103. searchMenu.holding = true;
  104. searchMenu.open(event.target);
  105. }, 500);
  106. } else {
  107. if (event.target !== searchMenu.pane && !event.target.classList.contains('image-search-option')) {
  108. searchMenu.clear();
  109. }
  110. }
  111. });
  112.  
  113. document.addEventListener('mouseup', (event) => {
  114. if (event.button === 2) {
  115. clearTimeout(searchMenu.timer);
  116. if (searchMenu.holding) {
  117. event.preventDefault();
  118. }
  119. }
  120. });
  121.  
  122. document.addEventListener('contextmenu', (event) => {
  123. if (searchMenu.holding) {
  124. event.preventDefault();
  125. } else {
  126. searchMenu.clear();
  127. }
  128. });
  129.  
  130. document.addEventListener('visibilitychange', () => {
  131. if (document.visibilityState === 'visible') {
  132. searchMenu.update();
  133. }
  134. });
  135. document.addEventListener('scroll', () => { searchMenu.update() });
  136. window.addEventListener('resize', () => { searchMenu.update() });
  137.  
  138. const newTab = (url) => {
  139. let tab = document.createElement('a');
  140. tab.href = url;
  141. tab.dispatchEvent(new MouseEvent('click', {ctrlKey: true, metaKey: true}));
  142. }
  143.  
  144. class searchMenuController {
  145. constructor() {
  146. this.panel = null;
  147. this.image = null;
  148. this.holding = false;
  149. this.timer = null;
  150. this.init();
  151. }
  152.  
  153. init() {
  154. this.panel = document.createElement('div');
  155. this.panel.id = 'image-search-menu';
  156. this.panel.addEventListener('click', (event) => {
  157. const action = event.target.dataset.action || false;
  158. console.log(action);
  159. if (action) {
  160. switch (action) {
  161. case 'ASCII2D':
  162. GM_xmlhttpRequest({
  163. method: 'POST',
  164. url: 'https://ascii2d.net/imagesearch/search/',
  165. data: JSON.stringify({ uri: this.image.src }),
  166. headers: {
  167. 'Content-Type': 'application/json',
  168. },
  169. timeout: 10000,
  170. onload: function(response) {
  171. if (response.status == 200) {
  172. newTab(response.finalUrl);
  173. }
  174. },
  175. onerror: function(error) {
  176. console.error('請求錯誤:', error);
  177. },
  178. ontimeout: function() {
  179. console.error('請求超時');
  180. }
  181. });
  182. break;
  183. default: {
  184. const option = searchOptions.get(action) || false;
  185. if (!option) break;
  186. newTab(option.url.replace('%s', this.image.src));
  187. break;
  188. }
  189. }
  190. }
  191. this.clear();
  192. });
  193. document.body.append(this.panel);
  194. }
  195.  
  196. open(target) {
  197. if (target.nodeName === 'IMG') {
  198. while (this.panel.hasChildNodes()) { this.panel.lastChild.remove() };
  199. for (const [key, option] of searchOptions) {
  200. if (!GM_getValue(key, true)) continue;
  201. const item = document.createElement('div');
  202. item.className = 'image-search-option';
  203. item.textContent = option.label;
  204. item.dataset.action = key;
  205. this.panel.append((item));
  206. }
  207. this.panel.style.minHeight = `calc(${this.panel.childNodes.length} * (1.5rem + 10px))`;
  208. this.image = target;
  209. this.update();
  210. this.panel.classList.add('show');
  211. }
  212. }
  213.  
  214. update() {
  215. if (this.image) {
  216. const status = {
  217. width: this.image.width,
  218. left: this.image.x,
  219. top: this.image.y
  220. };
  221. for (const key of Object.keys(status)) {
  222. this.panel.style[key] = `${status[key]}px`;
  223. };
  224. }
  225. }
  226.  
  227. clear() {
  228. this.image = null;
  229. this.panel.classList.remove('show');
  230. this.panel.style.width = 0;
  231. this.panel.style.height = 0;
  232. this.panel.style.left = 0;
  233. this.panel.style.top = 0;
  234. }
  235. }
  236.  
  237. const searchMenu = new searchMenuController();
  238. })();
  239.  
  240. GM_registerMenuCommand('Setting', () => config.open());
  241.  
  242. const config = new GM_config({
  243. 'id': 'image-search-setting',
  244. 'css': `
  245. #image-search-setting * {
  246. box-sizing: border-box;
  247. }
  248. #image-search-setting {
  249. box-sizing: border-box;
  250. width: 100%;
  251. height: 100%;
  252. padding: 10px;
  253. margin: 0;
  254. }
  255. #image-search-setting_buttons_holder {
  256. text-align: center;
  257. }
  258. .config_var {
  259. display: flex;
  260. align-items: center;
  261. flex-direction: row-reverse;
  262. justify-content: start;
  263. }
  264. `,
  265. 'title': 'Search Options',
  266. 'fields': {
  267. 'GOOGLE_LENS': {
  268. 'label': 'Google Lens',
  269. 'type': 'checkbox',
  270. 'default': true,
  271. },
  272. 'SAUCENAO': {
  273. 'label': 'SauceNAO',
  274. 'type': 'checkbox',
  275. 'default': true,
  276. },
  277. 'ASCII2D': {
  278. 'label': 'Ascii2D',
  279. 'type': 'checkbox',
  280. 'default': true,
  281. },
  282. 'IQDB': {
  283. 'label': 'IQDB',
  284. 'type': 'checkbox',
  285. 'default': true,
  286. },
  287. 'TINEYE': {
  288. 'label': 'TinEye',
  289. 'type': 'checkbox',
  290. 'default': true,
  291. },
  292. 'BAIDU': {
  293. 'label': 'Baidu',
  294. 'type': 'checkbox',
  295. 'default': true,
  296. },
  297. 'BING': {
  298. 'label': 'Bing',
  299. 'type': 'checkbox',
  300. 'default': true,
  301. }
  302. },
  303. 'events': {
  304. 'init': () => {
  305. for (const [key] of searchOptions) {
  306. config.set(key, GM_getValue(key, true));
  307. }
  308. },
  309. 'save': () => {
  310. for (const [key] of searchOptions) {
  311. GM_setValue(key, config.get(key));
  312. }
  313. config.close();
  314. }
  315. }
  316. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址