Building A Chrome Extension: Refresh N’ Click

I thought it might be fun to appear to be more active than I actually am on some well-known social media platform, and came up with a little experiment for the job at hand, which also enabled me to build my first Chrome extension.

Once installed and activated (by clicking the extension’s icon), it will automatically refresh the current tab at a random interval you define and then automatically click the specified selector on the page (in this case, div[aria-label=”Like”].  It also has a few other options:

  • Ability to like only certain friend’s posts
  • Ensure that the extension won’t like a post containing disallowed words
  • Only like posts with a minimum number of existing likes
  • Only run during specified hours
  • After a refresh occurs, support for an artificial delay before a click event is triggered
  • If multiple elements are matched, support for a delay (in milliseconds) between the first clicked item, and the second, and so on

Possible uses of the extension:

  • Automatically like or react to posts on social media platforms.
  • Automatically interact with a webpage at a predefined time.
  • Automate repetitive tasks on web applications by clicking through menus, buttons, etc.
  • Continuously refresh a web page to monitor changes or updates.

You can view the extension on GitHub.

Options page

Background.js

            // Global variables
let isExtensionActive = false;
let refreshIntervalID;
let liketheElementClickIntervalID;
let minPageRefreshInterval;
let maxPageRefreshInterval;
let minClickDelay;
let maxClickDelay;
let minDelayBetweenElementClicks;
let maxDelayBetweenElementClicks;
let theSelector;
let openHour;
let closeHour;

let configLoaded = false;

// Function to load configuration settings from storage
function loadConfig(callback) {
    if (configLoaded) {
        return callback();
    }

    chrome.storage.sync.get(["minPageRefreshInterval", "maxPageRefreshInterval", "minClickDelay", "maxClickDelay", "minDelayBetweenElementClicks", "maxDelayBetweenElementClicks", "theSelector", "openHour", "closeHour"], function (result) {
        // Set default values if settings are not found in storage
        minPageRefreshInterval = parseInt(result.minPageRefreshInterval) || 10 * 1000;
        maxPageRefreshInterval = parseInt(result.maxPageRefreshInterval) || 30 * 1000;
        minClickDelay = parseInt(result.minClickDelay) || 1 * 1000;
        maxClickDelay = parseInt(result.maxClickDelay) || 2 * 1000;
        minDelayBetweenElementClicks = parseInt(result.minDelayBetweenElementClicks) || 1 * 1000;
        maxDelayBetweenElementClicks = parseInt(result.maxDelayBetweenElementClicks) || 2 * 1000;
        theSelector = result.theSelector || 'div[aria-label="Like"]';
        openHour = parseInt(result.openHour) || 6;
        closeHour = parseInt(result.closeHour) || 23;

        // Log variables for debugging
        console.log("minPageRefreshInterval", minPageRefreshInterval);
        console.log("maxPageRefreshInterval", maxPageRefreshInterval);
        console.log("minClickDelay", minClickDelay);
        console.log("maxClickDelay", maxClickDelay);
        console.log("minDelayBetweenElementClicks", minDelayBetweenElementClicks);
        console.log("maxDelayBetweenElementClicks", maxDelayBetweenElementClicks);
        console.log("theSelector", theSelector);
        console.log("openHour", openHour);
        console.log("closeHour", closeHour);

        configLoaded = true;
        callback();
    });
}

function clickTheElement() {
    const now = new Date();
    const currentHour = now.getHours();

    if (currentHour >= openHour && currentHour < closeHour) {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
            if (tabs && tabs.length > 0 && tabs[0].id) {
                chrome.scripting.executeScript({
                    target: { tabId: tabs[0].id },
                    function: () => {
                        // Read theSelector and friendsToLike from chrome.storage.sync
                        chrome.storage.sync.get(["theSelector", "friendsToLike", "blocklistWords","minLikesRequired"], (data) => {
                            let theSelector = data.theSelector || 'div[aria-label="Like"]';
                            let friendsToLike = data.friendsToLike || "";
                            let blocklistWords = data.blocklistWords || "";
                            let minLikesRequired = data.minLikesRequired || "";

                            // Global variable to store clicked elements
                            let clickedElements = [];
                            const theElements = document.querySelectorAll(theSelector);

                            function getRandomDelay(minDelay, maxDelay) {
                                return Math.random() * (maxDelay - minDelay) + minDelay;
                            }

                            function isFriendPost(element, friendsToLike) {
                                const friendsArray = friendsToLike.split(",").map((friend) => friend.trim());

                                // Navigate 8 levels up to the parent div
                                let parentDiv = element;
                                for (let i = 0; i < 9; i++) {
                                    parentDiv = parentDiv.parentElement;
                                }

                                // Now drill 3 levels in to grab the <a> element
                                const aElement = parentDiv.querySelector("div div span a");

                                // Check if the friends array contains the href value
                                const friendName = aElement.href
                                    .replace(/^https:\/\/www.facebook.com\//, "")
                                    .split("?")[0]
                                    .trim();

                                if (friendsArray.includes(friendName)) {
                                    //console.log(`friendName: '${friendName}' found!`);
                                    return true;
                                }

                                return false;
                            }

                            function containsBlacklistWords(element, blocklist) {
                                // Navigate 8 levels up to the parent div
                                let parentDiv = element;
                                for (let i = 0; i < 9; i++) {
                                    parentDiv = parentDiv.parentElement;
                                }

                                // Check if there is a [data-ad-preview="message"] element inside the parent div
                                const messageElement = parentDiv.querySelector('[data-ad-preview="message"]');

                                if (messageElement) {
                                    const text = messageElement.textContent.toLowerCase();
                                    const blocklistArray = blocklist.split(",").map((word) => word.trim().toLowerCase());

                                    for (const word of blocklistArray) {
                                        if (text.includes(word)) {
                                            return true;
                                        }
                                    }
                                }

                                return false;
                            }

                            function likesGreaterThanOrEqualTo(theElement, numLikes) {
                                // Use the provided 'theElement' to find the like count, navigating 4 elements up
                                let parentDiv = theElement;
                                for (let i = 0; i < 5; i++) {
                                    parentDiv = parentDiv.parentElement;
                                }

                                const likeCount = extractLikeCountFromElement(parentDiv);

                                // Check if the like count is greater than or equal to 'numLikes'
                                return likeCount !== null && likeCount >= numLikes;
                            }

                            function extractLikeCountFromElement(element) {
                                // Find the div element with the specified aria-label within 'element'
                                const divElement = element.querySelector('div[aria-label^="Like: "]');

                                // Extract the integer value
                                if (divElement) {
                                    const ariaLabel = divElement.getAttribute("aria-label");
                                    const likeCountMatch = ariaLabel.match(/Like: (\d+) people/);

                                    if (likeCountMatch) {
                                        const likeCount = parseInt(likeCountMatch[1], 10);
                                        return likeCount;
                                    }
                                }

                                return null; // Like count not found
                            }

                            theElements.forEach((theElement, index) => {
                                if (
                                    isInViewport(theElement) && // is the like button in the viewport?
                                    !theElement.parentNode.classList.contains("__fb-light-mode") && // don't click like button on comments
                                    !clickedElements.includes(theElement) && // so items are clicked over and over
                                    likesGreaterThanOrEqualTo(theElement, minLikesRequired) // minimum like count before we like something
                                )
                                    if (isFriendPost(theElement, friendsToLike)) {
                                        if (!containsBlacklistWords(theElement, blocklistWords)) {
                                            {
                                                setTimeout(() => {
                                                    theElement.click();
                                                    console.log(`'${theSelector}' clicked at ${new Date().toLocaleString()}`);

                                                    // Add the clicked element to the array
                                                    clickedElements.push(theElement);
                                                }, getRandomDelay(minDelayBetweenElementClicks, maxDelayBetweenElementClicks) * index);
                                            }
                                        }
                                    }
                            });
                        });
                    },
                });
            }
        });
    }
}

