Facebook Business Automation

Automates actions in Facebook Business Manager, specifically Rights Manager.

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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