// ==UserScript==
// @name Instagram Sample Bot (Adaptive Scroll, Sample & Like)
// @namespace http://tampermonkey.net/
// @version 1.17
// @description Automates Instagram scrolling: fluid in foreground, jumpy in background. Detects and filters sample offers by popular beauty brands, and auto-likes relevant posts. Includes customizable settings and improved UI. Now with in-app Help and Update options in settings, improved modal visibility, and fluid UI panel animations.
// @author dprits419
// @match https://www.instagram.com/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function() {
'use strict';
// IMPORTANT: Make sure this version matches the @version in the header!
const SCRIPT_VERSION = "1.17";
// --- IMPORTANT: CONFIGURE THESE URLs IF YOU HOST YOUR SCRIPT ---
// This is the URL where your raw .user.js script is hosted.
// When the "Update Userscript" button is clicked, it opens this URL.
// Tampermonkey will detect the .user.js file and prompt for update if a newer version is available.
const SCRIPT_INSTALL_URL = 'https://raw.githubusercontent.com/dprits419/Tampermonkey-Scripts/main/instagram_bot.user.js'; // EXAMPLE: Replace with your actual GitHub Gist raw URL or Greasy Fork URL
// This is the URL for a help page or README for your script.
// NOTE: This URL is now primarily for reference, as help is shown in-app.
const SCRIPT_HELP_URL = 'https://github.com/dprits419/Tampermonkey-Scripts/blob/main/instagram_bot_README.md'; // EXAMPLE: Replace with your actual help/documentation URL
// --- END CONFIGURATION ---
console.log(`Instagram Bot script loaded! (Version ${SCRIPT_VERSION} - Fluid UI Panel)`);
// Global settings object with defaults
let settings = {
pixelsPerFrame: 4, // For fluid scrolling (foreground)
jumpyScrollInterval: 1000, // How often to jump in jumpy mode (background)
enableLiking: true, // Toggle auto-liking
enableSampleDetection: true, // Toggle sample offer detection
minConfidenceThreshold: 1.5, // Confidence required for sample detection
acceptOnlyPopularBrands: false, // Filter sample offers by popular brands
};
// Global state for bot activity
let scrolling = false;
// References to the two types of scrolling mechanisms
let fluidAnimationFrameId = null; // For requestAnimationFrame
let jumpyScrollIntervalId = null; // For setInterval (background)
let processPostsIntervalId = null; // Separate interval for processing posts
// Track last known scroll positions for end-of-feed detection
let lastScrollY = 0;
let lastScrollHeight = 0;
let endOfFeedConfirmationTimeout = null; // To confirm end of feed
// Processing parameters (these are not customizable via UI for now)
const processPostsInterval = 2500; // How often to process posts (liking, sample detection)
const endOfFeedConfirmDelay = 1500; // Milliseconds to confirm end of feed after hitting bottom
// Keywords for Sample Detection
const sampleKeywords = ['free sample', 'try now', 'get yours', 'claim yours', 'free product', 'giveaway', 'sponsored', 'trial', 'complimentary', 'discount code', 'coupon'];
const sampleActionTexts = ['sign up', 'learn more', 'shop now', 'claim here', 'get sample', 'redeem', 'click here', 'get offer'];
// Keywords for Auto-Liking
const cologneKeywords = ['cologne', 'fragrance', 'perfume', 'scent', 'eau de parfum', 'eau de toilette'];
const beautyKeywords = [
'skincare', 'makeup', 'cosmetics', 'beauty', 'haircare',
'dior', 'chanel', 'sephora', 'ulta', 'fenty', 'kylie cosmetics', 'nars', 'mac cosmetics',
'glossier', 'rare beauty', 'lancome', 'este lauder', 'clinique', 'shiseido',
'hourglass', 'charlotte tilbury', 'tatcha', 'kiehl\'s', 'cerave', 'la roche-posay',
'olaplex', 'sol de janeiro', 'lip gloss', 'mascara', 'eyeshadow', 'blush', 'foundation', 'concealer',
'serum', 'moisturizer', 'cleanser', 'sunscreen'
];
// List of popular beauty brands for filtering
const popularBeautyBrands = [
'sephora', 'ulta', 'dior', 'chanel', 'fenty beauty', 'kylie cosmetics', 'nars', 'mac cosmetics',
'glossier', 'rare beauty', 'lancome', 'este lauder', 'clinique', 'shiseido', 'hourglass',
'charlotte tilbury', 'tatcha', 'kiehl\'s', 'cerave', 'la roche-posay', 'olaplex', 'sol de janeiro',
'anastasia beverly hills', 'too faced', 'urban decay', 'benefit cosmetics', 'tarte', 'it cosmetics',
'fresh', 'sunday riley', 'drunk elephant', 'summer fridays', 'glow recipe', 'paula\'s choice',
'the ordinary', 'skinfix', 'dr. jart+', 'biossance', 'supergoop', 'milk makeup', 'kosas', 'saie',
'tower 28', 'innisfree', 'laneige', 'dr. brandt', 'peter thomas roth', 'first aid beauty', 'farmacy',
'youth to the people', 'herbivore botanicals', 'origins', 'clarins', 'guerlain', 'sisley', 'la mer',
'skinceuticals', 'obagi', 'elizabeth arden', 'aveeno', 'neutrogena', 'cetaphil', 'vichy', 'laroche-posay'
];
let statusBarElement; // Reference to the status bar element
// --- Settings Management ---
function loadSettings() {
try {
const savedSettings = JSON.parse(localStorage.getItem('instagramBotSettings'));
if (savedSettings) {
// Merge saved settings with defaults to ensure new settings are added but old ones persist
settings = { ...settings, ...savedSettings };
}
console.log("Settings loaded:", settings);
} catch (e) {
console.error("Error loading settings from localStorage:", e);
}
}
function saveSettings() {
try {
localStorage.setItem('instagramBotSettings', JSON.stringify(settings));
updateStatus("Settings saved!", 'var(--primary-color)');
console.log("Settings saved:", settings);
} catch (e) {
console.error("Error saving settings to localStorage:", e);
updateStatus("Failed to save settings!", 'var(--danger-color)');
}
}
// --- Utility Functions ---
// Helper to update the status bar
function updateStatus(message, color = 'var(--text-color, #333)') {
if (statusBarElement) {
statusBarElement.innerText = message;
statusBarElement.style.color = color;
}
console.log("STATUS: " + message);
}
// Helper for delays
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Helper to detect if a post is sponsored
function isPostSponsored(postElement) {
const sponsoredIndicator = postElement.querySelector('span[aria-label="Sponsored"], div[aria-label="Sponsored"], [data-testid="sponsored-label"], span:-webkit-any(span, div)[tabindex="-1"][role="button"]');
if (sponsoredIndicator && (sponsoredIndicator.innerText.toLowerCase().includes('sponsored') || sponsoredIndicator.getAttribute('aria-label') === 'Sponsored')) {
return true;
}
if (postElement.innerText.toLowerCase().includes('sponsored')) {
return true;
}
return false;
}
// --- Liking Logic ---
async function likePost(postElement) {
const unlikeButton = postElement.querySelector('svg[aria-label="Unlike"]')?.closest('button');
if (unlikeButton) {
return false; // Already liked
}
const likeButton = postElement.querySelector('svg[aria-label="Like"]')?.closest('button');
if (likeButton) {
likeButton.click();
return true; // Successfully clicked like button
}
return false; // Like button not found
}
// --- Scrolling Mode Management ---
// Starts continuous, fluid scrolling using requestAnimationFrame
function startFluidScrolling() {
if (fluidAnimationFrameId) return; // Already fluid scrolling
// Ensure jumpy scrolling is stopped
if (jumpyScrollIntervalId) {
clearInterval(jumpyScrollIntervalId);
jumpyScrollIntervalId = null;
}
function animateScroll() {
if (!scrolling) return; // Stop if scrolling is cancelled
window.scrollBy(0, settings.pixelsPerFrame); // Use setting
const currentScrollY = window.scrollY;
const currentScrollHeight = document.documentElement.scrollHeight;
if (currentScrollY === lastScrollY && currentScrollHeight === lastScrollHeight) {
if (!endOfFeedConfirmationTimeout) {
endOfFeedConfirmationTimeout = setTimeout(() => {
if (window.scrollY === lastScrollY && document.documentElement.scrollHeight === lastScrollHeight) {
updateStatus("Reached end of feed. Stopped.", 'var(--error-color, #f44336)');
stopBot();
}
endOfFeedConfirmationTimeout = null;
}, endOfFeedConfirmDelay);
}
} else {
clearTimeout(endOfFeedConfirmationTimeout);
endOfFeedConfirmationTimeout = null;
}
lastScrollY = currentScrollY;
lastScrollHeight = currentScrollHeight;
fluidAnimationFrameId = requestAnimationFrame(animateScroll);
}
fluidAnimationFrameId = requestAnimationFrame(animateScroll);
updateStatus("Scrolling feed fluidly...", 'var(--primary-color, #4CAF50)');
}
// Stops fluid scrolling
function stopFluidScrolling() {
if (fluidAnimationFrameId) {
cancelAnimationFrame(fluidAnimationFrameId);
fluidAnimationFrameId = null;
}
}
// Starts jumpy scrolling using setInterval (for background tabs)
function startJumpyScrolling() {
if (jumpyScrollIntervalId) return; // Already jumpy scrolling
// Ensure fluid scrolling is stopped
if (fluidAnimationFrameId) {
cancelAnimationFrame(fluidAnimationFrameId);
fluidAnimationFrameId = null;
}
jumpyScrollIntervalId = setInterval(() => {
if (!scrolling) {
clearInterval(jumpyScrollIntervalId);
jumpyScrollIntervalId = null;
return;
}
const currentScrollHeight = document.documentElement.scrollHeight;
// Use instant jump in background, smooth behavior is throttled anyway
window.scrollTo({ top: currentScrollHeight, behavior: 'instant' });
// End of feed detection for jumpy mode
if (currentScrollHeight === lastScrollHeight) {
if (!endOfFeedConfirmationTimeout) {
endOfFeedConfirmationTimeout = setTimeout(() => {
if (document.documentElement.scrollHeight === lastScrollHeight) {
updateStatus("Reached end of feed. Stopped.", 'var(--error-color, #f44336)');
stopBot();
}
endOfFeedConfirmationTimeout = null;
}, endOfFeedConfirmDelay);
}
} else {
clearTimeout(endOfFeedConfirmationTimeout);
endOfFeedConfirmationTimeout = null;
}
lastScrollHeight = currentScrollHeight;
}, settings.jumpyScrollInterval); // Use setting
updateStatus("Scrolling feed (jumpy in background)...", 'var(--primary-color, #4CAF50)');
}
// Stops jumpy scrolling
function stopJumpyScrolling() {
if (jumpyScrollIntervalId) {
clearInterval(jumpyScrollIntervalId);
jumpyScrollIntervalId = null;
}
}
// Main function to start the bot
function startBot() {
if (scrolling) return; // Bot is already active
scrolling = true;
// Start the post processing interval, which runs regardless of scroll mode
if (!processPostsIntervalId) {
processPostsIntervalId = setInterval(async () => {
await processPostsBatch();
}, processPostsInterval);
}
// Determine initial scrolling mode based on visibility
if (document.visibilityState === 'visible') {
startFluidScrolling();
} else {
startJumpyScrolling();
}
}
// Main function to stop the bot
function stopBot() {
if (!scrolling) return; // Bot is already stopped
scrolling = false;
stopFluidScrolling(); // Stop fluid scrolling if active
stopJumpyScrolling(); // Stop jumpy scrolling if active
// Stop the post processing interval
if (processPostsIntervalId) {
clearInterval(processPostsIntervalId);
processPostsIntervalId = null;
}
clearTimeout(endOfFeedConfirmationTimeout); // Clear any pending end-of-feed checks
endOfFeedConfirmationTimeout = null;
updateStatus("Stopped.", 'var(--warning-color, #f44336)');
}
// --- Event Listener for Tab Visibility Change ---
document.addEventListener('visibilitychange', () => {
if (scrolling) { // Only change mode if bot is currently active
// Stop current scrolling mode cleanly
stopFluidScrolling();
stopJumpyScrolling();
// Restart scrolling in the appropriate new mode
if (document.visibilityState === 'visible') {
startFluidScrolling();
} else {
startJumpyScrolling();
}
}
});
// --- Custom Confirmation Modal Function ---
function showCustomConfirmation(postSnippet, isSponsored, targetLinkHref) {
console.log("Attempting to show custom confirmation modal.");
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'tm-custom-modal-overlay';
Object.assign(overlay.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.7)', zIndex: '100000', display: 'flex',
justifyContent: 'center', alignItems: 'center', opacity: '1', // Set opacity directly
});
const modal = document.createElement('div');
modal.id = 'tm-custom-modal';
Object.assign(modal.style, {
backgroundColor: '#262626', color: '#F0F0F0', borderRadius: '12px', padding: '25px',
boxShadow: '0 8px 30px rgba(0, 0, 0, 0.5)', maxWidth: '450px', width: '90%',
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
display: 'flex', flexDirection: 'column', gap: '15px', transform: 'scale(1)', // Set transform directly
});
const header = document.createElement('div');
Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '10px', marginBottom: '10px' });
const title = document.createElement('h3');
title.innerText = 'Potential Offer Found!';
Object.assign(title.style, { margin: '0', fontSize: '18px', fontWeight: 'bold', color: '#66B3FF' });
const closeBtn = document.createElement('button');
closeBtn.innerText = '✕';
Object.assign(closeBtn.style, { background: 'none', border: 'none', color: '#F0F0F0', fontSize: '20px', cursor: 'pointer', padding: '5px', lineHeight: '1' });
closeBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
header.appendChild(title); header.appendChild(closeBtn); modal.appendChild(header);
const body = document.createElement('div');
Object.assign(body.style, { fontSize: '15px', lineHeight: '1.6', color: '#E0E0E0' });
const infoText = document.createElement('p');
infoText.innerHTML = `An offer has been detected based on your criteria.` + (isSponsored ? ` <span style="font-weight: bold; color: #FFEB3B;">(Sponsored Post)</span>` : '');
Object.assign(infoText.style, { margin: '0 0 10px 0' });
const snippetHeader = document.createElement('p');
snippetHeader.innerText = 'Post Snippet:';
Object.assign(snippetHeader.style, { margin: '0', fontWeight: 'bold', color: '#B3B3FF' });
const snippetContent = document.createElement('code');
snippetContent.innerText = postSnippet;
Object.assign(snippetContent.style, {
display: 'block', backgroundColor: 'rgba(0,0,0,0.2)', borderRadius: '5px', padding: '10px',
fontSize: '13px', whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: '100px',
overflowY: 'auto', marginTop: '5px', marginBottom: '10px', color: '#C0C0C0'
});
body.appendChild(infoText); body.appendChild(snippetHeader); body.appendChild(snippetContent);
if (targetLinkHref) {
const linkP = document.createElement('p');
linkP.innerText = 'Detected Link:';
Object.assign(linkP.style, { margin: '0', fontWeight: 'bold', color: '#B3B3FF' });
const linkElement = document.createElement('a');
linkElement.href = targetLinkHref; linkElement.target = '_blank';
linkElement.innerText = targetLinkHref.length > 60 ? targetLinkHref.substring(0, 57) + '...' : targetLinkHref;
Object.assign(linkElement.style, { display: 'block', marginTop: '5px', color: '#66B3FF', wordBreak: 'break-all', textDecoration: 'underline', fontSize: '13px' });
linkElement.onclick = (e) => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(true); };
body.appendChild(linkP); body.appendChild(linkElement);
} else {
const noLinkP = document.createElement('p');
noLinkP.innerText = "No direct link found. You'll need to manually inspect this post.";
Object.assign(noLinkP.style, { margin: '0', fontStyle: 'italic', color: '#A0A0A0' });
body.appendChild(noLinkP);
}
modal.appendChild(body);
const footer = document.createElement('div');
Object.assign(footer.style, { display: 'flex', justifyContent: 'flex-end', gap: '10px', paddingTop: '15px', borderTop: '1px solid rgba(255,255,255,0.1)', marginTop: '10px' });
const checkBtn = document.createElement('button');
checkBtn.innerText = 'Check it Out';
Object.assign(checkBtn.style, { padding: '10px 20px', borderRadius: '8px', border: 'none', backgroundColor: '#66B3FF', color: 'white', fontWeight: 'bold', cursor: 'pointer', transition: 'background-color 0.2s ease, transform 0.1s ease' });
checkBtn.onmouseover = () => checkBtn.style.backgroundColor = '#4DA8FF';
checkBtn.onmouseout = () => checkBtn.style.backgroundColor = '#66B3FF';
checkBtn.onmousedown = () => checkBtn.style.transform = 'translateY(1px)';
checkBtn.onmouseup = () => checkBtn.style.transform = 'translateY(0)';
checkBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(true); };
footer.appendChild(checkBtn);
const dismissBtn = document.createElement('button');
dismissBtn.innerText = 'Dismiss & Resume';
Object.assign(dismissBtn.style, { padding: '10px 20px', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.3)', backgroundColor: 'transparent', color: '#E0E0E0', cursor: 'pointer', transition: 'background-color 0.2s ease, color 0.2s ease, transform 0.1s ease' });
dismissBtn.onmouseover = () => { dismissBtn.style.backgroundColor = 'rgba(255,255,255,0.1)'; dismissBtn.style.color = '#FFFFFF'; };
dismissBtn.onmouseout = () => { dismissBtn.style.backgroundColor = 'transparent'; dismissBtn.style.color = '#E0E0E0'; };
dismissBtn.onmousedown = () => dismissBtn.style.transform = 'translateY(1px)';
dismissBtn.onmouseup = () => dismissBtn.style.transform = 'translateY(0)';
dismissBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
footer.appendChild(dismissBtn);
modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay);
// Removed setTimeout for instant display in showCustomConfirmation
// setTimeout(() => { overlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10);
const handleEscape = (e) => {
if (e.key === 'Escape') {
document.removeEventListener('keydown', handleEscape);
document.body.removeChild(overlay);
resolve(false);
}
};
document.addEventListener('keydown', handleEscape);
});
}
// --- Help Modal Function ---
async function showHelpModal() {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'tm-help-overlay';
Object.assign(overlay.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.7)', zIndex: '100001', display: 'flex',
justifyContent: 'center', alignItems: 'center', opacity: '0', transition: 'opacity 0.3s ease-in-out'
});
const modal = document.createElement('div');
modal.id = 'tm-help-modal';
Object.assign(modal.style, {
backgroundColor: '#262626', color: '#F0F0F0', borderRadius: '12px', padding: '25px',
boxShadow: '0 8px 30px rgba(0, 0, 0, 0.5)', maxWidth: '450px', width: '90%',
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
display: 'flex', flexDirection: 'column', gap: '15px', transform: 'scale(0.9)', transition: 'transform 0.3s ease-in-out'
});
const header = document.createElement('div');
Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '10px', marginBottom: '10px' });
const title = document.createElement('h3');
title.innerText = 'Bot Help & Troubleshooting';
Object.assign(title.style, { margin: '0', fontSize: '18px', fontWeight: 'bold', color: '#66B3FF' });
const closeBtn = document.createElement('button');
closeBtn.innerText = '✕';
Object.assign(closeBtn.style, { background: 'none', border: 'none', color: '#F0F0F0', fontSize: '20px', cursor: 'pointer', padding: '5px', lineHeight: '1' });
closeBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
header.appendChild(title); header.appendChild(closeBtn); modal.appendChild(header);
const content = document.createElement('div');
Object.assign(content.style, { fontSize: '14px', lineHeight: '1.6', color: '#E0E0E0', maxHeight: '300px', overflowY: 'auto' });
content.innerHTML = `
<p>If the Instagram Bot isn't working as expected, try these steps:</p>
<ul>
<li><strong>Ensure Tampermonkey is active:</strong> Check your browser's Tampermonkey extension icon; it should be enabled.</li>
<li><strong>Script Enabled:</strong> In the Tampermonkey dashboard, make sure 'Instagram Sample Bot' is toggled ON.</li>
<li><strong>Disable Ad Blockers:</strong> Ad blockers (e.g., uBlock Origin, AdBlock Plus) or privacy extensions can interfere. Try disabling them for instagram.com.</li>
<li><strong>Refresh Page:</strong> A simple page refresh (F5 or Ctrl+R / Cmd+R) can often resolve minor issues.</li>
<li><strong>Restart Bot:</strong> If the bot stops, try clicking the 'Start Bot' button in the UI.</li>
<li><strong>Check Console for Errors:</strong> Open your browser's developer tools (F12 or Ctrl+Shift+I / Cmd+Option+I), go to the 'Console' tab, and look for any red error messages. Report them if you need further help.</li>
<li><strong>Update Script:</strong> Click 'Update Userscript' in the settings to check for the latest version.</li>
<li><strong>Clear Cache/Cookies:</strong> As a last resort, clearing browser cache and cookies for instagram.com can sometimes fix persistent issues (note: this will log you out).</li>
</ul>
<p>If you still face problems, please contact the script developer with details!</p>
`;
modal.appendChild(content);
const footer = document.createElement('div');
Object.assign(footer.style, { display: 'flex', justifyContent: 'flex-end', gap: '10px', paddingTop: '15px', borderTop: '1px solid rgba(255,255,255,0.1)', marginTop: '10px' });
const closeButton = document.createElement('button');
closeButton.innerText = 'Close';
Object.assign(closeButton.style, { padding: '10px 20px', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.3)', backgroundColor: 'transparent', color: '#E0E0E0', cursor: 'pointer', transition: 'background-color 0.2s ease, color 0.2s ease, transform 0.1s ease' });
closeButton.onmouseover = () => { closeButton.style.backgroundColor = 'rgba(255,255,255,0.1)'; closeButton.style.color = '#FFFFFF'; };
closeButton.onmouseout = () => { closeButton.style.backgroundColor = 'transparent'; closeButton.style.color = '#E0E0E0'; };
closeButton.onmousedown = () => closeButton.style.transform = 'translateY(1px)';
closeButton.onmouseup = () => closeButton.style.transform = 'translateY(0)';
closeButton.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
footer.appendChild(closeButton);
modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay);
setTimeout(() => { overlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10);
const handleEscape = (e) => {
if (e.key === 'Escape') {
document.removeEventListener('keydown', handleEscape);
document.body.removeChild(overlay);
resolve(false);
}
};
document.addEventListener('keydown', handleEscape);
});
}
// --- Settings Modal Function ---
async function showSettingsModal() {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'tm-settings-overlay';
Object.assign(overlay.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.7)', zIndex: '100001', display: 'flex',
justifyContent: 'center', alignItems: 'center', opacity: '0', transition: 'opacity 0.3s ease-in-out'
});
const modal = document.createElement('div');
modal.id = 'tm-settings-modal';
Object.assign(modal.style, {
backgroundColor: '#262626', color: '#F0F0F0', borderRadius: '12px', padding: '25px',
boxShadow: '0 8px 30px rgba(0, 0, 0, 0.5)', maxWidth: '400px', width: '90%',
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
display: 'flex', flexDirection: 'column', gap: '15px', transform: 'scale(0.9)', transition: 'transform 0.3s ease-in-out'
});
const header = document.createElement('div');
Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '10px', marginBottom: '10px' });
const title = document.createElement('h3');
title.innerText = 'Bot Settings';
Object.assign(title.style, { margin: '0', fontSize: '18px', fontWeight: 'bold', color: '#66B3FF' });
const closeBtn = document.createElement('button');
closeBtn.innerText = '✕';
Object.assign(closeBtn.style, { background: 'none', border: 'none', color: '#F0F0F0', fontSize: '20px', cursor: 'pointer', padding: '5px', lineHeight: '1' });
closeBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
header.appendChild(title); header.appendChild(closeBtn); modal.appendChild(header);
const settingsForm = document.createElement('div');
Object.assign(settingsForm.style, { display: 'flex', flexDirection: 'column', gap: '12px' });
// Helper to create a setting row
const createSettingRow = (labelText, inputElement) => {
const row = document.createElement('div');
Object.assign(row.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center' });
const label = document.createElement('label');
label.innerText = labelText;
Object.assign(label.style, { flexShrink: '0', marginRight: '10px', fontSize: '14px', color: '#E0E0E0' });
row.appendChild(label);
row.appendChild(inputElement);
return row;
};
// Scrolling Speed (Fluid)
const fluidSpeedInput = document.createElement('input');
fluidSpeedInput.type = 'number';
fluidSpeedInput.min = '1'; fluidSpeedInput.max = '20'; fluidSpeedInput.step = '1';
fluidSpeedInput.value = settings.pixelsPerFrame;
Object.assign(fluidSpeedInput.style, { width: '80px', padding: '8px', borderRadius: '5px', border: '1px solid #555', backgroundColor: '#333', color: '#F0F0F0', fontSize: '14px' });
settingsForm.appendChild(createSettingRow('Fluid Scroll Speed (px/frame):', fluidSpeedInput));
// Scrolling Interval (Jumpy)
const jumpyIntervalInput = document.createElement('input');
jumpyIntervalInput.type = 'number';
jumpyIntervalInput.min = '100'; jumpyIntervalInput.max = '5000'; jumpyIntervalInput.step = '100';
jumpyIntervalInput.value = settings.jumpyScrollInterval;
Object.assign(jumpyIntervalInput.style, { width: '80px', padding: '8px', borderRadius: '5px', border: '1px solid #555', backgroundColor: '#333', color: '#F0F0F0', fontSize: '14px' });
settingsForm.appendChild(createSettingRow('Jumpy Scroll Interval (ms):', jumpyIntervalInput));
// Auto-Liking Toggle
const autoLikeToggle = document.createElement('input');
autoLikeToggle.type = 'checkbox';
autoLikeToggle.checked = settings.enableLiking;
Object.assign(autoLikeToggle.style, { width: '20px', height: '20px', cursor: 'pointer' });
settingsForm.appendChild(createSettingRow('Enable Auto-Liking:', autoLikeToggle));
// Sample Detection Toggle
const sampleDetectToggle = document.createElement('input');
sampleDetectToggle.type = 'checkbox';
sampleDetectToggle.checked = settings.enableSampleDetection;
Object.assign(sampleDetectToggle.style, { width: '20px', height: '20px', cursor: 'pointer' });
settingsForm.appendChild(createSettingRow('Enable Sample Detection:', sampleDetectToggle));
// Confidence Threshold for Sample Detection
const confidenceThresholdInput = document.createElement('input');
confidenceThresholdInput.type = 'number';
confidenceThresholdInput.min = '0.0'; confidenceThresholdInput.max = '3.0'; confidenceThresholdInput.step = '0.1';
confidenceThresholdInput.value = settings.minConfidenceThreshold;
Object.assign(confidenceThresholdInput.style, { width: '80px', padding: '8px', borderRadius: '5px', border: '1px solid #555', backgroundColor: '#333', color: '#F0F0F0', fontSize: '14px' });
settingsForm.appendChild(createSettingRow('Sample Confidence Threshold:', confidenceThresholdInput));
// Accept Only Popular Brands Toggle
const popularBrandsToggle = document.createElement('input');
popularBrandsToggle.type = 'checkbox';
popularBrandsToggle.checked = settings.acceptOnlyPopularBrands;
Object.assign(popularBrandsToggle.style, { width: '20px', height: '20px', cursor: 'pointer' });
settingsForm.appendChild(createSettingRow('Only Popular Beauty Brands:', popularBrandsToggle));
modal.appendChild(settingsForm);
const footer = document.createElement('div');
Object.assign(footer.style, { display: 'flex', justifyContent: 'flex-end', gap: '10px', paddingTop: '15px', borderTop: '1px solid rgba(255,255,255,0.1)', marginTop: '10px', flexWrap: 'wrap' }); // Added flexWrap
// UPDATED: Help Button now opens in-app modal
const helpBtn = document.createElement('button');
helpBtn.innerText = 'Help';
Object.assign(helpBtn.style, {
padding: '10px 15px', borderRadius: '8px', border: '1px solid rgba(102, 179, 255, 0.5)',
backgroundColor: 'transparent', color: '#66B3FF', fontWeight: 'bold', cursor: 'pointer',
transition: 'background-color 0.2s ease, color 0.2s ease, transform 0.1s ease',
flexShrink: '0' // Prevent shrinking
});
helpBtn.onmouseover = () => { helpBtn.style.backgroundColor = 'rgba(102, 179, 255, 0.1)'; };
helpBtn.onmouseout = () => { helpBtn.style.backgroundColor = 'transparent'; };
helpBtn.onmousedown = () => helpBtn.style.transform = 'translateY(1px)';
helpBtn.onmouseup = () => helpBtn.style.transform = 'translateY(0)';
helpBtn.onclick = async () => {
// Close settings modal first to prevent multiple overlays
document.removeEventListener('keydown', handleEscape);
document.body.removeChild(overlay);
await showHelpModal(); // Show help modal
resolve(false); // Do not save settings if help was opened
};
footer.appendChild(helpBtn);
// Update Userscript Button
const updateBtn = document.createElement('button');
updateBtn.innerText = 'Update Userscript';
Object.assign(updateBtn.style, {
padding: '10px 15px', borderRadius: '8px', border: '1px solid rgba(144, 238, 144, 0.5)',
backgroundColor: 'transparent', color: '#90EE90', fontWeight: 'bold', cursor: 'pointer',
transition: 'background-color 0.2s ease, color 0.2s ease, transform 0.1s ease',
flexShrink: '0' // Prevent shrinking
});
updateBtn.onmouseover = () => { updateBtn.style.backgroundColor = 'rgba(144, 238, 144, 0.1)'; };
updateBtn.onmouseout = () => { updateBtn.style.backgroundColor = 'transparent'; };
updateBtn.onmousedown = () => updateBtn.style.transform = 'translateY(1px)';
updateBtn.onmouseup = () => updateBtn.style.transform = 'translateY(0)';
updateBtn.onclick = () => {
window.open(SCRIPT_INSTALL_URL, '_blank');
};
footer.appendChild(updateBtn);
const saveBtn = document.createElement('button');
saveBtn.innerText = 'Save Settings';
Object.assign(saveBtn.style, { padding: '10px 20px', borderRadius: '8px', border: 'none', backgroundColor: '#66B3FF', color: 'white', fontWeight: 'bold', cursor: 'pointer', transition: 'background-color 0.2s ease, transform 0.1s ease', flexShrink: '0' });
saveBtn.onmouseover = () => saveBtn.style.backgroundColor = '#4DA8FF';
saveBtn.onmouseout = () => saveBtn.style.backgroundColor = '#66B3FF';
saveBtn.onmousedown = () => saveBtn.style.transform = 'translateY(1px)';
saveBtn.onmouseup = () => saveBtn.style.transform = 'translateY(0)';
saveBtn.onclick = () => {
settings.pixelsPerFrame = parseInt(fluidSpeedInput.value);
settings.jumpyScrollInterval = parseInt(jumpyIntervalInput.value);
settings.enableLiking = autoLikeToggle.checked;
settings.enableSampleDetection = sampleDetectToggle.checked;
settings.minConfidenceThreshold = parseFloat(confidenceThresholdInput.value);
settings.acceptOnlyPopularBrands = popularBrandsToggle.checked;
saveSettings();
document.removeEventListener('keydown', handleEscape);
document.body.removeChild(overlay);
resolve(true); // Indicate settings were saved
};
footer.appendChild(saveBtn);
const cancelBtn = document.createElement('button');
cancelBtn.innerText = 'Cancel';
Object.assign(cancelBtn.style, { padding: '10px 20px', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.3)', backgroundColor: 'transparent', color: '#E0E0E0', cursor: 'pointer', transition: 'background-color 0.2s ease, color 0.2s ease, transform 0.1s ease' });
cancelBtn.onmouseover = () => { cancelBtn.style.backgroundColor = 'rgba(255,255,255,0.1)'; cancelBtn.style.color = '#FFFFFF'; };
cancelBtn.onmouseout = () => { cancelBtn.style.backgroundColor = 'transparent'; cancelBtn.style.color = '#E0E0E0'; };
cancelBtn.onmousedown = () => cancelBtn.style.transform = 'translateY(1px)';
cancelBtn.onmouseup = () => cancelBtn.style.transform = 'translateY(0)';
cancelBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
footer.appendChild(cancelBtn);
modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay);
setTimeout(() => { overlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10);
const handleEscape = (e) => {
if (e.key === 'Escape') {
document.removeEventListener('keydown', handleEscape);
document.body.removeChild(overlay);
resolve(false);
}
};
document.addEventListener('keydown', handleEscape);
});
}
async function processPostsBatch() {
const posts = document.querySelectorAll('article');
for (const post of posts) {
const postText = post.innerText.toLowerCase();
// --- LIKING LOGIC ---
if (settings.enableLiking && !post.dataset.likedChecked) {
post.dataset.likedChecked = 'true';
const shouldLike = cologneKeywords.some(kw => postText.includes(kw)) ||
beautyKeywords.some(kw => postText.includes(kw));
if (shouldLike) {
const likedSuccessfully = await likePost(post);
if (likedSuccessfully) {
updateStatus("Liked a post!", 'var(--primary-color)');
await delay(1000 + Math.random() * 1000); // Wait 1-2 seconds after liking
}
}
}
// --- SAMPLE DETECTION LOGIC ---
if (!settings.enableSampleDetection || post.dataset.sampleChecked) {
continue;
}
// Filter by popular brands if setting is enabled
if (settings.acceptOnlyPopularBrands) {
const foundPopularBrand = popularBeautyBrands.some(brand => postText.includes(brand.toLowerCase()));
if (!foundPopularBrand) {
continue; // Skip this post if it's not from a popular brand
}
}
let confidence = 0;
let foundActionText = false;
let foundRelevantKeyword = false;
let targetLink = null;
const isSponsored = isPostSponsored(post);
if (isSponsored) {
confidence += 1.0;
}
const buttonsAndLinks = post.querySelectorAll('a, button');
for (const element of buttonsAndLinks) {
const elementText = element.innerText.toLowerCase();
if (sampleActionTexts.some(action => elementText.includes(action))) {
foundActionText = true;
if (element.tagName === 'A' && element.href && element.href !== '#' && !element.href.startsWith('javascript:')) {
targetLink = element;
}
break;
}
}
// This check for sample keywords (including 'free sample') runs IF a popular brand was found (or filter is off)
if (sampleKeywords.some(keyword => postText.includes(keyword))) {
foundRelevantKeyword = true;
}
if (foundActionText && foundRelevantKeyword) {
confidence += 1.0;
if (!targetLink) {
targetLink = post.querySelector('a[href]:not([href="#"]):not([href^="javascript:"])');
}
} else if (foundActionText && !foundRelevantKeyword) {
confidence += 0.2;
} else if (!foundActionText && foundRelevantKeyword) {
confidence += 0.3;
if (!targetLink) {
targetLink = post.querySelector('a[href]:not([href="#"]):not([href^="javascript:"])');
}
}
if (confidence >= settings.minConfidenceThreshold) {
updateStatus("Potential offer found! Waiting for your decision...", 'var(--highlight-color, #FFD700)');
// Stop all bot activity (scrolling and processing)
stopBot();
post.dataset.sampleChecked = 'true';
const postSnippet = postText.substring(0, Math.min(postText.length, 300));
const targetLinkHref = targetLink ? targetLink.href : null;
const userChoice = await showCustomConfirmation(postSnippet, isSponsored, targetLinkHref);
if (userChoice) {
if (targetLinkHref) {
window.open(targetLinkHref, '_blank');
updateStatus("Opened link. Resuming bot...", 'var(--primary-color, #4CAF50)');
} else {
updateStatus("No direct link found. Resuming bot...", 'var(--primary-color, #4CAF50)');
}
} else {
updateStatus("User chose to dismiss. Resuming bot...", 'var(--primary-color, #4CAF50)');
}
// Automatically restart the bot after user interaction
setTimeout(() => {
startBot();
}, 0);
return; // Stop processing other posts in this batch to avoid immediate re-trigger
}
}
}
// --- UI Creation ---
function createUI() {
const rootStyle = document.documentElement.style;
rootStyle.setProperty('--primary-color', '#4CAF50');
rootStyle.setProperty('--danger-color', '#f44336');
rootStyle.setProperty('--text-color', '#333');
rootStyle.setProperty('--bg-color', 'rgba(255, 255, 255, 0.7)');
rootStyle.setProperty('--border-color', 'rgba(200, 200, 200, 0.5)');
rootStyle.setProperty('--shadow-color', 'rgba(0, 0, 0, 0.1)');
rootStyle.setProperty('--highlight-color', '#FFD700');
rootStyle.setProperty('--warning-color', '#FFA500');
rootStyle.setProperty('--settings-icon-color', '#8A2BE2'); // A vibrant blue-violet for contrast
const uiContainer = document.createElement('div');
Object.assign(uiContainer.style, {
position: 'fixed', top: '20px', left: '20px',
backgroundColor: 'var(--bg-color)', backdropFilter: 'blur(8px)',
padding: '15px', border: '1px solid var(--border-color)', borderRadius: '12px',
boxShadow: '0 4px 15px var(--shadow-color)', zIndex: '99999',
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
display: 'flex', flexDirection: 'column', gap: '15px',
alignItems: 'center', justifyContent: 'center', minWidth: '240px',
// Initial state for fluid entry
opacity: '0',
transform: 'translateY(-20px)',
transition: 'opacity 0.5s ease-out, transform 0.5s ease-out',
});
const headerRow = document.createElement('div');
Object.assign(headerRow.style, {
display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%',
});
uiContainer.appendChild(headerRow);
const titleElement = document.createElement('h3');
titleElement.innerText = `Instagram Bot v${SCRIPT_VERSION}`;
Object.assign(titleElement.style, {
margin: '0', color: 'var(--text-color)', fontSize: '20px',
fontWeight: 'bold', flexGrow: '1', textAlign: 'center'
});
headerRow.appendChild(titleElement);
// Settings Button
const settingsButton = document.createElement('button');
settingsButton.innerHTML = '⚙️';
Object.assign(settingsButton.style, {
background: 'none', border: 'none', fontSize: '28px',
cursor: 'pointer', color: 'var(--settings-icon-color)',
padding: '0',
lineHeight: '1',
transition: 'transform 0.1s ease, color 0.2s ease',
});
settingsButton.onmouseover = () => settingsButton.style.transform = 'rotate(20deg)';
settingsButton.onmouseout = () => settingsButton.style.transform = 'rotate(0deg)';
settingsButton.onclick = showSettingsModal;
headerRow.appendChild(settingsButton);
const buttonsContainer = document.createElement('div');
Object.assign(buttonsContainer.style, {
display: 'flex', gap: '12px', width: '100%', justifyContent: 'center',
});
uiContainer.appendChild(buttonsContainer);
const buttonStyle = {
padding: '10px 18px', border: 'none', borderRadius: '8px',
fontSize: '14px', fontWeight: '600', cursor: 'pointer',
transition: 'background-color 0.2s ease, transform 0.1s ease',
boxShadow: '0 2px 5px var(--shadow-color)', flexGrow: '1',
};
const startButton = document.createElement('button');
startButton.innerText = 'Start Bot';
Object.assign(startButton.style, buttonStyle, { backgroundColor: 'var(--primary-color)', color: 'white' });
startButton.onmouseover = () => startButton.style.backgroundColor = '#45a049';
startButton.onmouseout = () => startButton.style.backgroundColor = 'var(--primary-color)';
startButton.onmousedown = () => startButton.style.transform = 'translateY(1px)';
startButton.onmouseup = () => startButton.style.transform = 'translateY(0)';
startButton.onclick = startBot;
buttonsContainer.appendChild(startButton);
const stopButton = document.createElement('button');
stopButton.innerText = 'Stop Bot';
Object.assign(stopButton.style, buttonStyle, { backgroundColor: 'var(--danger-color)', color: 'white' });
stopButton.onmouseover = () => stopButton.style.backgroundColor = '#da190b';
stopButton.onmouseout = () => stopButton.style.backgroundColor = 'var(--danger-color)';
stopButton.onmousedown = () => stopButton.style.transform = 'translateY(1px)';
stopButton.onmouseup = () => stopButton.style.transform = 'translateY(0)';
stopButton.onclick = stopBot;
buttonsContainer.appendChild(stopButton);
statusBarElement = document.createElement('div');
Object.assign(statusBarElement.style, {
marginTop: '0px',
fontSize: '12px', color: 'var(--text-color)',
textAlign: 'center', width: '100%', paddingTop: '0px',
});
uiContainer.appendChild(statusBarElement);
document.body.appendChild(uiContainer);
// Animate the UI container into view
requestAnimationFrame(() => {
uiContainer.style.opacity = '1';
uiContainer.style.transform = 'translateY(0)';
});
updateStatus("Ready. Click 'Start Bot'!", 'var(--text-color)');
}
// --- Initialization ---
window.addEventListener('load', () => {
loadSettings(); // Load settings first
createUI(); // Then create the UI
});
})();