// Function to generate a random delay
function getRandomDelay(minDelay, maxDelay) {
    return Math.random() * (maxDelay - minDelay) + minDelay;
}

// Function to refresh the active page at a random interval
function refreshPageWithRandomInterval() {
    refreshPage();
    const randomInterval = getRandomDelay(minPageRefreshInterval, maxPageRefreshInterval);
    clearInterval(refreshIntervalID);
    refreshIntervalID = setInterval(refreshPageWithRandomInterval, randomInterval);
}

// Function to refresh the active page
function refreshPage() {
    if (isExtensionActive) {
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
            if (tabs && tabs.length > 0 && tabs[0].id) {
                chrome.scripting.executeScript({
                    target: { tabId: tabs[0].id },
                    function: () => {
                        location.reload();
                    },
                });
            }
            console.log(`Page is refreshed at ${new Date().toLocaleString()}`);
        });
    }
}

// Function to check if an element is in the viewport
function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
}

// Function to toggle the extension's state/icon
function toggleExtensionState() {
    isExtensionActive = !isExtensionActive;
    console.log(`Extension state toggled. Extension is now ${isExtensionActive ? "active" : "inactive"} as of ${new Date().toLocaleString()}`);
    clearInterval(refreshIntervalID);
    clearInterval(liketheElementClickIntervalID);
    const newIconPath = isExtensionActive ? "images/icon-active.png" : "images/icon-128.png";
    chrome.action.setIcon({ path: newIconPath });

    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        chrome.tabs.sendMessage(tabs[0].id, { action: "clickTheElement", isExtensionActive });
    });

    if (isExtensionActive) {
        liketheElementClickIntervalID = setInterval(clickTheElement, getRandomDelay(minClickDelay, maxClickDelay));
    }

    if (isExtensionActive) {
        refreshPageWithRandomInterval();
    }
}

