lenso.ai image search helper

lenso.ai search helper

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

  1. // ==UserScript==
  2. // @name lenso.ai image search helper
  3. // @version 1.6.10.3
  4. // @description lenso.ai search helper
  5. // @match <all_urls>
  6. // @match *://lenso.ai/*
  7. // @include *
  8. // @author 864907600cc, aspen138
  9. // @run-at document-start
  10. // @grant GM.getValue
  11. // @grant GM.setValue
  12. // @grant GM.openInTab
  13. // @grant GM.registerMenuCommand
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_openInTab
  17. // @grant GM_registerMenuCommand
  18. // @namespace greasyfork.org
  19. // @license GPLv3
  20. // ==/UserScript==
  21.  
  22.  
  23.  
  24. // Modified from https://greasyfork.org/scripts/2998 by aspen138
  25. // 2024-11-07 Add lenso.ai search option
  26. // Infringement Contact aspen1382020 at gmail dot com to delete
  27.  
  28.  
  29. // 本脚本基于 GPLv3 协议开源 http://www.gnu.org/licenses/gpl.html
  30. // (c) 86497600cc. Some Rights Reserved.
  31. // Default setting: Press Ctrl and click right key on a image to search.
  32.  
  33. 'use strict';
  34. var default_setting = {
  35. "site_list": {
  36. "Google": "https://lens.google.com/uploadbyurl?url={%s}",
  37. "Baidu": "https://graph.baidu.com/details?isfromtusoupc=1&tn=pc&carousel=0&promotion_name=pc_image_shituindex&extUiData%5bisLogoShow%5d=1&image={%s}",
  38. "Bing": "https://www.bing.com/images/searchbyimage?cbir=sbi&iss=sbi&imgurl={%s}",
  39. "TinEye": "https://www.tineye.com/search?url={%s}",
  40. //"Cydral": "http://www.cydral.com/#url={%s}",
  41. "Yandex": "https://yandex.com/images/search?rpt=imageview&url={%s}", // change "Яндекс (Yandex)" to "Yandex"
  42. "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",
  43. "360 ShiTu": "http://st.so.com/stu?imgurl={%s}",
  44. "SauceNAO": "https://saucenao.com/search.php?db=999&url={%s}",
  45. "IQDB": "https://iqdb.org/?url={%s}",
  46. "3D IQDB": "https://3d.iqdb.org/?url={%s}",
  47. "WhatAnime": "https://trace.moe/?url={%s}",
  48. "Ascii2D": "https://ascii2d.net/search/url/{%s}"
  49. },
  50. "site_option": ["Google", "Baidu", "Bing", "TinEye", "Yandex", "Sogou", "360 ShiTu", "SauceNAO", "IQDB", "3D IQDB", "WhatAnime", "Ascii2D"],
  51. "hot_key": "ctrlKey",
  52. "server_url": "//sbi.ccloli.com/img/upload.php"
  53. };
  54.  
  55. /*var server_url = "//sbi.ccloli.com/img/upload.php";*/
  56. // 请直接在设置页进行设置(Firefox 请尽量选择支持 https 的服务器)
  57. // 地址前使用"//"表示按照当前页面设定决定是否使用 https
  58. // 地址前使用"http://"表示强制使用 http
  59. // 地址前使用"https://"表示强制使用 https(需确认服务器支持 ssl)
  60. // 如果需要自己架设上传服务器的话请访问 GitHub 项目页(https://github.com/ccloli/Search-By-Image)获取服务端
  61. // 其他可用的上传服务器如下:
  62. // Heroku: //search-by-image.herokuapp.com/img/upload.php (支持 https)
  63. // BeGet: http://fh13121a.bget.ru/img/upload.php (不支持 https)
  64. // OpenShift: //searchbyimage-864907600cc.rhcloud.com/img/upload.php (支持 https)
  65. // DigitalOcean VPS: //sbi.ccloli.com/img/upload.php (支持 https,thanks to Retaker)
  66. // 注意,部分服务器可能仅支持 http 协议,若您选择了这些服务器,请务必注明 "http://",且若您使用的是 Firefox 浏览器,在 https 页面下将不能上传文件搜索搜索(除非设置 security.mixed_content.block_active_content 为 false)
  67.  
  68. var search_panel = null;
  69. var setting = default_setting;
  70. var disable_contextmenu = false;
  71. var img_src = null;
  72. var data_version = 0;
  73. var last_update = 0;
  74. var xhr = new XMLHttpRequest();
  75. var reader = new FileReader();
  76. reader.onload = function(file) {
  77. upload_file(this.result);
  78. };
  79. var asyncGMAPI = false;
  80.  
  81. var i18n = {
  82. 'zh': {
  83. 'u2s': '上传图片并搜索',
  84. 'dh': '拖拽文件至此',
  85. 'ca': '确认终止上传文件吗?',
  86. 'a': '多搜',
  87. 'n': '名称',
  88. 'l': '地址(图片地址以 {%s} 代替)',
  89. 'cr': '确定将所有设置初始化么?\n(初始化将清除所有所有设置,且不可逆)',
  90. 'us': '上传完成!',
  91. 'uf': '上传失败!',
  92. 'sh': '热键',
  93. 'sa': '添加',
  94. 'sr': '重置',
  95. 'ss': '保存',
  96. 'sc': '取消'
  97. },
  98. 'en': {
  99. 'u2s': 'Upload image to search',
  100. 'dh': 'Drag file to here',
  101. 'ca': 'Are you sure to cancel uploading?',
  102. 'a': 'All',
  103. 'n': 'Name',
  104. 'l': 'Location (Image URL should be replace with {%s})',
  105. 'cr': 'Are you sure to reset all preferences (irreversible) ?',
  106. 'us': 'Upload finished!',
  107. 'uf': 'Upload failed!',
  108. 'sh': 'Hot Key',
  109. 'sa': 'Add',
  110. 'sr': 'Reset',
  111. 'ss': 'Save',
  112. 'sc': 'Cancel'
  113. }
  114. };
  115. var lang = i18n[navigator.language] ? navigator.language : navigator.languages ? navigator.languages.filter(function(elem) {
  116. return i18n[elem];
  117. })[0] : null;
  118. if (lang == null) lang = 'en';
  119.  
  120. var getValue;
  121. if (typeof GM_getValue === 'undefined' && typeof GM !== 'undefined') {
  122. self.GM_getValue = GM.getValue;
  123. self.GM_setValue = GM.setValue;
  124. self.GM_openInTab = GM.openInTab;
  125. self.GM_registerMenuCommand = GM.registerMenuCommand;
  126. getValue = GM.getValue;
  127. asyncGMAPI = true;
  128. } else {
  129. getValue = function(key, init) {
  130. return new Promise(function(resolve, reject) {
  131. try {
  132. resolve(GM_getValue(key, init));
  133. } catch (e) {
  134. reject(e);
  135. }
  136. });
  137. };
  138. }
  139.  
  140. function init() {
  141. return Promise.all([getValue('setting'), GM_getValue('version', 0), GM_getValue('timestamp', 0)]).then(function(res) {
  142. var s = res[0],
  143. v = res[1],
  144. t = res[2];
  145. setting = s ? JSON.parse(s) : default_setting;
  146. data_version = v;
  147. last_update = t;
  148. var cur = 9;
  149.  
  150. var applyMap = {
  151. 3: function() {
  152. var new_site_list = {};
  153. var new_site_option = [];
  154.  
  155. for (var i in setting.site_list) {
  156. // use for loop to keep order, will use array in 2.x
  157. switch (i) {
  158. case 'Baidu ShiTu':
  159. case 'Baidu Image':
  160. new_site_list['Baidu'] = default_setting.site_list['Baidu'];
  161. break;
  162.  
  163. case 'Bing':
  164. case 'Sogou':
  165. new_site_list[i] = default_setting.site_list[i];
  166. break;
  167.  
  168. default:
  169. new_site_list[i] = setting.site_list[i];
  170. }
  171. }
  172. new_site_list['WhatAnime'] = default_setting.site_list['WhatAnime'];
  173.  
  174. for (var i = 0; i < setting.site_option.length; i++) {
  175. if ((setting.site_option[i] === 'Baidu ShiTu' || setting.site_option[i] === 'Baidu Image') && !(/,?Baidu,?/.test(new_site_option.join(',')))) {
  176. new_site_option.push('Baidu');
  177. } else {
  178. new_site_option.push(setting.site_option[i]);
  179. }
  180. }
  181. new_site_option.push('WhatAnime');
  182.  
  183. setting.site_list = new_site_list;
  184. setting.site_option = new_site_option;
  185. },
  186. 4: function() {
  187. setting.site_list['Ascii2D'] = default_setting.site_list['Ascii2D'];
  188. setting.site_option.push('Ascii2D');
  189. },
  190. 5: function() {
  191. if (setting.site_list['WhatAnime']) {
  192. setting.site_list['WhatAnime'] = default_setting.site_list['WhatAnime'];
  193. }
  194. },
  195. 6: function() {
  196. if (setting.site_list['Baidu']) {
  197. setting.site_list['Baidu'] = default_setting.site_list['Baidu'];
  198. }
  199. },
  200. 7: function() {
  201. if (setting.site_list['Yandex']) {
  202. setting.site_list['Yandex'] = default_setting.site_list['Yandex'];
  203. }
  204. },
  205. 8: function() {
  206. if (setting.site_list['Google']) {
  207. setting.site_list['Google'] = default_setting.site_list['Google'];
  208. }
  209. if (setting.site_list['Sogou']) {
  210. setting.site_list['Sogou'] = default_setting.site_list['Sogou'];
  211. }
  212. }
  213. }
  214.  
  215. if (data_version < cur) {
  216. for (var i = data_version; i < cur; i++) {
  217. if (applyMap[i]) {
  218. applyMap[i]();
  219. }
  220. }
  221. set_setting(setting);
  222. GM_setValue('version', data_version = cur);
  223. }
  224.  
  225. var repeatTest = {};
  226. var finalOpt = [];
  227. for (var i = 0, len = setting.site_option.length; i < len; i++) {
  228. var cur = setting.site_option[i];
  229. if (!repeatTest[cur] && setting.site_list[cur]) {
  230. finalOpt.push(cur);
  231. repeatTest[cur] = 1;
  232. }
  233. }
  234. setting.site_option = finalOpt;
  235.  
  236. if (setting.server_url == null || setting.server_url == '') {
  237. setting.server_url = default_setting.server_url;
  238. set_setting(setting);
  239. }
  240. });
  241. }
  242.  
  243. var server_url = setting.server_url;
  244.  
  245. function set_setting(data) {
  246. GM_setValue('setting', JSON.stringify(data));
  247. GM_setValue('timestamp', new Date().getTime());
  248. }
  249.  
  250. function create_panel() {
  251. search_panel = document.createElement('div');
  252. 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;';
  253. document.body.appendChild(search_panel);
  254. var search_top = document.createElement('div');
  255. search_top.style.cssText = 'height: 24px; line-height: 24px; font-size: 12px; overflow: hidden; margin: 0 auto; padding: 0 5px;';
  256. search_top.className = 'image-search-top';
  257. 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>';
  258. search_panel.appendChild(search_top);
  259. var search_item = document.createElement('div');
  260. search_item.style.cssText = 'width: 100%; height: 24px; line-height: 24px; cursor: pointer;';
  261. search_item.className = 'image-search-item';
  262. for (var i in setting.site_list) {
  263. var search_item_child = search_item.cloneNode(true);
  264. search_item_child.textContent = i;
  265. search_item_child.setAttribute('search-option', i);
  266. search_panel.appendChild(search_item_child);
  267. }
  268.  
  269. var search_item_lenso = search_item.cloneNode(true);
  270. search_item_lenso.textContent = 'lenso.ai (≈5 seconds)';
  271. search_item_lenso.setAttribute('search-option', 'lenso');
  272. search_panel.appendChild(search_item_lenso);
  273.  
  274. search_item.textContent = 'All';
  275. search_item.setAttribute('search-option', 'all');
  276. search_panel.appendChild(search_item);
  277.  
  278. var search_item_setting = search_item.cloneNode(true);
  279. search_item_setting.textContent = 'Setting';
  280. search_item_setting.setAttribute('search-option', 'setting');
  281. search_panel.appendChild(search_item_setting);
  282. search_top.getElementsByTagName('input')[0].onchange = function() {
  283. reader.readAsDataURL(this.files[0]);
  284. };
  285. search_panel.ondragenter = function(event) {
  286. event.preventDefault();
  287. search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['dh'];
  288. };
  289. search_panel.ondragleave = function(event) {
  290. event.preventDefault();
  291. search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['u2s'];
  292. };
  293. search_panel.ondragover = function(event) {
  294. search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['dh'];
  295. event.preventDefault();
  296. };
  297. search_panel.ondrop = function(event) {
  298. event.stopPropagation();
  299. event.preventDefault();
  300. var files = event.target.files || event.dataTransfer.files;
  301. if (files[files.length - 1].type.indexOf('image') >= 0) reader.readAsDataURL(files[files.length - 1]);
  302. };
  303. search_top.getElementsByTagName('progress')[0].onclick = function() {
  304. if (xhr.readyState != 0 && confirm(i18n[lang]['ca']) == true) {
  305. xhr.abort();
  306. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-24px';
  307. }
  308. };
  309. if (navigator.userAgent.indexOf('Firefox') >= 0) {
  310. var paste_node_firefox = document.createElement('div');
  311. paste_node_firefox.setAttribute('contenteditable', 'true');
  312. paste_node_firefox.className = 'image-search-paste-node-firefox';
  313. paste_node_firefox.style.cssText = 'width: 0!important; height: 0!important; position: absolute; overflow: hidden;';
  314. paste_node_firefox.addEventListener('paste', get_clipboard, false);
  315. search_top.appendChild(paste_node_firefox);
  316. }
  317. }
  318.  
  319. function call_setting() {
  320. var setting_panel = document.createElement('div');
  321. 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;';
  322. document.body.appendChild(setting_panel);
  323. var setting_header = document.createElement('div');
  324. setting_header.style.cssText = 'width: 100%; height: 32px; line-height: 32px; font-size: 18px; line-height: 32px;';
  325. setting_header.className = 'image-search-setting-header';
  326. setting_header.textContent = 'Search By Image Setting';
  327. setting_panel.appendChild(setting_header);
  328. var setting_item = document.createElement('div');
  329. setting_item.style.cssText = 'width: 100%; height: 24px; line-height: 24px; margin: 1px 0;';
  330. setting_item.className = 'image-search-setting-title';
  331. 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>';
  332. setting_panel.appendChild(setting_item);
  333. for (var i in setting.site_list) {
  334. var setting_item_child = setting_item.cloneNode(true);
  335. setting_item_child.className = 'image-search-setting-item';
  336. 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>';
  337. setting_panel.appendChild(setting_item_child);
  338. setting_item_child.getElementsByTagName('div')[3].onclick = function() {
  339. var parent = this.parentElement;
  340. parent.parentElement.removeChild(parent);
  341. };
  342. }
  343. var setting_server = document.createElement('div');
  344. setting_server.className = 'image-search-setting-server';
  345. setting_server.innerHTML = 'Upload Server <input type="text" value="' + setting['server_url'] + '" placeholder="//sbi.ccloli.com/img/upload.php" style="width: 350px;">';
  346. setting_panel.appendChild(setting_server);
  347. var setting_footer = document.createElement('div');
  348. setting_footer.style.cssText = 'width: 100%; height: 32px; line-height: 32px; margin-top: 5px; text-align: right;';
  349. setting_footer.className = 'image-search-setting-footer';
  350. setting_panel.appendChild(setting_footer);
  351. var setting_hotkey = document.createElement('div');
  352. var setting_add = document.createElement('div');
  353. var setting_reset = document.createElement('div');
  354. var setting_save = document.createElement('div');
  355. var setting_cancel = document.createElement('div');
  356. setting_hotkey.style.cssText = 'height: 32px; display: inline-block; text-align: left; float: left;';
  357. setting_add.style.cssText = 'width: 90px; height: 32px; margin: 0 5px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  358. setting_reset.style.cssText = 'width: 90px; height: 32px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  359. setting_save.style.cssText = 'width: 90px; height: 32px; margin: 0 5px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  360. setting_cancel.style.cssText = 'width: 90px; height: 32px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
  361. setting_add.textContent = i18n[lang]['sa'];
  362. setting_reset.textContent = i18n[lang]['sr'];
  363. setting_save.textContent = i18n[lang]['ss'];
  364. setting_cancel.textContent = i18n[lang]['sc'];
  365. 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>';
  366. setting_footer.appendChild(setting_hotkey);
  367. setting_footer.appendChild(setting_add);
  368. setting_footer.appendChild(setting_reset);
  369. setting_footer.appendChild(setting_save);
  370. setting_footer.appendChild(setting_cancel);
  371. setting_add.onclick = function() {
  372. var setting_item_child = setting_item.cloneNode(true);
  373. setting_item_child.className = 'image-search-setting-item';
  374. 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>';
  375. setting_panel.insertBefore(setting_item_child, setting_footer);
  376. setting_item_child.getElementsByTagName('div')[3].onclick = function() {
  377. var parent = this.parentElement;
  378. parent.parentElement.removeChild(parent);
  379. };
  380. setting_panel.scrollTop = setting_panel.scrollHeight;
  381. };
  382. setting_reset.onclick = function() {
  383. if (confirm(i18n[lang]['cr']) == true) {
  384. setting = default_setting;
  385. set_setting(setting);
  386. setting_panel.outerHTML = '';
  387. if (search_panel != null) {
  388. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  389. search_panel = null;
  390. }
  391. call_setting();
  392. }
  393. };
  394. setting_save.onclick = function() {
  395. var setting_items = document.getElementsByClassName('image-search-setting-item');
  396. var setting_data = {
  397. "site_list": {},
  398. "site_option": [],
  399. "hot_key": null,
  400. "server_url": null
  401. };
  402. for (var i = 0; i < setting_items.length; i++) {
  403. if (setting_items[i].getElementsByTagName('input')[1].value != '') {
  404. if (setting_items[i].getElementsByTagName('input')[0].checked) setting_data.site_option.push(setting_items[i].getElementsByTagName('input')[1].value);
  405. setting_data.site_list[setting_items[i].getElementsByTagName('input')[1].value] = setting_items[i].getElementsByTagName('input')[2].value;
  406. }
  407. }
  408. setting_data.hot_key = setting_hotkey.getElementsByTagName('select')[0].value;
  409. setting_data.server_url = document.getElementsByClassName('image-search-setting-server')[0].getElementsByTagName('input')[0].value;
  410. if (setting_data.server_url == null || setting_data.server_url == '') {
  411. setting_data.server_url = default_setting.server_url;
  412. }
  413. setting = setting_data;
  414. server_url = setting.server_url;
  415. set_setting(setting);
  416. document.body.removeChild(setting_panel);
  417. if (search_panel != null) {
  418. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  419. search_panel = null;
  420. }
  421. };
  422. setting_cancel.onclick = function() {
  423. document.body.removeChild(setting_panel);
  424. };
  425. }
  426.  
  427. function upload_file(data) {
  428. if (xhr.readyState != 0) xhr.abort();
  429. xhr.onreadystatechange = function() {
  430. if (xhr.readyState == 4) {
  431. if (xhr.status == 200) {
  432. img_src = xhr.responseText;
  433. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '0px';
  434. search_panel.getElementsByClassName('search_top_url')[0].textContent = i18n[lang]['us'];
  435. }
  436. }
  437. };
  438. xhr.upload.onprogress = function(event) {
  439. search_panel.getElementsByTagName('progress')[0].value = event.loaded / event.total;
  440. };
  441. xhr.onerror = function() {
  442. alert(i18n[lang]['uf']);
  443. };
  444. var form = new FormData();
  445. xhr.open('POST', server_url);
  446. form.append('imgdata', data);
  447. xhr.send(form);
  448. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-48px';
  449. }
  450.  
  451. function get_clipboard(event) {
  452. var items = event.clipboardData.items;
  453. if (items[items.length - 1].type.indexOf('image') >= 0) reader.readAsDataURL(items[items.length - 1].getAsFile());
  454. }
  455.  
  456. function hide_panel() {
  457. if (!search_panel || !search_panel.parentElement) return;
  458. img_src = null;
  459. 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
  460. document.removeEventListener('paste', get_clipboard, false);
  461. }
  462.  
  463. function upload_blob_url(url) {
  464. if (!url) return;
  465. var req = new XMLHttpRequest();
  466. req.open('GET', url);
  467. req.responseType = 'blob';
  468. req.onload = function() {
  469. reader.readAsDataURL(req.response);
  470. };
  471. req.onerror = function() {
  472. alert(i18n[lang]['uf']);
  473. };
  474. req.send();
  475. }
  476.  
  477. document.addEventListener(document.onpointerdown === undefined ? 'mousedown' : 'pointerdown', function(event) {
  478. //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);
  479. if (disable_contextmenu == true) {
  480. document.oncontextmenu = null;
  481. disable_contextmenu = false;
  482. }
  483. if (event[setting.hot_key] == true && event.button == 2) {
  484. if (search_panel == null) create_panel();
  485. // GM 4.x api is async, so we cannot update it in time
  486. else {
  487. if (!asyncGMAPI) {
  488. if (last_update != GM_getValue('timestamp', 0)) {
  489. last_update = GM_getValue('timestamp', 0);
  490. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  491. setting = GM_getValue('setting') ? JSON.parse(GM_getValue('setting')) : default_setting;
  492. create_panel();
  493. } else document.body.appendChild(search_panel);
  494. } else {
  495. document.body.appendChild(search_panel);
  496. GM_getValue('timestamp', 0).then(function(t) {
  497. if (last_update != t) {
  498. last_update = t;
  499. search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
  500. GM_getValue('setting').then(function(s) {
  501. setting = s ? JSON.parse(s) : default_setting;
  502. });
  503. }
  504. });
  505. }
  506. }
  507. 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';
  508. 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';
  509. // Firefox doesn't support getComputedStyle(element).marginLeft/marginRight and it would return "0px" while the element's margin is "auto". See bugzila/381328.
  510. //search_panel.style.marginLeft = '-' + (navigator.userAgent.indexOf('Firefox') < 0 ? getComputedStyle(document.body).marginLeft : (document.documentElement.offsetWidth - document.body.offsetWidth) / 2 + 'px');
  511. //search_panel.style.marginTop = '-' + getComputedStyle(document.body).marginTop;
  512. disable_contextmenu = true;
  513. document.oncontextmenu = function() {
  514. return false;
  515. };
  516. if (event.target.tagName.toLowerCase() == 'img' && event.target.src != null) {
  517. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '0px';
  518. search_panel.getElementsByClassName('search_top_url')[0].textContent = event.target.src;
  519. if (/^data:\s*.*?;\s*base64,\s*/.test(event.target.src)) upload_file(event.target.src);
  520. else if (/^(?:blob:|filesystem:)/.test(event.target.src)) upload_blob_url(event.target.src);
  521. else img_src = event.target.src;
  522. } else {
  523. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-24px';
  524. var firefoxPasteNode = document.getElementsByClassName('image-search-paste-node-firefox')[0];
  525. if (navigator.userAgent.indexOf('Firefox') >= 0 && firefoxPasteNode) {
  526. firefoxPasteNode.innerHTML = '';
  527. firefoxPasteNode.focus();
  528. } else document.addEventListener('paste', get_clipboard, false);
  529. }
  530. } else if (search_panel != null) {
  531. if (event.target.compareDocumentPosition(search_panel) == 10 || event.target.compareDocumentPosition(search_panel) == 0) {
  532. if (event.target.className == 'image-search-item' && event.button == 0) {
  533. switch (event.target.getAttribute('search-option')) {
  534. case 'lenso':
  535. if (img_src != null) {
  536. // Loop through each site option
  537. for (var i = 0; i >= 0; i--) { //just execute once is ok.
  538. var rsrc = img_src;
  539. var turl = setting.site_list[setting.site_option[i]];
  540.  
  541. // Check if the URL requires encoding
  542. if (turl.split(/{%s+}/).shift().indexOf('?') >= 0) {
  543. var token = turl.match(/{%s+}/)[0];
  544. for (var j = 0; j < token.length - 3; j++) {
  545. rsrc = encodeURIComponent(rsrc);
  546. }
  547. }
  548.  
  549. // Make API call to search image
  550. fetch("https://lenso.ai/api/upload/process/url", {
  551. method: "POST",
  552. headers: {
  553. "Content-Type": "application/json"
  554. },
  555. body: JSON.stringify({
  556. "url": rsrc
  557. })
  558. })
  559. .then(response => {
  560. if (response.ok) {
  561. return response.json();
  562. } else {
  563. handleError(response.status);
  564. }
  565. })
  566. .then(data => {
  567. if (data && data.id) {
  568. // Construct the result URL and open it in a new tab
  569. var locale = "en"; // Set to current locale if needed
  570. var result_url = `https://lenso.ai/${locale}/results/${data.id}`;
  571. GM_openInTab(result_url, event[setting.hot_key]);
  572. }
  573. })
  574. .catch(error => {
  575. console.error("Error occurred:", error);
  576. handleError(500);
  577. });
  578. }
  579.  
  580. // Hide the panel after search is triggered
  581. hide_panel();
  582. }
  583. break;
  584. case 'all':
  585. if (img_src != null) {
  586. for (var i = setting.site_option.length - 1; i >= 0; i--) {
  587. var rsrc = img_src;
  588. var turl = setting.site_list[setting.site_option[i]];
  589. if (turl.split(/{%s+}/).shift().indexOf('?') >= 0) {
  590. var token = turl.match(/{%s+}/)[0];
  591. for (var j = 0; j < token.length - 3; j++) {
  592. rsrc = encodeURIComponent(rsrc);
  593. }
  594. }
  595. GM_openInTab(turl.replace(/{%s+}/, rsrc), event[setting.hot_key]);
  596. }
  597. hide_panel();
  598. }
  599. break;
  600. case 'setting':
  601. call_setting();
  602. hide_panel();
  603. break;
  604. default:
  605. if (img_src != null) {
  606. var rsrc = img_src;
  607. var turl = setting.site_list[event.target.getAttribute('search-option')];
  608. if (turl.split(/{%s+}/).shift().indexOf('?') >= 0) {
  609. var token = turl.match(/{%s+}/)[0];
  610. for (var j = 0; j < token.length - 3; j++) {
  611. rsrc = encodeURIComponent(rsrc);
  612. }
  613. }
  614.  
  615. GM_openInTab(turl.replace(/{%s+}/, rsrc), event[setting.hot_key]);
  616. hide_panel();
  617. }
  618. }
  619. } else if (event.button != 0) hide_panel();
  620. } else hide_panel();
  621. }
  622. }, true);
  623.  
  624. if (typeof GM_registerMenuCommand !== 'undefined') {
  625. var gm_callsetting = GM_registerMenuCommand('Search By Image Setting', call_setting);
  626. }
  627. init();