Facebook Business Automation

Automates actions in Facebook Business Manager, specifically Rights Manager.

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

// ==UserScript==
// @name         Facebook Business Automation
// @namespace    http://tampermonkey.net/
// @version      2.8
// @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
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/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 to find element by style attribute
    function findElementByStyle(styleAttribute, styleValue) {
        // Get all elements in the document
        const allElements = document.getElementsByTagName('*');

        // Iterate over all elements
        for (let element of allElements) {
            // Check if the element has the specified style attribute and value
            if (element.style[styleAttribute] === styleValue) {
                return element;
            }
        }
        return null;
    }

    function enterTextInTextarea(textareaPlaceholder, text) {
        return new Promise((resolve) => {
            // Query the textarea using the placeholder attribute
            const textarea = document.querySelector(`textarea[placeholder="${textareaPlaceholder}"]`);

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

                // 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 placeholder: ${textareaPlaceholder}`);
                setTimeout(() => { // ADDED WAIT TIME HERE
                    resolve();
                }, 6000);
            } else {
                console.warn(`Target textarea not found, moving on...`);
                resolve(); // Resolve without error to move on
            }
        });
    }

    // 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[1]/div[1]/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]/span[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 disputeTextareaName = 'Add details about your ownership'; // Use the name attribute
        const disputeText = "Per the supplied composition and CWR Data, West One Music Group is the owner of this sound recording.";
        const submitDisputeXPath = "/html[1]/body[1]/span[1]/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[2]/div[1]/div[2]/div[1]/span[1]/div[1]/div[1]/div[1]";

        function tryConfirmOwnership() {
            waitForElement(actionDivSelector)
                .then(() => {
                    const actionDivs = document.querySelectorAll(actionDivSelector);
                    let ownershipFound = false;

                    for (let i = 0; i < actionDivs.length; i++) {
                        const menuItemText = actionDivs[i].textContent;
                        if (menuItemText.includes("Confirm My Ownership")) {
                            actionDivs[i].click();
                            console.log("Clicked 'Confirm my ownership'");
                            ownershipFound = true;

                            setTimeout(() => { // ADDED WAIT TIME HERE
                                // Enter text, submit dispute, confirm, then move to next item
                                enterTextInTextarea(disputeTextareaName, disputeText)
                                    .then(() => {
                                        console.log("Waiting for 5 seconds before submitting dispute...");
                                        return new Promise(resolve => setTimeout(resolve, 6000)); // 5-second delay
                                    })
                                    .then(() => clickElementByXPath(submitDisputeXPath))
                                    .then(() => {
                                        console.log("Added 9-second delay after submitting dispute");
                                        return new Promise(resolve => setTimeout(resolve, 7000)); // 9-second delay
                                    })
                                    .then(() => clickConfirmButton()) // 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 enterTextInTextarea
                                        console.error("Error during enterTextInTextarea or subsequent actions:", error);
                                        console.log("Ignoring error and moving on...");
                                        // Continue with the next steps regardless of the error
                                        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);
                                            });
                                    });
                            }, 6000);

                            break; // Exit the loop once "Confirm my ownership" is found and clicked
                        }
                    }

                    if (!ownershipFound) {
                        console.log("Confirm my ownership not available. Moving to the next item.");
                        // 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 clickConfirmButton() {
        return new Promise((resolve, reject) => {
            const confirmButton = $("div:contains('Confirm')");
            if (confirmButton.length > 0) {
                confirmButton.click();
                console.log("Clicked confirm button using jQuery");
                resolve(true);
            } else {
                console.log("Confirm button not found using jQuery");
                reject(new Error("Confirm button not found"));
            }
        });
    }

    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();
})();