// Add a listener for the extension button click event
chrome.action.onClicked.addListener(() => {
    loadConfig(toggleExtensionState);
});

// Check if the extension was previously active and load configuration settings
chrome.storage.local.get("isExtensionActive", (data) => {
    if (data.isExtensionActive) {
        loadConfig(toggleExtensionState);
    } else {
        loadConfig(() => {
            refreshPageWithRandomInterval();
            liketheElementClickIntervalID = setInterval(clickTheElement, getRandomDelay(minClickDelay, maxClickDelay));
        });
    }
});

        

content.js

            let theSelector; // Stores the CSS selector for the elements to be clicked
let minDelayBetweenElementClicks; // Minimum delay between element clicks
let maxDelayBetweenElementClicks; // Maximum delay between element clicks

// Load configuration settings from storage
chrome.storage.sync.get(["theSelector", "minDelayBetweenElementClicks", "maxDelayBetweenElementClicks"], (result) => {
    theSelector = result.theSelector; // Default selector if not found in storage
    minDelayBetweenElementClicks = result.minDelayBetweenElementClicks; // Default minimum delay
    maxDelayBetweenElementClicks = result.maxDelayBetweenElementClicks; // Default maximum delay
});

// Function to click like buttons on the page
function clickTheElement() {
    const likeButtons = document.querySelectorAll(theSelector); // Find all elements matching the selector

    function getRandomDelay(minDelay, maxDelay) {
        return Math.random() * (maxDelay - minDelay) + minDelay; // Generate a random delay within the specified range
    }

    likeButtons.forEach((button, index) => {
        if (isInViewport(button)) { // Check if the element is in the viewport
            setTimeout(() => {
                button.click(); // Click the element
                console.log(`'${theSelector}' clicked at ${new Date().toLocaleString()}`);
            }, getRandomDelay(minDelayBetweenElementClicks, maxDelayBetweenElementClicks) * index); // Apply a random delay
        }
    });
}

// Function to check if an element is in the viewport
function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
}

// Listen for messages from the extension popup or background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "clickTheElement") {
        clickTheElement(); // Trigger the clickTheElement function when a message is received
    }
});

        

Install the extension after downloading by selecting More Tools in Chrome and then choosing Extensions from the browser menu.  Finally make sure to enable the Developer mode before clicking Load unpacked and selecting the unzipped folder.