Google Search Extra Buttons

Add buttons (last 1/2/3 days, weeks, PDF search etc.) for results of Google search page

目前为 2015-11-30 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Google Search Extra Buttons
  3. // @name:ru GoogleSearchExtraButtons
  4. // @description Add buttons (last 1/2/3 days, weeks, PDF search etc.) for results of Google search page
  5. // @description:ru Кнопки вариантов поиска для результатов Google (1-2-3 дня, недели, PDF, ...)
  6. // @version 10.2015.11.30
  7. // @namespace spmbt.github.com
  8. // @include http://www.google.*/search*
  9. // @include https://www.google.*/search*
  10. // @include https://encrypted.google.*/search*
  11. // @include https://spmbt.github.io/googleSearchExtraButtons/saveYourLocalStorage.html
  12. // ==/UserScript==
  13. if(location.host=='spmbt.github.io'){
  14. window.addEventListener('message', function(ev){
  15. if(/^https?:\/\/www\.google\./.test(ev.origin)){
  16. var d = JSON.parse(ev.data), tok = d.tok, key = d.key;
  17. switch(d.do){
  18. case 'set':
  19. localStorage[key] = JSON.stringify(d.val);
  20. break;
  21. case 'get':
  22. var h = JSON.parse(localStorage[key]);
  23. break;
  24. case 'remove':
  25. localStorage.removeItem(key);
  26. }
  27. console.log('[io]', d, tok);
  28. ev.source.postMessage(JSON.stringify(h ? {tok: tok, h: h} : {tok: tok}), ev.origin);
  29. }},!1);
  30. }else
  31.  
  32. (function(setts){ //lang, sites, lastHoursLess
  33.  
  34. var $x = function(el, h){if(h) for(var i in h) el[i] = h[i]; return el;} //===extend===
  35. ,$pd = function(ev){ev.preventDefault();}
  36. ,d = document
  37. ,$e = function(g){ //===create or use existing element=== //g={el|clone,cl,ht,cs,at,atRemove,on,apT}
  38. g.el = g.el || g.clone ||'DIV';
  39. var o = g.o = g.clone && g.clone.cloneNode && g.clone.cloneNode(!0)
  40. || (typeof g.el =='string' ? d.createElement(g.el) : g.el);
  41. if(o){ //execute if exist
  42. if(g.cl)
  43. o.className = g.cl;
  44. if(g.clAdd)
  45. o.classList.add(g.clAdd);
  46. if(g.cs)
  47. $x(o.style, g.cs);
  48. if(g.ht || g.at){
  49. var at = g.at ||{}; if(g.ht) at.innerHTML = g.ht;}
  50. if(at)
  51. for(var i in at){
  52. if(i=='innerHTML') o[i] = at[i];
  53. else o.setAttribute(i, at[i]);}
  54. if(g.atRemove)
  55. for(var i in g.atRemove)
  56. o.removeAttribute(g.atRemove[i]);
  57. if(g.on)
  58. for(var i in g.on) if(g.on[i])
  59. o.addEventListener(i, g.on[i],!1);
  60. g.ap && o.appendChild(g.ap);
  61. g.apT && g.apT.appendChild(o);
  62. }
  63. return o;
  64. },
  65. addRules = function(css){
  66. var heads = d.getElementsByTagName('head')
  67. ,node = d.createElement('style');
  68. heads.length && heads[0].appendChild(node);
  69. node.appendChild(d.createTextNode(css));
  70. };
  71. /**
  72. * check occurrence of third-party event with growing interval
  73. * @constructor
  74. * @param{Number} h.t start period of check
  75. * @param{Number} h.i number of checks
  76. * @param{Number} h.m multiplier of period increment
  77. * @param{Function} h.check event condition
  78. * @param{Function} h.occur event handler
  79. */
  80. var Tout = function(h){
  81. var th = this;
  82. (function(){
  83. if((h.dat = h.check() )) //wait of positive result, then occurs
  84. h.occur();
  85. else if(h.i-- >0) //next slower step
  86. th.ww = setTimeout(arguments.callee, (h.t *= h.m) );
  87. })();
  88. }
  89. //for xLocStor:
  90. ,xLocStorOrigin = d.location.protocol +'//spmbt.github.io'
  91. ,qr, qrs ={} //set of queries "key-calls" (ок, toutLitt, toutLong, noService, noStorage)
  92. ,qrI = 0 //queries counter
  93. ,qrN = 12 //max number of waiting queries
  94. ,errIMax = 120, errNMax = errIMax //max number of errors
  95. ,ns ='googXButtons_' //namespace for keys
  96. ,getLocStor = function(name){
  97. return (JSON.parse(localStorage && localStorage[ns + name] ||'{}')).h;}
  98. ,removeLocStor = function(name){localStorage && localStorage.removeItem(ns + name);}
  99. ,listenMsg
  100. /**
  101. * external localStorage for using another domain if current domain storage is erased anywhere
  102. * @param{String} h.do - action: set|get|remove
  103. * @param{String} h.key
  104. * @param{Object|undefined} h.val (any type)
  105. * @param{Number|undefined} h.toutLitt
  106. * @param{Number|undefined} h.tout
  107. * @param{Function} h.cB - callback with one argument
  108. * @param{Function|undefined} h.err - callback for err with one argument
  109. */
  110. ,xLocStor = function(h){
  111. var h0 = h;
  112. h.toutLitt = h.toutLitt || 400;
  113. h.tout = h.tout || 4000;
  114. var ifr = d.getElementById('xLocStor')
  115. ,query = function(){
  116. if((qrI += 1) > qrN){
  117. xCatch('longQrs', null, h);
  118. return;}
  119. ifr.contentWindow.postMessage(JSON.stringify($x({
  120. do: h.do
  121. ,tok: token
  122. ,key: ns + h.key
  123. }, h.val !==undefined ? {val: h.val}:{}) )
  124. , xLocStorOrigin);
  125. qrs[token] = $x({ //for wait of response
  126. wToutLitt: (function(h, qrI, errIMax){return setTimeout(function(){
  127. qrI -= 1;
  128. if((errIMax -= 1) >=0)
  129. console.warn('toutLitt', h);
  130. chkErrMax();
  131. }, h.toutLitt);})(h, qrI, errIMax)
  132. ,wTout: (function(h, qrI){return setTimeout(function(){
  133. qrI -= 1;
  134. //xCatch('tout', null, h);
  135. //xLocStor(h0);
  136. }, h.tout);})(h, qrI)
  137. }, h);
  138. }
  139. ,token = +new Date() + (Math.random()+'').substr(1,8)
  140. ,el = h.el;
  141. delete h.el;
  142. if(ifr) query();
  143. else ifr = $e({
  144. el: 'iframe',
  145. at:{id: 'xLocStor'
  146. ,src: xLocStorOrigin +'/googleSearchExtraButtons/saveYourLocalStorage.html'},
  147. cs: {display: 'none'},
  148. on: {load: query},
  149. apT: el || d.body
  150. });
  151. if(!listenMsg) addEventListener('message', function(ev){
  152. if(ev.origin == xLocStorOrigin){ // {"tok":"<value>"[,"err":"<txt>"],"h":...}
  153. console.log('from_io', JSON.parse(ev.data))
  154. var resp = ev.data && ev.data[0] =='{' && JSON.parse(ev.data);
  155. if(!resp) xCatch('bad_format', resp, h);
  156. if(( qr = qrs[resp.tok] )){
  157. qrI -= 1;
  158. qr.cB(resp.h);
  159. var er = qr.err;
  160. delete qrs[resp.tok];} // else ignore unsufficient token
  161. if(resp.err && (!er || er(resp.err)) ) //individual or common error processing depends of er()
  162. xCatch(resp.err, resp, h);
  163. }},!1);
  164. listenMsg =1;
  165. },
  166. //for tests: localStorage.googXButtons_dwmyh = JSON.stringify({h:[1,2,1,1,1]})
  167. //$('#xLocStor').contentWindow.postMessage('{"do":"get","key":"googXButtons_dwmyh"}','https://spmbt.github.io')
  168. xCatch = function(er, resp, h){
  169. if((errIMax -= 1) >=0)
  170. console.error('tok:', resp && resp.tok ||'--','; err:', er,'; h:', h,'; respH:', resp && resp.h);
  171. chkErrMax();
  172. },
  173. chkErrMax = function(){if(!errIMax) console.error('Too many err messages:', errNMax)}
  174. ,$l ={ru:{
  175. 'search in PDF files':'поиск по документам PDF'
  176. ,'search in':'искать по'
  177. ,'from / to':'за период'
  178. ,'last':['за последний','за последние','за последнюю']
  179. ,'day':'сутки'
  180. ,'days':['дня','дней']
  181. ,'week':'неделю'
  182. ,'weeks':['недели','недель']
  183. ,'month':'месяц'
  184. ,'months':['месяца','месяцев']
  185. ,'year':'год'
  186. ,'years':['года','лет']
  187. ,'hour':'час'
  188. ,'hours':['часа','часов']
  189. ,'Settings':'Настройки'
  190. ,'of userscript':'юзерскрипта'
  191. ,'reload page for effect':'перезагрузить страницу'
  192. ,'Interface language':'Язык интерфейса'
  193. ,'Sites':'Сайты'
  194. },fr:{
  195. 'search in PDF files':'la recherche dans les fichiers PDF'
  196. ,'search in':'rechercher dans'
  197. ,'from / to':'pour la période'
  198. ,'last':['le dernier','dans les derniers','dans les derniers']
  199. ,'day':'jour'
  200. ,'days':['jours','jours']
  201. ,'week':'semaine'
  202. ,'weeks':['semaines','semaines']
  203. ,'month':'mois'
  204. ,'months':['mois','mois']
  205. ,'year':'an'
  206. ,'years':['ans','ans']
  207. ,'hour':'heure'
  208. ,'hours':['heures','heures']
  209. ,'Settings':'Paramètres'
  210. ,'of userscript':'de Userscript'
  211. ,'reload page for effect':'recharger la page pour effet'
  212. ,'Interface language':'Langue de l\'interface'
  213. ,'Sites':'Les sites'
  214. },de:{
  215. 'search in PDF files':'suche in PDF-Dateien'
  216. ,'search in':'suche in'
  217. ,'from / to':'im Zeitraum'
  218. ,'last':['letzte','letzte','letzte']
  219. ,'day':'Tag'
  220. ,'days':['Tage','Tagen']
  221. ,'week':'Woche'
  222. ,'weeks':['Wochen','Wochen']
  223. ,'month':'Monat'
  224. ,'months':['Monate','Monaten']
  225. ,'year':'Jahr'
  226. ,'years':['Jahre','Jahre']
  227. ,'hour':'Stunde'
  228. ,'hours':['Stunden','Stunden']
  229. ,'Settings':'Einstellungen'
  230. ,'of userscript':'von userscript'
  231. ,'reload page for effect':'nachladen Seite für Effekt'
  232. ,'Interface language':'Schnittstellensprache'
  233. ,'Sites':'Webseiten'
  234. },es:{
  235. 'search in PDF files':'búsqueda en archivos PDF'
  236. ,'search in':'busca en'
  237. ,'from / to':'para el período'
  238. ,'last':['el último','en los últimos','en los últimos']
  239. ,'day':'día'
  240. ,'days':['días','días']
  241. ,'week':'Semana'
  242. ,'weeks':['semanas','semanas']
  243. ,'month':'mes'
  244. ,'months':['meses','meses']
  245. ,'year':'año'
  246. ,'years':['años','años']
  247. ,'hour':'hora'
  248. ,'hours':['horas','horas']
  249. ,'Settings':'Ajustes'
  250. ,'of userscript':'de userscript'
  251. ,'reload page for effect':'página para efecto de recargar'
  252. ,'Interface language':'Idioma de interfaz'
  253. ,'Sites':'Sitios'
  254. }}; //if !lang, then no hints
  255. addRules('.siteList:hover button{display: block}'
  256. +'.gb_Ib >.gb_e{height:47px}.gb_Fb{z-index:1087}.tsf-p{z-index:203}'
  257. +'.lsbb .xButt,.lsbb >.siteList{opacity: 0.64; line-height:14px; width:34px; height:17px; padding:0 2px;'
  258. +'font-size:14px; border:1px solid transparent; background-color:#4889f1; color:#fff}'
  259. +'.lsbb >.siteList{width:32px; height:auto; padding:1px 0 2px; text-align:center}'
  260. +'.lsbb >.siteList .lsb{font-weight: normal; color:#d4d4d4}.lsbb .lsb:hover{opacity: 1; color:#fff}'
  261. +'.siteList .sett .txt{padding: 0 2px}'
  262. +'.siteList .settIn{display: none; width: 250px; padding: 2px 4px; text-align:left; border:1px solid #48f;'
  263. +'background-color:#eef; color:#336}'
  264. +'.siteList .settIn hr{margin:2px 0}'
  265. +'.siteList .sett:hover .settIn, .siteList .settIn.changed{display: block}'
  266. +'.siteList .settIn .reload{display: none}.siteList .settIn.changed .reload{display: block}');
  267. var S ={}, settsLength =0; for(var i in setts) settsLength++;
  268. for(var i in setts)
  269. {S[i] = setts[i]; settsLength--;}
  270. //xLocStor({op:'get', key: i, cB: (function(i){return function(dat){S[i] = dat; settsLength--;}})(i) }); //TODO promises
  271.  
  272. new Tout({t:120, i:8, m: 1.6
  273. ,check: function(){
  274. return /*!settsLength &&*/ d && d.getElementsByName("q") && d.getElementsByName('q')[0];
  275. },
  276. occur: function(){
  277. //alert(11)
  278. var lang = S.lang != null && S.lang || setts.lang
  279. ,sites = S.sites && S.sites.length && S.sites || setts.sites;
  280. sites = sites instanceof Array && sites || [sites] || setts.sites ||[];
  281. var strSites = sites.join('\n')
  282. ,$LSettings
  283. ,$L = $l[lang] || $l.ru; //default template of lang
  284. if(!lang || !$l[lang] || lang =='en') for(var l in $L){ //replace 'en' lang for default or substitution
  285. if($L[l] instanceof Array) for(var l2 in $L[l])
  286. $L[l][l2] = l;
  287. else
  288. $L[l] = l;
  289. }
  290. if(sites.length)
  291. sites.push($LSettings = $L['Settings']);
  292. var inputSearch = this.dat
  293. ,buttSearch = d.getElementsByName("btnG") && d.getElementsByName('btnG')[0]
  294. ,buttS ={
  295. PDF:{url:'filetype:pdf', txt:$L['search in PDF files']}
  296. ,site:{url:'site:'+ sites[0], txt:$L['search in']+' '+ sites[0], one:'day'} //you may comment this line
  297. ,'.. : ..':{url:'', txt:$L['from / to']}
  298. ,'1D':{url:'&tbs=qdr:d', txt:$L['last'][1] +' '+ $L['day'], one:'day', up:13}
  299. ,'7D':{url:'&tbs=qdr:w', txt:$L['last'][2] +' '+ $L['week'], one:'week', up:10}
  300. ,'1M':{url:'&tbs=qdr:m', txt:$L['last'][0] +' '+ $L['month'], one:'month', up:11}
  301. ,'1Y':{url:'&tbs=qdr:y', txt:$L['last'][0] +' '+ $L['year'], one:'year', up:10}
  302. ,'1H':{url:'&tbs=qdr:h', txt:$L['last'][0] +' '+ $L['hour'], one:'hour', up:23}
  303. }, ii =0;
  304. !sites && delete buttS.site;
  305. buttSearch.parentNode.style.position ='relative';
  306. //xLocStor({do:'get', key:'aaa', val:{}, cB: function(dat){console.info(112, dat);}, el: buttSearch.parentNode});
  307. if(buttSearch && top == self) for(var i in buttS) if(i !='site'|| S.sites){ //buttons under search input line
  308. var bI = buttS[i]
  309. , butt2 = $e({clone: i =='site'|| i.length ==2
  310. ? $e({cl: 'siteList', cs: {cursor:'default'}, at: {site: sites[0], date: bI.url} })
  311. : buttSearch
  312. ,clAdd:'xButt'
  313. ,atRemove: ['id', 'name']
  314. ,at: {value: i, innerHTML: '<span class=txt title="'+(lang || i=='site'|| i=='.. : ..' ? bI.txt :'')+'">'+ i +'</span>'}
  315. ,cs: {position: 'absolute', top: '33px', left: (-127 + 37 * ii++) +'px'}
  316. ,on: {click: (function(bI, i){
  317. //console.log('clic:',i,bI)
  318. return /PDF|site/.test(i)
  319. ? function(ev){
  320. if(!ev.target.getAttribute('site') || ev.target.getAttribute('site')==$LSettings) return;
  321. inputSearch.value = inputSearch.value.replace(/ site\:[\w.]+$/i, '')
  322. .replace(' filetype:pdf', '') +' '
  323. + (i =='PDF' ? bI.url : 'site:'+ ev.target.getAttribute('site'));
  324. if(ev.target.className =='siteList') this.form.click();
  325. }: !bI.url ? function(ev){
  326. var el = d.querySelector('#cdrlnk'), o;
  327. el && el.dispatchEvent(((o = d.createEvent('Events')).initEvent('click', !0, !1), o));
  328. $pd(ev);
  329. }: function(ev){
  330. location.href = '/search?q='+ encodeURIComponent(inputSearch.value)
  331. +(ev.target.getAttribute('date') + ev.target.getAttribute('site').replace(/\D/g,'') || bI.url);
  332. $pd(ev);
  333. }
  334. })(bI, i),
  335. mouseover: i =='site' || i.length ==2 ? (function(bI,i){return function(ev){
  336. clearTimeout(bI.ww);
  337. ev.currentTarget.querySelector('.list').style.display ='block';
  338. }})(bI,i) :'',
  339. mouseout: i =='site' || i.length ==2 ? (function(bI,i){return function(ev){
  340. var t = ev.currentTarget;
  341. clearTimeout(bI.ww);
  342. bI.ww = setTimeout(function(){
  343. t.querySelector('.list').style.display ='none';
  344. }, 450);
  345. }})(bI,i) :'',
  346. change: function(ev){
  347. xLocStor({op:'set', key: ev.target.name, val: ev.target.type=='INPUT'&& ev.target.value
  348. || ev.target.value.replace(/^[ \n]*|[ \n]*$/g,'').split('\n')
  349. ,cB: function(){console.info('Settings are saved.')}});
  350. d.querySelector('.siteList .settIn').classList.add('changed');
  351. } }
  352. ,apT: buttSearch.parentNode
  353. });
  354. bI.el = butt2;
  355. if(i =='site' || i.length ==2){ //dropdown lists under some buttons
  356. var siteList = $e({cl:'list',cs:{display:'none'}, apT: butt2}), arr =[];
  357. for(var j =0; j <= bI.up -1; j++) if(i !='1H' || !S.lastHoursLess || j < 8 || j % 2 )
  358. arr.push((j+1) +' '+ (j % 10 || j==10 ? $L[bI.one +'s'][j % 10 <4 && (j/10|0)!=1 ?0:1] : $L[bI.one]));
  359. if(!S.sites && i =='1H')
  360. arr.push($LSettings = $L['Settings']);
  361. var list = i == 'site' ? sites : arr;
  362. for(var j in list) if(j !=0)
  363. var sI = list[j]
  364. ,butt3 = $e({clone: sI==$LSettings
  365. ? $e({cl: 'sett lsb'})
  366. : buttSearch
  367. ,clAdd:'xButt'
  368. ,atRemove:['id','name']
  369. ,at:{value: sI
  370. ,site: sI
  371. ,date: bI.url
  372. ,title: sI==$LSettings || !lang ?'':(i =='site'?$L['search in']:$L['last'][1]) +' '+ sI
  373. ,innerHTML:'<span class=txt>'+ sI +'</span>'+ (sI != $LSettings &&!(!S.sites && i =='1H')
  374. ?'':'<div class="settIn">'
  375. +$L.Settings +' '+ $L['of userscript'] +'<br>"Google Search Extra Buttons"<hr>'
  376. +$L['Interface language'] +': <input size=4 value="'+ lang +'"/><br>' //TODO select Tag for accessible langs
  377. +$L['Sites'] +': <br><textarea style="width:97%" rows=8>'
  378. + strSites +'</textarea><br>'
  379. +'<a class="reload" href=# onclick="location.reload();return!1">'
  380. + $L['reload page for effect'] +'</a>'
  381. +'</div>')}
  382. ,cs: {position: sI != $LSettings ?'static':'absolute',display:'block', width:'auto', height: sI != $LSettings ?'18px':'16px'
  383. ,margin:'2px 0 -1px -13px', padding:0, textAlign:'left', fontWeight:'normal', opacity:1}
  384. ,apT: siteList
  385. });
  386. siteList.style.height ='auto'; siteList.style.textAlign ='center';
  387. }
  388. }
  389. }
  390. });
  391.  
  392. })({ //write "lang:''," to remove hints; 'en' for English hints (fr - Français, es - espagnol), 'ru' for Russian
  393. lang:''|| (navigator.languages && navigator.languages[1] || navigator.language.substr(0,2)) //='' if hide hints, or 2 letters from $l{}
  394. ,sites: [ //=array or one site in string
  395. 'slashdot.org','engadget.com','techcrunch.com','habrahabr.ru','geektimes.ru'
  396. ,'smashingmagazine.com','maketecheasier.com'] //write your favorite sites
  397. ,lastHoursLess: 1 //=boolean - not show odd values of hours after 8 h
  398. ,dwmyh: [1,1,1,1,1] //=array of numbers - current vals of days, weeks, months, years, hours
  399. });