405 lines
16 KiB
JavaScript
405 lines
16 KiB
JavaScript
// ==UserScript==
|
|
// @name fuck-twitter
|
|
// @namespace http://tampermonkey.net/
|
|
// @version 1.3
|
|
// @match https://x.com/*
|
|
// @grant none
|
|
// ==/UserScript==
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Configuration
|
|
const SCROLL_INCREMENT_MIN = 100; // Minimum scroll step in pixels
|
|
const SCROLL_INCREMENT_MAX = 300; // Maximum scroll step in pixels
|
|
const SCROLL_DELAY_MIN = 200; // Minimum delay between scroll steps in ms
|
|
const SCROLL_DELAY_MAX = 500; // Maximum delay between scroll steps in ms
|
|
const CLICK_DELAY_MIN = 500; // Minimum delay before clicking in ms
|
|
const CLICK_DELAY_MAX = 1500; // Maximum delay before clicking in ms
|
|
const RETWEET_CONFIRM_DELAY_MIN = 500; // Minimum delay before confirming retweet in ms
|
|
const RETWEET_CONFIRM_DELAY_MAX = 1500; // Maximum delay before confirming retweet in ms
|
|
|
|
// State variables
|
|
let isRunning = false;
|
|
let isRetweetEnabled = false;
|
|
let maxInteractions = 0; // 0 means unlimited
|
|
let currentInteractions = 0;
|
|
let speedScale = 1; // Default scale factor
|
|
let scrollTimeout = null;
|
|
let clickTimeout = null;
|
|
let retweetConfirmTimeout = null;
|
|
|
|
// Create Control Panel
|
|
const controlPanel = document.createElement('div');
|
|
controlPanel.style.position = 'fixed';
|
|
controlPanel.style.bottom = '20px';
|
|
controlPanel.style.right = '20px';
|
|
controlPanel.style.padding = '15px';
|
|
controlPanel.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
|
|
controlPanel.style.border = '1px solid #ccc';
|
|
controlPanel.style.borderRadius = '8px';
|
|
controlPanel.style.zIndex = '1000';
|
|
controlPanel.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
|
|
controlPanel.style.fontFamily = 'Arial, sans-serif';
|
|
controlPanel.style.fontSize = '14px';
|
|
controlPanel.style.color = '#333';
|
|
controlPanel.style.width = '250px';
|
|
|
|
// Create Start/Pause Button
|
|
const toggleButton = document.createElement('button');
|
|
toggleButton.innerText = 'Start Auto Like';
|
|
toggleButton.style.marginBottom = '10px';
|
|
toggleButton.style.padding = '8px 16px';
|
|
toggleButton.style.backgroundColor = '#1DA1F2';
|
|
toggleButton.style.color = '#fff';
|
|
toggleButton.style.border = 'none';
|
|
toggleButton.style.borderRadius = '4px';
|
|
toggleButton.style.cursor = 'pointer';
|
|
toggleButton.style.width = '100%';
|
|
toggleButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
|
|
controlPanel.appendChild(toggleButton);
|
|
|
|
// Create Retweet Checkbox
|
|
const retweetContainer = document.createElement('div');
|
|
retweetContainer.style.display = 'flex';
|
|
retweetContainer.style.alignItems = 'center';
|
|
retweetContainer.style.marginTop = '10px';
|
|
|
|
const retweetCheckbox = document.createElement('input');
|
|
retweetCheckbox.type = 'checkbox';
|
|
retweetCheckbox.id = 'enableRetweet';
|
|
retweetCheckbox.style.marginRight = '8px';
|
|
|
|
const retweetLabel = document.createElement('label');
|
|
retweetLabel.htmlFor = 'enableRetweet';
|
|
retweetLabel.innerText = 'Enable Retweeting';
|
|
|
|
retweetContainer.appendChild(retweetCheckbox);
|
|
retweetContainer.appendChild(retweetLabel);
|
|
controlPanel.appendChild(retweetContainer);
|
|
|
|
// Create Interaction Limit Input
|
|
const interactionContainer = document.createElement('div');
|
|
interactionContainer.style.marginTop = '10px';
|
|
|
|
const interactionLabel = document.createElement('label');
|
|
interactionLabel.htmlFor = 'maxInteractions';
|
|
interactionLabel.innerText = 'Max Interactions: ';
|
|
interactionLabel.style.display = 'block';
|
|
interactionLabel.style.marginBottom = '4px';
|
|
|
|
const interactionInput = document.createElement('input');
|
|
interactionInput.type = 'number';
|
|
interactionInput.id = 'maxInteractions';
|
|
interactionInput.min = '0';
|
|
interactionInput.placeholder = '0 for unlimited';
|
|
interactionInput.style.width = '100%';
|
|
interactionInput.style.padding = '6px';
|
|
interactionInput.style.border = '1px solid #ccc';
|
|
interactionInput.style.borderRadius = '4px';
|
|
|
|
interactionContainer.appendChild(interactionLabel);
|
|
interactionContainer.appendChild(interactionInput);
|
|
controlPanel.appendChild(interactionContainer);
|
|
|
|
// Create Interaction Counter Display
|
|
const counterDisplay = document.createElement('div');
|
|
counterDisplay.style.marginTop = '10px';
|
|
counterDisplay.style.display = 'none'; // Hidden by default
|
|
|
|
const counterText = document.createElement('span');
|
|
counterText.id = 'interactionCounter';
|
|
counterText.innerText = 'Interactions: 0/0';
|
|
counterDisplay.appendChild(counterText);
|
|
controlPanel.appendChild(counterDisplay);
|
|
|
|
// Create Speed Scale Input
|
|
const speedContainer = document.createElement('div');
|
|
speedContainer.style.marginTop = '10px';
|
|
|
|
const speedLabel = document.createElement('label');
|
|
speedLabel.htmlFor = 'speedScale';
|
|
speedLabel.innerText = 'Speed Scale: ';
|
|
speedLabel.style.display = 'block';
|
|
speedLabel.style.marginBottom = '4px';
|
|
|
|
const speedInput = document.createElement('input');
|
|
speedInput.type = 'number';
|
|
speedInput.id = 'speedScale';
|
|
speedInput.min = '0.5';
|
|
speedInput.max = '3';
|
|
speedInput.step = '0.1';
|
|
speedInput.value = '1';
|
|
speedInput.style.width = '100%';
|
|
speedInput.style.padding = '6px';
|
|
speedInput.style.border = '1px solid #ccc';
|
|
speedInput.style.borderRadius = '4px';
|
|
|
|
speedContainer.appendChild(speedLabel);
|
|
speedContainer.appendChild(speedInput);
|
|
controlPanel.appendChild(speedContainer);
|
|
|
|
// Append the Control Panel to the Document Body
|
|
document.body.appendChild(controlPanel);
|
|
|
|
// Event Listener for Toggle Button
|
|
toggleButton.addEventListener('click', () => {
|
|
isRunning = !isRunning;
|
|
toggleButton.innerText = isRunning ? 'Pause Auto Like' : 'Start Auto Like';
|
|
toggleButton.style.backgroundColor = isRunning ? '#e0245e' : '#1DA1F2';
|
|
|
|
if (isRunning) {
|
|
// Retrieve and set maximum interactions
|
|
const maxInput = parseInt(interactionInput.value, 10);
|
|
maxInteractions = isNaN(maxInput) || maxInput < 0 ? 0 : maxInput;
|
|
currentInteractions = 0;
|
|
updateCounterDisplay();
|
|
|
|
// Show the counter display if maxInteractions is set
|
|
if (maxInteractions > 0) {
|
|
counterDisplay.style.display = 'block';
|
|
counterText.innerText = `Interactions: ${currentInteractions}/${maxInteractions}`;
|
|
} else {
|
|
counterDisplay.style.display = 'none';
|
|
}
|
|
|
|
// Retrieve and set speed scale
|
|
const speedValue = parseFloat(speedInput.value);
|
|
speedScale = isNaN(speedValue) || speedValue <= 0 ? 1 : speedValue;
|
|
|
|
startScrolling();
|
|
} else {
|
|
clearTimeout(scrollTimeout);
|
|
clearTimeout(clickTimeout);
|
|
clearTimeout(retweetConfirmTimeout);
|
|
}
|
|
});
|
|
|
|
// Event Listener for Retweet Checkbox
|
|
retweetCheckbox.addEventListener('change', (e) => {
|
|
isRetweetEnabled = e.target.checked;
|
|
console.log('Retweeting Enabled:', isRetweetEnabled);
|
|
});
|
|
|
|
// Event Listener for Speed Scale Input
|
|
speedInput.addEventListener('change', (e) => {
|
|
const speedValue = parseFloat(e.target.value);
|
|
speedScale = isNaN(speedValue) || speedValue <= 0 ? 1 : speedValue;
|
|
console.log('Speed Scale set to:', speedScale);
|
|
});
|
|
|
|
// Utility function to get a random integer between min and max (inclusive), multiplied by speedScale
|
|
function getRandomIntScaled(min, max) {
|
|
const scaledMin = min * speedScale;
|
|
const scaledMax = max * speedScale;
|
|
return Math.floor(Math.random() * (scaledMax - scaledMin + 1)) + scaledMin;
|
|
}
|
|
|
|
// Function to update the interaction counter display
|
|
function updateCounterDisplay() {
|
|
counterText.innerText = `Interactions: ${currentInteractions}/${maxInteractions}`;
|
|
}
|
|
|
|
// Function to smoothly scroll down
|
|
function startScrolling() {
|
|
if (!isRunning) return;
|
|
|
|
// Check if maximum interactions reached
|
|
if (maxInteractions > 0 && currentInteractions >= maxInteractions) {
|
|
console.log('Maximum interactions reached. Stopping the script.');
|
|
isRunning = false;
|
|
toggleButton.innerText = 'Start Auto Like';
|
|
toggleButton.style.backgroundColor = '#1DA1F2';
|
|
return;
|
|
}
|
|
|
|
// Scroll by a random small increment, scaled by speedScale
|
|
const scrollStep = getRandomIntScaled(SCROLL_INCREMENT_MIN, SCROLL_INCREMENT_MAX);
|
|
window.scrollBy({
|
|
top: scrollStep,
|
|
behavior: 'smooth'
|
|
});
|
|
|
|
// After scrolling, check for the target elements
|
|
// Delay to allow scrolling to complete
|
|
scrollTimeout = setTimeout(() => {
|
|
processElements();
|
|
}, getRandomIntScaled(SCROLL_DELAY_MIN, SCROLL_DELAY_MAX));
|
|
}
|
|
|
|
// Function to process Like and Retweet buttons
|
|
function processElements() {
|
|
if (!isRunning) return;
|
|
|
|
const likeButton = findVisibleLikeButton();
|
|
const retweetButton = isRetweetEnabled ? findVisibleRetweetButton() : null;
|
|
|
|
if (likeButton) {
|
|
handleLikeButton(likeButton);
|
|
}
|
|
|
|
if (retweetButton) {
|
|
handleRetweetButton(retweetButton);
|
|
}
|
|
|
|
// Continue scrolling if no actions were taken
|
|
if (!likeButton && !retweetButton) {
|
|
startScrolling();
|
|
}
|
|
}
|
|
|
|
// Function to find a <button> with data-testid="like" that is fully visible and not yet clicked
|
|
function findVisibleLikeButton() {
|
|
const buttons = document.querySelectorAll('button[data-testid="like"]');
|
|
for (let btn of buttons) {
|
|
if (isElementFullyVisible(btn) && isButtonEligible(btn)) {
|
|
return btn;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Function to find a <button> with data-testid="retweet" that is fully visible and not yet clicked
|
|
function findVisibleRetweetButton() {
|
|
const buttons = document.querySelectorAll('button[data-testid="retweet"]');
|
|
for (let btn of buttons) {
|
|
if (isElementFullyVisible(btn) && isButtonEligible(btn)) {
|
|
return btn;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Function to handle clicking the Like button
|
|
function handleLikeButton(btn) {
|
|
// Scroll to make sure the button is fully visible
|
|
btn.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
// Wait for scrolling to finish
|
|
setTimeout(() => {
|
|
// Pause for a random short duration before clicking
|
|
const clickDelay = getRandomIntScaled(CLICK_DELAY_MIN, CLICK_DELAY_MAX);
|
|
clickTimeout = setTimeout(() => {
|
|
// Double-check before clicking
|
|
if (isButtonEligible(btn)) {
|
|
btn.click();
|
|
console.log('Like button clicked:', btn);
|
|
// Mark the button as clicked to prevent double-clicking
|
|
btn.setAttribute('data-autoclicked', 'true');
|
|
// Increment interaction counter
|
|
incrementInteractions();
|
|
}
|
|
// Continue the loop
|
|
startScrolling();
|
|
}, clickDelay);
|
|
}, getRandomIntScaled(500, 1000)); // Additional delay to ensure scrolling has completed
|
|
}
|
|
|
|
// Function to handle clicking the Retweet button and confirming
|
|
function handleRetweetButton(btn) {
|
|
// Scroll to make sure the button is fully visible
|
|
btn.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
// Wait for scrolling to finish
|
|
setTimeout(() => {
|
|
// Pause for a random short duration before clicking
|
|
const clickDelay = getRandomIntScaled(CLICK_DELAY_MIN, CLICK_DELAY_MAX);
|
|
clickTimeout = setTimeout(() => {
|
|
// Double-check before clicking
|
|
if (isButtonEligible(btn)) {
|
|
btn.click();
|
|
console.log('Retweet button clicked:', btn);
|
|
// Mark the button as clicked to prevent double-clicking
|
|
btn.setAttribute('data-autoclicked', 'true');
|
|
// Increment interaction counter
|
|
|
|
incrementInteractions();
|
|
|
|
// After clicking retweet, wait and confirm
|
|
retweetConfirmTimeout = setTimeout(() => {
|
|
const confirmButton = findRetweetConfirmButton();
|
|
if (confirmButton) {
|
|
confirmButton.click();
|
|
console.log('Retweet confirmed:', confirmButton);
|
|
// Mark the confirm button as clicked
|
|
confirmButton.setAttribute('data-autoclicked', 'true');
|
|
// Increment interaction counter
|
|
incrementInteractions();
|
|
}
|
|
// Continue the loop
|
|
startScrolling();
|
|
}, getRandomIntScaled(RETWEET_CONFIRM_DELAY_MIN, RETWEET_CONFIRM_DELAY_MAX));
|
|
} else {
|
|
// Continue the loop
|
|
startScrolling();
|
|
}
|
|
}, clickDelay);
|
|
}, getRandomIntScaled(500, 1000)); // Additional delay to ensure scrolling has completed
|
|
}
|
|
|
|
// Function to find the Retweet Confirm button
|
|
function findRetweetConfirmButton() {
|
|
const buttons = document.querySelectorAll('div[data-testid="retweetConfirm"]');
|
|
for (let btn of buttons) {
|
|
if (isElementFullyVisible(btn) && isButtonEligible(btn)) {
|
|
return btn;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Function to check if an element is fully visible in the viewport
|
|
function isElementFullyVisible(el) {
|
|
const rect = el.getBoundingClientRect();
|
|
return (
|
|
rect.top >= 0 &&
|
|
rect.left >= 0 &&
|
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
);
|
|
}
|
|
|
|
// Function to check if the button is eligible for clicking
|
|
function isButtonEligible(btn) {
|
|
// Check if the button has already been clicked by this script
|
|
if (btn.getAttribute('data-autoclicked') === 'true') {
|
|
return false;
|
|
}
|
|
|
|
// Ensure the button is still the correct type ('like', 'retweet', or 'retweetConfirm')
|
|
const currentTestId = btn.getAttribute('data-testid');
|
|
if (currentTestId !== 'like' && currentTestId !== 'retweet' && currentTestId !== 'retweetConfirm') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Function to increment interactions and update the counter
|
|
function incrementInteractions() {
|
|
currentInteractions += 1;
|
|
if (maxInteractions > 0) {
|
|
counterText.innerText = `Interactions: ${currentInteractions}/${maxInteractions}`;
|
|
if (currentInteractions >= maxInteractions) {
|
|
console.log('Maximum interactions reached. Stopping the script.');
|
|
isRunning = false;
|
|
toggleButton.innerText = 'Start Auto Like';
|
|
toggleButton.style.backgroundColor = '#1DA1F2';
|
|
clearTimeout(scrollTimeout);
|
|
clearTimeout(clickTimeout);
|
|
clearTimeout(retweetConfirmTimeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Optional: Observe DOM changes to handle dynamically loaded content
|
|
const observer = new MutationObserver((mutations) => {
|
|
if (isRunning) {
|
|
// You can trigger actions based on specific mutations if needed
|
|
}
|
|
});
|
|
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
|
|
})();
|