Twitter scroller & like clicker

Scroll and like in twitter

  1. // ==UserScript==
  2. // @name Twitter scroller & like clicker
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4
  5. // @description Scroll and like in twitter
  6. // @author You
  7. // @match https://twitter.com/*
  8. // @icon https://www.google.com/s2/favicons?domain=twitter.com
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. setTimeout(() => {
  14. 'use strict';
  15.  
  16. // UI elements
  17. const arrowSvgString = '<svg viewBox="0 0 24 24"><g><path d="M12 4.656l8.72 8.72c.293.293.768.293 1.06 0s.294-.768 0-1.06l-9.25-9.25c-.292-.294-.767-.294-1.06 0l-9.25 9.25c-.146.145-.22.337-.22.53s.073.383.22.53c.293.292.768.292 1.06 0L12 4.656z"></path><path d="M12 12.465l8.72 8.72c.293.293.768.293 1.06 0s.294-.768 0-1.06l-9.25-9.25c-.292-.294-.767-.294-1.06 0l-9.25 9.25c-.146.145-.22.337-.22.53s.073.383.22.53c.293.292.768.292 1.06 0l8.72-8.72z"></path></g></svg>';
  18. const likeSvgString = '<svg viewBox="0 0 24 24"><g><path d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.034 11.596 8.55 11.658 1.518-.062 8.55-5.917 8.55-11.658 0-2.267-1.823-4.255-3.903-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.014-.03-1.425-2.965-3.954-2.965z"></path></g></svg>';
  19.  
  20. // Utilities
  21. const random = (min, max) => Math.random() * (max - min) + min;
  22.  
  23. const fireMouseEvent = (el, etype, x, y) => {
  24. el.dispatchEvent(new MouseEvent(etype, {
  25. view: window,
  26. bubbles: true,
  27. cancelable: true,
  28. clientX: x,
  29. clientY: y,
  30. button: 0
  31. }));
  32. };
  33.  
  34. const clickElement = (el) => {
  35. var box = el.getBoundingClientRect(),
  36. x = box.left + (box.right - box.left) / 2,
  37. y = box.top + (box.bottom - box.top) / 2;
  38.  
  39. fireMouseEvent(el, "mousedown", x, y);
  40. fireMouseEvent(el, "mouseup", x, y);
  41. fireMouseEvent(el, "click", x, y);
  42. }
  43.  
  44. const createFromHTML = (htmlString) => {
  45. var div = document.createElement('div');
  46. div.innerHTML = htmlString.trim();
  47.  
  48. // Change this to div.childNodes to support multiple top-level nodes
  49. return div.firstChild;
  50. }
  51.  
  52. // Get elements Utilites
  53.  
  54. const arrowSvg = createFromHTML(arrowSvgString);
  55. const heartSvg = createFromHTML(likeSvgString);
  56. const blueColor = 'rgba(29,161,242,1.00)';
  57.  
  58. arrowSvg.setAttribute("style", "transform: rotate(180deg);");
  59. const getNav = () => document.querySelector('nav[role="navigation"]');
  60. const getHearts = () => Array.from(document.querySelectorAll('main [d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.034 11.596 8.55 11.658 1.518-.062 8.55-5.917 8.55-11.658 0-2.267-1.823-4.255-3.903-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.014-.03-1.425-2.965-3.954-2.965z"]'))
  61. const getTweets = (current) => {
  62. const all = Array.from(document.querySelectorAll('article'));
  63. return all.slice(all.indexOf(current) + 1);
  64. }
  65.  
  66. // Setup settings
  67. const getStorageKey = (key) => `t-${key}-${location.pathname}`
  68. const getLocalSettings = () => ({
  69. scroll: !!localStorage.getItem(getStorageKey('scroll')),
  70. like: !!localStorage.getItem(getStorageKey('like')),
  71. })
  72.  
  73. let settings = getLocalSettings();
  74.  
  75. // Setup loops
  76. let tweets = [];
  77. let currentIndex = 0;
  78. let reloads = 0;
  79. const rndReloads = random(10, 20);
  80.  
  81. const startInterval = (callback, ms) => {
  82. callback();
  83. return setInterval(callback, ms);
  84. }
  85.  
  86. const getIntervalMap = {
  87. like: () => startInterval(() => {
  88. settings.like && getHearts().forEach((h) => h !== setTimeout(() => settings.like && clickElement(h), random(300, 3300)));
  89. }, 5000, 'like'),
  90. scroll: () => startInterval(() => {
  91. if (settings.scroll) {
  92. if (reloads > rndReloads) {
  93. window.location.reload();
  94. return;
  95. }
  96.  
  97. let tweet = tweets[currentIndex++];
  98. if (!tweet) {
  99. tweets = getTweets(tweets[currentIndex - 2]);
  100. currentIndex = 0;
  101. tweet = tweets[currentIndex++];
  102. reloads++;
  103. }
  104. if (tweet) tweet.scrollIntoView({ behavior: 'smooth', block: 'center' });
  105. }
  106. }, 5000, 'scroll')
  107. };
  108.  
  109. const intervalRefMap = {
  110. scroll: settings.scroll ? getIntervalMap.scroll() : null, like: settings.like ? getIntervalMap.like() : null
  111. };
  112.  
  113. // Setup UX
  114. const uiContainer = getNav();
  115.  
  116. const createNavElement = (newIcon, text, clickHandler) => {
  117. const newElement = uiContainer.lastChild.cloneNode(true);
  118. const oldIcon = newElement.querySelector('svg');
  119.  
  120. newIcon.className.baseVal = oldIcon.className.baseVal;
  121.  
  122. oldIcon.parentElement.appendChild(newIcon);
  123. oldIcon.parentElement.removeChild(oldIcon);
  124.  
  125. const textSpan = newElement.querySelector('span');
  126. if (textSpan) {
  127. textSpan.innerHTML = text;
  128. }
  129.  
  130. newElement.addEventListener('click', (e) => {
  131. clickHandler(e);
  132. e.preventDefault && e.preventDefault();
  133. return false;
  134. });
  135.  
  136. return newElement;
  137. }
  138.  
  139. const doButtonStyle = (button, activate) => {
  140. const svg = button.querySelector('svg');
  141. const text = button.querySelector('span');
  142.  
  143. button.style.color = activate ? blueColor : null;
  144.  
  145. if (svg) {
  146. svg.style.fill = activate ? blueColor : null
  147. }
  148.  
  149. if (text) {
  150. text.style.color = activate ? blueColor : null;
  151. }
  152. }
  153.  
  154. const handleClick = (settingKey) => ({ target }) => {
  155. const button = target.matches('[role="button"]') ? target : target.closest('[role="button"]');
  156. if (settings[settingKey] = !settings[settingKey]) {
  157. intervalRefMap[settingKey] = getIntervalMap[settingKey]();
  158. localStorage.setItem(getStorageKey(settingKey), '1');
  159. doButtonStyle(button, true);
  160. } else {
  161. localStorage.setItem(getStorageKey(settingKey), '');
  162. intervalRefMap[settingKey] = clearTimeout(intervalRefMap[settingKey]);
  163. doButtonStyle(button, false);
  164. }
  165. };
  166.  
  167. const scrollButton = createNavElement(arrowSvg, 'Auto Scroll', handleClick('scroll'));
  168. if (settings.scroll) doButtonStyle(scrollButton, true);
  169.  
  170. const likeButton = createNavElement(heartSvg, 'Auto Like', handleClick('like'));
  171. if (settings.like) doButtonStyle(likeButton, true);
  172.  
  173. uiContainer.prepend(likeButton);
  174. uiContainer.prepend(scrollButton);
  175.  
  176. const getButton = (key) => ({
  177. scroll: scrollButton,
  178. like: likeButton,
  179. })[key];
  180.  
  181.  
  182. // Polling for client side navigation and tab inactivity
  183. let wasHidden = false;
  184. let { href } = window.location;
  185. setInterval(() => {
  186. if (href !== window.location.href) {
  187. href = window.location.href;
  188. settings = getLocalSettings();
  189.  
  190. Object.entries(intervalRefMap).forEach(([key, value]) => {
  191. const button = getButton(key);
  192. if (settings[key]) {
  193. if (!value) intervalRefMap[key] = getIntervalMap[key]();
  194. doButtonStyle(button, true);
  195. } else {
  196. clearTimeout(value);
  197. doButtonStyle(button, false);
  198. }
  199. });
  200. }
  201. if (document.hidden) {
  202. if (!wasHidden) {
  203. wasHidden = true;
  204. Object.entries(intervalRefMap).forEach(([key, value]) => { intervalRefMap[key] = clearInterval(value) });
  205. }
  206. } else if (wasHidden) {
  207. wasHidden = false;
  208. Object.keys(intervalRefMap).forEach((key) => { settings[key] && !intervalRefMap[key] && (intervalRefMap[key] = getIntervalMap[key]()) });
  209. }
  210. }, 50);
  211. }, 3000)
  212. })();

QingJ © 2025

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