Facebook Business Automation

Automates actions in Facebook Business Manager, specifically Rights Manager.

目前為 2025-02-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Facebook Business Automation
// @namespace    http://tampermonkey.net/
// @version      2.2 
// @description  Automates actions in Facebook Business Manager, specifically Rights Manager.
// @author       You
// @match        https://business.facebook.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Function to get element by XPath
    function getElementByXpath(path) {
        return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }

    // Utility function to wait for an element to appear in the DOM
    function waitForElement(selector, timeoutSeconds = 15) {
        return new Promise((resolve, reject) => {
            const intervalTime = 500; // Check every 500ms
            let elapsedTime = 0;

            const interval = setInterval(() => {
                const element = typeof selector === 'string' ? document.querySelector(selector) : selector();
                if (element) {
                    clearInterval(interval); // Stop checking
                    resolve(element); // Resolve with the found element
                } else if (elapsedTime >= timeoutSeconds * 1000) {
                    clearInterval(interval); // Stop checking after timeout
                    reject(new Error(`Element not found: ${selector}`));
                }
                elapsedTime += intervalTime;
            }, intervalTime);
        });
    }

    // Function to click on an element using XPath and return a promise
    function clickElementByXPath(xpath) {
        return new Promise((resolve, reject) => {
            const element = getElementByXpath(xpath);
            if (element) {
                element.click();
                console.log(`Clicked element with XPath: ${xpath}`);
                resolve(true);
            } else {
                console.log(`Element with XPath not found: ${xpath}`);
                reject(new Error(`Element not found: ${xpath}`));
            }
        });
    }

   // Function to simulate typing into a textarea
    function simulateTyping(element, text) {
        return new Promise(resolve => {
            let index = 0;
            const typingInterval = 50; // Adjust typing speed here (milliseconds per character)

            function typeNextCharacter() {
                if (index < text.length) {
                    const char = text[index];
                    const keyCode = char.charCodeAt(0);

                    // Trigger keydown event
                    const keydownEvent = new KeyboardEvent('keydown', {
                        key: char,
                        code: `Key${char.toUpperCase()}`,
                        which: keyCode,
                        keyCode: keyCode,
                        bubbles: true,
                        cancelable: true
                    });
                    element.dispatchEvent(keydownEvent);

                    // Trigger keypress event
                    const keypressEvent = new KeyboardEvent('keypress', {
                        key: char,
                        code: `Key${char.toUpperCase()}`,
                        which: keyCode,
                        keyCode: keyCode,
                        bubbles: true,
                        cancelable: true
                    });
                    element.dispatchEvent(keypressEvent);

                    // Update the textarea value
                    element.value += char;
                    element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

                    // Trigger keyup event
                    const keyupEvent = new KeyboardEvent('keyup', {
                        key: char,
                        code: `Key${char.toUpperCase()}`,
                        which: keyCode,
                        keyCode: keyCode,
                        bubbles: true,
                        cancelable: true
                    });
                    element.dispatchEvent(keyupEvent);

                    index++;
                    setTimeout(typeNextCharacter, typingInterval);
                } else {
                    // All characters have been typed
                    element.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
                    resolve();
                }
            }

            typeNextCharacter();
        });
    }

    // Function to insert text using execCommand
    function insertTextWithExecCommand(textarea, text) {
        textarea.focus();
         try {
            const success = document.execCommand('insertText', false, text);
            if (!success) {
                console.warn("execCommand('insertText') failed, falling back to simulateTyping");
                 simulateTyping(textarea, text); // Use simulateTyping as a fallback
            }
        } catch (err) {
            console.error("Error using execCommand, falling back to simulateTyping:", err);
             simulateTyping(textarea, text); // Use simulateTyping as a fallback
        }
         // Dispatch necessary events
        textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
        textarea.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
    }

    function scrollToTextareaAndEnterText(containerXPath, textareaXPath, text, maxScrollAttempts = 10) {
        return new Promise((resolve, reject) => {
            const container = getElementByXpath(containerXPath);
            if (!container) {
                return reject(new Error(`Scrollable container not found: ${containerXPath}`));
            }

            let attempts = 0;

            function tryScroll() {
                // Query the textarea directly using the placeholder attribute.
                 const textarea = getElementByXpath(textareaXPath);

                if (textarea) {
                    console.log(`Found target textarea with XPath: ${textareaXPath}`);

                    // Clear existing text reliably
                    textarea.value = '';
                    textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

                    // Insert text using execCommand
                    insertTextWithExecCommand(textarea, text);
                    console.log(`Entered text into textarea with XPath: ${textareaXPath}`);
                    setTimeout(() => { // ADDED WAIT TIME HERE
                         resolve();
                    }, 6000)

                } else if (attempts < maxScrollAttempts) {
                    container.scrollBy(0, 300); // Scroll down by 300px
                    attempts++;
                    console.log(`Scrolling attempt ${attempts}...`);
                    setTimeout(tryScroll, 500); // Retry after a short delay
                } else {
                    reject(new Error(`Target textarea not found after ${maxScrollAttempts} scrolling attempts.`));
                }
            }

            tryScroll();
        });
    }

    // Function to check and click "Confirm my ownership" and handle dispute submission
    // Function to check and click "Confirm my ownership" and handle dispute submission
