您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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!
- // ==UserScript==
- // @name Bulk Upvoter for Reddit
- // @namespace Violentmonkey Scripts
- // @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!
- // @match https://www.reddit.com/*
- // @include https://www.reddit.com/*
- // @include https://old.reddit.com/*
- // @include https://new.reddit.com/*
- // @grant none
- // @version 1.5.3
- // @author Jupiter Liar
- // @license CC BY
- // @author -
- // @description 4/8/2024, 10:23 pm
- // ==/UserScript==
- var seconds;
- var minsec;
- var config = loadConfigFromLocalStorage();
- var debounceTimer;
- var listMode;
- var debugMode = false;
- var showSettingsPrompt;
- var currentHotkey;
- var oldRedditTextSize = '13.5px';
- // Function to load the saved config from local storage
- function loadConfigFromLocalStorage() {
- var savedConfig = localStorage.getItem('BulkUpvoterConfig');
- config = savedConfig ? JSON.parse(savedConfig) : {};
- if (Object.keys(config).length === 0) {
- // console.log('Config is blank.');
- saveDefaultsToLocalStorage();
- config = loadConfigFromLocalStorage();
- }
- // console.log('Loaded config:', config);
- // Load the seconds value if specified
- if (config.hasOwnProperty('seconds')) {
- seconds = parseFloat(config.seconds);
- // console.log('seconds: ' + seconds);
- } else {
- // console.log('seconds not found.');
- seconds = 0.5;
- // console.log('seconds: ' + seconds);
- }
- // Load the minsec value if specified
- if (config.hasOwnProperty('min-sec')) {
- minsec = parseFloat(config['min-sec']);
- // console.log('minsec: ' + minsec);
- } else {
- // console.log('minsec not found.');
- minsec = 0.25;
- // console.log('minsec: ' + minsec);
- }
- // Load listMode based on radio values
- if (config.hasOwnProperty('radio0') && config.radio0 === true) {
- listMode = 'everywhere';
- } else if (config.hasOwnProperty('radio1') && config.radio1 === true) {
- listMode = 'whitelist';
- } else if (config.hasOwnProperty('radio2') && config.radio2 === true) {
- listMode = 'blacklist';
- } else if (config.hasOwnProperty('radio3') && config.radio3 === true) {
- listMode = 'off';
- } else {
- // Set a default value for listMode if none of the radios are true
- listMode = 'everywhere';
- }
- if (config.hasOwnProperty('hide-settings-checkbox') && config['hide-settings-checkbox'] === true) {
- showSettingsPrompt = false;
- } else {
- showSettingsPrompt = true;
- }
- // console.log('listMode: ' + listMode);
- return config;
- }
- function saveDefaultsToLocalStorage() {
- // console.log('Saving default settings to local storage');
- var defaultConfig = {
- radio0: true,
- radio1: false,
- radio2: false,
- radio3: false,
- redWhitelistTextBox: "",
- uWhitelistTextBox: "",
- redBlacklistTextBox: "",
- uBlacklistTextBox: "",
- seconds: "0.5",
- 'min-sec': "0.25",
- 'hide-settings-checkbox': false,
- 'hotkey-checkbox': true,
- oneModifier: true,
- twoModifier: false,
- firstModifierDropdown: "ALT",
- secondModifierDropdown: "",
- hotkeyBaseKey: "U"
- };
- // Save the default config to local storage
- localStorage.setItem('BulkUpvoterConfig', JSON.stringify(defaultConfig));
- // console.log('Default settings saved to local storage:', localStorage.getItem('BulkUpvoterConfig'));
- }
- // Function to construct the hotkey based on saved settings
- function constructHotkey() {
- // console.log('Constructing hotkey...');
- // Check if hotkey-checkbox is false
- if (!config['hotkey-checkbox']) {
- // console.log('Hotkey checkbox is false. No action needed.');
- return;
- }
- // // Default hotkey settings
- // var defaultHotkey = {
- // 'hotkey-checkbox': true,
- // 'oneModifier': true,
- // 'twoModifier': false,
- // 'firstModifierDropdown': 'ALT',
- // 'secondModifierDropdown': '',
- // 'hotkeyBaseKey': 'U'
- // };
- // Check if hotkey settings are not in the config (first-time setup)
- if (!config.hasOwnProperty('hotkey-checkbox')) {
- config = Object.assign({}, defaultHotkey);
- // saveInputsToLocalStorage();
- // console.log('Hotkey defaults saved:', config);
- }
- // Check if hotkeyBaseKey is blank
- if (config.hotkeyBaseKey === '') {
- // console.log('Hotkey base key is blank. No action needed.');
- return;
- }
- // Check if both ModifierDropdown items are blank
- if (config.oneModifier && config.firstModifierDropdown === '' || config.twoModifier &&
- config.firstModifierDropdown === '' && config.secondModifierDropdown === '') {
- // console.log('All ModifierDropdown items are blank. No action needed.');
- return;
- }
- // Check if oneModifier is true and firstModifierDropdown is blank
- if (config.oneModifier && config.firstModifierDropdown === '') {
- // console.log('One modifier is true, but firstModifierDropdown is blank. No action needed.');
- return;
- }
- // Construct the hotkey
- var hotkey = '';
- if (config.oneModifier || (config.twoModifier && config.secondModifierDropdown == '')) {
- // console.log('hotkey builder first case.');
- hotkey += getModifierKey(config.firstModifierDropdown) + '+';
- }
- if (config.twoModifier && config.firstModifierDropdown == '' && config.secondModifierDropdown !== '') {
- // console.log('hotkey builder second case.');
- hotkey += getModifierKey(config.secondModifierDropdown) + '+';
- }
- if (config.twoModifier && config.firstModifierDropdown !== '' && config.secondModifierDropdown !== '') {
- // console.log('hotkey builder third case.');
- hotkey += getModifierKey(config.firstModifierDropdown) + '+' +
- getModifierKey(config.secondModifierDropdown) + '+';
- }
- hotkey += config.hotkeyBaseKey;
- // console.log('Constructed hotkey:', hotkey);
- currentHotkey = hotkey;
- return hotkey;
- }
- // Helper function to get the actual modifier key based on the dropdown value
- function getModifierKey(modifier) {
- switch (modifier) {
- case 'CTRL':
- return 'Control';
- case 'ALT':
- return 'Alt';
- case 'SHIFT':
- return 'Shift';
- default:
- return '';
- }
- }
- // Helper function to check if the hotkey is pressed
- function isHotkeyPressed(event, hotkey) {
- var pressedKeys = hotkey.split('+').map(key => key.trim().toUpperCase());
- return pressedKeys.every(key => {
- if (key === 'CONTROL') {
- return event.ctrlKey || event.metaKey;
- } else if (key === 'ALT') {
- return event.altKey;
- } else if (key === 'SHIFT') {
- return event.shiftKey;
- } else {
- return event.key.toUpperCase() === key;
- }
- });
- }
- var hotkey = constructHotkey();
- // Named function for the keydown event listener
- function hotkeyEventListener(event) {
- // Construct the hotkey
- // Check if the pressed key matches the hotkey
- if (isHotkeyPressed(event, hotkey)) {
- // Hotkey is pressed, add your logic here
- // console.log('Activated Hotkey:', hotkey);
- showOrHideUpvoter();
- // Add your logic to perform actions when the hotkey is activated
- }
- }
- // Function to activate the hotkey
- function activateHotkey() {
- // console.log('Attempting to activate hotkey...');
- document.removeEventListener('keydown', hotkeyEventListener);
- hotkey = constructHotkey();
- // Check if hotkeyBaseKey is blank or both ModifierDropdown items are blank
- if (hotkey === '') {
- // console.log('Hotkey is null. No action needed.');
- return;
- }
- // Add an event listener for keydown events
- document.addEventListener('keydown', hotkeyEventListener);
- }
- // Call the function to activate the hotkey
- activateHotkey();
- function addAutoUpvoter() {
- // Create a div element
- var newUpvoterDiv = document.createElement('div');
- // Set top margin and padding for the div
- newUpvoterDiv.style.display = 'block';
- newUpvoterDiv.style.marginTop = '52px';
- newUpvoterDiv.style.padding = '8px';
- newUpvoterDiv.id = 'upvoter-div';
- newUpvoterDiv.style.position = 'fixed';
- newUpvoterDiv.style.top = '0';
- newUpvoterDiv.style.right = '0';
- newUpvoterDiv.style.zIndex = '99';
- // Create a "Do it" button element
- var upvoteAllButton = document.createElement('button');
- upvoteAllButton.id = 'upvote-all-button';
- upvoteAllButton.textContent = 'Upvote All';
- upvoteAllButton.style.background = '#ccc';
- upvoteAllButton.style.display = 'inline-block';
- upvoteAllButton.style.borderRadius = '4px';
- upvoteAllButton.style.padding = '6px';
- upvoteAllButton.style.fontSize = '12pt';
- upvoteAllButton.style.lineHeight = '12pt';
- upvoteAllButton.style.marginBottom = '4px';
- upvoteAllButton.style.border = '1px solid black';
- upvoteAllButton.style.alignItems = 'center';
- // Create a "Stop" button element
- var stopButton = document.createElement('button');
- stopButton.id = 'stop-upvoting-button';
- stopButton.textContent = 'Stop';
- stopButton.style.background = '#ccc';
- stopButton.style.display = 'inline-block';
- stopButton.style.borderRadius = '4px';
- stopButton.style.padding = '6px';
- stopButton.style.fontSize = '12pt';
- stopButton.style.lineHeight = '12pt';
- stopButton.style.marginLeft = '4px'; // Add margin to separate the buttons
- stopButton.style.border = '1px solid black';
- stopButton.style.alignItems = 'center';
- // Create a line break element
- var lineBreak = document.createElement('br');
- // Create a div for the rate controls
- var rateDiv = document.createElement('div');
- rateDiv.style.display = 'flex';
- rateDiv.style.alignItems = 'center';
- rateDiv.id = 'rate-div';
- // Create a one-line numerical text field
- var secondsTextField = document.createElement('input');
- secondsTextField.id = 'seconds-text-field';
- secondsTextField.type = 'number';
- secondsTextField.style.border = '1px solid black';
- secondsTextField.style.padding = '0 0.25em';
- secondsTextField.step = '0.05';
- // secondsTextField.style.width = '9.4em';
- secondsTextField.style.width = '4.5em';
- secondsTextField.min = '0';
- secondsTextField.style.fontSize = '14px';
- secondsTextField.style.display = 'inline-flex';
- secondsTextField.style.height = '1.5em';
- secondsTextField.style.boxSizing = 'content-box';
- secondsTextField.style.marginBottom = '0';
- secondsTextField.style.borderRadius = '0';
- var secPerVote = document.createElement('span');
- secPerVote.style.background = 'white';
- secPerVote.textContent = 'sec/vote'
- secPerVote.style.width = '4em';
- secPerVote.style.marginLeft = '.25em';
- if (window.location.href.includes('old.reddit.com')) {
- secPerVote.style.padding = '0 .5em 0 .25em';
- } else {
- secPerVote.style.padding = '0 .25em';
- }
- secPerVote.style.fontSize = '14px';
- secPerVote.style.border = '1px solid black';
- secPerVote.style.display = 'inline-flex';
- secPerVote.style.alignItems = 'center';
- secPerVote.style.height = '1.5em';
- // Create a line break element
- // var lineBreak3 = document.createElement('br');
- // Create a new div with the specified properties
- var upvotesRemainingDiv = document.createElement('div');
- upvotesRemainingDiv.id = 'upvotesRemainingDiv';
- upvotesRemainingDiv.style.background = 'white';
- upvotesRemainingDiv.style.border = '1px solid black';
- upvotesRemainingDiv.style.marginTop = '4px';
- upvotesRemainingDiv.style.display = 'none';
- upvotesRemainingDiv.style.padding = '0.25em';
- upvotesRemainingDiv.style.fontSize = '14px';
- // Create a span within the div with the specified properties
- var upvotesRemainingSpan = document.createElement('span');
- upvotesRemainingSpan.id = 'upvotesRemaining';
- var lineBreak2 = document.createElement('br');
- var seeSettingsPagePrompt = document.createElement('div');
- seeSettingsPagePrompt.style.color = 'black';
- 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)';
- seeSettingsPagePrompt.style.fontSize = '12px';
- seeSettingsPagePrompt.style.width = '11.5rem';
- seeSettingsPagePrompt.style.marginTop = '4px';
- var currentSubdomain = window.location.hostname.split('.')[0];
- if (currentSubdomain === 'new') {
- // If the subdomain is "new"
- 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 + '.';
- } else if (currentSubdomain === 'old') {
- // If the subdomain is "old"
- 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 + '.';
- } else {
- // For any other subdomain
- 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 + '.';
- }
- // Append the "Do it" button, "Stop" button, line break, and text field to the div
- newUpvoterDiv.appendChild(upvoteAllButton);
- newUpvoterDiv.appendChild(stopButton);
- newUpvoterDiv.appendChild(lineBreak);
- rateDiv.appendChild(secondsTextField);
- rateDiv.appendChild(secPerVote);
- newUpvoterDiv.appendChild(rateDiv);
- // newUpvoterDiv.appendChild(lineBreak3);
- newUpvoterDiv.appendChild(upvotesRemainingDiv);
- upvotesRemainingDiv.appendChild(upvotesRemainingSpan);
- if (showSettingsPrompt) {
- newUpvoterDiv.appendChild(lineBreak2);
- newUpvoterDiv.appendChild(seeSettingsPagePrompt);
- }
- // Append the div to the body
- document.body.appendChild(newUpvoterDiv);
- // Set the value of the text field to the stored seconds
- secondsTextField.value = seconds;
- // Debounce function to update the seconds variable
- secondsTextField.addEventListener('input', function () {
- clearTimeout(debounceTimer);
- debounceTimer = setTimeout(function () {
- if (secondsTextField.value >= minsec) {
- seconds = secondsTextField.value;
- // console.log('seconds: ' + seconds);
- } else {
- seconds = minsec;
- secondsTextField.value = config.seconds;
- }
- updateConfigSeconds();
- updateInterval();
- }, 1000);
- });
- // New function to update config seconds and save to local storage
- function updateConfigSeconds() {
- // console.log('config: ' + JSON.stringify(config));
- config.seconds = seconds;
- // console.log('config.seconds: ' + config.seconds);
- localStorage.setItem('BulkUpvoterConfig', JSON.stringify(config));
- // console.log('config: ' + JSON.stringify(config));
- config = loadConfigFromLocalStorage(); // Update the config variable
- // console.log('config: ' + JSON.stringify(config));
- secondsTextField.value = config.seconds;
- // console.log('seconds: ' + seconds);
- // Update the value of an input element with id 'seconds' if it exists
- var secondsInput = document.querySelector('#bulk-upvoter-option-tab-menu #seconds');
- if (secondsInput) {
- secondsInput.value = config.seconds;
- }
- }
- // Variables
- var stopClicking = true;
- var clickInterval;
- // Function to simulate clicks on elements with specific attributes
- function simulateClicks() {
- var overlayContainer = document.getElementById('overlayScrollContainer');
- const mainContent = document.querySelector('#main-content');
- var allShadowRoots;
- if (mainContent) {
- // Get all shadow roots within #main-content
- allShadowRoots = mainContent.querySelectorAll('*:not(style)');
- // console.log('allShadowRoots.length: ' + allShadowRoots.length);
- }
- var buttonsToClick;
- if (overlayContainer) {
- // If overlay container exists, build array from elements within it
- buttonsToClick = Array.from(overlayContainer.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]'));
- // console.log('Overlay container case.');
- // buttonsToClick = [
- // ...Array.from(overlayContainer.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]')),
- // ...Array.from(overlayContainer.querySelectorAll('button[class*="upvote"][aria-pressed="false"]'))
- // ];
- } else if (window.location.href.match(/https?:\/\/new\.reddit\.com\/(r|user)\/(?!.*\/comment).*$/)) {
- // console.log('New Reddit subreddit page.');
- buttonsToClick = [];
- // Get all post containers
- const postContainers = document.querySelectorAll('[data-testid="post-container"]');
- // console.log('Number of post containers:', postContainers.length);
- postContainers.forEach((postContainer, index) => {
- // console.log(`Processing post container ${index + 1}:`);
- // Get all upvote buttons in the post container
- const upvoteButtons = postContainer.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]');
- // console.log('Number of upvote buttons:', upvoteButtons.length);
- // Process upvote buttons
- if (upvoteButtons.length > 1) {
- // console.log('More than one upvote button. Adding the second one to the array.');
- buttonsToClick.push(upvoteButtons[1]);
- } else if (upvoteButtons.length === 1) {
- // console.log('Exactly one upvote button. Adding it to the array.');
- buttonsToClick.push(upvoteButtons[0]);
- } else {
- // console.log('No upvote buttons found in this post container.');
- }
- });
- // console.log('Final buttons to click array:', buttonsToClick);
- } else {
- // console.log('Third case.');
- // Otherwise, build array the normal way
- buttonsToClick = Array.from(document.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]'));
- // console.log('Normal case.');
- // console.log('The point before the experiment.');
- // buttonsToClick = Array.from(document.querySelectorAll('button[aria-label="upvote"][aria-pressed="false"]')).filter(button => {
- // var currentElement = button;
- // while (currentElement) {
- // const styles = window.getComputedStyle(currentElement);
- // if (styles.display === 'none' || styles.visibility === 'hidden') {
- // return false; // Exclude the button
- // }
- // currentElement = currentElement.parentElement;
- // }
- // return true; // Include the button
- // }).concat(Array.from(document.querySelectorAll('div[role="button"].arrow.up')));
- if (mainContent) {
- // Iterate through each shadow host
- allShadowRoots.forEach(shadowHost => {
- // Access the shadow root directly
- const shadowRoot = shadowHost.shadowRoot;
- // Check if shadowRoot is available and find buttons
- if (shadowRoot) {
- const buttonsInShadowRoot = Array.from(shadowRoot.querySelectorAll('button[upvote][aria-pressed="false"]'));
- buttonsToClick = buttonsToClick.concat(buttonsInShadowRoot);
- }
- });
- }
- }
- // console.log('buttonsToClick.length: ', buttonsToClick.length);
- clearInterval(clickInterval); // Stop the interval if it's running
- clearTimeout(debounceTimer); // Clear the debounce timer
- function clickNextButton() {
- if (buttonsToClick.length > 0 && !stopClicking) {
- var currentButton = buttonsToClick.shift(); // Remove the first button from the array
- // Check if aria-pressed is still "false"
- if (currentButton.getAttribute('aria-pressed') === 'false' || currentButton.tagName.toLowerCase() === 'div') {
- currentButton.click();
- // Update remaining upvotes info
- // Determine the text content based on the URL condition
- var isCommentsPage = window.location.href.includes("/comments");
- upvotesRemainingSpan.textContent = isCommentsPage ? buttonsToClick.length + ' remaining' : (buttonsToClick.length - 1) / 2 + ' remaining';
- upvotesRemainingSpan.textContent = buttonsToClick.length + ' remaining';
- upvotesRemainingDiv.style.display = 'block';
- } else {
- // If the button is already pressed, immediately go to the next without waiting for the interval
- clickNextButton();
- }
- } else {
- clearInterval(clickInterval); // Stop the interval when all buttons are clicked or stopClicking is true
- // After a delay, hide the remaining upvotes info
- stopClicking = 'true';
- setTimeout(function () {
- upvotesRemainingDiv.style.display = 'none';
- }, 2500);
- }
- }
- // Initial click
- clickNextButton();
- // Set interval for subsequent clicks
- clickInterval = setInterval(clickNextButton, seconds * 1000);
- }
- // Event listener for the "Do it" button
- upvoteAllButton.addEventListener('click', function () {
- stopClicking = false; // Reset stopClicking to false
- simulateClicks(); // Call the simulateClicks function
- });
- // Event listener for the "Stop" button
- stopButton.addEventListener('click', function () {
- stopClicking = true;
- clearInterval(clickInterval); // Stop the interval if it's running
- clearTimeout(debounceTimer); // Clear the debounce timer
- setTimeout(function () {
- upvotesRemainingDiv.style.display = 'none';
- }, 2500);
- });
- // Update the interval when the seconds variable changes
- function updateInterval() {
- // console.log('Updating interval.');
- clearInterval(clickInterval); // Clear the existing interval
- simulateClicks(); // Restart the clicking process with the updated seconds variable
- }
- upvoterDiv = document.getElementById('upvoter-div');
- }
- var upvoterDiv = document.getElementById('upvoter-div');
- // Adding the Option Tab
- // Function to check if the current URL matches the desired pattern
- function isSettingsPage() {
- var settingsPagePattern = /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/(?:settings|prefs).*$/i;
- return settingsPagePattern.test(window.location.href);
- }
- function isOldReddit() {
- var settingsPagePattern = /^https?:\/\/?old.reddit\.com\/prefs.*$/i;
- return settingsPagePattern.test(window.location.href);
- }
- // var bulkUpvoterOptionTabElement;
- var commonClasses;
- function handleRemoval() {
- addBulkUpvoterToTablist();
- }
- // Function to add the new option to the tablist if it doesn't already exist
- function addBulkUpvoterToTablist() {
- // Check if the element with the specified ID already exists
- var existingOption = document.getElementById('bulk-upvoter-option-tab');
- // If it doesn't exist, create and add the new option
- if (!existingOption) {
- // Create a new anchor element
- var bulkUpvoterOptionTab;
- bulkUpvoterOptionTab = document.createElement('a');
- bulkUpvoterOptionTab.setAttribute('aria-selected', 'false');
- bulkUpvoterOptionTab.setAttribute('role', 'tab');
- bulkUpvoterOptionTab.id = 'bulk-upvoter-option-tab';
- bulkUpvoterOptionTab.style.color = 'blue';
- bulkUpvoterOptionTab.textContent = 'Bulk Upvoter';
- // Find the tablist element
- var tablist;
- if (isOldReddit()) {
- tablist = document.querySelector('ul.tabmenu');
- } else {
- tablist = document.querySelector('[role="tablist"]');
- }
- // Find all the A elements in the tablist
- var tabs;
- if (isOldReddit()) {
- tabs = tablist.querySelectorAll('li');
- } else {
- tabs = tablist.querySelectorAll('a');
- }
- // Get the classes of the first tab (assuming there is at least one tab)
- commonClasses = tabs.length > 0 ? Array.from(tabs[0].classList) : [];
- // Loop through the tabs to find common classes
- for (var i = 1; i < tabs.length; i++) {
- var tabClasses = Array.from(tabs[i].classList);
- commonClasses = commonClasses.filter(value => tabClasses.includes(value));
- }
- // Create list element for Old Reddit
- var bulkUpvoterOptionTabLi = document.createElement('li');
- bulkUpvoterOptionTabLi.id = 'bulk-upvoter-option-tab-li';
- // Add the common classes to the new option
- if (isOldReddit()) {
- bulkUpvoterOptionTabLi.className = commonClasses.join(' ');
- } else {
- bulkUpvoterOptionTab.className = commonClasses.join(' ');
- }
- // Add the new option after everything else in the tablist
- if (isOldReddit()) {
- tablist.appendChild(bulkUpvoterOptionTabLi);
- bulkUpvoterOptionTabLi.appendChild(bulkUpvoterOptionTab);
- bulkUpvoterOptionTab.style.cursor = 'pointer';
- } else {
- tablist.appendChild(bulkUpvoterOptionTab);
- }
- // Add click event listener to the new option
- bulkUpvoterOptionTab.addEventListener('click', function () {
- handleBulkUpvoterClick(tablist);
- });
- // Create a MutationObserver instance
- var observer = new MutationObserver(function (mutations) {
- mutations.forEach(function (mutation) {
- // Check if the new option is removed
- if (mutation.removedNodes && mutation.removedNodes.length > 0) {
- for (var i = 0; i < mutation.removedNodes.length; i++) {
- if (mutation.removedNodes[i].id === 'bulk-upvoter-option-tab') {
- // Handle the removal by adding it back
- handleRemoval();
- break;
- }
- }
- }
- });
- });
- // Configure and start the observer
- var observerConfig = {
- childList: true,
- subtree: true
- };
- observer.observe(tablist, observerConfig);
- }
- }
- // Check if the current page is the settings page
- if (isSettingsPage()) {
- if (isOldReddit()) {
- // console.log('This appears to be the Old Reddit settings page.');
- } else {
- // console.log('This is not the Old Reddit settings page.');
- }
- addBulkUpvoterToTablist();
- // Wait for the entire window to be fully loaded
- window.onload = function () {
- // Add the new option to the tablist during onload, just in case
- addBulkUpvoterToTablist();
- };
- }
- var currentClass;
- // Function to handle the click on the new option
- function handleBulkUpvoterClick(tablist) {
- // Find the new option
- var bulkUpvoterOptionTab = document.getElementById('bulk-upvoter-option-tab');
- var bulkUpvoterOptionTabLi = document.getElementById('bulk-upvoter-option-tab-li');
- if (isOldReddit()) {
- // Assuming you have a reference to the ul.tabmenu element
- const tabmenu = document.querySelector('ul.tabmenu');
- if (tabmenu) {
- // Get all li elements inside ul.tabmenu
- const tabmenuItems = tabmenu.querySelectorAll('li');
- // Iterate through each li element
- tabmenuItems.forEach(item => {
- // Remove the class "selected" if it exists
- item.classList.remove('selected');
- });
- }
- bulkUpvoterOptionTabLi.className = 'selected';
- }
- showBulkUpvoterMenu(tablist);
- // Check if the new option already has the class
- if (bulkUpvoterOptionTab.classList.contains('stored-class')) {
- return; // Exit if the class is already added
- }
- // Find all A elements in the tablist (excluding the new option)
- var tabs = document.querySelectorAll('[role="tablist"] a:not(#bulk-upvoter-option-tab)');
- // Loop through the tabs to find the unique class for bottom border
- for (var i = 0; i < tabs.length; i++) {
- var currentTab = tabs[i];
- // Check if the tab has a bottom border
- var computedStyle = window.getComputedStyle(currentTab);
- if (computedStyle.getPropertyValue('border-bottom-width') !== '0px') {
- // Loop through the classes to find the unique class for bottom border
- for (var j = 0; j < currentTab.classList.length; j++) {
- currentClass = currentTab.classList[j];
- // Check if the class is not in the common classes
- if (!commonClasses.includes(currentClass)) {
- // Remove the unique class from the current tab
- currentTab.classList.remove(currentClass);
- // Add the unique class to the new option
- bulkUpvoterOptionTab.classList.add(currentClass);
- // Log the stored class
- // console.log('Stored class: ' + currentClass);
- // Exit the loop
- return;
- }
- }
- }
- }
- }
- // Function to handle the click on other A elements in the tablist
- function handleTabClick(event) {
- // Find the new option
- var bulkUpvoterOptionTab = document.getElementById('bulk-upvoter-option-tab');
- // Check if the new option has the class
- if (bulkUpvoterOptionTab.classList.contains(currentClass)) {
- // Remove the class from the new option
- bulkUpvoterOptionTab.classList.remove(currentClass);
- // Log the removed class
- // console.log('Removed class from new option: ' + currentClass);
- }
- // Get the clicked A element
- var clickedTab = event.target;
- // Check if the clicked A element is the new option
- if (clickedTab === bulkUpvoterOptionTab) {
- return;
- }
- // Add the class to the clicked A element
- clickedTab.classList.add(currentClass);
- // Log the added class
- // console.log('Added class to clicked A element: ' + currentClass);
- hideBulkUpvoterMenu();
- }
- // Attach click event listener to all A elements in the tablist
- var tabs = document.querySelectorAll('[role="tablist"] a:not(#bulk-upvoter-option-tab)');
- tabs.forEach(function (tab) {
- tab.addEventListener('click', handleTabClick);
- });
- // Show/hide the new menu.
- // Function to create or reveal the new option menu
- function showBulkUpvoterMenu(tablist) {
- // Check if the menu already exists
- var menu = document.getElementById('bulk-upvoter-option-tab-menu');
- // var tablist = document.querySelector('[role="tablist"]');
- // If it doesn't exist, create and style the menu
- if (!menu) {
- menu = document.createElement('div');
- menu.id = 'bulk-upvoter-option-tab-menu';
- menu.style.backgroundColor = '#ddd';
- menu.style.border = '1px solid black';
- menu.style.minHeight = '40px';
- menu.style.display = 'block'; // Initial display option
- menu.style.position = 'absolute'; // Set position to absolute
- menu.style.boxSizing = 'border-box';
- menu.style.padding = '8px';
- menu.style.fontSize = '16px';
- menu.style.fontWeight = '500';
- menu.style.filter = 'drop-shadow(2px 6px 8px black)';
- menu.style.left = '50vw';
- menu.style.translate = '-50% 0';
- // Append the menu to the body
- document.body.appendChild(menu);
- // Set up a ResizeObserver to watch for changes in the tablist's dimensions
- var resizeObserver = new ResizeObserver(function () {
- // Adjust the menu's position and width dynamically
- positionMenu(tablist, menu);
- });
- // Start observing the tablist
- resizeObserver.observe(tablist);
- // Populate the menu with config and set up input listeners
- populateMenu(menu);
- setupInputListeners(menu);
- // Populate the menu with the loaded config
- populateMenuWithConfig(menu);
- } else {
- // If it exists, set its display property to block
- menu.style.display = 'block';
- }
- // Adjust the menu's position and width dynamically
- positionMenu(tablist, menu);
- }
- // Function to dynamically position the menu below the tablist
- function positionMenu(tablist, menu) {
- // Get the tablist's bounding box
- var tablistRect = tablist.getBoundingClientRect();
- // Get the tablist's computed style to include margins
- var tablistStyle = window.getComputedStyle(tablist);
- // Convert left and right margins to numbers
- var paddingLeft = parseFloat(tablistStyle.paddingLeft);
- var marginLeft = parseFloat(tablistStyle.marginLeft);
- // console.log('paddingLeft: ' + paddingLeft);
- var paddingRight = parseFloat(tablistStyle.paddingRight);
- var marginRight = parseFloat(tablistStyle.marginRight);
- // console.log('paddingRight: ' + paddingRight);
- // Set the menu's width to match the tablist's width
- menu.style.width = tablistRect.width - paddingLeft - paddingRight + 'px';
- // // Set the menu's left position to center it horizontally under the tablist
- // menu.style.left = tablistRect.left + paddingLeft + 'px';
- // // Set the menu's top position to be directly below the tablist
- // menu.style.top = tablistRect.bottom + 10 + 'px';
- // Get the tablist's top position
- var tablistTop = window.scrollY + tablistRect.top;
- // Set the menu's top position to be the bottom of the tablist
- menu.style.top = tablistTop + tablistRect.height + 10 + 'px';
- }
- // Function to hide the new option menu
- function hideBulkUpvoterMenu() {
- // Check if the menu exists
- var menu = document.getElementById('bulk-upvoter-option-tab-menu');
- // If it exists, set its display property to none
- if (menu) {
- menu.style.display = 'none';
- }
- }
- // What goes in the menu:
- // Function to populate the menu with options
- function populateMenu(menu) {
- // Create a div for radio buttons
- var radioButtonsContainer = document.createElement('div');
- radioButtonsContainer.style.display = 'flex';
- // Create radio button divs for "Everywhere", "Whitelist", "Blacklist", "Off"
- var radioLabels = ['Everywhere', 'Whitelist', 'Blacklist', 'Off'];
- for (var i = 0; i < radioLabels.length; i++) {
- var radioDiv = createOptionDiv('radio' + i, radioLabels[i]);
- radioButtonsContainer.appendChild(radioDiv);
- }
- menu.appendChild(radioButtonsContainer);
- // Create a div for "Whitelists" label and text boxes
- var whitelistsLabelDiv = createLabelDiv('whitelistsLabel', 'Whitelists');
- menu.appendChild(whitelistsLabelDiv);
- // Create a flex container for "Red" and "U" text boxes
- var redUWhitelistContainer = document.createElement('div');
- redUWhitelistContainer.style.display = 'grid';
- redUWhitelistContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
- redUWhitelistContainer.style.gap = '8px';
- // Create divs for "Red" and "U" with text boxes
- var redWhitelistDiv = createTextBoxDiv('redWhitelist', 'Subreddits');
- redUWhitelistContainer.appendChild(redWhitelistDiv);
- var uWhitelistDiv = createTextBoxDiv('uWhitelist', 'Users');
- redUWhitelistContainer.appendChild(uWhitelistDiv);
- menu.appendChild(redUWhitelistContainer);
- // Create a div for "Blacklists" label and text boxes
- var blacklistsLabelDiv = createLabelDiv('blacklistsLabel', 'Blacklists');
- menu.appendChild(blacklistsLabelDiv);
- // Create a flex container for "Red" and "U" text boxes
- var redUBlacklistContainer = document.createElement('div');
- redUBlacklistContainer.style.display = 'grid';
- redUBlacklistContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
- redUBlacklistContainer.style.gap = '8px';
- // Create divs for "Red" and "U" with text boxes
- var redBlacklistDiv = createTextBoxDiv('redBlacklist', 'Subreddits');
- redUBlacklistContainer.appendChild(redBlacklistDiv);
- var uBlacklistDiv = createTextBoxDiv('uBlacklist', 'Users');
- redUBlacklistContainer.appendChild(uBlacklistDiv);
- menu.appendChild(redUBlacklistContainer);
- var wildcardInfoDiv = document.createElement('div');
- wildcardInfoDiv.style.paddingTop = '8px';
- var wildcardInfo = document.createElement('span');
- wildcardInfo.textContent = 'Enter the names of subreddits or users, one per line. Use * as a wildcard.';
- wildcardInfo.style.fontSize = '14px';
- menu.appendChild(wildcardInfoDiv);
- wildcardInfoDiv.appendChild(wildcardInfo);
- // Function to create a horizontal black line
- function createHorizontalLine() {
- var line = document.createElement('div');
- line.style.borderTop = '1px solid black';
- line.style.margin = '8px 0';
- return line;
- }
- // Function to create the "One click every" section
- function createClickIntervalSection() {
- var section = document.createElement('div');
- section.style.display = 'flex';
- section.style.fontWeight = '100';
- // Text: "One click every"
- var text1 = document.createElement('span');
- text1.textContent = 'One click every ';
- text1.style.paddingTop = '0.1em';
- // Text input for seconds, id: "seconds"
- var secondsInput = document.createElement('input');
- secondsInput.type = 'text';
- secondsInput.id = 'seconds';
- secondsInput.style.margin = '0px .5em';
- secondsInput.style.width = '4em';
- secondsInput.style.padding = '0 0.25em';
- secondsInput.type = 'number';
- secondsInput.style.border = '1px solid black';
- secondsInput.step = '0.05';
- secondsInput.min = '0';
- // Text: "seconds, with a minimum allowed value of"
- var text2 = document.createElement('span');
- text2.textContent = ' seconds, with a minimum allowed value of ';
- text2.style.paddingTop = '0.1em';
- // Text input for min-sec, id: "min-sec"
- var minSecInput = document.createElement('input');
- minSecInput.type = 'text';
- minSecInput.id = 'min-sec';
- minSecInput.style.margin = '0px .5em';
- minSecInput.style.width = '4em';
- minSecInput.style.padding = '0 0.25em';
- minSecInput.type = 'number';
- minSecInput.style.border = '1px solid black';
- minSecInput.step = '0.05';
- minSecInput.min = '0';
- // Text: "seconds."
- var text3 = document.createElement('span');
- text3.textContent = ' seconds.';
- text3.style.paddingTop = '0.1em';
- // Appending elements to the section
- section.appendChild(text1);
- section.appendChild(secondsInput);
- section.appendChild(text2);
- section.appendChild(minSecInput);
- section.appendChild(text3);
- minSecInput.addEventListener('input', function () {
- // Update minsec variable when minSecInput value changes
- minsec = parseFloat(minSecInput.value);
- // console.log('minsec: ' + minsec);
- debounceTimer = setTimeout(function () {
- if (secondsInput.value >= minsec) {
- seconds = secondsInput.value;
- } else {
- seconds = minsec;
- secondsInput.value = seconds;
- }
- config.seconds = seconds;
- var secondsTextField = document.getElementById('seconds-text-field');
- saveInputsToLocalStorage(menu);
- loadConfigFromLocalStorage();
- // updateInterval();
- }, 950);
- });
- secondsInput.addEventListener('input', function () {
- // console.log('minsec: ' + minsec);
- clearTimeout(debounceTimer);
- debounceTimer = setTimeout(function () {
- if (secondsInput.value >= minsec) {
- config.seconds = secondsInput.value;
- seconds = config.seconds;
- // console.log('seconds has been set to: ' + seconds);
- } else {
- config.seconds = minsec;
- seconds = config.seconds;
- secondsInput.value = config.seconds;
- // console.log('seconds went too low and has been set to: ' + seconds);
- }
- seconds = config.seconds;
- saveInputsToLocalStorage(menu);
- loadConfigFromLocalStorage();
- // updateInterval();
- }, 950);
- });
- if (isOldReddit()) {
- text1.style.fontSize = oldRedditTextSize;
- secondsInput.style.fontSize = oldRedditTextSize;
- text2.style.fontSize = oldRedditTextSize;
- minSecInput.style.fontSize = oldRedditTextSize;
- text3.style.fontSize = oldRedditTextSize;
- }
- return section;
- }
- // Append the horizontal black line and "One click every" section to the menu
- menu.appendChild(createHorizontalLine());
- menu.appendChild(createClickIntervalSection());
- var showHideSettingsPromptDiv = document.createElement('div');
- showHideSettingsPromptDiv.style.display = 'flex';
- showHideSettingsPromptDiv.style.paddingTop = '8px';
- showHideSettingsPromptDiv.style.alignItems = 'center';
- var showHideSettingsPromptText = document.createElement('span');
- showHideSettingsPromptText.textContent = 'Hide \"Go to settings\" prompt on upvoter:'
- showHideSettingsPromptText.style.paddingRight = '0.5em';
- showHideSettingsPromptText.style.fontWeight = '100';
- var hideSettingsPromptCheckbox = document.createElement('input');
- hideSettingsPromptCheckbox.id = 'hide-settings-checkbox';
- hideSettingsPromptCheckbox.type = 'checkbox';
- hideSettingsPromptCheckbox.style.translate = '0 1px';
- hideSettingsPromptCheckbox.style.height = '15px';
- hideSettingsPromptCheckbox.style.width = '15px';
- showHideSettingsPromptDiv.appendChild(showHideSettingsPromptText);
- showHideSettingsPromptDiv.appendChild(hideSettingsPromptCheckbox);
- menu.appendChild(showHideSettingsPromptDiv);
- // Hotkey section
- // Create the container div
- var hotkeyContainer = document.createElement('div');
- hotkeyContainer.style.display = 'flex';
- hotkeyContainer.style.paddingTop = '8px';
- hotkeyContainer.style.alignItems = 'center';
- hotkeyContainer.id = 'hotkey-settings';
- // Create the span
- var hotkeyLabel = document.createElement('span');
- hotkeyLabel.textContent = 'Hotkey to display the upvoter:';
- hotkeyLabel.style.paddingRight = '0.5em';
- hotkeyLabel.style.fontWeight = '100';
- hotkeyContainer.appendChild(hotkeyLabel);
- var hotkeyCheckbox = document.createElement('input');
- hotkeyCheckbox.type = 'checkbox';
- hotkeyCheckbox.id = 'hotkey-checkbox';
- hotkeyCheckbox.style.width = '15px';
- hotkeyCheckbox.style.height = '15px';
- hotkeyCheckbox.style.translate = '0 1px';
- hotkeyCheckbox.style.marginRight = '1.5em';
- hotkeyContainer.appendChild(hotkeyCheckbox);
- // Attach listener to the checkbox
- hotkeyCheckbox.addEventListener('change', toggleHotkeySettings);
- // Create radio buttons
- var oneModifierRadio = createRadioButton('modifierRadio', 'oneModifier', 'One Modifier Key', true);
- var twoModifierRadio = createRadioButton('modifierRadio', 'twoModifier', 'Two Modifier Keys', false);
- hotkeyContainer.appendChild(oneModifierRadio);
- hotkeyContainer.appendChild(twoModifierRadio);
- // Create dropdown boxes
- var firstDropdown = createDropdown('firstModifierDropdown', ['', 'CTRL', 'ALT', 'SHIFT']);
- var secondDropdown = createDropdown('secondModifierDropdown', ['', 'CTRL', 'ALT', 'SHIFT']);
- hotkeyContainer.appendChild(firstDropdown);
- hotkeyContainer.appendChild(secondDropdown);
- // Create the textbox
- var hotkeyTextbox = document.createElement('input');
- hotkeyTextbox.type = 'text';
- hotkeyTextbox.id = 'hotkeyBaseKey';
- hotkeyTextbox.maxLength = 1;
- hotkeyTextbox.style.padding = '0 0.5em';
- hotkeyTextbox.style.textAlign = 'center';
- hotkeyTextbox.style.width = '1.5em';
- hotkeyTextbox.style.border = '1px solid black';
- hotkeyContainer.appendChild(hotkeyTextbox);
- hotkeyTextbox.addEventListener('input', function () {
- this.value = this.value.toUpperCase();
- });
- // Append the container to the body
- menu.appendChild(hotkeyContainer);
- // Add event listeners
- oneModifierRadio.addEventListener('change', toggleDropdownVisibility);
- twoModifierRadio.addEventListener('change', toggleDropdownVisibility);
- firstDropdown.addEventListener('change', disableEnableOptions);
- // Helper function to create radio buttons
- function createRadioButton(name, radioID, label, checked) {
- // console.log('Radio buttons being created');
- var radioLabelDiv = document.createElement('div');
- radioLabelDiv.style.display = 'flex';
- radioLabelDiv.style.fontSize = '14px';
- var radioLabel = document.createElement('label');
- radioLabel.setAttribute('for', radioID); // Use the radioId here
- radioLabel.textContent = label;
- radioLabel.style.marginRight = '1em';
- var radio = document.createElement('input');
- radio.type = 'radio';
- radio.name = name;
- radio.id = radioID; // Set the id directly on the radio button
- radio.checked = checked;
- radio.style.marginRight = '0.25em';
- radioLabelDiv.appendChild(radio);
- radioLabelDiv.appendChild(radioLabel);
- return radioLabelDiv;
- }
- // Helper function to create dropdown boxes
- function createDropdown(id, options) {
- var dropdown = document.createElement('select');
- dropdown.id = id;
- dropdown.style.fontSize = '15px';
- dropdown.style.marginRight = '0.5em';
- dropdown.style.padding = '2px';
- options.forEach(function (option) {
- var optionElement = document.createElement('option');
- optionElement.value = option;
- optionElement.textContent = option;
- dropdown.appendChild(optionElement);
- });
- return dropdown;
- }
- // Event listener to toggle visibility of the second dropdown
- function toggleDropdownVisibility() {
- // console.log('twoModifierRadio: ' + config.twoModifier);
- secondDropdown.style.display = config.twoModifier ? 'none' : 'inline-block';
- }
- // Event listener to disable/enable options in the second dropdown
- function disableEnableOptions() {
- var selectedOption = firstDropdown.value;
- for (var i = 0; i < secondDropdown.options.length; i++) {
- var option = secondDropdown.options[i];
- // Check if the first dropdown's selected option is null
- if (selectedOption !== '') {
- option.disabled = option.value === selectedOption;
- // If the currently selected option in the second dropdown becomes disabled, reset to null
- if (option.value === secondDropdown.value && option.disabled) {
- secondDropdown.value = '';
- }
- } else {
- // If the first dropdown's selected option is null, don't disable the null option in the second dropdown
- option.disabled = false;
- }
- }
- // Filter out disabled options from the second dropdown
- updateDropdownVisibility();
- }
- // Helper function to filter out disabled options from the second dropdown
- function updateDropdownVisibility() {
- Array.from(secondDropdown.options).forEach(option => {
- option.hidden = option.disabled;
- });
- }
- menu.appendChild(createHorizontalLine());
- var navigationInfoDiv = document.createElement('div');
- // navigationInfoDiv.style.paddingTop = '8px';
- var navigationInfo = document.createElement('span');
- 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.';
- navigationInfo.style.fontSize = '12.5px';
- navigationInfo.style.fontWeight = '100';
- menu.appendChild(navigationInfoDiv);
- navigationInfoDiv.appendChild(navigationInfo);
- var buttonInfoDiv = document.createElement('div');
- buttonInfoDiv.style.paddingTop = '4px';
- var buttonInfo = document.createElement('span');
- 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.';
- buttonInfo.style.fontSize = '12.5px';
- buttonInfo.style.fontWeight = '100';
- menu.appendChild(buttonInfoDiv);
- buttonInfoDiv.appendChild(buttonInfo);
- // Example buttons to trigger the functions
- var saveToFileButton = document.createElement('button');
- saveToFileButton.textContent = 'Export Config to File';
- saveToFileButton.addEventListener('click', saveConfigToFile);
- // saveToFileButton.style.marginTop = '8px';
- saveToFileButton.style.background = 'white';
- saveToFileButton.style.border = '1px solid white';
- saveToFileButton.style.padding = '6px 9px';
- saveToFileButton.style.borderRadius = '6px';
- saveToFileButton.style.border = '1px solid black';
- saveToFileButton.style.marginRight = '0.5em';
- var loadFromFileButton = document.createElement('button');
- loadFromFileButton.textContent = 'Load Config from File';
- loadFromFileButton.addEventListener('click', loadConfigFromFile);
- // loadFromFileButton.style.marginTop = '8px';
- loadFromFileButton.style.background = 'white';
- loadFromFileButton.style.border = '1px solid white';
- loadFromFileButton.style.padding = '6px 9px';
- loadFromFileButton.style.borderRadius = '6px';
- loadFromFileButton.style.border = '1px solid black';
- var saveLoadDiv = document.createElement('div');
- saveLoadDiv.style.display = 'flex';
- saveLoadDiv.style.justifyContent = 'center';
- // Append the buttons to the menu or wherever you want them
- menu.appendChild(createHorizontalLine());
- saveLoadDiv.appendChild(saveToFileButton);
- saveLoadDiv.appendChild(loadFromFileButton);
- menu.appendChild(saveLoadDiv);
- if (isOldReddit()) {
- showHideSettingsPromptText.style.fontSize = oldRedditTextSize;
- hideSettingsPromptCheckbox.style.fontSize = oldRedditTextSize;
- hotkeyLabel.style.fontSize = oldRedditTextSize;
- hotkeyCheckbox.style.fontSize = oldRedditTextSize;
- oneModifierRadio.style.fontSize = oldRedditTextSize;
- twoModifierRadio.style.fontSize = oldRedditTextSize;
- firstDropdown.style.fontSize = oldRedditTextSize;
- secondDropdown.style.fontSize = oldRedditTextSize;
- hotkeyTextbox.style.fontSize = oldRedditTextSize;
- hotkeyTextbox.style.padding = '0.15em 0.5em';
- saveToFileButton.style.fontSize = oldRedditTextSize;
- loadFromFileButton.style.fontSize = oldRedditTextSize;
- }
- // Function to load config from a text file
- function loadConfigFromFile() {
- var input = document.createElement('input');
- input.type = 'file';
- input.accept = '.txt';
- const secondsTextField = document.getElementById('seconds-text-field');
- input.addEventListener('change', function (event) {
- var file = event.target.files[0];
- if (file) {
- var reader = new FileReader();
- reader.onload = function (e) {
- try {
- var loadedConfig = JSON.parse(e.target.result);
- // console.log('loadedConfig: ' + loadedConfig);
- config = loadedConfig;
- // config = '';
- // config = Object.assign({}, config, loadedConfig);
- console.log('Config loaded from file:', config);
- localStorage.setItem('BulkUpvoterConfig', JSON.stringify(config));
- loadConfigFromLocalStorage();
- populateMenuWithConfig(menu);
- if (secondsTextField) {
- secondsTextField.value = seconds;
- }
- } catch (error) {
- console.error('Error parsing file:', error);
- }
- };
- reader.readAsText(file);
- }
- });
- input.click();
- }
- // Erase Button
- var eraseDiv = document.createElement('div');
- eraseDiv.style.display = 'none';
- eraseDiv.style.textAlign = 'center';
- if (debugMode) {
- eraseDiv.style.display = 'block';
- }
- var eraseSpan = document.createElement('span');
- eraseSpan.textContent = 'You have revealed the hidden erase button.'
- eraseSpan.style.display = 'block';
- var eraseSettingsButton = document.createElement('button');
- eraseSettingsButton.textContent = 'Erase All Settings';
- eraseSettingsButton.addEventListener('click', eraseSettingsFromLocalStorage);
- eraseSettingsButton.style.marginTop = '8px';
- eraseSettingsButton.style.background = 'white';
- eraseSettingsButton.style.border = '1px solid white';
- eraseSettingsButton.style.padding = '6px 9px';
- eraseSettingsButton.style.borderRadius = '6px';
- eraseSettingsButton.style.border = '1px solid black';
- eraseDiv.appendChild(createHorizontalLine());
- eraseDiv.appendChild(eraseSpan);
- eraseDiv.appendChild(eraseSettingsButton);
- menu.appendChild(eraseDiv);
- }
- // Function to toggle hotkey settings
- function toggleHotkeySettings() {
- // Get the hotkey settings div
- var hotkeySettingsDiv = document.getElementById('hotkey-settings');
- // Check if the checkbox is checked
- var hotkeyCheckbox = document.getElementById('hotkey-checkbox');
- var isEnabled = hotkeyCheckbox.checked;
- // Get all input elements within the hotkey settings div, excluding the checkbox
- var inputs = hotkeySettingsDiv.querySelectorAll('input:not(#hotkey-checkbox), select');
- // Iterate over each input element and enable/disable based on the checkbox state
- inputs.forEach(function (input) {
- input.disabled = !isEnabled;
- });
- }
- // Function to create an option div with radio and label
- function createOptionDiv(id, labelText) {
- // Create the div
- var optionDiv = document.createElement('div');
- optionDiv.style.marginRight = '32px';
- // Create a flex container for radio button and label
- var radioFlexContainer = document.createElement('div');
- radioFlexContainer.style.display = 'flex';
- // Create the radio button
- var radio = document.createElement('input');
- radio.type = 'radio';
- radio.id = id;
- radio.style.marginRight = '8px';
- radio.name = 'options'; // Make sure they are in the same group
- radioFlexContainer.appendChild(radio);
- // Create the label
- var label = document.createElement('label');
- label.textContent = labelText;
- label.setAttribute('for', id);
- label.style.fontWeight = '600';
- label.style.fontSize = '19px';
- radioFlexContainer.appendChild(label);
- optionDiv.appendChild(radioFlexContainer);
- return optionDiv;
- }
- // Function to create a label div with text boxes
- function createLabelDiv(id, labelText) {
- var labelDiv = document.createElement('div');
- labelDiv.id = id;
- labelDiv.style.margin = '16px 0 12px';
- labelDiv.style.fontSize = '18px';
- // Create the label
- var label = document.createElement('label');
- label.textContent = labelText;
- labelDiv.appendChild(label);
- return labelDiv;
- }
- // Function to create a text box div
- function createTextBoxDiv(id, textLabel) {
- var textBoxDiv = document.createElement('div');
- textBoxDiv.id = id;
- textBoxDiv.style.display = 'flex';
- textBoxDiv.style.flexDirection = 'column';
- // Create the label
- var label = document.createElement('label');
- label.textContent = textLabel;
- label.style.marginBottom = '4px';
- textBoxDiv.appendChild(label);
- // Create the text box
- var textBox = document.createElement('textarea');
- textBox.id = id + 'TextBox'; // Unique ID for the text box
- // textBox.cols = 20; // Set the number of columns as needed
- textBox.rows = 8; // Set the number of rows as needed
- textBox.style.resize = 'none';
- textBox.style.padding = '4px';
- textBox.style.border = '1px solid black';
- if (isOldReddit()) {
- textBox.style.fontSize = oldRedditTextSize;
- textBox.style.lineHeight = oldRedditTextSize * 1.5;
- }
- textBoxDiv.appendChild(textBox);
- return textBoxDiv;
- }
- // Function to set up input change listeners
- function setupInputListeners(menu) {
- // Get all input elements within the menu
- var inputs = menu.querySelectorAll('input, textarea, select');
- // Iterate over each input element and set up change listeners
- inputs.forEach(function (input) {
- // Set up a debounced change listener for each input
- var debouncedSave = debounce(function () {
- saveInputsToLocalStorage(menu);
- }, 1000);
- // Add event listener based on input type
- if (input.type === 'radio' || input.type === 'checkbox') {
- input.addEventListener('change', debouncedSave);
- } else {
- input.addEventListener('input', debouncedSave);
- }
- });
- }
- function toggleAndAddAutoUpvoter() {
- // console.log('Attempting to toggle upvoter.');
- upvoterDiv = document.getElementById('upvoter-div');
- if (upvoterDiv) {
- // console.log('upvoterDiv found');
- // Get current display property
- var currentDisplay = upvoterDiv.style.display;
- // Remove newUpvoterDiv
- upvoterDiv.remove();
- // Run the function to add auto upvoter
- addAutoUpvoter();
- // Set display property back to its original value
- upvoterDiv.style.display = currentDisplay;
- }
- }
- // Function to save all input values to local storage
- function saveInputsToLocalStorage(menu) {
- // console.log('Saving inputs to local storage');
- // Get all input elements within the menu, if it exists
- var inputs = menu ? menu.querySelectorAll('input, textarea, select') : [];
- config = loadConfigFromLocalStorage();
- // Iterate over each input element and update its value in the config
- inputs.forEach(function (input) {
- if (input.type === 'radio' || input.type === 'checkbox') {
- config[input.id] = input.checked;
- } else {
- config[input.id] = input.value;
- }
- });
- // Save the updated config to local storage
- localStorage.setItem('BulkUpvoterConfig', JSON.stringify(config));
- console.log('Saved to local storage:' + JSON.stringify(config));
- config = loadConfigFromLocalStorage();
- activateHotkey();
- toggleAndAddAutoUpvoter();
- }
- // Function to erase all saved settings from local storage
- function eraseSettingsFromLocalStorage() {
- // console.log('Erasing settings from local storage');
- localStorage.removeItem('BulkUpvoterConfig');
- }
- // Function to save config to a text file
- function saveConfigToFile() {
- var configText = JSON.stringify(config, null, 2);
- var blob = new Blob([configText], {
- type: 'text/plain'
- });
- var a = document.createElement('a');
- a.href = URL.createObjectURL(blob);
- a.download = 'BulkUpvoterConfig.txt';
- a.click();
- }
- // Debounce function to limit the frequency of function execution
- function debounce(func, delay) {
- let timeout;
- return function () {
- const context = this;
- const args = arguments;
- clearTimeout(timeout);
- timeout = setTimeout(function () {
- func.apply(context, args);
- }, delay);
- };
- }
- // Function to populate the menu with the loaded config
- function populateMenuWithConfig(menu) {
- // Get all input elements within the menu
- var inputs = menu.querySelectorAll('input, textarea, select');
- // Iterate over each input element and set its value from the loaded config
- inputs.forEach(function (input) {
- if (input.id === 'secondsTextField') {
- // Special handling for 'secondsTextField'
- var inputValue = config.seconds;
- if (inputValue !== undefined) {
- input.value = inputValue;
- }
- } else if (input.type === 'radio' || input.type === 'checkbox') {
- // Check if the radio exists in the config
- if (config[input.id] !== undefined && config[input.id] === true) {
- input.checked = true;
- }
- } else {
- var inputValue = config[input.id];
- if (inputValue !== undefined) {
- input.value = inputValue;
- }
- }
- });
- if (config.oneModifier) {
- const secondModifierDropdown = document.getElementById('secondModifierDropdown');
- secondModifierDropdown.style.display = 'none';
- }
- toggleHotkeySettings();
- }
- // Function to check if the current URL matches the criteria
- function displayUpvoterBasedOnUrl(currentURL) {
- // console.log('Trying to do the thing.');
- // Check if the current URL matches the whitelist criteria
- if (
- (listMode === 'whitelist' &&
- (matchesRedWhitelist(currentURL) || matchesUWhitelist(currentURL))) ||
- // Check if the current URL matches the blacklist criteria
- (listMode === 'blacklist' &&
- matchesBlacklist(currentURL) &&
- !matchesRedBlacklist(currentURL) &&
- !matchesUBlacklist(currentURL)) ||
- listMode === 'everywhere'
- ) {
- // Perform the action if the conditions are met
- displayUpvoter();
- }
- }
- // Function to check if the current URL matches the red whitelist
- function matchesRedWhitelist(currentURL) {
- return matchItemsFromList(currentURL, config.redWhitelistTextBox, 'r');
- }
- // Function to check if the current URL matches the U whitelist
- function matchesUWhitelist(currentURL) {
- return (
- matchItemsFromList(currentURL, config.uWhitelistTextBox, 'u') ||
- matchItemsFromList(currentURL, config.uWhitelistTextBox, 'user')
- );
- }
- // Function to check if the current URL matches the blacklist
- function matchesBlacklist(currentURL) {
- return (
- /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/r\/.*$/i.test(currentURL) ||
- /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/u\/.*$/i.test(currentURL) ||
- /^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/user\/.*$/i.test(currentURL)
- );
- }
- // Function to check if the current URL matches the red blacklist
- function matchesRedBlacklist(currentURL) {
- return matchItemsFromList(currentURL, config.redBlacklistTextBox, 'r');
- }
- // Function to check if the current URL matches the U whitelist
- function matchesUBlacklist(currentURL) {
- return (
- matchItemsFromList(currentURL, config.uBlacklistTextBox, 'u') ||
- matchItemsFromList(currentURL, config.uBlacklistTextBox, 'user')
- );
- }
- // Function to match items from the list
- function matchItemsFromList(currentURL, list, prefix) {
- return list.split('\n').some(item => {
- if (item === '*') {
- // Asterisk acts as a wildcard for the specified list type
- const regex = new RegExp(
- `^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/${prefix}\/.*$`,
- 'i'
- );
- return currentURL.match(regex);
- }
- const regex = new RegExp(
- `^https?:\/\/(?:www\.|old\.|new\.)?reddit\.com\/${prefix}\/${item}(?:\/.*|$)$`,
- 'i'
- );
- return currentURL.match(regex);
- });
- }
- // Function to perform the action
- function displayUpvoter() {
- // Your action code here
- upvoterDiv = document.getElementById('upvoter-div');
- if (!upvoterDiv) {
- // console.log('Doing the thing!');
- addAutoUpvoter();
- }
- }
- function showOrHideUpvoter() {
- var upvoterDiv = document.getElementById('upvoter-div');
- if (!upvoterDiv) {
- // If upvoterDiv doesn't exist, add it
- addAutoUpvoter();
- } else {
- // Toggle the display property
- if (upvoterDiv.style.display === 'block') {
- // If currently visible, hide it
- // console.log('Hiding upvoter.');
- upvoterDiv.style.display = 'none';
- } else {
- // If currently hidden, show it
- // console.log('Showing upvoter.');
- upvoterDiv.style.display = 'block';
- }
- }
- }
- // Get the current URL
- var currentURL = window.location.href;
- displayUpvoterBasedOnUrl(currentURL);
- // Function to handle URL changes
- function handleUrlChange() {
- currentURL = window.location.href;
- // console.log('URL changed: ', currentURL);
- // Call your function or do something with the new URL
- displayUpvoterBasedOnUrl(currentURL);
- }
- // Event listener for popstate
- window.addEventListener('popstate', handleUrlChange);
- // Event listener for pushstate
- window.addEventListener('pushstate', handleUrlChange);
- // Event listener for replacestate
- window.addEventListener('replacestate', handleUrlChange);
- // // Watch for URL changes using MutationObserver on the body tag
- const observer = new MutationObserver(() => {
- if (currentURL != window.location.href) {
- handleUrlChange();
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });