lenso.ai图像搜索助手 | 以图搜图

lenso.ai搜索助手 | 以图搜图

  1. // ==UserScript==
  2. // @name lenso.ai image search helper | Image Search
  3. // @name:ar مساعد البحث بالصور من lenso.ai | البحث بالصور
  4. // @name:bg Помощник за търсене на изображения в lenso.ai | Търсене по изображение
  5. // @name:cs Pomocník vyhledávání obrázků pro lenso.ai | Vyhledávání podle obrázku
  6. // @name:da lenso.ai billedsøgningshjælper | Søg efter billede
  7. // @name:de lenso.ai Bildersuche-Assistent | Bild-Suche
  8. // @name:el Βοηθός αναζήτησης εικόνων του lenso.ai | Αναζήτηση με εικόνα
  9. // @name:en lenso.ai image search helper | Image Search
  10. // @name:eo lenso.ai bildserca helpilo | Bildo-serĉo
  11. // @name:es Ayudante de búsqueda de imágenes de lenso.ai | Búsqueda por imagen
  12. // @name:fi lenso.ai-kuvahakuapuri | Kuvahaku
  13. // @name:fr Assistant de recherche d'images lenso.ai | Recherche par image
  14. // @name:fr-CA Assistant de recherche d'images lenso.ai | Recherche par image
  15. // @name:he עוזר חיפוש תמונות של lenso.ai | חיפוש תמונה
  16. // @name:hr Pomoćnik za pretraživanje slika lenso.ai | Pretraživanje po slici
  17. // @name:hu lenso.ai képkeresési segéd | Képkeresés
  18. // @name:id Pembantu pencarian gambar lenso.ai | Pencarian Gambar
  19. // @name:it Assistente ricerca immagini di lenso.ai | Ricerca per immagine
  20. // @name:ja lenso.ai 画像検索ヘルパー | 画像で検索
  21. // @name:ka lenso.ai-ის გამოსახულების ძიების დამხმარე | გამოსახულებით ძიება
  22. // @name:ko lenso.ai 이미지 검색 도우미 | 이미지로 검색
  23. // @name:nb lenso.ai bildesøkshjelper | Søk med bilde
  24. // @name:nl lenso.ai afbeeldingszoekassistent | Afbeelding zoeken
  25. // @name:pl Pomocnik wyszukiwania obrazów lenso.ai | Wyszukiwanie po obrazie
  26. // @name:pt-BR Assistente de pesquisa de imagens lenso.ai | Pesquisar por imagem
  27. // @name:ro Asistent de căutare imagini lenso.ai | Căutare după imagine
  28. // @name:ru Помощник поиска изображений lenso.ai | Поиск по изображению
  29. // @name:sk Pomocník vyhľadávania obrázkov lenso.ai | Vyhľadávanie podľa obrázka
  30. // @name:sr Помоћник за претрагу слика lenso.ai | Претрага по слици
  31. // @name:sv lenso.ai bildssökningshjälp | Sök efter bild
  32. // @name:th ตัวช่วยค้นหาภาพ lenso.ai | ค้นหาด้วยภาพ
  33. // @name:tr lenso.ai görüntü arama yardımcısı | Görüntüyle Ara
  34. // @name:ug lenso.ai سۈرەت ئىزدەش ياردەمچىسى | سۈرەت بىلەن ئىزدەش
  35. // @name:uk Помічник пошуку зображень lenso.ai | Пошук за зображенням
  36. // @name:vi Trợ thủ tìm kiếm hình ảnh lenso.ai | Tìm kiếm bằng hình ảnh
  37. // @name:zh lenso.ai图像搜索助手 | 以图搜图
  38. // @name:zh-CN lenso.ai图像搜索助手 | 以图搜图
  39. // @name:zh-HK lenso.ai圖像搜索助手 | 以圖搜圖
  40. // @name:zh-SG lenso.ai图像搜索助手 | 以图搜图
  41. // @name:zh-TW lenso.ai圖像搜索助手 | 以圖搜圖
  42.  
  43. // @description lenso.ai search helper | image search by image
  44. // @description:ar مساعد البحث من lenso.ai | البحث بالصور
  45. // @description:bg Помощник за търсене в lenso.ai | Търсене по изображение
  46. // @description:cs Pomocník vyhledávání pro lenso.ai | Vyhledávání podle obrázku
  47. // @description:da lenso.ai søgningshjælper | Søg efter billede
  48. // @description:de lenso.ai Suchassistent | Bild-Suche
  49. // @description:el Βοηθός αναζήτησης του lenso.ai | Αναζήτηση με εικόνα
  50. // @description:en lenso.ai search helper | Image Search
  51. // @description:eo lenso.ai serĉa helpilo | Bildo-serĉo
  52. // @description:es Ayudante de búsqueda de lenso.ai | Búsqueda por imagen
  53. // @description:fi lenso.ai-hakuapuri | Kuvahaku
  54. // @description:fr Assistant de recherche lenso.ai | Recherche par image
  55. // @description:fr-CA Assistant de recherche lenso.ai | Recherche par image
  56. // @description:he עוזר חיפוש של lenso.ai | חיפוש תמונה
  57. // @description:hr Pomoćnik za pretraživanje lenso.ai | Pretraživanje po slici
  58. // @description:hu lenso.ai keresési segéd | Képkeresés
  59. // @description:id Pembantu pencarian lenso.ai | Pencarian Gambar
  60. // @description:it Assistente ricerca di lenso.ai | Ricerca per immagine
  61. // @description:ja lenso.ai 検索ヘルパー | 画像で検索
  62. // @description:ka lenso.ai-ის ძიების დამხმარე | გამოსახულებით ძიება
  63. // @description:ko lenso.ai 검색 도우미 | 이미지로 검색
  64. // @description:nb lenso.ai søkshjelper | Søk med bilde
  65. // @description:nl lenso.ai zoekassistent | Afbeelding zoeken
  66. // @description:pl Pomocnik wyszukiwania lenso.ai | Wyszukiwanie po obrazie
  67. // @description:pt-BR Assistente de pesquisa lenso.ai | Pesquisar por imagem
  68. // @description:ro Asistent de căutare lenso.ai | Căutare după imagine
  69. // @description:ru Помощник поиска lenso.ai | Поиск по изображению
  70. // @description:sk Pomocník vyhľadávania lenso.ai | Vyhľadávanie podľa obrázka
  71. // @description:sr Помоћник за претрагу lenso.ai | Претрага по слици
  72. // @description:sv lenso.ai sökhjälp | Sök efter bild
  73. // @description:th ตัวช่วยค้นหา lenso.ai | ค้นหาด้วยภาพ
  74. // @description:tr lenso.ai arama yardımcısı | Görüntüyle Ara
  75. // @description:ug lenso.ai ئىزدەش ياردەمچىسى | سۈرەت بىلەن ئىزدەش
  76. // @description:uk Помічник пошуку lenso.ai | Пошук за зображенням
  77. // @description:vi Trợ thủ tìm kiếm lenso.ai | Tìm kiếm bằng hình ảnh
  78. // @description:zh lenso.ai搜索助手 | 以图搜图
  79. // @description:zh-CN lenso.ai搜索助手 | 以图搜图
  80. // @description:zh-HK lenso.ai搜索助手 | 以圖搜圖
  81. // @description:zh-SG lenso.ai搜索助手 | 以图搜图
  82. // @description:zh-TW lenso.ai搜索助手 | 以圖搜圖
  83. // @version 1.6.10.10
  84. // @match <all_urls>
  85. // @match *://lenso.ai/*
  86. // @include *
  87. // @author 864907600cc, aspen138
  88. // @run-at document-start
  89. // @grant GM.getValue
  90. // @grant GM.setValue
  91. // @grant GM.openInTab
  92. // @grant GM.registerMenuCommand
  93. // @grant GM_getValue
  94. // @grant GM_setValue
  95. // @grant GM_openInTab
  96. // @grant GM_registerMenuCommand
  97. // @grant GM_addElement
  98. // @grant GM_xmlhttpRequest
  99. // @connect lenso.ai
  100. // @namespace greasyfork.org
  101. // @license GPLv3
  102. // ==/UserScript==
  103.  
  104.  
  105. // If it can not work, try to go to Network page, make `Disable Cache` checked. It may work.
  106. // If it claims that you have reached your 10 daily free search quota, copy the url, open browser private mode, paste the url then go.
  107.  
  108. // How did I find this method?
  109. // I foudn this js file, https://lenso.ai/build/assets/SearchByUrlPage-ILptEkXi.js
  110.  
  111.  
  112.  
  113.  
  114.  
  115. // Modified from https://greasyfork.org/scripts/2998 by aspen138
  116. // 2024-11-07 Add lenso.ai search option
  117. // Infringement Contact aspen1382020 at gmail dot com to delete
  118.  
  119.  
  120. // 本脚本基于 GPLv3 协议开源 http://www.gnu.org/licenses/gpl.html
  121. // (c) 86497600cc. Some Rights Reserved.
  122. // Default setting: Press Ctrl and click right key on a image to search.
  123.  
  124. 'use strict';
  125. var default_setting = {
  126. "site_list": {
  127. "Google": "https://lens.google.com/uploadbyurl?url={%s}",
  128. "Baidu": "https://graph.baidu.com/details?isfromtusoupc=1&tn=pc&carousel=0&promotion_name=pc_image_shituindex&extUiData%5bisLogoShow%5d=1&image={%s}",
  129. "Bing": "https://www.bing.com/images/searchbyimage?cbir=sbi&iss=sbi&imgurl={%s}",
  130. "TinEye": "https://www.tineye.com/search?url={%s}",
  131. //"Cydral": "http://www.cydral.com/#url={%s}",
  132. "Yandex": "https://yandex.com/images/search?rpt=imageview&url={%s}", // change "Яндекс (Yandex)" to "Yandex"
  133. "Sogou": "https://pic.sogou.com/ris?query=https%3A%2F%2Fimg03.sogoucdn.com%2Fv2%2Fthumb%2Fretype_exclude_gif%2Fext%2Fauto%3Fappid%3D122%26url%3D{%ss}&flag=1&drag=0",
  134. "360 ShiTu": "http://st.so.com/stu?imgurl={%s}",
  135. "SauceNAO": "https://saucenao.com/search.php?db=999&url={%s}",
  136. "IQDB": "https://iqdb.org/?url={%s}",
  137. "3D IQDB": "https://3d.iqdb.org/?url={%s}",
  138. "WhatAnime": "https://trace.moe/?url={%s}",
  139. "Ascii2D": "https://ascii2d.net/search/url/{%s}"
  140. },
  141. "site_option": ["Google", "Baidu", "Bing", "TinEye", "Yandex", "Sogou", "360 ShiTu", "SauceNAO", "IQDB", "3D IQDB", "WhatAnime", "Ascii2D"],
  142. "hot_key": "ctrlKey",
  143. "server_url": "//sbi.ccloli.com/img/upload.php"
  144. };
  145.  
  146. /*var server_url = "//sbi.ccloli.com/img/upload.php";*/
  147. // 请直接在设置页进行设置(Firefox 请尽量选择支持 https 的服务器)
  148. // 地址前使用"//"表示按照当前页面设定决定是否使用 https
  149. // 地址前使用"http://"表示强制使用 http
  150. // 地址前使用"https://"表示强制使用 https(需确认服务器支持 ssl)
  151. // 如果需要自己架设上传服务器的话请访问 GitHub 项目页(https://github.com/ccloli/Search-By-Image)获取服务端
  152. // 其他可用的上传服务器如下:
  153. // Heroku: //search-by-image.herokuapp.com/img/upload.php (支持 https)
  154. // BeGet: http://fh13121a.bget.ru/img/upload.php (不支持 https)
  155. // OpenShift: //searchbyimage-864907600cc.rhcloud.com/img/upload.php (支持 https)
  156. // DigitalOcean VPS: //sbi.ccloli.com/img/upload.php (支持 https,thanks to Retaker)
  157. // 注意,部分服务器可能仅支持 http 协议,若您选择了这些服务器,请务必注明 "http://",且若您使用的是 Firefox 浏览器,在 https 页面下将不能上传文件搜索搜索(除非设置 security.mixed_content.block_active_content 为 false)
  158.  
  159. var search_panel = null;
  160. var setting = default_setting;
  161. var disable_contextmenu = false;
  162. var img_src = null;
  163. var data_version = 0;
  164. var last_update = 0;
  165. var xhr = new XMLHttpRequest();
  166. var reader = new FileReader();
  167. reader.onload = function(file) {
  168. upload_file(this.result);
  169. };
  170. var asyncGMAPI = false;
  171.  
  172. var i18n = {
  173. 'zh': {
  174. 'u2s': '上传图片并搜索',
  175. 'dh': '拖拽文件至此',
  176. 'ca': '确认终止上传文件吗?',
  177. 'a': '多搜',
  178. 'n': '名称',
  179. 'l': '地址(图片地址以 {%s} 代替)',
  180. 'cr': '确定将所有设置初始化么?\n(初始化将清除所有所有设置,且不可逆)',
  181. 'us': '上传完成!',
  182. 'uf': '上传失败!',
  183. 'sh': '热键',
  184. 'sa': '添加',
  185. 'sr': '重置',
  186. 'ss': '保存',
  187. 'sc': '取消'
  188. },
  189. 'en': {
  190. 'u2s': 'Upload image to search',
  191. 'dh': 'Drag file to here',
  192. 'ca': 'Are you sure to cancel uploading?',
  193. 'a': 'All',
  194. 'n': 'Name',
  195. 'l': 'Location (Image URL should be replace with {%s})',
  196. 'cr': 'Are you sure to reset all preferences (irreversible) ?',
  197. 'us': 'Upload finished!',
  198. 'uf': 'Upload failed!',
  199. 'sh': 'Hot Key',
  200. 'sa': 'Add',
  201. 'sr': 'Reset',
  202. 'ss': 'Save',
  203. 'sc': 'Cancel'
  204. }
  205. };
  206. var lang = i18n[navigator.language] ? navigator.language : navigator.languages ? navigator.languages.filter(function(elem) {
  207. return i18n[elem];
  208. })[0] : null;
  209. if (lang == null) lang = 'en';
  210.  
  211. var getValue;
  212. if (typeof GM_getValue === 'undefined' && typeof GM !== 'undefined') {
  213. self.GM_getValue = GM.getValue;
  214. self.GM_setValue = GM.setValue;
  215. self.GM_openInTab = GM.openInTab;
  216. self.GM_registerMenuCommand = GM.registerMenuCommand;
  217. getValue = GM.getValue;
  218. asyncGMAPI = true;
  219. } else {
  220. getValue = function(key, init) {
  221. return new Promise(function(resolve, reject) {
  222. try {
  223. resolve(GM_getValue(key, init));
  224. } catch (e) {
  225. reject(e);
  226. }
  227. });
  228. };
  229. }
  230.  
  231. function init() {
  232. return Promise.all([getValue('setting'), GM_getValue('version', 0), GM_getValue('timestamp', 0)]).then(function(res) {
  233. var s = res[0],
  234. v = res[1],
  235. t = res[2];
  236. setting = s ? JSON.parse(s) : default_setting;
  237. data_version = v;
  238. last_update = t;
  239. var cur = 9;
  240.  
  241. var applyMap = {
  242. 3: function() {
  243. var new_site_list = {};
  244. var new_site_option = [];
  245.  
  246. for (var i in setting.site_list) {
  247. // use for loop to keep order, will use array in 2.x
  248. switch (i) {
  249. case 'Baidu ShiTu':
  250. case 'Baidu Image':
  251. new_site_list['Baidu'] = default_setting.site_list['Baidu'];
  252. break;
  253.  
  254. case 'Bing':
  255. case 'Sogou':
  256. new_site_list[i] = default_setting.site_list[i];
  257. break;
  258.  
  259. default:
  260. new_site_list[i] = setting.site_list[i];
  261. }
  262. }
  263. new_site_list['WhatAnime'] = default_setting.site_list['WhatAnime'];
  264.  
  265. for (var i = 0; i < setting.site_option.length; i++) {
  266. if ((setting.site_option[i] === 'Baidu ShiTu' || setting.site_option[i] === 'Baidu Image') && !(/,?Baidu,?/.test(new_site_option.join(',')))) {
  267. new_site_option.push('Baidu');
  268. } else {
  269. new_site_option.push(setting.site_option[i]);
  270. }
  271. }
  272. new_site_option.push('WhatAnime');
  273.  
  274. setting.site_list = new_site_list;
  275. setting.site_option = new_site_option;
  276. },
  277. 4: function() {
  278. setting.site_list['Ascii2D'] = default_setting.site_list['Ascii2D'];
  279. setting.site_option.push('Ascii2D');
  280. },
  281. 5: function() {
  282. if (setting.site_list['WhatAnime']) {
  283. setting.site_list['WhatAnime'] = default_setting.site_list['WhatAnime'];
  284. }
  285. },
  286. 6: function() {
  287. if (setting.site_list['Baidu']) {
  288. setting.site_list['Baidu'] = default_setting.site_list['Baidu'];
  289. }
  290. },
  291. 7: function() {
  292. if (setting.site_list['Yandex']) {
  293. setting.site_list['Yandex'] = default_setting.site_list['Yandex'];
  294. }
  295. },
  296. 8: function() {
  297. if (setting.site_list['Google']) {
  298. setting.site_list['Google'] = default_setting.site_list['Google'];
  299. }
  300. if (setting.site_list['Sogou']) {
  301. setting.site_list['Sogou'] = default_setting.site_list['Sogou'];
  302. }
  303. }
  304. }
  305.  
  306. if (data_version < cur) {
  307. for (var i = data_version; i < cur; i++) {
  308. if (applyMap[i]) {
  309. applyMap[i]();
  310. }
  311. }
  312. set_setting(setting);
  313. GM_setValue('version', data_version = cur);
  314. }
  315.  
  316. var repeatTest = {};
  317. var finalOpt = [];
  318. for (var i = 0, len = setting.site_option.length; i < len; i++) {
  319. var cur = setting.site_option[i];
  320. if (!repeatTest[cur] && setting.site_list[cur]) {
  321. finalOpt.push(cur);
  322. repeatTest[cur] = 1;
  323. }
  324. }
  325. setting.site_option = finalOpt;
  326.  
  327. if (setting.server_url == null || setting.server_url == '') {
  328. setting.server_url = default_setting.server_url;
  329. set_setting(setting);
  330. }
  331. });
  332. }
  333.  
  334. var server_url = setting.server_url;
  335.  
  336. function set_setting(data) {
  337. GM_setValue('setting', JSON.stringify(data));
  338. GM_setValue('timestamp', new Date().getTime());
  339. }
  340.  
  341. function create_panel() {
  342. search_panel = document.createElement('div');
  343. search_panel.style.cssText = 'width: 198px; font-size: 14px; text-align: center; position: absolute; color: #000; z-index: 9999999999; box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); border: 1px solid #CCC; background: rgba(255, 255, 255, 0.9); border-top-right-radius: 2px; border-bottom-left-radius: 2px; font-family: "Arial"; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;';
  344. document.body.appendChild(search_panel);
  345. var search_top = document.createElement('div');
  346. search_top.style.cssText = 'height: 24px; line-height: 24px; font-size: 12px; overflow: hidden; margin: 0 auto; padding: 0 5px;';
  347. search_top.className = 'image-search-top';
  348. search_top.innerHTML = '<div class="search_top_url" style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis; width: 100%; height: 24px;"></div><div class="search_top_file" style="width: 100%; height: 24px; line-height: 24px;" draggable="true"><label for="image-search-file">' + i18n[lang]['u2s'] + '</label><input type="file" id="image-search-file" accept="image/*" style="width: 0px; height: 0px; max-height: 0px; max-width: 0px; margin: 0; padding: 0;"></div><div class="search_top_progress"><progress style="width: 100%; height: 16px; vertical-align: middle; margin: 4px 0;" max="1"></progerss></div><style>.image-search-item{color: #000000; transition: all 0.2s linear; -webkit-transition: all 0.1s linear;}.image-search-item:hover{background: #eeeeee;}</style>';
  349. search_panel.appendChild(search_top);
  350. var search_item = document.createElement('div');
  351. search_item.style.cssText = 'width: 100%; height: 24px; line-height: 24px; cursor: pointer;';
  352. search_item.className = 'image-search-item';
  353. for (var i in setting.site_list) {
  354. var search_item_child = search_item.cloneNode(true);
  355. search_item_child.textContent = i;
  356. search_item_child.setAttribute('search-option', i);
  357. search_panel.appendChild(search_item_child);
  358. }
  359.  
  360. var search_item_lenso = search_item.cloneNode(true);
  361. search_item_lenso.textContent = 'lenso.ai (10 daily free search)';
  362. search_item_lenso.setAttribute('search-option', 'lenso');
  363. search_panel.appendChild(search_item_lenso);
  364.  
  365. search_item.textContent = 'All';
  366. search_item.setAttribute('search-option', 'all');
  367. search_panel.appendChild(search_item);
  368.  
  369. var search_item_setting = search_item.cloneNode(true);
  370. search_item_setting.textContent = 'Setting';
  371. search_item_setting.setAttribute('search-option', 'setting');
  372. search_panel.appendChild(search_item_setting);
  373. search_top.getElementsByTagName('input')[0].onchange = function() {
  374. reader.readAsDataURL(this.files[0]);
  375. };
  376. search_panel.ondragenter = function(event) {
  377. event.preventDefault();
  378. search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['dh'];
  379. };
  380. search_panel.ondragleave = function(event) {
  381. event.preventDefault();
  382. search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['u2s'];
  383. };
  384. search_panel.ondragover = function(event) {
  385. search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['dh'];
  386. event.preventDefault();
  387. };
  388. search_panel.ondrop = function(event) {
  389. event.stopPropagation();
  390. event.preventDefault();
  391. var files = event.target.files || event.dataTransfer.files;
  392. if (files[files.length - 1].type.indexOf('image') >= 0) reader.readAsDataURL(files[files.length - 1]);
  393. };
  394. search_top.getElementsByTagName('progress')[0].onclick = function() {
  395. if (xhr.readyState != 0 && confirm(i18n[lang]['ca']) == true) {
  396. xhr.abort();
  397. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-24px';
  398. }
  399. };
  400. if (navigator.userAgent.indexOf('Firefox') >= 0) {
  401. var paste_node_firefox = document.createElement('div');
  402. paste_node_firefox.setAttribute('contenteditable', 'true');
  403. paste_node_firefox.className = 'image-search-paste-node-firefox';
  404. paste_node_firefox.style.cssText = 'width: 0!important; height: 0!important; position: absolute; overflow: hidden;';
  405. paste_node_firefox.addEventListener('paste', get_clipboard, false);
  406. search_top.appendChild(paste_node_firefox);
  407. }
  408. }
  409.  
  410. function call_setting() {
  411. var setting_panel = document.createElement('div');
  412. setting_panel.style.cssText = 'width: 520px; font-size: 14px; position: fixed; color: #000; z-index: 9999999999; box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); border: 1px solid #CCC; background: rgba(255, 255, 255, 0.9); border-top-right-radius: 2px; border-bottom-left-radius: 2px; padding: 10px; left: 0; right: 0; top: 0; bottom: 0; margin: auto; font-family: "Arial"; height: 400px; max-height: 90%; overflow: auto; text-align: center; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;';
  413. document.body.appendChild(setting_panel);
  414. var setting_header = document.createElement('div');
  415. setting_header.style.cssText = 'width: 100%; height: 32px; line-height: 32px; font-size: 18px; line-height: 32px;';
  416. setting_header.className = 'image-search-setting-header';
  417. setting_header.textContent = 'Search By Image Setting';
  418. setting_panel.appendChild(setting_header);
  419. var setting_item = document.createElement('div');
  420. setting_item.style.cssText = 'width: 100%; height: 24px; line-height: 24px; margin: 1px 0;';
  421. setting_item.className = 'image-search-setting-title';
  422. setting_item.innerHTML = '<div style="text-align: center; display: inline-block; width: 30px;">' + i18n[lang]['a'] + '</div><div style="width: 100px; text-align: center; display: inline-block;">' + i18n[lang]['n'] + '</div><div style="width: 350px; text-align: center; display: inline-block;">' + i18n[lang]['l'] + '</div><div style="width: 20px; display: inline-block;"></div>';
  423. setting_panel.appendChild(setting_item);
  424. for (var i in setting.site_list) {
  425. var setting_item_child = setting_item.cloneNode(true);
  426. setting_item_child.className = 'image-search-setting-item';
  427. setting_item_child.innerHTML = '<div style="text-align: center; display: inline-block; width: 30px; vertical-align: middle;"><input type="checkbox"' + (setting.site_option.join('\n').indexOf(i) >= 0 ? ' checked="checked"' : '') + '></div><div style="width: 100px; text-align: center; display: inline-block;"><input style="width: 90px;" type="text" value="' + i + '"></div><div style="width: 350px; text-align: center; display: inline-block;"><input style="width: 340px;" type="text" value="' + setting.site_list[i] + '"></div><div style="text-align: center; display: inline-block; cursor: pointer; width: 20px;">×</div>';
  428. setting_panel.appendChild(setting_item_child);
  429. setting_item_child.getElementsByTagName('div')[3].onclick = function() {
  430. var parent = this.parentElement;
  431. parent.parentElement.removeChild(parent);
  432. };
  433. }
  434. var setting_server = document.createElement('div');
  435. setting_server.className = 'image-search-setting-server';
  436. setting_server.innerHTML = 'Upload Server <input type="text" value="' + setting['server_url'] + '" placeholder="//sbi.ccloli.com/img/upload.php" style="width: 350px;">';
  437. setting_panel.appendChild(setting_server);
  438. var setting_footer = document.createElement('div');
  439. setting_footer.style.cssText = 'width: 100%; height: 32px; line-height: 32px; margin-top: 5px; text-align: right;';
  440. setting_footer.className = 'image-search-setting-footer';
  441. setting_panel.appendChild(setting_footer);
  442. var setting_hotkey = document.createElement('div');
  443. var setting_add = document.createElement('div');
  444. var setting_reset = document.createElement('div');
  445. var setting_save = document.createElement('div');
  446. var setting_cancel = document.createElement('div');
  447. setting_hotkey.style.cssText = 'height: 32px; display: inline-block; text-align: left; float: left;';
  448. setting_add.style.cssText = 'width: 90px; height: 32px; margin: 0 5px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  449. setting_reset.style.cssText = 'width: 90px; height: 32px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  450. setting_save.style.cssText = 'width: 90px; height: 32px; margin: 0 5px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  451. setting_cancel.style.cssText = 'width: 90px; height: 32px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  452. setting_add.textContent = i18n[lang]['sa'];
  453. setting_reset.textContent = i18n[lang]['sr'];
  454. setting_save.textContent = i18n[lang]['ss'];
  455. setting_cancel.textContent = i18n[lang]['sc'];
  456. setting_hotkey.innerHTML = i18n[lang]['sh'] + ' <select><option value="ctrlKey"' + (setting.hot_key == 'ctrlKey' ? ' selected' : '') + '>Ctrl</option><option value="shiftKey"' + (setting.hot_key == 'shiftKey' ? ' selected' : '') + '>Shift</option><option value="altKey"' + (setting.hot_key == 'altKey' ? ' selected' : '') + '>Alt</option></select>';
  457. setting_footer.appendChild(setting_hotkey);
  458. setting_footer.appendChild(setting_add);
  459. setting_footer.appendChild(setting_reset);
  460. setting_footer.appendChild(setting_save);
  461. setting_footer.appendChild(setting_cancel);
  462. setting_add.onclick = function() {
  463. var setting_item_child = setting_item.cloneNode(true);
  464. setting_item_child.className = 'image-search-setting-item';
  465. setting_item_child.innerHTML = '<div style="text-align: center; display: inline-block; width: 30px; vertical-align: middle;"><input type="checkbox"></div><div style="width: 100px; text-align: center; display: inline-block;"><input style="width: 90px;" type="text"></div><div style="width: 350px; text-align: center; display: inline-block;"><input style="width: 340px;" type="text"></div><div style="text-align: center; display: inline-block; cursor: pointer; width: 20px;">×</div>';
  466. setting_panel.insertBefore(setting_item_child, setting_footer);
  467. setting_item_child.getElementsByTagName('div')[3].onclick = function() {
  468. var parent = this.parentElement;
  469. parent.parentElement.removeChild(parent);
  470. };
  471. setting_panel.scrollTop = setting_panel.scrollHeight;
  472. };
  473. setting_reset.onclick = function() {
  474. if (confirm(i18n[lang]['cr']) == true) {
  475. setting = default_setting;
  476. set_setting(setting);
  477. setting_panel.outerHTML = '';
  478. if (search_panel != null) {
  479. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  480. search_panel = null;
  481. }
  482. call_setting();
  483. }
  484. };
  485. setting_save.onclick = function() {
  486. var setting_items = document.getElementsByClassName('image-search-setting-item');
  487. var setting_data = {
  488. "site_list": {},
  489. "site_option": [],
  490. "hot_key": null,
  491. "server_url": null
  492. };
  493. for (var i = 0; i < setting_items.length; i++) {
  494. if (setting_items[i].getElementsByTagName('input')[1].value != '') {
  495. if (setting_items[i].getElementsByTagName('input')[0].checked) setting_data.site_option.push(setting_items[i].getElementsByTagName('input')[1].value);
  496. setting_data.site_list[setting_items[i].getElementsByTagName('input')[1].value] = setting_items[i].getElementsByTagName('input')[2].value;
  497. }
  498. }
  499. setting_data.hot_key = setting_hotkey.getElementsByTagName('select')[0].value;
  500. setting_data.server_url = document.getElementsByClassName('image-search-setting-server')[0].getElementsByTagName('input')[0].value;
  501. if (setting_data.server_url == null || setting_data.server_url == '') {
  502. setting_data.server_url = default_setting.server_url;
  503. }
  504. setting = setting_data;
  505. server_url = setting.server_url;
  506. set_setting(setting);
  507. document.body.removeChild(setting_panel);
  508. if (search_panel != null) {
  509. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  510. search_panel = null;
  511. }
  512. };
  513. setting_cancel.onclick = function() {
  514. document.body.removeChild(setting_panel);
  515. };
  516. }
  517.  
  518. function upload_file(data) {
  519. if (xhr.readyState != 0) xhr.abort();
  520. xhr.onreadystatechange = function() {
  521. if (xhr.readyState == 4) {
  522. if (xhr.status == 200) {
  523. img_src = xhr.responseText;
  524. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '0px';
  525. search_panel.getElementsByClassName('search_top_url')[0].textContent = i18n[lang]['us'];
  526. }
  527. }
  528. };
  529. xhr.upload.onprogress = function(event) {
  530. search_panel.getElementsByTagName('progress')[0].value = event.loaded / event.total;
  531. };
  532. xhr.onerror = function() {
  533. alert(i18n[lang]['uf']);
  534. };
  535. var form = new FormData();
  536. xhr.open('POST', server_url);
  537. form.append('imgdata', data);
  538. xhr.send(form);
  539. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-48px';
  540. }
  541.  
  542. function get_clipboard(event) {
  543. var items = event.clipboardData.items;
  544. if (items[items.length - 1].type.indexOf('image') >= 0) reader.readAsDataURL(items[items.length - 1].getAsFile());
  545. }
  546.  
  547. function hide_panel() {
  548. if (!search_panel || !search_panel.parentElement) return;
  549. img_src = null;
  550. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel); // Remove search panel to fix the bug that it may be left in some WYSIWYG editor (eg. MDN WYSIWYG editor). See issue: http://tieba.baidu.com/p/3682475061
  551. document.removeEventListener('paste', get_clipboard, false);
  552. }
  553.  
  554. function upload_blob_url(url) {
  555. if (!url) return;
  556. var req = new XMLHttpRequest();
  557. req.open('GET', url);
  558. req.responseType = 'blob';
  559. req.onload = function() {
  560. reader.readAsDataURL(req.response);
  561. };
  562. req.onerror = function() {
  563. alert(i18n[lang]['uf']);
  564. };
  565. req.send();
  566. }
  567.  
  568. document.addEventListener(document.onpointerdown === undefined ? 'mousedown' : 'pointerdown', function(event) {
  569. //console.log('Search Image >>\nevent.ctrlKey: '+event.ctrlKey+'\nevent.button: '+event.button+'\nevent.target:'+event.target+'\nevent.target.tagName: '+event.target.tagName+'\nevent.target.src: '+event.target.src+'\nevent.pageX: '+event.pageX+'\nevent.pageY: '+event.pageY+'\ndocument.documentElement.clientWidth: '+document.documentElement.clientWidth+'\ndocument.documentElement.clientHeight: '+document.documentElement.clientHeight+'\ndocument.documentElement.scrollWidth: '+document.documentElement.scrollWidth+'\ndocument.documentElement.scrollHeight: '+document.documentElement.scrollHeight+'\ndocument.documentElement.scrollLeft: '+document.documentElement.scrollLeft+'\ndocument.documentElement.scrollTop: '+document.documentElement.scrollTop);
  570. if (disable_contextmenu == true) {
  571. document.oncontextmenu = null;
  572. disable_contextmenu = false;
  573. }
  574. if (event[setting.hot_key] == true && event.button == 2) {
  575. if (search_panel == null) create_panel();
  576. // GM 4.x api is async, so we cannot update it in time
  577. else {
  578. if (!asyncGMAPI) {
  579. if (last_update != GM_getValue('timestamp', 0)) {
  580. last_update = GM_getValue('timestamp', 0);
  581. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  582. setting = GM_getValue('setting') ? JSON.parse(GM_getValue('setting')) : default_setting;
  583. create_panel();
  584. } else document.body.appendChild(search_panel);
  585. } else {
  586. document.body.appendChild(search_panel);
  587. GM_getValue('timestamp', 0).then(function(t) {
  588. if (last_update != t) {
  589. last_update = t;
  590. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  591. GM_getValue('setting').then(function(s) {
  592. setting = s ? JSON.parse(s) : default_setting;
  593. });
  594. }
  595. });
  596. }
  597. }
  598. search_panel.style.left = (document.documentElement.offsetWidth + (document.documentElement.scrollLeft || document.body.scrollLeft) - event.pageX >= 200 ? event.pageX : event.pageX >= 200 ? event.pageX - 200 : 0) + 'px';
  599. search_panel.style.top = (event.pageY + search_panel.offsetHeight < (document.documentElement.scrollTop || document.body.scrollTop) + document.documentElement.clientHeight ? event.pageY : event.pageY >= search_panel.scrollHeight ? event.pageY - search_panel.offsetHeight : 0) + 'px';
  600. // Firefox doesn't support getComputedStyle(element).marginLeft/marginRight and it would return "0px" while the element's margin is "auto". See bugzila/381328.
  601. //search_panel.style.marginLeft = '-' + (navigator.userAgent.indexOf('Firefox') < 0 ? getComputedStyle(document.body).marginLeft : (document.documentElement.offsetWidth - document.body.offsetWidth) / 2 + 'px');
  602. //search_panel.style.marginTop = '-' + getComputedStyle(document.body).marginTop;
  603. disable_contextmenu = true;
  604. document.oncontextmenu = function() {
  605. return false;
  606. };
  607. if (event.target.tagName.toLowerCase() == 'img' && event.target.src != null) {
  608. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '0px';
  609. search_panel.getElementsByClassName('search_top_url')[0].textContent = event.target.src;
  610. if (/^data:\s*.*?;\s*base64,\s*/.test(event.target.src)) upload_file(event.target.src);
  611. else if (/^(?:blob:|filesystem:)/.test(event.target.src)) upload_blob_url(event.target.src);
  612. else img_src = event.target.src;
  613. } else {
  614. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-24px';
  615. var firefoxPasteNode = document.getElementsByClassName('image-search-paste-node-firefox')[0];
  616. if (navigator.userAgent.indexOf('Firefox') >= 0 && firefoxPasteNode) {
  617. firefoxPasteNode.innerHTML = '';
  618. firefoxPasteNode.focus();
  619. } else document.addEventListener('paste', get_clipboard, false);
  620. }
  621. } else if (search_panel != null) {
  622. if (event.target.compareDocumentPosition(search_panel) == 10 || event.target.compareDocumentPosition(search_panel) == 0) {
  623. if (event.target.className == 'image-search-item' && event.button == 0) {
  624. // Helper function to encode the URL if needed
  625. const encodeUrlIfNeeded = (url, rsrc) => {
  626. if (url.split(/{%s+}/).shift().indexOf('?') >= 0) {
  627. const token = url.match(/{%s+}/)[0];
  628. for (let j = 0; j < token.length - 3; j++) {
  629. rsrc = encodeURIComponent(rsrc);
  630. }
  631. }
  632. return rsrc;
  633. };
  634.  
  635. // Helper function to make an API call to lenso.ai
  636. const searchLensoApi_backup20241213 = (rsrc) => {
  637. return fetch("https://lenso.ai/api/upload/process/url", {
  638. mode: 'no-cors',
  639. //mode: 'cors',
  640. method: "POST",
  641. headers: {
  642. "Content-Type": "application/json"
  643. },
  644. body: JSON.stringify({
  645. "url": rsrc
  646. })
  647. })
  648. .then(response => {
  649. if (response.ok) {
  650. console.log("response=",response);
  651. return response.json();
  652. } else {
  653. console.log("response=",response);
  654. handleError(response.status);
  655. return null;
  656. }
  657. })
  658. .catch(error => {
  659. console.error("Error occurred:", error);
  660. handleError(500);
  661. return null;
  662. });
  663. };
  664.  
  665. const searchLensoApi = (rsrc) => {
  666. return new Promise((resolve, reject) => {
  667. GM_xmlhttpRequest({
  668. method: "POST",
  669. url: "https://lenso.ai/api/upload/process/url",
  670. headers: {
  671. "Content-Type": "application/json"
  672. },
  673. data: JSON.stringify({
  674. "url": rsrc
  675. }),
  676. onload: function(response) {
  677. if (response.status === 200) {
  678. try {
  679. const responseData = JSON.parse(response.responseText);
  680. console.log("response=", response);
  681. resolve(responseData);
  682. } catch (error) {
  683. console.error("Error parsing JSON:", error);
  684. handleError(500);
  685. resolve(null);
  686. }
  687. } else {
  688. console.log("response=", response);
  689. handleError(response.status);
  690. resolve(null);
  691. }
  692. },
  693. onerror: function(error) {
  694. console.error("Error occurred:", error);
  695. handleError(500);
  696. resolve(null);
  697. }
  698. });
  699. });
  700. };
  701.  
  702. // Helper function to handle site searches
  703. const performSiteSearch = (turl, rsrc) => {
  704. const encodedRsrc = encodeUrlIfNeeded(turl, rsrc);
  705. GM_openInTab(turl.replace(/{%s+}/, encodedRsrc), event[setting.hot_key]);
  706. };
  707.  
  708. // Main switch statement handling search options
  709. switch (event.target.getAttribute('search-option')) {
  710. case 'lenso':
  711. if (img_src != null) {
  712. for (let i = setting.site_option.length - 1; i >= 0; i--) {
  713. const rsrc = encodeUrlIfNeeded(setting.site_list[setting.site_option[i]], img_src);
  714.  
  715. // Make API call and open result in a new tab
  716. searchLensoApi(rsrc).then(data => {
  717. if (data && data.id) {
  718. const locale = "en"; // Set to current locale if needed
  719. const result_url = `https://lenso.ai/${locale}/results/${data.id}`;
  720. GM_openInTab(result_url, event[setting.hot_key]);
  721. }
  722. });
  723. }
  724. hide_panel();
  725. }
  726. break;
  727.  
  728. case 'all':
  729. if (img_src != null) {
  730. for (let i = setting.site_option.length - 1; i >= 0; i--) {
  731. performSiteSearch(setting.site_list[setting.site_option[i]], img_src);
  732. }
  733. for (let i = setting.site_option.length - 1; i >= 0; i--) {
  734. const rsrc = encodeUrlIfNeeded(setting.site_list[setting.site_option[i]], img_src);
  735.  
  736. // Make API call and open result in a new tab
  737. searchLensoApi(rsrc).then(data => {
  738. if (data && data.id) {
  739. const locale = "en"; // Set to current locale if needed
  740. const result_url = `https://lenso.ai/${locale}/results/${data.id}`;
  741. GM_openInTab(result_url, event[setting.hot_key]);
  742. }
  743. });
  744. }
  745. hide_panel();
  746. }
  747. break;
  748.  
  749. case 'setting':
  750. call_setting();
  751. hide_panel();
  752. break;
  753.  
  754. default:
  755. if (img_src != null) {
  756. const searchOption = event.target.getAttribute('search-option');
  757. const turl = setting.site_list[searchOption];
  758. performSiteSearch(turl, img_src);
  759. hide_panel();
  760. }
  761. }
  762.  
  763. } else if (event.button != 0) hide_panel();
  764. } else hide_panel();
  765. }
  766. }, true);
  767.  
  768. if (typeof GM_registerMenuCommand !== 'undefined') {
  769. var gm_callsetting = GM_registerMenuCommand('Search By Image Setting', call_setting);
  770. }
  771. init();