Bulk Upvoter for Reddit

Upvote a whole page of posts or comments with the click of a button! Includes support for whitelists and blacklists, hotkeys, and saving settings to file. Now works on old, new, and vanilla Reddit!

  1. // ==UserScript==
  2. // @name Bulk Upvoter for Reddit
  3. // @namespace Violentmonkey Scripts
  4. // @description Upvote a whole page of posts or comments with the click of a button! Includes support for whitelists and blacklists, hotkeys, and saving settings to file. Now works on old, new, and vanilla Reddit!
  5. // @match https://www.reddit.com/*
  6. // @include https://www.reddit.com/*
  7. // @include https://old.reddit.com/*
  8. // @include https://new.reddit.com/*
  9. // @grant none
  10. // @version 1.5.3
  11. // @author Jupiter Liar
  12. // @license CC BY
  13. // @author -
  14. // @description 4/8/2024, 10:23 pm
  15. // ==/UserScript==
  16.  
  17.  
  18. var seconds;
  19. var minsec;
  20. var config = loadConfigFromLocalStorage();
  21. var debounceTimer;
  22. var listMode;
  23. var debugMode = false;
  24. var showSettingsPrompt;
  25. var currentHotkey;
  26. var oldRedditTextSize = '13.5px';
  27.  
  28.  
  29. // Function to load the saved config from local storage
  30. function loadConfigFromLocalStorage() {
  31. var savedConfig = localStorage.getItem('BulkUpvoterConfig');
  32. config = savedConfig ? JSON.parse(savedConfig) : {};
  33.  
  34. if (Object.keys(config).length === 0) {
  35. // console.log('Config is blank.');
  36. saveDefaultsToLocalStorage();
  37. config = loadConfigFromLocalStorage();
  38. }
  39.  
  40. // console.log('Loaded config:', config);
  41.  
  42. // Load the seconds value if specified
  43. if (config.hasOwnProperty('seconds')) {
  44. seconds = parseFloat(config.seconds);
  45. // console.log('seconds: ' + seconds);
  46. } else {
  47. // console.log('seconds not found.');
  48. seconds = 0.5;
  49. // console.log('seconds: ' + seconds);
  50. }
  51.  
  52. // Load the minsec value if specified
  53. if (config.hasOwnProperty('min-sec')) {
  54. minsec = parseFloat(config['min-sec']);
  55. // console.log('minsec: ' + minsec);
  56. } else {
  57. // console.log('minsec not found.');
  58. minsec = 0.25;
  59. // console.log('minsec: ' + minsec);
  60. }
  61.  
  62. // Load listMode based on radio values
  63. if (config.hasOwnProperty('radio0') && config.radio0 === true) {
  64. listMode = 'everywhere';
  65. } else if (config.hasOwnProperty('radio1') && config.radio1 === true) {
  66. listMode = 'whitelist';
  67. } else if (config.hasOwnProperty('radio2') && config.radio2 === true) {
  68. listMode = 'blacklist';
  69. } else if (config.hasOwnProperty('radio3') && config.radio3 === true) {
  70. listMode = 'off';
  71. } else {
  72. // Set a default value for listMode if none of the radios are true
  73. listMode = 'everywhere';
  74. }
  75.  
  76. if (config.hasOwnProperty('hide-settings-checkbox') && config['hide-settings-checkbox'] === true) {
  77. showSettingsPrompt = false;
  78. } else {
  79. showSettingsPrompt = true;
  80. }
  81.  
  82. // console.log('listMode: ' + listMode);
  83.  
  84. return config;
  85. }
  86.  
  87.  
  88. function saveDefaultsToLocalStorage() {
  89. // console.log('Saving default settings to local storage');
  90. var defaultConfig = {
  91. radio0: true,
  92. radio1: false,
  93. radio2: false,
  94. radio3: false,
  95. redWhitelistTextBox: "",
  96. uWhitelistTextBox: "",
  97. redBlacklistTextBox: "",
  98. uBlacklistTextBox: "",
  99. seconds: "0.5",
  100. 'min-sec': "0.25",
  101. 'hide-settings-checkbox': false,
  102. 'hotkey-checkbox': true,
  103. oneModifier: true,
  104. twoModifier: false,
  105. firstModifierDropdown: "ALT",
  106. secondModifierDropdown: "",
  107. hotkeyBaseKey: "U"
  108. };
  109.  
  110. // Save the default config to local storage
  111. localStorage.setItem('BulkUpvoterConfig', JSON.stringify(defaultConfig));
  112. // console.log('Default settings saved to local storage:', localStorage.getItem('BulkUpvoterConfig'));
  113. }
  114.  
  115.  
  116.  
  117. // Function to construct the hotkey based on saved settings
  118. function constructHotkey() {
  119. // console.log('Constructing hotkey...');
  120.  
  121. // Check if hotkey-checkbox is false
  122. if (!config['hotkey-checkbox']) {
  123. // console.log('Hotkey checkbox is false. No action needed.');
  124. return;
  125. }
  126.  
  127. // // Default hotkey settings
  128. // var defaultHotkey = {
  129. // 'hotkey-checkbox': true,
  130. // 'oneModifier': true,
  131. // 'twoModifier': false,
  132. // 'firstModifierDropdown': 'ALT',
  133. // 'secondModifierDropdown': '',
  134. // 'hotkeyBaseKey': 'U'
  135. // };
  136.  
  137. // Check if hotkey settings are not in the config (first-time setup)
  138. if (!config.hasOwnProperty('hotkey-checkbox')) {
  139. config = Object.assign({}, defaultHotkey);
  140. // saveInputsToLocalStorage();
  141. // console.log('Hotkey defaults saved:', config);
  142. }
  143.  
  144. // Check if hotkeyBaseKey is blank
  145. if (config.hotkeyBaseKey === '') {
  146. // console.log('Hotkey base key is blank. No action needed.');
  147. return;
  148. }
  149.  
  150. // Check if both ModifierDropdown items are blank
  151. if (config.oneModifier && config.firstModifierDropdown === '' || config.twoModifier &&
  152. config.firstModifierDropdown === '' && config.secondModifierDropdown === '') {
  153. // console.log('All ModifierDropdown items are blank. No action needed.');
  154. return;
  155. }
  156.  
  157. // Check if oneModifier is true and firstModifierDropdown is blank
  158. if (config.oneModifier && config.firstModifierDropdown === '') {
  159. // console.log('One modifier is true, but firstModifierDropdown is blank. No action needed.');
  160. return;
  161. }
  162.  
  163. // Construct the hotkey
  164. var hotkey = '';
  165. if (config.oneModifier || (config.twoModifier && config.secondModifierDropdown == '')) {
  166. // console.log('hotkey builder first case.');
  167. hotkey += getModifierKey(config.firstModifierDropdown) + '+';
  168. }
  169.  
  170. if (config.twoModifier && config.firstModifierDropdown == '' && config.secondModifierDropdown !== '') {
  171. // console.log('hotkey builder second case.');
  172. hotkey += getModifierKey(config.secondModifierDropdown) + '+';
  173. }
  174.  
  175. if (config.twoModifier && config.firstModifierDropdown !== '' && config.secondModifierDropdown !== '') {
  176. // console.log('hotkey builder third case.');
  177. hotkey += getModifierKey(config.firstModifierDropdown) + '+' +
  178. getModifierKey(config.secondModifierDropdown) + '+';
  179. }
  180.  
  181. hotkey += config.hotkeyBaseKey;
  182.  
  183. // console.log('Constructed hotkey:', hotkey);
  184. currentHotkey = hotkey;
  185. return hotkey;
  186. }
  187.  
  188. // Helper function to get the actual modifier key based on the dropdown value
  189. function getModifierKey(modifier) {
  190. switch (modifier) {
  191. case 'CTRL':
  192. return 'Control';
  193. case 'ALT':
  194. return 'Alt';
  195. case 'SHIFT':
  196. return 'Shift';
  197. default:
  198. return '';
  199. }
  200. }
  201.  
  202. // Helper function to check if the hotkey is pressed
  203. function isHotkeyPressed(event, hotkey) {
  204. var pressedKeys = hotkey.split('+').map(key => key.trim().toUpperCase());
  205. return pressedKeys.every(key => {
  206. if (key === 'CONTROL') {
  207. return event.ctrlKey || event.metaKey;
  208. } else if (key === 'ALT') {
  209. return event.altKey;
  210. } else if (key === 'SHIFT') {
  211. return event.shiftKey;
  212. } else {
  213. return event.key.toUpperCase() === key;
  214. }
  215. });
  216. }
  217.  
  218. var hotkey = constructHotkey();
  219.  
  220. // Named function for the keydown event listener
  221. function hotkeyEventListener(event) {
  222. // Construct the hotkey
  223.  
  224. // Check if the pressed key matches the hotkey
  225. if (isHotkeyPressed(event, hotkey)) {
  226. // Hotkey is pressed, add your logic here
  227. // console.log('Activated Hotkey:', hotkey);
  228. showOrHideUpvoter();
  229. // Add your logic to perform actions when the hotkey is activated
  230. }
  231. }
  232.  
  233. // Function to activate the hotkey
  234. function activateHotkey() {
  235. // console.log('Attempting to activate hotkey...');
  236.  
  237. document.removeEventListener('keydown', hotkeyEventListener);
  238.  
  239. hotkey = constructHotkey();
  240.  
  241. // Check if hotkeyBaseKey is blank or both ModifierDropdown items are blank
  242. if (hotkey === '') {
  243. // console.log('Hotkey is null. No action needed.');
  244. return;
  245. }
  246.  
  247. // Add an event listener for keydown events
  248. document.addEventListener('keydown', hotkeyEventListener);
  249. }
  250.  
  251. // Call the function to activate the hotkey
  252. activateHotkey();
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260. function addAutoUpvoter() {
  261. // Create a div element
  262. var newUpvoterDiv = document.createElement('div');
  263.  
  264. // Set top margin and padding for the div
  265. newUpvoterDiv.style.display = 'block';
  266. newUpvoterDiv.style.marginTop = '52px';
  267. newUpvoterDiv.style.padding = '8px';
  268. newUpvoterDiv.id = 'upvoter-div';
  269. newUpvoterDiv.style.position = 'fixed';
  270. newUpvoterDiv.style.top = '0';
  271. newUpvoterDiv.style.right = '0';
  272. newUpvoterDiv.style.zIndex = '99';
  273.  
  274. // Create a "Do it" button element
  275. var upvoteAllButton = document.createElement('button');
  276. upvoteAllButton.id = 'upvote-all-button';
  277. upvoteAllButton.textContent = 'Upvote All';
  278. upvoteAllButton.style.background = '#ccc';
  279. upvoteAllButton.style.display = 'inline-block';
  280. upvoteAllButton.style.borderRadius = '4px';
  281. upvoteAllButton.style.padding = '6px';
  282. upvoteAllButton.style.fontSize = '12pt';
  283. upvoteAllButton.style.lineHeight = '12pt';
  284. upvoteAllButton.style.marginBottom = '4px';
  285. upvoteAllButton.style.border = '1px solid black';
  286. upvoteAllButton.style.alignItems = 'center';
  287.  
  288. // Create a "Stop" button element
  289. var stopButton = document.createElement('button');
  290. stopButton.id = 'stop-upvoting-button';
  291. stopButton.textContent = 'Stop';
  292. stopButton.style.background = '#ccc';
  293. stopButton.style.display = 'inline-block';
  294. stopButton.style.borderRadius = '4px';
  295. stopButton.style.padding = '6px';
  296. stopButton.style.fontSize = '12pt';
  297. stopButton.style.lineHeight = '12pt';
  298. stopButton.style.marginLeft = '4px'; // Add margin to separate the buttons
  299. stopButton.style.border = '1px solid black';
  300. stopButton.style.alignItems = 'center';
  301.  
  302. // Create a line break element
  303. var lineBreak = document.createElement('br');
  304.  
  305. // Create a div for the rate controls
  306. var rateDiv = document.createElement('div');
  307. rateDiv.style.display = 'flex';
  308. rateDiv.style.alignItems = 'center';
  309. rateDiv.id = 'rate-div';
  310.  
  311.  
  312. // Create a one-line numerical text field
  313. var secondsTextField = document.createElement('input');
  314. secondsTextField.id = 'seconds-text-field';
  315. secondsTextField.type = 'number';
  316. secondsTextField.style.border = '1px solid black';
  317. secondsTextField.style.padding = '0 0.25em';
  318. secondsTextField.step = '0.05';
  319. // secondsTextField.style.width = '9.4em';
  320. secondsTextField.style.width = '4.5em';
  321. secondsTextField.min = '0';
  322. secondsTextField.style.fontSize = '14px';
  323. secondsTextField.style.display = 'inline-flex';
  324. secondsTextField.style.height = '1.5em';
  325. secondsTextField.style.boxSizing = 'content-box';
  326. secondsTextField.style.marginBottom = '0';
  327. secondsTextField.style.borderRadius = '0';
  328.  
  329. var secPerVote = document.createElement('span');
  330. secPerVote.style.background = 'white';
  331. secPerVote.textContent = 'sec/vote'
  332. secPerVote.style.width = '4em';
  333. secPerVote.style.marginLeft = '.25em';
  334. if (window.location.href.includes('old.reddit.com')) {
  335. secPerVote.style.padding = '0 .5em 0 .25em';
  336. } else {
  337. secPerVote.style.padding = '0 .25em';
  338. }
  339. secPerVote.style.fontSize = '14px';
  340. secPerVote.style.border = '1px solid black';
  341. secPerVote.style.display = 'inline-flex';
  342. secPerVote.style.alignItems = 'center';
  343. secPerVote.style.height = '1.5em';
  344.  
  345. // Create a line break element
  346. // var lineBreak3 = document.createElement('br');
  347.  
  348. // Create a new div with the specified properties
  349. var upvotesRemainingDiv = document.createElement('div');
  350. upvotesRemainingDiv.id = 'upvotesRemainingDiv';
  351. upvotesRemainingDiv.style.background = 'white';
  352. upvotesRemainingDiv.style.border = '1px solid black';
  353. upvotesRemainingDiv.style.marginTop = '4px';
  354. upvotesRemainingDiv.style.display = 'none';
  355. upvotesRemainingDiv.style.padding = '0.25em';
  356. upvotesRemainingDiv.style.fontSize = '14px';
  357.  
  358.  
  359. // Create a span within the div with the specified properties
  360. var upvotesRemainingSpan = document.createElement('span');
  361. upvotesRemainingSpan.id = 'upvotesRemaining';
  362.  
  363. var lineBreak2 = document.createElement('br');
  364.  
  365. var seeSettingsPagePrompt = document.createElement('div');
  366. seeSettingsPagePrompt.style.color = 'black';
  367. seeSettingsPagePrompt.style.filter = 'drop-shadow(0px 0px 1px white) drop-shadow(0px 0px 0px white) drop-shadow(0px 0px 0px white) drop-shadow(0px 0px 0px white) drop-shadow(0px 0px 0px white) drop-shadow(0px 0px 0px white) drop-shadow(0px 0px 0px white)';
  368. seeSettingsPagePrompt.style.fontSize = '12px';
  369. seeSettingsPagePrompt.style.width = '11.5rem';
  370. seeSettingsPagePrompt.style.marginTop = '4px';
  371.  
  372. var currentSubdomain = window.location.hostname.split('.')[0];
  373.  
  374. if (currentSubdomain === 'new') {
  375. // If the subdomain is "new"
  376. seeSettingsPagePrompt.textContent = 'Go to new.reddit.com/settings and click Bulk Upvoter tab to change options. The hotkey to show or hide the upvoter is ' + currentHotkey + '.';
  377. } else if (currentSubdomain === 'old') {
  378. // If the subdomain is "old"
  379. seeSettingsPagePrompt.textContent = 'Go to old.reddit.com/prefs and click Bulk Upvoter tab to change options. The hotkey to show or hide the upvoter is ' + currentHotkey + '.';
  380. } else {
  381. // For any other subdomain
  382. seeSettingsPagePrompt.textContent = 'Go to reddit.com/settings and click Bulk Upvoter tab to change options. The hotkey to show or hide the upvoter is ' + currentHotkey + '.';
  383. }
  384.  
  385. // Append the "Do it" button, "Stop" button, line break, and text field to the div
  386. newUpvoterDiv.appendChild(upvoteAllButton);
  387. newUpvoterDiv.appendChild(stopButton);
  388. newUpvoterDiv.appendChild(lineBreak);
  389. rateDiv.appendChild(secondsTextField);
  390. rateDiv.appendChild(secPerVote);
  391. newUpvoterDiv.appendChild(rateDiv);
  392. // newUpvoterDiv.appendChild(lineBreak3);
  393. newUpvoterDiv.appendChild(upvotesRemainingDiv);
  394. upvotesRemainingDiv.appendChild(upvotesRemainingSpan);
  395. if (showSettingsPrompt) {
  396. newUpvoterDiv.appendChild(lineBreak2);
  397. newUpvoterDiv.appendChild(seeSettingsPagePrompt);
  398. }
  399.  
  400. // Append the div to the body
  401. document.body.appendChild(newUpvoterDiv);
  402.  
  403.  
  404.  
  405. // Set the value of the text field to the stored seconds
  406. secondsTextField.value = seconds;
  407.  
  408. // Debounce function to update the seconds variable
  409.  
  410. secondsTextField.addEventListener('input', function () {
  411. clearTimeout(debounceTimer);
  412. debounceTimer = setTimeout(function () {
  413. if (secondsTextField.value >= minsec) {
  414. seconds = secondsTextField.value;
  415. // console.log('seconds: ' + seconds);
  416. } else {
  417. seconds = minsec;
  418. secondsTextField.value = config.seconds;
  419. }
  420. updateConfigSeconds();
  421. updateInterval();
  422. }, 1000);
  423. });
  424.  
  425.  
  426. // New function to update config seconds and save to local storage
  427. function updateConfigSeconds() {
  428. // console.log('config: ' + JSON.stringify(config));
  429. config.seconds = seconds;
  430. // console.log('config.seconds: ' + config.seconds);
  431. localStorage.setItem('BulkUpvoterConfig', JSON.stringify(config));
  432. // console.log('config: ' + JSON.stringify(config));
  433. config = loadConfigFromLocalStorage(); // Update the config variable
  434. // console.log('config: ' + JSON.stringify(config));
  435. secondsTextField.value = config.seconds;
  436. // console.log('seconds: ' + seconds);
  437.  
  438. // Update the value of an input element with id 'seconds' if it exists
  439. var secondsInput = document.querySelector('#bulk-upvoter-option-tab-menu #seconds');
  440. if (secondsInput) {
  441. secondsInput.value = config.seconds;
  442. }
  443. }
  444.  
  445.  
  446. // Variables
  447. var stopClicking = true;
  448. var clickInterval;
  449.  
  450. // Function to simulate clicks on elements with specific attributes
  451. function simulateClicks() {
  452. var overlayContainer = document.getElementById('overlayScrollContainer');
  453. const mainContent = document.querySelector('#main-content');
  454. var allShadowRoots;
  455.  
  456. if (mainContent) {
  457. // Get all shadow roots within #main-content
  458. allShadowRoots = mainContent.querySelectorAll('*:not(style)');
  459.  
  460. // console.log('allShadowRoots.length: ' + allShadowRoots.length);
  461. }
  462.  
  463. var buttonsToClick;
  464.  
  465. if (overlayContainer) {
  466. // If overlay container exists, build array from elements within it
  467. buttonsToClick = Array.from(overlayContainer.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]'));
  468. // console.log('Overlay container case.');
  469. // buttonsToClick = [
  470. // ...Array.from(overlayContainer.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]')),
  471. // ...Array.from(overlayContainer.querySelectorAll('button[class*="upvote"][aria-pressed="false"]'))
  472. // ];
  473. } else if (window.location.href.match(/https?:\/\/new\.reddit\.com\/(r|user)\/(?!.*\/comment).*$/)) {
  474. // console.log('New Reddit subreddit page.');
  475. buttonsToClick = [];
  476.  
  477. // Get all post containers
  478. const postContainers = document.querySelectorAll('[data-testid="post-container"]');
  479. // console.log('Number of post containers:', postContainers.length);
  480.  
  481. postContainers.forEach((postContainer, index) => {
  482. // console.log(`Processing post container ${index + 1}:`);
  483.  
  484. // Get all upvote buttons in the post container
  485. const upvoteButtons = postContainer.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]');
  486. // console.log('Number of upvote buttons:', upvoteButtons.length);
  487.  
  488. // Process upvote buttons
  489. if (upvoteButtons.length > 1) {
  490. // console.log('More than one upvote button. Adding the second one to the array.');
  491. buttonsToClick.push(upvoteButtons[1]);
  492. } else if (upvoteButtons.length === 1) {
  493. // console.log('Exactly one upvote button. Adding it to the array.');
  494. buttonsToClick.push(upvoteButtons[0]);
  495. } else {
  496. // console.log('No upvote buttons found in this post container.');
  497. }
  498. });
  499.  
  500. // console.log('Final buttons to click array:', buttonsToClick);
  501. } else {
  502. // console.log('Third case.');
  503. // Otherwise, build array the normal way
  504. buttonsToClick = Array.from(document.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]'));
  505. // console.log('Normal case.');
  506.  
  507. // console.log('The point before the experiment.');
  508.  
  509. // buttonsToClick = Array.from(document.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]')).filter(button => {
  510. // var currentElement = button;
  511. // while (currentElement) {
  512. // const styles = window.getComputedStyle(currentElement);
  513. // if (styles.display === 'none' || styles.visibility === 'hidden') {
  514. // return false; // Exclude the button
  515. // }
  516. // currentElement = currentElement.parentElement;
  517. // }
  518. // return true; // Include the button
  519. // }).concat(Array.from(document.querySelectorAll('div[role="button"].arrow.up')));
  520.  
  521. if (mainContent) {
  522. // Iterate through each shadow host
  523. allShadowRoots.forEach(shadowHost => {
  524. // Access the shadow root directly
  525. const shadowRoot = shadowHost.shadowRoot;
  526.  
  527. // Check if shadowRoot is available and find buttons
  528. if (shadowRoot) {
  529. const buttonsInShadowRoot = Array.from(shadowRoot.querySelectorAll('button[upvote][aria-pressed="false"]'));
  530. buttonsToClick = buttonsToClick.concat(buttonsInShadowRoot);
  531. }
  532. });
  533. }
  534.  
  535. }
  536.  
  537. // console.log('buttonsToClick.length: ', buttonsToClick.length);
  538.  
  539. clearInterval(clickInterval); // Stop the interval if it's running
  540. clearTimeout(debounceTimer); // Clear the debounce timer
  541.  
  542. function clickNextButton() {
  543.  
  544. if (buttonsToClick.length > 0 && !stopClicking) {
  545. var currentButton = buttonsToClick.shift(); // Remove the first button from the array
  546.  
  547. // Check if aria-pressed is still "false"
  548. if (currentButton.getAttribute('aria-pressed') === 'false' || currentButton.tagName.toLowerCase() === 'div') {
  549. currentButton.click();
  550. // Update remaining upvotes info
  551. // Determine the text content based on the URL condition
  552. var isCommentsPage = window.location.href.includes("/comments");
  553. upvotesRemainingSpan.textContent = isCommentsPage ? buttonsToClick.length + ' remaining' : (buttonsToClick.length - 1) / 2 + ' remaining';
  554. upvotesRemainingSpan.textContent = buttonsToClick.length + ' remaining';
  555. upvotesRemainingDiv.style.display = 'block';
  556. } else {
  557. // If the button is already pressed, immediately go to the next without waiting for the interval
  558. clickNextButton();
  559. }
  560. } else {
  561. clearInterval(clickInterval); // Stop the interval when all buttons are clicked or stopClicking is true
  562. // After a delay, hide the remaining upvotes info
  563. stopClicking = 'true';
  564. setTimeout(function () {
  565. upvotesRemainingDiv.style.display = 'none';
  566. }, 2500);
  567. }
  568. }
  569.  
  570. // Initial click
  571. clickNextButton();
  572.  
  573. // Set interval for subsequent clicks
  574. clickInterval = setInterval(clickNextButton, seconds * 1000);
  575. }
  576.  
  577. // Event listener for the "Do it" button
  578. upvoteAllButton.addEventListener('click', function () {
  579. stopClicking = false; // Reset stopClicking to false
  580. simulateClicks(); // Call the simulateClicks function
  581. });
  582.  
  583. // Event listener for the "Stop" button
  584. stopButton.addEventListener('click', function () {
  585. stopClicking = true;
  586. clearInterval(clickInterval); // Stop the interval if it's running
  587. clearTimeout(debounceTimer); // Clear the debounce timer
  588. setTimeout(function () {
  589. upvotesRemainingDiv.style.display = 'none';
  590. }, 2500);
  591. });
  592.  
  593. // Update the interval when the seconds variable changes
  594. function updateInterval() {
  595. // console.log('Updating interval.');
  596. clearInterval(clickInterval); // Clear the existing interval
  597. simulateClicks(); // Restart the clicking process with the updated seconds variable
  598. }
  599.  
  600. upvoterDiv = document.getElementById('upvoter-div');
  601. }
  602.  
  603.  
  604.  
  605.  
  606. var upvoterDiv = document.getElementById('upvoter-div');
  607.  
  608.  
  609.  
  610. // Adding the Option Tab
  611.  
  612. // Function to check if the current URL matches the desired pattern
  613. function isSettingsPage() {
  614. var settingsPagePattern = /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/(?:settings|prefs).*$/i;
  615. return settingsPagePattern.test(window.location.href);
  616. }
  617.  
  618. function isOldReddit() {
  619. var settingsPagePattern = /^https?:\/\/?old.reddit\.com\/prefs.*$/i;
  620. return settingsPagePattern.test(window.location.href);
  621. }
  622.  
  623. // var bulkUpvoterOptionTabElement;
  624.  
  625. var commonClasses;
  626.  
  627. function handleRemoval() {
  628. addBulkUpvoterToTablist();
  629. }
  630.  
  631. // Function to add the new option to the tablist if it doesn't already exist
  632. function addBulkUpvoterToTablist() {
  633. // Check if the element with the specified ID already exists
  634. var existingOption = document.getElementById('bulk-upvoter-option-tab');
  635.  
  636. // If it doesn't exist, create and add the new option
  637. if (!existingOption) {
  638. // Create a new anchor element
  639. var bulkUpvoterOptionTab;
  640. bulkUpvoterOptionTab = document.createElement('a');
  641. bulkUpvoterOptionTab.setAttribute('aria-selected', 'false');
  642. bulkUpvoterOptionTab.setAttribute('role', 'tab');
  643. bulkUpvoterOptionTab.id = 'bulk-upvoter-option-tab';
  644. bulkUpvoterOptionTab.style.color = 'blue';
  645. bulkUpvoterOptionTab.textContent = 'Bulk Upvoter';
  646.  
  647. // Find the tablist element
  648. var tablist;
  649. if (isOldReddit()) {
  650. tablist = document.querySelector('ul.tabmenu');
  651. } else {
  652. tablist = document.querySelector('[role="tablist"]');
  653. }
  654.  
  655. // Find all the A elements in the tablist
  656. var tabs;
  657. if (isOldReddit()) {
  658. tabs = tablist.querySelectorAll('li');
  659. } else {
  660. tabs = tablist.querySelectorAll('a');
  661. }
  662.  
  663. // Get the classes of the first tab (assuming there is at least one tab)
  664. commonClasses = tabs.length > 0 ? Array.from(tabs[0].classList) : [];
  665.  
  666. // Loop through the tabs to find common classes
  667. for (var i = 1; i < tabs.length; i++) {
  668. var tabClasses = Array.from(tabs[i].classList);
  669. commonClasses = commonClasses.filter(value => tabClasses.includes(value));
  670. }
  671.  
  672. // Create list element for Old Reddit
  673. var bulkUpvoterOptionTabLi = document.createElement('li');
  674. bulkUpvoterOptionTabLi.id = 'bulk-upvoter-option-tab-li';
  675.  
  676. // Add the common classes to the new option
  677. if (isOldReddit()) {
  678. bulkUpvoterOptionTabLi.className = commonClasses.join(' ');
  679. } else {
  680. bulkUpvoterOptionTab.className = commonClasses.join(' ');
  681. }
  682.  
  683. // Add the new option after everything else in the tablist
  684. if (isOldReddit()) {
  685. tablist.appendChild(bulkUpvoterOptionTabLi);
  686. bulkUpvoterOptionTabLi.appendChild(bulkUpvoterOptionTab);
  687. bulkUpvoterOptionTab.style.cursor = 'pointer';
  688. } else {
  689. tablist.appendChild(bulkUpvoterOptionTab);
  690. }
  691.  
  692. // Add click event listener to the new option
  693. bulkUpvoterOptionTab.addEventListener('click', function () {
  694. handleBulkUpvoterClick(tablist);
  695. });
  696.  
  697.  
  698. // Create a MutationObserver instance
  699. var observer = new MutationObserver(function (mutations) {
  700. mutations.forEach(function (mutation) {
  701. // Check if the new option is removed
  702. if (mutation.removedNodes && mutation.removedNodes.length > 0) {
  703. for (var i = 0; i < mutation.removedNodes.length; i++) {
  704. if (mutation.removedNodes[i].id === 'bulk-upvoter-option-tab') {
  705. // Handle the removal by adding it back
  706. handleRemoval();
  707. break;
  708. }
  709. }
  710. }
  711. });
  712. });
  713.  
  714. // Configure and start the observer
  715. var observerConfig = {
  716. childList: true,
  717. subtree: true
  718. };
  719. observer.observe(tablist, observerConfig);
  720.  
  721.  
  722. }
  723. }
  724.  
  725.  
  726.  
  727.  
  728.  
  729. // Check if the current page is the settings page
  730. if (isSettingsPage()) {
  731. if (isOldReddit()) {
  732. // console.log('This appears to be the Old Reddit settings page.');
  733. } else {
  734. // console.log('This is not the Old Reddit settings page.');
  735. }
  736. addBulkUpvoterToTablist();
  737. // Wait for the entire window to be fully loaded
  738. window.onload = function () {
  739. // Add the new option to the tablist during onload, just in case
  740. addBulkUpvoterToTablist();
  741. };
  742. }
  743.  
  744. var currentClass;
  745.  
  746. // Function to handle the click on the new option
  747. function handleBulkUpvoterClick(tablist) {
  748. // Find the new option
  749. var bulkUpvoterOptionTab = document.getElementById('bulk-upvoter-option-tab');
  750. var bulkUpvoterOptionTabLi = document.getElementById('bulk-upvoter-option-tab-li');
  751.  
  752. if (isOldReddit()) {
  753.  
  754. // Assuming you have a reference to the ul.tabmenu element
  755. const tabmenu = document.querySelector('ul.tabmenu');
  756.  
  757. if (tabmenu) {
  758. // Get all li elements inside ul.tabmenu
  759. const tabmenuItems = tabmenu.querySelectorAll('li');
  760.  
  761. // Iterate through each li element
  762. tabmenuItems.forEach(item => {
  763. // Remove the class "selected" if it exists
  764. item.classList.remove('selected');
  765. });
  766. }
  767.  
  768. bulkUpvoterOptionTabLi.className = 'selected';
  769. }
  770.  
  771. showBulkUpvoterMenu(tablist);
  772.  
  773. // Check if the new option already has the class
  774. if (bulkUpvoterOptionTab.classList.contains('stored-class')) {
  775. return; // Exit if the class is already added
  776. }
  777.  
  778. // Find all A elements in the tablist (excluding the new option)
  779. var tabs = document.querySelectorAll('[role="tablist"] a:not(#bulk-upvoter-option-tab)');
  780.  
  781. // Loop through the tabs to find the unique class for bottom border
  782. for (var i = 0; i < tabs.length; i++) {
  783. var currentTab = tabs[i];
  784.  
  785. // Check if the tab has a bottom border
  786. var computedStyle = window.getComputedStyle(currentTab);
  787. if (computedStyle.getPropertyValue('border-bottom-width') !== '0px') {
  788. // Loop through the classes to find the unique class for bottom border
  789. for (var j = 0; j < currentTab.classList.length; j++) {
  790. currentClass = currentTab.classList[j];
  791.  
  792. // Check if the class is not in the common classes
  793. if (!commonClasses.includes(currentClass)) {
  794. // Remove the unique class from the current tab
  795. currentTab.classList.remove(currentClass);
  796.  
  797. // Add the unique class to the new option
  798. bulkUpvoterOptionTab.classList.add(currentClass);
  799.  
  800. // Log the stored class
  801. // console.log('Stored class: ' + currentClass);
  802.  
  803. // Exit the loop
  804. return;
  805. }
  806. }
  807. }
  808. }
  809.  
  810. }
  811.  
  812.  
  813.  
  814.  
  815. // Function to handle the click on other A elements in the tablist
  816. function handleTabClick(event) {
  817. // Find the new option
  818. var bulkUpvoterOptionTab = document.getElementById('bulk-upvoter-option-tab');
  819.  
  820. // Check if the new option has the class
  821. if (bulkUpvoterOptionTab.classList.contains(currentClass)) {
  822. // Remove the class from the new option
  823. bulkUpvoterOptionTab.classList.remove(currentClass);
  824.  
  825. // Log the removed class
  826. // console.log('Removed class from new option: ' + currentClass);
  827. }
  828.  
  829. // Get the clicked A element
  830. var clickedTab = event.target;
  831.  
  832. // Check if the clicked A element is the new option
  833. if (clickedTab === bulkUpvoterOptionTab) {
  834. return;
  835. }
  836.  
  837. // Add the class to the clicked A element
  838. clickedTab.classList.add(currentClass);
  839.  
  840. // Log the added class
  841. // console.log('Added class to clicked A element: ' + currentClass);
  842.  
  843. hideBulkUpvoterMenu();
  844. }
  845.  
  846. // Attach click event listener to all A elements in the tablist
  847. var tabs = document.querySelectorAll('[role="tablist"] a:not(#bulk-upvoter-option-tab)');
  848. tabs.forEach(function (tab) {
  849. tab.addEventListener('click', handleTabClick);
  850. });
  851.  
  852.  
  853.  
  854. // Show/hide the new menu.
  855.  
  856.  
  857. // Function to create or reveal the new option menu
  858. function showBulkUpvoterMenu(tablist) {
  859. // Check if the menu already exists
  860. var menu = document.getElementById('bulk-upvoter-option-tab-menu');
  861. // var tablist = document.querySelector('[role="tablist"]');
  862.  
  863. // If it doesn't exist, create and style the menu
  864. if (!menu) {
  865. menu = document.createElement('div');
  866. menu.id = 'bulk-upvoter-option-tab-menu';
  867. menu.style.backgroundColor = '#ddd';
  868. menu.style.border = '1px solid black';
  869. menu.style.minHeight = '40px';
  870. menu.style.display = 'block'; // Initial display option
  871. menu.style.position = 'absolute'; // Set position to absolute
  872. menu.style.boxSizing = 'border-box';
  873. menu.style.padding = '8px';
  874. menu.style.fontSize = '16px';
  875. menu.style.fontWeight = '500';
  876. menu.style.filter = 'drop-shadow(2px 6px 8px black)';
  877. menu.style.left = '50vw';
  878. menu.style.translate = '-50% 0';
  879.  
  880. // Append the menu to the body
  881. document.body.appendChild(menu);
  882.  
  883. // Set up a ResizeObserver to watch for changes in the tablist's dimensions
  884. var resizeObserver = new ResizeObserver(function () {
  885. // Adjust the menu's position and width dynamically
  886. positionMenu(tablist, menu);
  887. });
  888.  
  889. // Start observing the tablist
  890. resizeObserver.observe(tablist);
  891.  
  892. // Populate the menu with config and set up input listeners
  893. populateMenu(menu);
  894. setupInputListeners(menu);
  895.  
  896. // Populate the menu with the loaded config
  897. populateMenuWithConfig(menu);
  898.  
  899.  
  900. } else {
  901. // If it exists, set its display property to block
  902. menu.style.display = 'block';
  903. }
  904.  
  905. // Adjust the menu's position and width dynamically
  906. positionMenu(tablist, menu);
  907. }
  908.  
  909. // Function to dynamically position the menu below the tablist
  910. function positionMenu(tablist, menu) {
  911. // Get the tablist's bounding box
  912. var tablistRect = tablist.getBoundingClientRect();
  913.  
  914. // Get the tablist's computed style to include margins
  915. var tablistStyle = window.getComputedStyle(tablist);
  916.  
  917. // Convert left and right margins to numbers
  918. var paddingLeft = parseFloat(tablistStyle.paddingLeft);
  919. var marginLeft = parseFloat(tablistStyle.marginLeft);
  920. // console.log('paddingLeft: ' + paddingLeft);
  921. var paddingRight = parseFloat(tablistStyle.paddingRight);
  922. var marginRight = parseFloat(tablistStyle.marginRight);
  923. // console.log('paddingRight: ' + paddingRight);
  924.  
  925. // Set the menu's width to match the tablist's width
  926. menu.style.width = tablistRect.width - paddingLeft - paddingRight + 'px';
  927.  
  928. // // Set the menu's left position to center it horizontally under the tablist
  929. // menu.style.left = tablistRect.left + paddingLeft + 'px';
  930.  
  931. // // Set the menu's top position to be directly below the tablist
  932. // menu.style.top = tablistRect.bottom + 10 + 'px';
  933.  
  934. // Get the tablist's top position
  935. var tablistTop = window.scrollY + tablistRect.top;
  936.  
  937. // Set the menu's top position to be the bottom of the tablist
  938. menu.style.top = tablistTop + tablistRect.height + 10 + 'px';
  939.  
  940.  
  941. }
  942.  
  943.  
  944. // Function to hide the new option menu
  945. function hideBulkUpvoterMenu() {
  946. // Check if the menu exists
  947. var menu = document.getElementById('bulk-upvoter-option-tab-menu');
  948.  
  949. // If it exists, set its display property to none
  950. if (menu) {
  951. menu.style.display = 'none';
  952. }
  953. }
  954.  
  955.  
  956. // What goes in the menu:
  957.  
  958. // Function to populate the menu with options
  959. function populateMenu(menu) {
  960. // Create a div for radio buttons
  961. var radioButtonsContainer = document.createElement('div');
  962. radioButtonsContainer.style.display = 'flex';
  963.  
  964. // Create radio button divs for "Everywhere", "Whitelist", "Blacklist", "Off"
  965. var radioLabels = ['Everywhere', 'Whitelist', 'Blacklist', 'Off'];
  966. for (var i = 0; i < radioLabels.length; i++) {
  967. var radioDiv = createOptionDiv('radio' + i, radioLabels[i]);
  968. radioButtonsContainer.appendChild(radioDiv);
  969. }
  970.  
  971. menu.appendChild(radioButtonsContainer);
  972.  
  973. // Create a div for "Whitelists" label and text boxes
  974. var whitelistsLabelDiv = createLabelDiv('whitelistsLabel', 'Whitelists');
  975. menu.appendChild(whitelistsLabelDiv);
  976.  
  977. // Create a flex container for "Red" and "U" text boxes
  978. var redUWhitelistContainer = document.createElement('div');
  979. redUWhitelistContainer.style.display = 'grid';
  980. redUWhitelistContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
  981. redUWhitelistContainer.style.gap = '8px';
  982.  
  983. // Create divs for "Red" and "U" with text boxes
  984. var redWhitelistDiv = createTextBoxDiv('redWhitelist', 'Subreddits');
  985. redUWhitelistContainer.appendChild(redWhitelistDiv);
  986.  
  987. var uWhitelistDiv = createTextBoxDiv('uWhitelist', 'Users');
  988. redUWhitelistContainer.appendChild(uWhitelistDiv);
  989.  
  990. menu.appendChild(redUWhitelistContainer);
  991.  
  992. // Create a div for "Blacklists" label and text boxes
  993. var blacklistsLabelDiv = createLabelDiv('blacklistsLabel', 'Blacklists');
  994. menu.appendChild(blacklistsLabelDiv);
  995.  
  996. // Create a flex container for "Red" and "U" text boxes
  997. var redUBlacklistContainer = document.createElement('div');
  998. redUBlacklistContainer.style.display = 'grid';
  999. redUBlacklistContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
  1000. redUBlacklistContainer.style.gap = '8px';
  1001. // Create divs for "Red" and "U" with text boxes
  1002. var redBlacklistDiv = createTextBoxDiv('redBlacklist', 'Subreddits');
  1003. redUBlacklistContainer.appendChild(redBlacklistDiv);
  1004.  
  1005. var uBlacklistDiv = createTextBoxDiv('uBlacklist', 'Users');
  1006. redUBlacklistContainer.appendChild(uBlacklistDiv);
  1007.  
  1008. menu.appendChild(redUBlacklistContainer);
  1009.  
  1010. var wildcardInfoDiv = document.createElement('div');
  1011. wildcardInfoDiv.style.paddingTop = '8px';
  1012.  
  1013. var wildcardInfo = document.createElement('span');
  1014. wildcardInfo.textContent = 'Enter the names of subreddits or users, one per line. Use * as a wildcard.';
  1015. wildcardInfo.style.fontSize = '14px';
  1016.  
  1017. menu.appendChild(wildcardInfoDiv);
  1018. wildcardInfoDiv.appendChild(wildcardInfo);
  1019.  
  1020.  
  1021.  
  1022.  
  1023.  
  1024.  
  1025.  
  1026.  
  1027. // Function to create a horizontal black line
  1028. function createHorizontalLine() {
  1029. var line = document.createElement('div');
  1030. line.style.borderTop = '1px solid black';
  1031. line.style.margin = '8px 0';
  1032. return line;
  1033. }
  1034.  
  1035.  
  1036.  
  1037. // Function to create the "One click every" section
  1038. function createClickIntervalSection() {
  1039. var section = document.createElement('div');
  1040. section.style.display = 'flex';
  1041. section.style.fontWeight = '100';
  1042.  
  1043. // Text: "One click every"
  1044. var text1 = document.createElement('span');
  1045. text1.textContent = 'One click every ';
  1046. text1.style.paddingTop = '0.1em';
  1047.  
  1048. // Text input for seconds, id: "seconds"
  1049. var secondsInput = document.createElement('input');
  1050. secondsInput.type = 'text';
  1051. secondsInput.id = 'seconds';
  1052. secondsInput.style.margin = '0px .5em';
  1053. secondsInput.style.width = '4em';
  1054. secondsInput.style.padding = '0 0.25em';
  1055. secondsInput.type = 'number';
  1056. secondsInput.style.border = '1px solid black';
  1057. secondsInput.step = '0.05';
  1058. secondsInput.min = '0';
  1059.  
  1060. // Text: "seconds, with a minimum allowed value of"
  1061. var text2 = document.createElement('span');
  1062. text2.textContent = ' seconds, with a minimum allowed value of ';
  1063. text2.style.paddingTop = '0.1em';
  1064.  
  1065. // Text input for min-sec, id: "min-sec"
  1066. var minSecInput = document.createElement('input');
  1067. minSecInput.type = 'text';
  1068. minSecInput.id = 'min-sec';
  1069. minSecInput.style.margin = '0px .5em';
  1070. minSecInput.style.width = '4em';
  1071. minSecInput.style.padding = '0 0.25em';
  1072. minSecInput.type = 'number';
  1073. minSecInput.style.border = '1px solid black';
  1074. minSecInput.step = '0.05';
  1075. minSecInput.min = '0';
  1076.  
  1077. // Text: "seconds."
  1078. var text3 = document.createElement('span');
  1079. text3.textContent = ' seconds.';
  1080. text3.style.paddingTop = '0.1em';
  1081.  
  1082. // Appending elements to the section
  1083. section.appendChild(text1);
  1084. section.appendChild(secondsInput);
  1085. section.appendChild(text2);
  1086. section.appendChild(minSecInput);
  1087. section.appendChild(text3);
  1088.  
  1089. minSecInput.addEventListener('input', function () {
  1090. // Update minsec variable when minSecInput value changes
  1091. minsec = parseFloat(minSecInput.value);
  1092. // console.log('minsec: ' + minsec);
  1093. debounceTimer = setTimeout(function () {
  1094. if (secondsInput.value >= minsec) {
  1095. seconds = secondsInput.value;
  1096. } else {
  1097. seconds = minsec;
  1098. secondsInput.value = seconds;
  1099. }
  1100. config.seconds = seconds;
  1101. var secondsTextField = document.getElementById('seconds-text-field');
  1102. saveInputsToLocalStorage(menu);
  1103. loadConfigFromLocalStorage();
  1104. // updateInterval();
  1105. }, 950);
  1106.  
  1107. });
  1108.  
  1109. secondsInput.addEventListener('input', function () {
  1110. // console.log('minsec: ' + minsec);
  1111. clearTimeout(debounceTimer);
  1112. debounceTimer = setTimeout(function () {
  1113. if (secondsInput.value >= minsec) {
  1114. config.seconds = secondsInput.value;
  1115. seconds = config.seconds;
  1116. // console.log('seconds has been set to: ' + seconds);
  1117. } else {
  1118. config.seconds = minsec;
  1119. seconds = config.seconds;
  1120. secondsInput.value = config.seconds;
  1121. // console.log('seconds went too low and has been set to: ' + seconds);
  1122. }
  1123. seconds = config.seconds;
  1124. saveInputsToLocalStorage(menu);
  1125. loadConfigFromLocalStorage();
  1126. // updateInterval();
  1127. }, 950);
  1128. });
  1129.  
  1130. if (isOldReddit()) {
  1131. text1.style.fontSize = oldRedditTextSize;
  1132. secondsInput.style.fontSize = oldRedditTextSize;
  1133. text2.style.fontSize = oldRedditTextSize;
  1134. minSecInput.style.fontSize = oldRedditTextSize;
  1135. text3.style.fontSize = oldRedditTextSize;
  1136. }
  1137.  
  1138.  
  1139. return section;
  1140. }
  1141.  
  1142.  
  1143. // Append the horizontal black line and "One click every" section to the menu
  1144. menu.appendChild(createHorizontalLine());
  1145. menu.appendChild(createClickIntervalSection());
  1146.  
  1147. var showHideSettingsPromptDiv = document.createElement('div');
  1148. showHideSettingsPromptDiv.style.display = 'flex';
  1149. showHideSettingsPromptDiv.style.paddingTop = '8px';
  1150. showHideSettingsPromptDiv.style.alignItems = 'center';
  1151.  
  1152. var showHideSettingsPromptText = document.createElement('span');
  1153. showHideSettingsPromptText.textContent = 'Hide \"Go to settings\" prompt on upvoter:'
  1154. showHideSettingsPromptText.style.paddingRight = '0.5em';
  1155. showHideSettingsPromptText.style.fontWeight = '100';
  1156.  
  1157. var hideSettingsPromptCheckbox = document.createElement('input');
  1158. hideSettingsPromptCheckbox.id = 'hide-settings-checkbox';
  1159. hideSettingsPromptCheckbox.type = 'checkbox';
  1160. hideSettingsPromptCheckbox.style.translate = '0 1px';
  1161. hideSettingsPromptCheckbox.style.height = '15px';
  1162. hideSettingsPromptCheckbox.style.width = '15px';
  1163.  
  1164. showHideSettingsPromptDiv.appendChild(showHideSettingsPromptText);
  1165. showHideSettingsPromptDiv.appendChild(hideSettingsPromptCheckbox);
  1166. menu.appendChild(showHideSettingsPromptDiv);
  1167.  
  1168.  
  1169.  
  1170. // Hotkey section
  1171.  
  1172. // Create the container div
  1173. var hotkeyContainer = document.createElement('div');
  1174. hotkeyContainer.style.display = 'flex';
  1175. hotkeyContainer.style.paddingTop = '8px';
  1176. hotkeyContainer.style.alignItems = 'center';
  1177. hotkeyContainer.id = 'hotkey-settings';
  1178.  
  1179. // Create the span
  1180. var hotkeyLabel = document.createElement('span');
  1181. hotkeyLabel.textContent = 'Hotkey to display the upvoter:';
  1182. hotkeyLabel.style.paddingRight = '0.5em';
  1183. hotkeyLabel.style.fontWeight = '100';
  1184. hotkeyContainer.appendChild(hotkeyLabel);
  1185.  
  1186. var hotkeyCheckbox = document.createElement('input');
  1187. hotkeyCheckbox.type = 'checkbox';
  1188. hotkeyCheckbox.id = 'hotkey-checkbox';
  1189. hotkeyCheckbox.style.width = '15px';
  1190. hotkeyCheckbox.style.height = '15px';
  1191. hotkeyCheckbox.style.translate = '0 1px';
  1192. hotkeyCheckbox.style.marginRight = '1.5em';
  1193. hotkeyContainer.appendChild(hotkeyCheckbox);
  1194.  
  1195. // Attach listener to the checkbox
  1196. hotkeyCheckbox.addEventListener('change', toggleHotkeySettings);
  1197.  
  1198.  
  1199. // Create radio buttons
  1200. var oneModifierRadio = createRadioButton('modifierRadio', 'oneModifier', 'One Modifier Key', true);
  1201. var twoModifierRadio = createRadioButton('modifierRadio', 'twoModifier', 'Two Modifier Keys', false);
  1202. hotkeyContainer.appendChild(oneModifierRadio);
  1203. hotkeyContainer.appendChild(twoModifierRadio);
  1204.  
  1205. // Create dropdown boxes
  1206. var firstDropdown = createDropdown('firstModifierDropdown', ['', 'CTRL', 'ALT', 'SHIFT']);
  1207. var secondDropdown = createDropdown('secondModifierDropdown', ['', 'CTRL', 'ALT', 'SHIFT']);
  1208. hotkeyContainer.appendChild(firstDropdown);
  1209. hotkeyContainer.appendChild(secondDropdown);
  1210.  
  1211. // Create the textbox
  1212. var hotkeyTextbox = document.createElement('input');
  1213. hotkeyTextbox.type = 'text';
  1214. hotkeyTextbox.id = 'hotkeyBaseKey';
  1215. hotkeyTextbox.maxLength = 1;
  1216. hotkeyTextbox.style.padding = '0 0.5em';
  1217. hotkeyTextbox.style.textAlign = 'center';
  1218. hotkeyTextbox.style.width = '1.5em';
  1219. hotkeyTextbox.style.border = '1px solid black';
  1220. hotkeyContainer.appendChild(hotkeyTextbox);
  1221.  
  1222. hotkeyTextbox.addEventListener('input', function () {
  1223. this.value = this.value.toUpperCase();
  1224. });
  1225.  
  1226. // Append the container to the body
  1227. menu.appendChild(hotkeyContainer);
  1228.  
  1229. // Add event listeners
  1230. oneModifierRadio.addEventListener('change', toggleDropdownVisibility);
  1231. twoModifierRadio.addEventListener('change', toggleDropdownVisibility);
  1232. firstDropdown.addEventListener('change', disableEnableOptions);
  1233.  
  1234. // Helper function to create radio buttons
  1235. function createRadioButton(name, radioID, label, checked) {
  1236. // console.log('Radio buttons being created');
  1237.  
  1238. var radioLabelDiv = document.createElement('div');
  1239. radioLabelDiv.style.display = 'flex';
  1240. radioLabelDiv.style.fontSize = '14px';
  1241.  
  1242. var radioLabel = document.createElement('label');
  1243. radioLabel.setAttribute('for', radioID); // Use the radioId here
  1244. radioLabel.textContent = label;
  1245. radioLabel.style.marginRight = '1em';
  1246.  
  1247. var radio = document.createElement('input');
  1248. radio.type = 'radio';
  1249. radio.name = name;
  1250. radio.id = radioID; // Set the id directly on the radio button
  1251. radio.checked = checked;
  1252. radio.style.marginRight = '0.25em';
  1253.  
  1254. radioLabelDiv.appendChild(radio);
  1255. radioLabelDiv.appendChild(radioLabel);
  1256.  
  1257. return radioLabelDiv;
  1258. }
  1259.  
  1260.  
  1261. // Helper function to create dropdown boxes
  1262. function createDropdown(id, options) {
  1263. var dropdown = document.createElement('select');
  1264. dropdown.id = id;
  1265. dropdown.style.fontSize = '15px';
  1266. dropdown.style.marginRight = '0.5em';
  1267. dropdown.style.padding = '2px';
  1268.  
  1269. options.forEach(function (option) {
  1270. var optionElement = document.createElement('option');
  1271. optionElement.value = option;
  1272. optionElement.textContent = option;
  1273. dropdown.appendChild(optionElement);
  1274. });
  1275.  
  1276. return dropdown;
  1277. }
  1278.  
  1279. // Event listener to toggle visibility of the second dropdown
  1280. function toggleDropdownVisibility() {
  1281. // console.log('twoModifierRadio: ' + config.twoModifier);
  1282. secondDropdown.style.display = config.twoModifier ? 'none' : 'inline-block';
  1283. }
  1284.  
  1285. // Event listener to disable/enable options in the second dropdown
  1286. function disableEnableOptions() {
  1287. var selectedOption = firstDropdown.value;
  1288.  
  1289. for (var i = 0; i < secondDropdown.options.length; i++) {
  1290. var option = secondDropdown.options[i];
  1291.  
  1292. // Check if the first dropdown's selected option is null
  1293. if (selectedOption !== '') {
  1294. option.disabled = option.value === selectedOption;
  1295.  
  1296. // If the currently selected option in the second dropdown becomes disabled, reset to null
  1297. if (option.value === secondDropdown.value && option.disabled) {
  1298. secondDropdown.value = '';
  1299. }
  1300. } else {
  1301. // If the first dropdown's selected option is null, don't disable the null option in the second dropdown
  1302. option.disabled = false;
  1303. }
  1304. }
  1305.  
  1306. // Filter out disabled options from the second dropdown
  1307. updateDropdownVisibility();
  1308. }
  1309.  
  1310. // Helper function to filter out disabled options from the second dropdown
  1311. function updateDropdownVisibility() {
  1312. Array.from(secondDropdown.options).forEach(option => {
  1313. option.hidden = option.disabled;
  1314. });
  1315. }
  1316.  
  1317.  
  1318.  
  1319.  
  1320.  
  1321. menu.appendChild(createHorizontalLine());
  1322.  
  1323. var navigationInfoDiv = document.createElement('div');
  1324. // navigationInfoDiv.style.paddingTop = '8px';
  1325.  
  1326. var navigationInfo = document.createElement('span');
  1327. navigationInfo.textContent = 'The lists work according to page URL\'s. Due to how Reddit navigation works, the upvoter may remain in place after you have navigated away from listed addresses. If you want to make it go away, use the hotkey or simply refresh the page.';
  1328. navigationInfo.style.fontSize = '12.5px';
  1329. navigationInfo.style.fontWeight = '100';
  1330.  
  1331. menu.appendChild(navigationInfoDiv);
  1332. navigationInfoDiv.appendChild(navigationInfo);
  1333.  
  1334. var buttonInfoDiv = document.createElement('div');
  1335. buttonInfoDiv.style.paddingTop = '4px';
  1336.  
  1337. var buttonInfo = document.createElement('span');
  1338. buttonInfo.textContent = 'The upvote button targets all posts currently loaded on the webpage. To target posts that are loaded afterward when scrolling down, click the button again.';
  1339. buttonInfo.style.fontSize = '12.5px';
  1340. buttonInfo.style.fontWeight = '100';
  1341.  
  1342. menu.appendChild(buttonInfoDiv);
  1343. buttonInfoDiv.appendChild(buttonInfo);
  1344.  
  1345. // Example buttons to trigger the functions
  1346. var saveToFileButton = document.createElement('button');
  1347. saveToFileButton.textContent = 'Export Config to File';
  1348. saveToFileButton.addEventListener('click', saveConfigToFile);
  1349. // saveToFileButton.style.marginTop = '8px';
  1350. saveToFileButton.style.background = 'white';
  1351. saveToFileButton.style.border = '1px solid white';
  1352. saveToFileButton.style.padding = '6px 9px';
  1353. saveToFileButton.style.borderRadius = '6px';
  1354. saveToFileButton.style.border = '1px solid black';
  1355. saveToFileButton.style.marginRight = '0.5em';
  1356.  
  1357. var loadFromFileButton = document.createElement('button');
  1358. loadFromFileButton.textContent = 'Load Config from File';
  1359. loadFromFileButton.addEventListener('click', loadConfigFromFile);
  1360. // loadFromFileButton.style.marginTop = '8px';
  1361. loadFromFileButton.style.background = 'white';
  1362. loadFromFileButton.style.border = '1px solid white';
  1363. loadFromFileButton.style.padding = '6px 9px';
  1364. loadFromFileButton.style.borderRadius = '6px';
  1365. loadFromFileButton.style.border = '1px solid black';
  1366.  
  1367. var saveLoadDiv = document.createElement('div');
  1368. saveLoadDiv.style.display = 'flex';
  1369. saveLoadDiv.style.justifyContent = 'center';
  1370.  
  1371.  
  1372. // Append the buttons to the menu or wherever you want them
  1373. menu.appendChild(createHorizontalLine());
  1374. saveLoadDiv.appendChild(saveToFileButton);
  1375. saveLoadDiv.appendChild(loadFromFileButton);
  1376. menu.appendChild(saveLoadDiv);
  1377.  
  1378. if (isOldReddit()) {
  1379. showHideSettingsPromptText.style.fontSize = oldRedditTextSize;
  1380. hideSettingsPromptCheckbox.style.fontSize = oldRedditTextSize;
  1381. hotkeyLabel.style.fontSize = oldRedditTextSize;
  1382. hotkeyCheckbox.style.fontSize = oldRedditTextSize;
  1383. oneModifierRadio.style.fontSize = oldRedditTextSize;
  1384. twoModifierRadio.style.fontSize = oldRedditTextSize;
  1385. firstDropdown.style.fontSize = oldRedditTextSize;
  1386. secondDropdown.style.fontSize = oldRedditTextSize;
  1387. hotkeyTextbox.style.fontSize = oldRedditTextSize;
  1388. hotkeyTextbox.style.padding = '0.15em 0.5em';
  1389. saveToFileButton.style.fontSize = oldRedditTextSize;
  1390. loadFromFileButton.style.fontSize = oldRedditTextSize;
  1391. }
  1392.  
  1393.  
  1394. // Function to load config from a text file
  1395. function loadConfigFromFile() {
  1396. var input = document.createElement('input');
  1397. input.type = 'file';
  1398. input.accept = '.txt';
  1399. const secondsTextField = document.getElementById('seconds-text-field');
  1400.  
  1401. input.addEventListener('change', function (event) {
  1402. var file = event.target.files[0];
  1403.  
  1404. if (file) {
  1405. var reader = new FileReader();
  1406. reader.onload = function (e) {
  1407. try {
  1408. var loadedConfig = JSON.parse(e.target.result);
  1409. // console.log('loadedConfig: ' + loadedConfig);
  1410. config = loadedConfig;
  1411. // config = '';
  1412. // config = Object.assign({}, config, loadedConfig);
  1413. console.log('Config loaded from file:', config);
  1414. localStorage.setItem('BulkUpvoterConfig', JSON.stringify(config));
  1415. loadConfigFromLocalStorage();
  1416. populateMenuWithConfig(menu);
  1417. if (secondsTextField) {
  1418. secondsTextField.value = seconds;
  1419. }
  1420. } catch (error) {
  1421. console.error('Error parsing file:', error);
  1422. }
  1423. };
  1424.  
  1425. reader.readAsText(file);
  1426. }
  1427. });
  1428.  
  1429. input.click();
  1430. }
  1431.  
  1432.  
  1433.  
  1434. // Erase Button
  1435.  
  1436. var eraseDiv = document.createElement('div');
  1437. eraseDiv.style.display = 'none';
  1438. eraseDiv.style.textAlign = 'center';
  1439. if (debugMode) {
  1440. eraseDiv.style.display = 'block';
  1441. }
  1442.  
  1443. var eraseSpan = document.createElement('span');
  1444. eraseSpan.textContent = 'You have revealed the hidden erase button.'
  1445. eraseSpan.style.display = 'block';
  1446.  
  1447. var eraseSettingsButton = document.createElement('button');
  1448. eraseSettingsButton.textContent = 'Erase All Settings';
  1449. eraseSettingsButton.addEventListener('click', eraseSettingsFromLocalStorage);
  1450. eraseSettingsButton.style.marginTop = '8px';
  1451. eraseSettingsButton.style.background = 'white';
  1452. eraseSettingsButton.style.border = '1px solid white';
  1453. eraseSettingsButton.style.padding = '6px 9px';
  1454. eraseSettingsButton.style.borderRadius = '6px';
  1455. eraseSettingsButton.style.border = '1px solid black';
  1456.  
  1457. eraseDiv.appendChild(createHorizontalLine());
  1458. eraseDiv.appendChild(eraseSpan);
  1459. eraseDiv.appendChild(eraseSettingsButton);
  1460. menu.appendChild(eraseDiv);
  1461.  
  1462. }
  1463.  
  1464. // Function to toggle hotkey settings
  1465. function toggleHotkeySettings() {
  1466. // Get the hotkey settings div
  1467. var hotkeySettingsDiv = document.getElementById('hotkey-settings');
  1468.  
  1469. // Check if the checkbox is checked
  1470. var hotkeyCheckbox = document.getElementById('hotkey-checkbox');
  1471. var isEnabled = hotkeyCheckbox.checked;
  1472.  
  1473. // Get all input elements within the hotkey settings div, excluding the checkbox
  1474. var inputs = hotkeySettingsDiv.querySelectorAll('input:not(#hotkey-checkbox), select');
  1475.  
  1476. // Iterate over each input element and enable/disable based on the checkbox state
  1477. inputs.forEach(function (input) {
  1478. input.disabled = !isEnabled;
  1479. });
  1480. }
  1481.  
  1482. // Function to create an option div with radio and label
  1483. function createOptionDiv(id, labelText) {
  1484. // Create the div
  1485. var optionDiv = document.createElement('div');
  1486. optionDiv.style.marginRight = '32px';
  1487.  
  1488. // Create a flex container for radio button and label
  1489. var radioFlexContainer = document.createElement('div');
  1490. radioFlexContainer.style.display = 'flex';
  1491.  
  1492. // Create the radio button
  1493. var radio = document.createElement('input');
  1494. radio.type = 'radio';
  1495. radio.id = id;
  1496. radio.style.marginRight = '8px';
  1497. radio.name = 'options'; // Make sure they are in the same group
  1498. radioFlexContainer.appendChild(radio);
  1499.  
  1500. // Create the label
  1501. var label = document.createElement('label');
  1502. label.textContent = labelText;
  1503. label.setAttribute('for', id);
  1504. label.style.fontWeight = '600';
  1505. label.style.fontSize = '19px';
  1506. radioFlexContainer.appendChild(label);
  1507.  
  1508. optionDiv.appendChild(radioFlexContainer);
  1509.  
  1510. return optionDiv;
  1511. }
  1512.  
  1513. // Function to create a label div with text boxes
  1514. function createLabelDiv(id, labelText) {
  1515. var labelDiv = document.createElement('div');
  1516. labelDiv.id = id;
  1517. labelDiv.style.margin = '16px 0 12px';
  1518. labelDiv.style.fontSize = '18px';
  1519.  
  1520. // Create the label
  1521. var label = document.createElement('label');
  1522. label.textContent = labelText;
  1523. labelDiv.appendChild(label);
  1524.  
  1525. return labelDiv;
  1526. }
  1527.  
  1528. // Function to create a text box div
  1529. function createTextBoxDiv(id, textLabel) {
  1530. var textBoxDiv = document.createElement('div');
  1531. textBoxDiv.id = id;
  1532. textBoxDiv.style.display = 'flex';
  1533. textBoxDiv.style.flexDirection = 'column';
  1534.  
  1535. // Create the label
  1536. var label = document.createElement('label');
  1537. label.textContent = textLabel;
  1538. label.style.marginBottom = '4px';
  1539. textBoxDiv.appendChild(label);
  1540.  
  1541. // Create the text box
  1542. var textBox = document.createElement('textarea');
  1543. textBox.id = id + 'TextBox'; // Unique ID for the text box
  1544. // textBox.cols = 20; // Set the number of columns as needed
  1545. textBox.rows = 8; // Set the number of rows as needed
  1546. textBox.style.resize = 'none';
  1547. textBox.style.padding = '4px';
  1548. textBox.style.border = '1px solid black';
  1549. if (isOldReddit()) {
  1550. textBox.style.fontSize = oldRedditTextSize;
  1551. textBox.style.lineHeight = oldRedditTextSize * 1.5;
  1552. }
  1553.  
  1554. textBoxDiv.appendChild(textBox);
  1555.  
  1556. return textBoxDiv;
  1557. }
  1558.  
  1559.  
  1560.  
  1561. // Function to set up input change listeners
  1562. function setupInputListeners(menu) {
  1563. // Get all input elements within the menu
  1564. var inputs = menu.querySelectorAll('input, textarea, select');
  1565.  
  1566. // Iterate over each input element and set up change listeners
  1567. inputs.forEach(function (input) {
  1568. // Set up a debounced change listener for each input
  1569. var debouncedSave = debounce(function () {
  1570. saveInputsToLocalStorage(menu);
  1571. }, 1000);
  1572.  
  1573. // Add event listener based on input type
  1574. if (input.type === 'radio' || input.type === 'checkbox') {
  1575. input.addEventListener('change', debouncedSave);
  1576. } else {
  1577. input.addEventListener('input', debouncedSave);
  1578. }
  1579. });
  1580. }
  1581.  
  1582. function toggleAndAddAutoUpvoter() {
  1583. // console.log('Attempting to toggle upvoter.');
  1584. upvoterDiv = document.getElementById('upvoter-div');
  1585. if (upvoterDiv) {
  1586. // console.log('upvoterDiv found');
  1587. // Get current display property
  1588. var currentDisplay = upvoterDiv.style.display;
  1589.  
  1590. // Remove newUpvoterDiv
  1591. upvoterDiv.remove();
  1592.  
  1593. // Run the function to add auto upvoter
  1594. addAutoUpvoter();
  1595.  
  1596. // Set display property back to its original value
  1597. upvoterDiv.style.display = currentDisplay;
  1598. }
  1599. }
  1600.  
  1601.  
  1602. // Function to save all input values to local storage
  1603. function saveInputsToLocalStorage(menu) {
  1604. // console.log('Saving inputs to local storage');
  1605. // Get all input elements within the menu, if it exists
  1606. var inputs = menu ? menu.querySelectorAll('input, textarea, select') : [];
  1607. config = loadConfigFromLocalStorage();
  1608.  
  1609. // Iterate over each input element and update its value in the config
  1610. inputs.forEach(function (input) {
  1611. if (input.type === 'radio' || input.type === 'checkbox') {
  1612. config[input.id] = input.checked;
  1613. } else {
  1614. config[input.id] = input.value;
  1615. }
  1616. });
  1617.  
  1618. // Save the updated config to local storage
  1619. localStorage.setItem('BulkUpvoterConfig', JSON.stringify(config));
  1620. console.log('Saved to local storage:' + JSON.stringify(config));
  1621. config = loadConfigFromLocalStorage();
  1622. activateHotkey();
  1623. toggleAndAddAutoUpvoter();
  1624. }
  1625.  
  1626. // Function to erase all saved settings from local storage
  1627. function eraseSettingsFromLocalStorage() {
  1628. // console.log('Erasing settings from local storage');
  1629. localStorage.removeItem('BulkUpvoterConfig');
  1630. }
  1631.  
  1632.  
  1633.  
  1634.  
  1635. // Function to save config to a text file
  1636. function saveConfigToFile() {
  1637. var configText = JSON.stringify(config, null, 2);
  1638. var blob = new Blob([configText], {
  1639. type: 'text/plain'
  1640. });
  1641. var a = document.createElement('a');
  1642. a.href = URL.createObjectURL(blob);
  1643. a.download = 'BulkUpvoterConfig.txt';
  1644. a.click();
  1645. }
  1646.  
  1647.  
  1648.  
  1649. // Debounce function to limit the frequency of function execution
  1650. function debounce(func, delay) {
  1651. let timeout;
  1652. return function () {
  1653. const context = this;
  1654. const args = arguments;
  1655. clearTimeout(timeout);
  1656. timeout = setTimeout(function () {
  1657. func.apply(context, args);
  1658. }, delay);
  1659. };
  1660. }
  1661.  
  1662.  
  1663. // Function to populate the menu with the loaded config
  1664. function populateMenuWithConfig(menu) {
  1665. // Get all input elements within the menu
  1666. var inputs = menu.querySelectorAll('input, textarea, select');
  1667.  
  1668. // Iterate over each input element and set its value from the loaded config
  1669. inputs.forEach(function (input) {
  1670. if (input.id === 'secondsTextField') {
  1671. // Special handling for 'secondsTextField'
  1672. var inputValue = config.seconds;
  1673. if (inputValue !== undefined) {
  1674. input.value = inputValue;
  1675. }
  1676. } else if (input.type === 'radio' || input.type === 'checkbox') {
  1677. // Check if the radio exists in the config
  1678. if (config[input.id] !== undefined && config[input.id] === true) {
  1679. input.checked = true;
  1680. }
  1681. } else {
  1682. var inputValue = config[input.id];
  1683. if (inputValue !== undefined) {
  1684. input.value = inputValue;
  1685. }
  1686. }
  1687. });
  1688.  
  1689. if (config.oneModifier) {
  1690. const secondModifierDropdown = document.getElementById('secondModifierDropdown');
  1691. secondModifierDropdown.style.display = 'none';
  1692. }
  1693.  
  1694. toggleHotkeySettings();
  1695. }
  1696.  
  1697.  
  1698.  
  1699.  
  1700. // Function to check if the current URL matches the criteria
  1701. function displayUpvoterBasedOnUrl(currentURL) {
  1702. // console.log('Trying to do the thing.');
  1703. // Check if the current URL matches the whitelist criteria
  1704. if (
  1705. (listMode === 'whitelist' &&
  1706. (matchesRedWhitelist(currentURL) || matchesUWhitelist(currentURL))) ||
  1707. // Check if the current URL matches the blacklist criteria
  1708. (listMode === 'blacklist' &&
  1709. matchesBlacklist(currentURL) &&
  1710. !matchesRedBlacklist(currentURL) &&
  1711. !matchesUBlacklist(currentURL)) ||
  1712. listMode === 'everywhere'
  1713. ) {
  1714. // Perform the action if the conditions are met
  1715. displayUpvoter();
  1716. }
  1717. }
  1718.  
  1719. // Function to check if the current URL matches the red whitelist
  1720. function matchesRedWhitelist(currentURL) {
  1721. return matchItemsFromList(currentURL, config.redWhitelistTextBox, 'r');
  1722. }
  1723.  
  1724. // Function to check if the current URL matches the U whitelist
  1725. function matchesUWhitelist(currentURL) {
  1726. return (
  1727. matchItemsFromList(currentURL, config.uWhitelistTextBox, 'u') ||
  1728. matchItemsFromList(currentURL, config.uWhitelistTextBox, 'user')
  1729. );
  1730. }
  1731.  
  1732. // Function to check if the current URL matches the blacklist
  1733. function matchesBlacklist(currentURL) {
  1734. return (
  1735. /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/r\/.*$/i.test(currentURL) ||
  1736. /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/u\/.*$/i.test(currentURL) ||
  1737. /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/user\/.*$/i.test(currentURL)
  1738. );
  1739. }
  1740.  
  1741. // Function to check if the current URL matches the red blacklist
  1742. function matchesRedBlacklist(currentURL) {
  1743. return matchItemsFromList(currentURL, config.redBlacklistTextBox, 'r');
  1744. }
  1745.  
  1746. // Function to check if the current URL matches the U whitelist
  1747. function matchesUBlacklist(currentURL) {
  1748. return (
  1749. matchItemsFromList(currentURL, config.uBlacklistTextBox, 'u') ||
  1750. matchItemsFromList(currentURL, config.uBlacklistTextBox, 'user')
  1751. );
  1752. }
  1753.  
  1754. // Function to match items from the list
  1755. function matchItemsFromList(currentURL, list, prefix) {
  1756. return list.split('\n').some(item => {
  1757. if (item === '*') {
  1758. // Asterisk acts as a wildcard for the specified list type
  1759. const regex = new RegExp(
  1760. `^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/${prefix}\/.*$`,
  1761. 'i'
  1762. );
  1763. return currentURL.match(regex);
  1764. }
  1765. const regex = new RegExp(
  1766. `^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/${prefix}\/${item}(?:\/.*|$)$`,
  1767. 'i'
  1768. );
  1769. return currentURL.match(regex);
  1770. });
  1771. }
  1772.  
  1773. // Function to perform the action
  1774. function displayUpvoter() {
  1775. // Your action code here
  1776. upvoterDiv = document.getElementById('upvoter-div');
  1777. if (!upvoterDiv) {
  1778. // console.log('Doing the thing!');
  1779. addAutoUpvoter();
  1780. }
  1781. }
  1782.  
  1783. function showOrHideUpvoter() {
  1784. var upvoterDiv = document.getElementById('upvoter-div');
  1785.  
  1786. if (!upvoterDiv) {
  1787. // If upvoterDiv doesn't exist, add it
  1788. addAutoUpvoter();
  1789. } else {
  1790. // Toggle the display property
  1791. if (upvoterDiv.style.display === 'block') {
  1792. // If currently visible, hide it
  1793. // console.log('Hiding upvoter.');
  1794. upvoterDiv.style.display = 'none';
  1795. } else {
  1796. // If currently hidden, show it
  1797. // console.log('Showing upvoter.');
  1798. upvoterDiv.style.display = 'block';
  1799. }
  1800. }
  1801. }
  1802.  
  1803.  
  1804. // Get the current URL
  1805. var currentURL = window.location.href;
  1806.  
  1807. displayUpvoterBasedOnUrl(currentURL);
  1808.  
  1809.  
  1810. // Function to handle URL changes
  1811. function handleUrlChange() {
  1812. currentURL = window.location.href;
  1813. // console.log('URL changed: ', currentURL);
  1814.  
  1815. // Call your function or do something with the new URL
  1816. displayUpvoterBasedOnUrl(currentURL);
  1817. }
  1818.  
  1819. // Event listener for popstate
  1820. window.addEventListener('popstate', handleUrlChange);
  1821.  
  1822. // Event listener for pushstate
  1823. window.addEventListener('pushstate', handleUrlChange);
  1824.  
  1825. // Event listener for replacestate
  1826. window.addEventListener('replacestate', handleUrlChange);
  1827.  
  1828. // // Watch for URL changes using MutationObserver on the body tag
  1829. const observer = new MutationObserver(() => {
  1830. if (currentURL != window.location.href) {
  1831. handleUrlChange();
  1832. }
  1833. });
  1834.  
  1835. observer.observe(document.body, {
  1836. childList: true,
  1837. subtree: true
  1838. });