function checkAndClickConfirmOwnership() {
    const actionDivSelector = 'div[aria-labelledby][role="menuitem"]';
    const nextItemXPath = "/html[1]/body[1]/span[2]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[4]/div[1]/div[1]/div[1]/div[2]/div[1]/div[1]";
    const chooseActionXPath1 = "/html/body/span[2]/div/div[1]/div[1]/div/div/div/div/div/div[3]/div/div[1]/div/div/div/div/div/div/div/div/div[1]/div[2]/div/div/div[2]/div/div/div/div[2]/div/div/div/div[2]/div[1]";
    const chooseActionXPath2 = "/html/body/span/div/div[1]/div[1]/div/div/div/div/div/div[3]/div/div[1]/div/div/div/div/div/div/div/div/div[1]/div[2]/div/div/div[2]/div/div/div/div[2]/div/div/div/div[2]/div[1]/span/div/div/div[1]";
    const disputeTextareaXPath = "/html[1]/body[1]/span[2]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[3]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[2]/div[1]/div[1]/div[2]/div[1]/div[3]/div[3]/div[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/div[1]/div[2]/div[1]/div[1]/div[1]/div[1]/div[2]/textarea[1]";
    const disputeText = "Per the supplied composition and CWR Data, West One Music Group is the owner of this sound recording.";
    const submitDisputeXPath = "/html/body/span[2]/div/div[1]/div[1]/div/div/div/div/div/div[3]/div/div[1]/div/div/div/div/div/div/div/div[2]/div/div[2]/div/span/div/div/div";
    const confirmButtonXPath = "/html/body/div[6]/div[1]/div[1]/div/div/div/div/div[3]/div/div[2]/div/span/div/div/div"; // Confirm button after Submit Dispute
    const scrollableContainerXPath = "/html/body/span[2]/div/div[1]/div[1]/div/div/div/div/div/div[3]/div/div[1]/div/div/div/div/div/div/div/div[1]/div[1]";

    function tryConfirmOwnership() {
        waitForElement(actionDivSelector)
            .then(() => {
                const actionDivs = document.querySelectorAll(actionDivSelector);
                for (let i = 0; i < actionDivs.length; i++) {
                    if (actionDivs[i].textContent.includes("Confirm my ownership")) {
                        actionDivs[i].click();
                        console.log("Clicked 'Confirm my ownership'");
                        setTimeout(() => { // ADDED WAIT TIME HERE
                            // Scroll to textarea, enter text, submit dispute, confirm, then move to next item
                            scrollToTextareaAndEnterText(scrollableContainerXPath, disputeTextareaXPath, disputeText)
                                .then(() => clickElementByXPath(submitDisputeXPath))
                                .then(() => {
                                    console.log("Added 9-second delay after submitting dispute");
                                    return new Promise(resolve => setTimeout(resolve, 7000)); // 9-second delay
                                })
                                // .then(() => waitForElement(() => getElementByXpath(confirmButtonXPath), 15)) // Wait for the confirm button to appear
                                .then(() => clickElementByXPath(confirmButtonXPath)) // Click the confirm button
                                .then(() => {
                                    console.log("Waiting for 5 seconds before submitting dispute...");
                                    return new Promise(resolve => setTimeout(resolve, 6000)); // 5-second delay
                                })
                                .then(() => {
                                    console.log("Submitted dispute and confirmed. Moving to next item.");
                                    // Add a 5-second wait here
                                    setTimeout(function () {
                                        // Move to the next item and loop indefinitely
                                        clickElementByXPath(nextItemXPath)
                                            .then(() => clickElementByXPath(chooseActionXPath1))
                                            .catch(() => clickElementByXPath(chooseActionXPath2)) // Fallback to the second XPath
                                            .then(() => {
                                                console.log("Waiting for 4 seconds before submitting dispute...");
                                                return new Promise(resolve => setTimeout(resolve, 4000)); // 5-second delay
                                            })
                                            .then(() => tryConfirmOwnership())
                                            .catch(error => {
                                                console.error("Error moving to next item:", error);
                                                console.log("Retrying from beginning...");
                                                setTimeout(resumeAutomation, 5000);
                                            });
                                    }, 5000); // 5000 milliseconds = 5 seconds
                                })
                                .catch(error => { // This is the important part - catching the rejection from scrollToTextareaAndEnterText
                                    console.error("Error during scrollToTextareaAndEnterText or subsequent actions:", error);
                                    console.log("Retrying from beginning...");
                                    setTimeout(resumeAutomation, 5000);
                                });
                        }, 6000)

                        return; // Stop looking for "Confirm my ownership" in this iteration
                    }
                }
                console.log("Confirm my ownership not available");
                // Move to the next item if "Confirm my ownership" is not available
                clickElementByXPath(nextItemXPath)
                    .then(() => {
                        console.log("Waiting for 5 seconds before submitting dispute...");
                        return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
                    })
                    .then(() => clickElementByXPath(chooseActionXPath1))
                    .catch(() => clickElementByXPath(chooseActionXPath2)) // Fallback to the second XPath
                    .then(() => {
                        console.log("Waiting for 5 seconds before submitting dispute...");
                        return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
                    })
                    .then(() => tryConfirmOwnership())
                    .catch(error => {
                        console.error("Error moving to next item:", error);
                    });
            })
            .catch(error => {
                console.error("Error in checkAndClickConfirmOwnership:", error);
            });
    }

    tryConfirmOwnership();
}



    function navigateAndLog() {
        GM_setValue('automationActive', true);
        window.location.href = 'https://business.facebook.com/latest/rights_manager/rights_manager_action_items?asset_id=114882491608990';
    }

    // Function to wait for a non-empty first row in <tbody>
    function waitForNonEmptyFirstRow(selector, timeoutSeconds = 30) {
        return new Promise((resolve, reject) => {
            const intervalTime = 500; // Check every 500ms
            let elapsedTime = 0;

            const interval = setInterval(() => {
                const firstRow = document.querySelector(selector);
                if (firstRow && firstRow.textContent.trim() !== '') {
                    clearInterval(interval); // Stop checking when non-empty row is found
                    resolve(firstRow);
                } else if (elapsedTime >= timeoutSeconds * 1000) {
                    clearInterval(interval); // Stop checking after timeout
                    reject(new Error(`Non-empty first row not found in: ${selector}`));
                }
                elapsedTime += intervalTime;
            }, intervalTime);
        });
    }

    function resumeAutomation() {
        const automationActive = GM_getValue('automationActive', false);
    
        if (automationActive) {
            console.log('Resuming automation...');
            waitForNonEmptyFirstRow('tbody tr:first-child', 30)
                .then((firstRow) => {
                    console.log('First item in <tbody>:', firstRow.textContent.trim());
    
                    setTimeout(() => {
                        const checkbox = firstRow.querySelector('input[type="checkbox"]');
                        if (checkbox) {
                            checkbox.click();
                            console.log('Checkbox clicked!');
                            setTimeout(() => {
                                const see_details_xpath = "/html/body/div[1]/div/div[1]/div/div[2]/div/div/div[1]/span/div/div/div[1]/div[1]/div/div/div/div/div/div/div/div/div/div[2]/div[2]/div/div/div/div/div[1]/div[2]/span[2]/div[8]/div[1]/div/div[3]/div[2]/div[1]/div";
    
                                clickElementByXPath(see_details_xpath)
                                    .then(() => {
                                        console.log("Clicked 'See Details', waiting for 'Choose Action'...");
                                        return waitForElement(() => findChooseActionDiv(), 15);
                                    })
                                    .then(() => {
                                        console.log("Waiting for 5 seconds before submitting dispute...");
                                        return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
                                    })
                                    .then(() => {
                                        console.log("'Choose Action' element found, clicking...");
                                        return findAndClickChooseActionDiv();
                                    })
                                    .then(() => {
                                        console.log("Waiting for 5 seconds before submitting dispute...");
                                        return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
                                    })
                                    .then(() => {
                                        console.log("Clicked 'Choose Action'");
                                        setTimeout(checkAndClickConfirmOwnership, 3000);
                                    })
                                    .then(() => {
                                        console.log("Waiting for 5 seconds before submitting dispute...");
                                        return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
                                    })
                                    .catch(error => {
                                        console.error("Error in automation sequence:", error);
                                    });
                            }, 5000);
                        } else {
                            console.warn('Checkbox not found in the first row.');
                        }
    
                        GM_setValue('automationActive', false);
                    }, 7000);
                })
                .catch((error) => {
                    console.error(error.message);
                    console.log("Retrying to find a non-empty first row...");
                    resumeAutomation(); // Retry until a non-empty row is found
                });
        }
    }
    
    function findChooseActionDiv() {
        const divs = document.querySelectorAll('div');
        for (let div of divs) {
            if (div.textContent.trim() === "Choose action") {
                return div;
            }
        }
        return null;
    }
    
    function findAndClickChooseActionDiv() {
        return new Promise((resolve, reject) => {
            const div = findChooseActionDiv();
            if (div) {
                div.click();
                console.log("Clicked 'Choose action' div");
                resolve();
            } else {
                reject(new Error("'Choose action' div not found"));
            }
        });
    }
    

    // Add Start button
    const startButton = document.createElement('button');
    startButton.textContent = 'Start';
    startButton.style.position = 'fixed';
    startButton.style.top = '20px';
    startButton.style.left = '20px';
    startButton.style.zIndex = '1000';
    startButton.style.backgroundColor = '#4267B2';
    startButton.style.color = 'white';
    startButton.style.border = 'none';
    startButton.style.padding = '10px 20px';
    startButton.style.cursor = 'pointer';
    startButton.style.borderRadius = '5px';

    startButton.addEventListener('click', navigateAndLog);
    document.body.appendChild(startButton);

    resumeAutomation();
})()