Search By Image

Search By Image | 以图搜图

目前为 2014-09-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Search By Image
  3. // @version 1.4.5
  4. // @description Search By Image | 以图搜图
  5. // @match <all_urls>
  6. // @include *
  7. // @author 864907600cc
  8. // @icon http://1.gravatar.com/avatar/147834caf9ccb0a66b2505c753747867
  9. // @run-at document-start
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_openInTab
  13. // @grant GM_registerMenuCommand
  14. // @namespace http://ext.ccloli.com
  15. // ==/UserScript==
  16.  
  17. // 本脚本基于 GPLv3 协议开源 http://www.gnu.org/licenses/gpl.html‎
  18. // (c) 86497600cc. Some Rights Reserved.
  19. // Default setting: Press Ctrl and click right key on a image to search.
  20.  
  21. 'use strict';
  22. var default_setting={
  23. "site_list":{
  24. "Google":"https://www.google.com/searchbyimage?image_url={%s}",
  25. "Baidu ShiTu":"http://stu.baidu.com/i?ct=1&tn=baiduimage&objurl={%s}",
  26. "Baidu Image":"http://image.baidu.com/i?rainbow=1&ct=1&tn=shituresultpc&objurl={%s}",
  27. "Bing":"http://cn.bing.com/images/searchbyimage?FORM=IRSBIQ&cbir=sbi&imgurl={%s}",
  28. "TinEye":"http://www.tineye.com/search?url={%s}",
  29. //"Cydral":"http://www.cydral.com/#url={%s}",
  30. "Яндекс (Yandex)":"http://yandex.ru/images/search?rpt=imageview&img_url={%s}",
  31. "Sogou":"http://pic.sogou.com/ris?query={%s}",
  32. "360 ShiTu":"http://st.so.com/stu?imgurl={%s}",
  33. "SauceNAO":"http://saucenao.com/search.php?db=999&url={%s}",
  34. "IQDB":"http://iqdb.org/?url={%s}",
  35. "3D IQDB":"http://3d.iqdb.org/?url={%s}"
  36. },
  37. "site_option":["Google","Baidu ShiTu","Baidu Image","Bing","TinEye","Яндекс (Yandex)","Sogou","360 ShiTu","SauceNAO","IQDB","3D IQDB"],
  38. "hot_key":"ctrlKey"
  39. };
  40.  
  41. var server_url="//sbi.ccloli.com/img/upload.php";
  42. // 在此处直接输入完整的上传页面的地址(Firefox 请尽量选择支持 https 的服务器)
  43. // 地址前使用"//"表示按照当前页面设定决定是否使用 https
  44. // 地址前使用"http://"表示强制使用 http
  45. // 地址前使用"https://"表示强制使用 https(需确认服务器支持 ssl)
  46. // 如果需要自己架设上传服务器的话请访问 GitHub 项目页(https://github.com/ccloli/Search-By-Image)获取服务端
  47. // 其他可用的上传服务器如下:
  48. // Heroku: //search-by-image.herokuapp.com/img/upload.php (支持 https)
  49. // BeGet: http://fh13121a.bget.ru/img/upload.php (不支持 https)
  50. // OpenShift: //searchbyimage-864907600cc.rhcloud.com/img/upload.php (支持 https)
  51. // DigitalOcean VPS: //sbi.ccloli.com/img/upload.php (支持 https,thanks to Retaker)
  52. // 注意,部分服务器可能仅支持 http 协议,若您选择了这些服务器,请务必注明 "http://",且若您使用的是 Firefox 浏览器,在 https 页面下将不能上传文件搜索搜索(除非设置 security.mixed_content.block_active_content 为 false)
  53.  
  54. var search_panel=null;
  55. var setting=GM_getValue('setting')?JSON.parse(GM_getValue('setting')):default_setting;
  56. var disable_contextmenu=false;
  57. var img_src=null;
  58. var data_version=GM_getValue('version',0);
  59. var last_update=GM_getValue('timestamp',0);
  60. var xhr=new XMLHttpRequest();
  61. var reader=new FileReader();
  62. reader.onload=function(file){upload_file(this.result);};
  63.  
  64. function set_setting(data){
  65. GM_setValue('setting',JSON.stringify(data));
  66. GM_setValue('timestamp',new Date().getTime());
  67. }
  68.  
  69. function create_panel(){
  70. search_panel=document.createElement('div');
  71. 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';
  72. document.body.appendChild(search_panel);
  73. var search_top=document.createElement('div');
  74. search_top.style.cssText='width:90%;height:24px;line-height:24px;font-size:12px;overflow:hidden;margin:0 auto';
  75. search_top.className='image-search-top';
  76. 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">上传图片并搜索</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.2s linear}.image-search-item::hover{color:#5B97FF;background:#EEEEEE}';
  77. search_panel.appendChild(search_top);
  78. var search_item=document.createElement('div');
  79. search_item.style.cssText='width:100%;height:24px;line-height:24px;cursor:pointer';
  80. search_item.className='image-search-item';
  81. for(var i in setting.site_list){
  82. var search_item_child=search_item.cloneNode(true);
  83. search_item_child.textContent=i;
  84. search_item_child.setAttribute('search-option',i);
  85. search_panel.appendChild(search_item_child);
  86. }
  87. search_item.textContent='All';
  88. search_item.setAttribute('search-option','all');
  89. search_panel.appendChild(search_item);
  90. var search_item_setting=search_item.cloneNode(true);
  91. search_item_setting.textContent='Setting';
  92. search_item_setting.setAttribute('search-option','setting');
  93. search_panel.appendChild(search_item_setting);
  94. search_top.getElementsByTagName('input')[0].onchange=function(){reader.readAsDataURL(this.files[0]);};
  95. search_panel.ondragenter=function(event){
  96. event.preventDefault();
  97. search_top.getElementsByTagName('label')[0].textContent='拖拽文件至此';
  98. };
  99. search_panel.ondragleave=function(event){
  100. event.preventDefault();
  101. search_top.getElementsByTagName('label')[0].textContent='上传图片并搜索';
  102. };
  103. search_panel.ondragover=function(event){
  104. search_top.getElementsByTagName('label')[0].textContent='拖拽文件至此';
  105. event.preventDefault();
  106. };
  107. search_panel.ondrop=function(event){
  108. event.stopPropagation();
  109. event.preventDefault();
  110. var files=event.target.files||event.dataTransfer.files;
  111. if(files[files.length-1].type.indexOf('image')>=0)reader.readAsDataURL(files[files.length-1]);
  112. };
  113. search_top.getElementsByTagName('progress')[0].onclick=function(){
  114. if(xhr.readyState!=0&&confirm('确认终止上传文件吗?')==true){
  115. xhr.abort();
  116. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop='-24px';
  117. }
  118. };
  119. if(navigator.userAgent.indexOf('Firefox')>=0){
  120. var paste_node_firefox=document.createElement('div');
  121. paste_node_firefox.setAttribute('contenteditable','true');
  122. paste_node_firefox.className='image-search-paste-node-firefox';
  123. paste_node_firefox.style.cssText='width:0!important;height:0!important;position:absolute;overflow:hidden';
  124. paste_node_firefox.addEventListener('paste',function(event){
  125. setTimeout(function(){
  126. var _images=paste_node_firefox.getElementsByTagName('img');
  127. if(_images.length>0){
  128. var _img_src=_images[_images.length-1].src;
  129. if(_img_src.match(/^data:.*?;base64,/))upload_file(_img_src);
  130. }
  131. },500);
  132. },false);
  133. search_top.appendChild(paste_node_firefox);
  134. }
  135. }
  136.  
  137. function call_setting(){
  138. var setting_panel=document.createElement('div');
  139. 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';
  140. document.body.appendChild(setting_panel);
  141. var setting_header=document.createElement('div');
  142. setting_header.style.cssText='width:100%;height:32px;line-height:32px;font-size:18px;line-height:32px';
  143. setting_header.className='image-search-setting-header';
  144. setting_header.textContent='Search By Image Setting';
  145. setting_panel.appendChild(setting_header);
  146. var setting_item=document.createElement('div');
  147. setting_item.style.cssText='width:100%;height:24px;line-height:24px;margin:1px 0';
  148. setting_item.className='image-search-setting-title';
  149. setting_item.innerHTML='<div style="text-align:center;display:inline-block;width:30px">多搜</div><div style="width:100px;text-align:center;display:inline-block">名称</div><div style="width:350px;text-align:center;display:inline-block">地址(图片地址以 {%s} 代替)</div><div style="width:20px;display:inline-block"></div>';
  150. setting_panel.appendChild(setting_item);
  151. for(var i in setting.site_list){
  152. var setting_item_child=setting_item.cloneNode(true);
  153. setting_item_child.className='image-search-setting-item';
  154. 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>';
  155. setting_panel.appendChild(setting_item_child);
  156. setting_item_child.getElementsByTagName('div')[3].onclick=function(){this.parentElement.outerHTML='';};
  157. }
  158. var setting_footer=document.createElement('div');
  159. setting_footer.style.cssText='width:100%;height:32px;line-height:32px;margin-top:5px;text-align:right';
  160. setting_footer.className='image-search-setting-footer';
  161. setting_panel.appendChild(setting_footer);
  162. var setting_hotkey=document.createElement('div');
  163. var setting_add=document.createElement('div');
  164. var setting_reset=document.createElement('div');
  165. var setting_save=document.createElement('div');
  166. var setting_cancel=document.createElement('div');
  167. setting_hotkey.style.cssText='height:32px;display:inline-block;text-align:left;float:left';
  168. setting_add.style.cssText='width:90px;height:32px;margin:0 5px;background:#666;color:#FFF;display:inline-block;text-align:center;cursor:pointer';
  169. setting_reset.style.cssText='width:90px;height:32px;background:#666;color:#FFF;display:inline-block;text-align:center;cursor:pointer';
  170. setting_save.style.cssText='width:90px;height:32px;margin:0 5px;background:#666;color:#FFF;display:inline-block;text-align:center;cursor:pointer';
  171. setting_cancel.style.cssText='width:90px;height:32px;background:#666;color:#FFF;display:inline-block;text-align:center;cursor:pointer';
  172. setting_add.textContent='Add Item';
  173. setting_reset.textContent='Reset';
  174. setting_save.textContent='Save';
  175. setting_cancel.textContent='Cancel';
  176. setting_hotkey.innerHTML='Hot Key <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>';
  177. setting_footer.appendChild(setting_hotkey);
  178. setting_footer.appendChild(setting_add);
  179. setting_footer.appendChild(setting_reset);
  180. setting_footer.appendChild(setting_save);
  181. setting_footer.appendChild(setting_cancel);
  182. setting_add.onclick=function(){
  183. var setting_item_child=setting_item.cloneNode(true);
  184. setting_item_child.className='image-search-setting-item';
  185. 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>';
  186. setting_panel.insertBefore(setting_item_child,setting_footer);
  187. setting_item_child.getElementsByTagName('div')[3].onclick=function(){this.parentElement.outerHTML='';};
  188. setting_panel.scrollTop=setting_panel.scrollHeight;
  189. };
  190. setting_reset.onclick=function(){
  191. if(confirm('确定将所有设置初始化么?\n\n(初始化将清除所有所有设置,且不可逆)')==true){
  192. setting=default_setting;
  193. set_setting(setting);
  194. setting_panel.outerHTML='';
  195. if(search_panel!=null){
  196. search_panel.outerHTML='';
  197. search_panel=null;
  198. }
  199. call_setting();
  200. }
  201. };
  202. setting_save.onclick=function(){
  203. var setting_items=document.getElementsByClassName('image-search-setting-item');
  204. var setting_data={"site_list":{},"site_option":[],"hot_key":null};
  205. for(var i=0;i<setting_items.length;i++){
  206. if(setting_items[i].getElementsByTagName('input')[1].value!=''){
  207. if(setting_items[i].getElementsByTagName('input')[0].checked)setting_data.site_option.push(setting_items[i].getElementsByTagName('input')[1].value);
  208. setting_data.site_list[setting_items[i].getElementsByTagName('input')[1].value]=setting_items[i].getElementsByTagName('input')[2].value;
  209. }
  210. }
  211. setting_data.hot_key=setting_hotkey.getElementsByTagName('select')[0].value;
  212. console.log(setting_data);
  213. setting=setting_data;
  214. set_setting(setting);
  215. setting_panel.outerHTML='';
  216. if(search_panel!=null){
  217. search_panel.outerHTML='';
  218. search_panel=null;
  219. }
  220. };
  221. setting_cancel.onclick=function(){setting_panel.outerHTML='';};
  222. }
  223.  
  224. function upload_file(data){
  225. if(xhr.readyState!=0)xhr.abort();
  226. xhr.onreadystatechange=function(){
  227. if(xhr.readyState==4){
  228. if(xhr.status==200){
  229. img_src=xhr.responseText;
  230. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop='0px';
  231. search_panel.getElementsByClassName('search_top_url')[0].textContent='上传完成!';
  232. }
  233. }
  234. };
  235. xhr.upload.onprogress=function(event){search_panel.getElementsByTagName('progress')[0].value=event.loaded/event.total;};
  236. xhr.onerror=function(){alert('上传失败!');};
  237. var form=new FormData();
  238. xhr.open('POST',server_url);
  239. form.append('imgdata',data);
  240. xhr.send(form);
  241. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop='-48px';
  242. }
  243.  
  244. function get_clipboard(event){
  245. var items=event.clipboardData.items;
  246. if(items[items.length-1].type.indexOf('image')>=0)reader.readAsDataURL(items[items.length-1].getAsFile());
  247. }
  248.  
  249. function hide_panel(){
  250. img_src=null;
  251. search_panel.style.display='none';
  252. document.removeEventListener('paste',get_clipboard,false);
  253. }
  254.  
  255. document.addEventListener('mousedown',function(event){ // In order to fix a bug on Chrome Tampermonkey
  256. //document.onmousedown=function(event){
  257. //console.log('Search Image >>\nevent.ctrlKey: '+event.ctrlKey+'\nevent.button: '+event.button+'\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);
  258. if(disable_contextmenu==true){
  259. document.oncontextmenu=null;
  260. disable_contextmenu=false;
  261. }
  262. if(event[setting.hot_key]==true&&event.button==2){
  263. if(search_panel==null)create_panel();
  264. else if(last_update!=GM_getValue('timestamp',0)){
  265. last_update=GM_getValue('timestamp',0)
  266. search_panel.outerHTML='';
  267. setting=GM_getValue('setting')?JSON.parse(GM_getValue('setting')):default_setting;
  268. create_panel();
  269. }
  270. else search_panel.style.display='block';
  271. search_panel.style.left=(document.documentElement.clientWidth+document.body.scrollLeft-event.pageX>=200?event.pageX:event.pageX>=200?event.pageX-200:0)+'px';
  272. search_panel.style.top=(document.documentElement.scrollHeight-event.pageY>=search_panel.scrollHeight?event.pageY:event.pageY>=search_panel.scrollHeight?event.pageY-search_panel.scrollHeight:0)+'px';
  273. disable_contextmenu=true;
  274. //for(var i=0;i<setting.site_option.length;i++)GM_openInTab(setting.site_list[setting.site_option[i]].replace(/\{%s\}/,encodeURIComponent(event.target.src)));
  275. document.oncontextmenu=function(){return false;};
  276. if(event.target.tagName.toLowerCase()=='img'&&event.target.src!=null){
  277. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop='0px';
  278. search_panel.getElementsByClassName('search_top_url')[0].textContent=event.target.src;
  279. if(event.target.src.match(/^data:.*?;base64,/)!=null)upload_file(event.target.src);
  280. else img_src=event.target.src;
  281. }
  282. else{
  283. search_panel.getElementsByClassName('search_top_url')[0].style.marginTop='-24px';
  284. if(navigator.userAgent.indexOf('Firefox')>=0){
  285. document.getElementsByClassName('image-search-paste-node-firefox')[0].innerHTML='';
  286. document.getElementsByClassName('image-search-paste-node-firefox')[0].focus();
  287. }
  288. else document.addEventListener('paste',get_clipboard,false);
  289. }
  290. }
  291. else if(search_panel!=null){
  292. if(event.target.compareDocumentPosition(search_panel)==10||event.target.compareDocumentPosition(search_panel)==0){
  293. if(event.target.className=='image-search-item'&&event.button==0){
  294. switch(event.target.getAttribute('search-option')){
  295. case 'all':
  296. if(img_src!=null){
  297. for(var i=setting.site_option.length-1;i>=0;i--)GM_openInTab(setting.site_list[setting.site_option[i]].replace(/\{%s\}/,encodeURIComponent(img_src)));
  298. hide_panel();
  299. }
  300. break;
  301. case 'setting':
  302. call_setting();
  303. hide_panel();
  304. break;
  305. default:
  306. if(img_src!=null){
  307. GM_openInTab(setting.site_list[event.target.getAttribute('search-option')].replace(/\{%s\}/,encodeURIComponent(img_src)));
  308. hide_panel();
  309. }
  310. }
  311. }
  312. else if(event.button!=0)hide_panel();
  313. }
  314. else hide_panel();
  315. }
  316. },false);
  317.  
  318. var gm_callsetting=GM_registerMenuCommand('Search By Image Setting',call_setting);