Google Tools Button Clicker

在Google搜索结果的页面上自动单击工具按钮。

  1. // ==UserScript==
  2. // @name Google Tools Button Clicker
  3. // @name:ja Google Tools Button Clicker
  4. // @name:zh-CN Google Tools Button Clicker
  5. // @description Automatically clicks Tools button on Google search.
  6. // @description:ja Google検索結果のページでツールボタンを自動的にクリックします。
  7. // @description:zh-CN 在Google搜索结果的页面上自动单击工具按钮。
  8. // @namespace knoa.jp
  9. // @include https://www.google.*/search*
  10. // @version 3.0.0
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function(){
  15. const SCRIPTID = 'GoogleToolsButtonClicker';
  16. const SCRIPTNAME = 'Google Tools Button Clicker';
  17. const DEBUG = false;/*
  18. [update] 3.0.0
  19. Fix for google's update. It no longer auto-clicks on mouseovers. Internal changes.
  20.  
  21. [bug]
  22.  
  23. [todo]
  24.  
  25. [possible]
  26.  
  27. [research]
  28.  
  29. [memo]
  30. */
  31. if(window === top && console.time) console.time(SCRIPTID);
  32. const MS = 1, SECOND = 1000*MS, MINUTE = 60*SECOND, HOUR = 60*MINUTE, DAY = 24*HOUR, WEEK = 7*DAY, MONTH = 30*DAY, YEAR = 365*DAY;
  33. const site = {
  34. targets: {
  35. tools: () => document.querySelector('#hdtb-tls'),
  36. },
  37. };
  38. let elements = {}, flags = {}, timers = {};
  39. const core = {
  40. initialize: function(){
  41. elements.html = document.documentElement;
  42. elements.html.classList.add(SCRIPTID);
  43. core.ready();
  44. },
  45. ready: function(){
  46. core.getTargets(site.targets).then(() => {
  47. log("I'm ready.");
  48. core.click();
  49. }).catch(e => {
  50. console.error(`${SCRIPTID}:`, e);
  51. });
  52. },
  53. click: function(){
  54. timers.expand = setInterval(() => {
  55. const tools = elements.tools, click = tools.click.bind(tools);
  56. const activeElement = document.activeElement;
  57. /* already opened? */
  58. if(tools.getAttribute('aria-expanded') === 'true') return clearInterval(timers.expand);
  59. /* now being closed, so */
  60. tools.click(), activeElement.focus();
  61. }, 250);
  62. },
  63. getTarget: function(selector, retry = 10, interval = 1*SECOND){
  64. const key = selector.name;
  65. const get = function(resolve, reject){
  66. let selected = selector();
  67. if(selected === null || selected.length === 0){
  68. if(--retry) return log(`Not found: ${key}, retrying... (${retry})`), setTimeout(get, interval, resolve, reject);
  69. else return reject(new Error(`Not found: ${selector.name}, I give up.`));
  70. }else{
  71. if(selected.nodeType === Node.ELEMENT_NODE) selected.dataset.selector = key;/* element */
  72. else selected.forEach((s) => s.dataset.selector = key);/* elements */
  73. elements[key] = selected;
  74. resolve(selected);
  75. }
  76. };
  77. return new Promise(function(resolve, reject){
  78. get(resolve, reject);
  79. });
  80. },
  81. getTargets: function(selectors, retry = 10, interval = 1*SECOND){
  82. return Promise.all(Object.values(selectors).map(selector => core.getTarget(selector, retry, interval)));
  83. },
  84. addStyle: function(name = 'style', d = document){
  85. if(html[name] === undefined) return;
  86. if(d.head){
  87. let style = createElement(html[name]()), id = SCRIPTID + '-' + name, old = d.getElementById(id);
  88. style.id = id;
  89. d.head.appendChild(style);
  90. if(old) old.remove();
  91. }
  92. else{
  93. let observer = observe(d.documentElement, function(){
  94. if(!d.head) return;
  95. observer.disconnect();
  96. core.addStyle(name);
  97. });
  98. }
  99. },
  100. };
  101. const html = {
  102. style: () => `
  103. <style type="text/css">
  104. </style>
  105. `,
  106. };
  107. const setTimeout = window.setTimeout.bind(window), clearTimeout = window.clearTimeout.bind(window), setInterval = window.setInterval.bind(window), clearInterval = window.clearInterval.bind(window), requestAnimationFrame = window.requestAnimationFrame.bind(window), requestIdleCallback = window.requestIdleCallback.bind(window);
  108. const alert = window.alert.bind(window), confirm = window.confirm.bind(window), prompt = window.prompt.bind(window), getComputedStyle = window.getComputedStyle.bind(window), fetch = window.fetch.bind(window);
  109. if(!('isConnected' in Node.prototype)) Object.defineProperty(Node.prototype, 'isConnected', {get: function(){return document.contains(this)}});
  110. const $ = function(s, f = undefined){
  111. let target = document.querySelector(s);
  112. if(target === null) return null;
  113. return f ? f(target) : target;
  114. };
  115. const $$ = function(s, f = undefined){
  116. let targets = document.querySelectorAll(s);
  117. return f ? f(targets) : targets;
  118. };
  119. const createElement = function(html = '<div></div>'){
  120. let outer = document.createElement('div');
  121. outer.insertAdjacentHTML('afterbegin', html);
  122. return outer.firstElementChild;
  123. };
  124. const observe = function(element, callback, options = {childList: true, subtree: false, characterData: false, attributes: false, attributeFilter: undefined}){
  125. let observer = new MutationObserver(callback.bind(element));
  126. observer.observe(element, options);
  127. return observer;
  128. };
  129. const match = function(string, regexp, f, g = undefined){
  130. let m = string.match(regexp);
  131. if(m !== null) return f(m);
  132. else return g ? g() : null;
  133. };
  134. const log = function(){
  135. if(typeof DEBUG === 'undefined') return;
  136. let l = log.last = log.now || new Date(), n = log.now = new Date();
  137. let error = new Error(), line = log.format.getLine(error), callers = log.format.getCallers(error);
  138. //console.log(error.stack);
  139. console.log(
  140. SCRIPTID + ':',
  141. /* 00:00:00.000 */ n.toLocaleTimeString() + '.' + n.getTime().toString().slice(-3),
  142. /* +0.000s */ '+' + ((n-l)/1000).toFixed(3) + 's',
  143. /* :00 */ ':' + line,
  144. /* caller.caller */ (callers[2] ? callers[2] + '() => ' : '') +
  145. /* caller */ (callers[1] || '') + '()',
  146. ...arguments
  147. );
  148. };
  149. log.formats = [{
  150. name: 'Firefox Scratchpad',
  151. detector: /MARKER@Scratchpad/,
  152. getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1],
  153. getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
  154. }, {
  155. name: 'Firefox Console',
  156. detector: /MARKER@debugger/,
  157. getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1],
  158. getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
  159. }, {
  160. name: 'Firefox Greasemonkey 3',
  161. detector: /\/gm_scripts\//,
  162. getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1],
  163. getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
  164. }, {
  165. name: 'Firefox Greasemonkey 4+',
  166. detector: /MARKER@user-script:/,
  167. getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1] - 500,
  168. getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
  169. }, {
  170. name: 'Firefox Tampermonkey',
  171. detector: /MARKER@moz-extension:/,
  172. getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1] - 2,
  173. getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
  174. }, {
  175. name: 'Chrome Console',
  176. detector: /at MARKER \(<anonymous>/,
  177. getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)?$/)[1],
  178. getCallers: (e) => e.stack.match(/[^ ]+(?= \(<anonymous>)/gm),
  179. }, {
  180. name: 'Chrome Tampermonkey',
  181. detector: /at MARKER \(chrome-extension:.*?\/userscript.html\?name=/,
  182. getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)?$/)[1] - 1,
  183. getCallers: (e) => e.stack.match(/[^ ]+(?= \(chrome-extension:)/gm),
  184. }, {
  185. name: 'Chrome Extension',
  186. detector: /at MARKER \(chrome-extension:/,
  187. getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)?$/)[1],
  188. getCallers: (e) => e.stack.match(/[^ ]+(?= \(chrome-extension:)/gm),
  189. }, {
  190. name: 'Edge Console',
  191. detector: /at MARKER \(eval/,
  192. getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)$/)[1],
  193. getCallers: (e) => e.stack.match(/[^ ]+(?= \(eval)/gm),
  194. }, {
  195. name: 'Edge Tampermonkey',
  196. detector: /at MARKER \(Function/,
  197. getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)$/)[1] - 4,
  198. getCallers: (e) => e.stack.match(/[^ ]+(?= \(Function)/gm),
  199. }, {
  200. name: 'Safari',
  201. detector: /^MARKER$/m,
  202. getLine: (e) => 0,/*e.lineが用意されているが最終呼び出し位置のみ*/
  203. getCallers: (e) => e.stack.split('\n'),
  204. }, {
  205. name: 'Default',
  206. detector: /./,
  207. getLine: (e) => 0,
  208. getCallers: (e) => [],
  209. }];
  210. log.format = log.formats.find(function MARKER(f){
  211. if(!f.detector.test(new Error().stack)) return false;
  212. //console.log('////', f.name, 'wants', 0/*line*/, '\n' + new Error().stack);
  213. return true;
  214. });
  215. core.initialize();
  216. if(window === top && console.timeEnd) console.timeEnd(SCRIPTID);
  217. })();

QingJ © 2025

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