Facebook Business Automation

Automates actions in Facebook Business Manager, specifically Rights Manager.

当前为 2025-02-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Facebook Business Automation
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description Automates actions in Facebook Business Manager, specifically Rights Manager.
  6. // @author You
  7. // @match https://business.facebook.com/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_addStyle
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // Function to get element by XPath
  18. function getElementByXpath(path) {
  19. return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  20. }
  21.  
  22. // Utility function to wait for an element to appear in the DOM
  23. function waitForElement(selector, timeoutSeconds = 15) {
  24. return new Promise((resolve, reject) => {
  25. const intervalTime = 500; // Check every 500ms
  26. let elapsedTime = 0;
  27.  
  28. const interval = setInterval(() => {
  29. const element = typeof selector === 'string' ? document.querySelector(selector) : selector();
  30. if (element) {
  31. clearInterval(interval); // Stop checking
  32. resolve(element); // Resolve with the found element
  33. } else if (elapsedTime >= timeoutSeconds * 1000) {
  34. clearInterval(interval); // Stop checking after timeout
  35. reject(new Error(`Element not found: ${selector}`));
  36. }
  37. elapsedTime += intervalTime;
  38. }, intervalTime);
  39. });
  40. }
  41.  
  42. // Function to click on an element using XPath and return a promise
  43. function clickElementByXPath(xpath) {
  44. return new Promise((resolve, reject) => {
  45. const element = getElementByXpath(xpath);
  46. if (element) {
  47. element.click();
  48. console.log(`Clicked element with XPath: ${xpath}`);
  49. resolve(true);
  50. } else {
  51. console.log(`Element with XPath not found: ${xpath}`);
  52. reject(new Error(`Element not found: ${xpath}`));
  53. }
  54. });
  55. }
  56.  
  57. // Function to simulate typing into a textarea
  58. function simulateTyping(element, text) {
  59. return new Promise(resolve => {
  60. let index = 0;
  61. const typingInterval = 50; // Adjust typing speed here (milliseconds per character)
  62.  
  63. function typeNextCharacter() {
  64. if (index < text.length) {
  65. const char = text[index];
  66. const keyCode = char.charCodeAt(0);
  67.  
  68. // Trigger keydown event
  69. const keydownEvent = new KeyboardEvent('keydown', {
  70. key: char,
  71. code: `Key${char.toUpperCase()}`,
  72. which: keyCode,
  73. keyCode: keyCode,
  74. bubbles: true,
  75. cancelable: true
  76. });
  77. element.dispatchEvent(keydownEvent);
  78.  
  79. // Trigger keypress event
  80. const keypressEvent = new KeyboardEvent('keypress', {
  81. key: char,
  82. code: `Key${char.toUpperCase()}`,
  83. which: keyCode,
  84. keyCode: keyCode,
  85. bubbles: true,
  86. cancelable: true
  87. });
  88. element.dispatchEvent(keypressEvent);
  89.  
  90. // Update the textarea value
  91. element.value += char;
  92. element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
  93.  
  94. // Trigger keyup event
  95. const keyupEvent = new KeyboardEvent('keyup', {
  96. key: char,
  97. code: `Key${char.toUpperCase()}`,
  98. which: keyCode,
  99. keyCode: keyCode,
  100. bubbles: true,
  101. cancelable: true
  102. });
  103. element.dispatchEvent(keyupEvent);
  104.  
  105. index++;
  106. setTimeout(typeNextCharacter, typingInterval);
  107. } else {
  108. // All characters have been typed
  109. element.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
  110. resolve();
  111. }
  112. }
  113.  
  114. typeNextCharacter();
  115. });
  116. }
  117.  
  118. // Function to insert text using execCommand
  119. function insertTextWithExecCommand(textarea, text) {
  120. textarea.focus();
  121. try {
  122. const success = document.execCommand('insertText', false, text);
  123. if (!success) {
  124. console.warn("execCommand('insertText') failed, falling back to simulateTyping");
  125. simulateTyping(textarea, text); // Use simulateTyping as a fallback
  126. }
  127. } catch (err) {
  128. console.error("Error using execCommand, falling back to simulateTyping:", err);
  129. simulateTyping(textarea, text); // Use simulateTyping as a fallback
  130. }
  131. // Dispatch necessary events
  132. textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
  133. textarea.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
  134. }
  135.  
  136. function scrollToTextareaAndEnterText(containerXPath, textareaXPath, text, maxScrollAttempts = 10) {
  137. return new Promise((resolve, reject) => {
  138. const container = getElementByXpath(containerXPath);
  139. if (!container) {
  140. return reject(new Error(`Scrollable container not found: ${containerXPath}`));
  141. }
  142.  
  143. let attempts = 0;
  144.  
  145. function tryScroll() {
  146. // Query the textarea directly using the placeholder attribute.
  147. const textarea = getElementByXpath(textareaXPath);
  148.  
  149. if (textarea) {
  150. console.log(`Found target textarea with XPath: ${textareaXPath}`);
  151.  
  152. // Clear existing text reliably
  153. textarea.value = '';
  154. textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
  155.  
  156. // Insert text using execCommand
  157. insertTextWithExecCommand(textarea, text);
  158. console.log(`Entered text into textarea with XPath: ${textareaXPath}`);
  159. setTimeout(() => { // ADDED WAIT TIME HERE
  160. resolve();
  161. }, 6000)
  162.  
  163. } else if (attempts < maxScrollAttempts) {
  164. container.scrollBy(0, 300); // Scroll down by 300px
  165. attempts++;
  166. console.log(`Scrolling attempt ${attempts}...`);
  167. setTimeout(tryScroll, 500); // Retry after a short delay
  168. } else {
  169. reject(new Error(`Target textarea not found after ${maxScrollAttempts} scrolling attempts.`));
  170. }
  171. }
  172.  
  173. tryScroll();
  174. });
  175. }
  176.  
  177. // Function to check and click "Confirm my ownership" and handle dispute submission
  178. // Function to check and click "Confirm my ownership" and handle dispute submission
  179. function checkAndClickConfirmOwnership() {
  180. const actionDivSelector = 'div[aria-labelledby][role="menuitem"]';
  181. 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]";
  182. 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]";
  183. 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]";
  184. 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]";
  185. const disputeText = "Per the supplied composition and CWR Data, West One Music Group is the owner of this sound recording.";
  186. 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";
  187. 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
  188. 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]";
  189. function tryConfirmOwnership() {
  190. waitForElement(actionDivSelector)
  191. .then(() => {
  192. const actionDivs = document.querySelectorAll(actionDivSelector);
  193. for (let i = 0; i < actionDivs.length; i++) {
  194. if (actionDivs[i].textContent.includes("Confirm my ownership")) {
  195. actionDivs[i].click();
  196. console.log("Clicked 'Confirm my ownership'");
  197. setTimeout(() => { // ADDED WAIT TIME HERE
  198. // Scroll to textarea, enter text, submit dispute, confirm, then move to next item
  199. scrollToTextareaAndEnterText(scrollableContainerXPath, disputeTextareaXPath, disputeText)
  200. .then(() => clickElementByXPath(submitDisputeXPath))
  201. .then(() => {
  202. console.log("Added 9-second delay after submitting dispute");
  203. return new Promise(resolve => setTimeout(resolve, 7000)); // 9-second delay
  204. })
  205. // .then(() => waitForElement(() => getElementByXpath(confirmButtonXPath), 15)) // Wait for the confirm button to appear
  206. .then(() => clickElementByXPath(confirmButtonXPath)) // Click the confirm button
  207. .then(() => {
  208. console.log("Waiting for 5 seconds before submitting dispute...");
  209. return new Promise(resolve => setTimeout(resolve, 6000)); // 5-second delay
  210. })
  211. .then(() => {
  212. console.log("Submitted dispute and confirmed. Moving to next item.");
  213. // Add a 5-second wait here
  214. setTimeout(function () {
  215. // Move to the next item and loop indefinitely
  216. clickElementByXPath(nextItemXPath)
  217. .then(() => clickElementByXPath(chooseActionXPath1))
  218. .catch(() => clickElementByXPath(chooseActionXPath2)) // Fallback to the second XPath
  219. .then(() => {
  220. console.log("Waiting for 4 seconds before submitting dispute...");
  221. return new Promise(resolve => setTimeout(resolve, 4000)); // 5-second delay
  222. })
  223. .then(() => tryConfirmOwnership())
  224. .catch(error => {
  225. console.error("Error moving to next item:", error);
  226. console.log("Retrying from beginning...");
  227. setTimeout(resumeAutomation, 5000);
  228. });
  229. }, 5000); // 5000 milliseconds = 5 seconds
  230. })
  231. .catch(error => { // This is the important part - catching the rejection from scrollToTextareaAndEnterText
  232. console.error("Error during scrollToTextareaAndEnterText or subsequent actions:", error);
  233. console.log("Retrying from beginning...");
  234. setTimeout(resumeAutomation, 5000);
  235. });
  236. }, 6000)
  237. return; // Stop looking for "Confirm my ownership" in this iteration
  238. }
  239. }
  240. console.log("Confirm my ownership not available");
  241. // Move to the next item if "Confirm my ownership" is not available
  242. clickElementByXPath(nextItemXPath)
  243. .then(() => {
  244. console.log("Waiting for 5 seconds before submitting dispute...");
  245. return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
  246. })
  247. .then(() => clickElementByXPath(chooseActionXPath1))
  248. .catch(() => clickElementByXPath(chooseActionXPath2)) // Fallback to the second XPath
  249. .then(() => {
  250. console.log("Waiting for 5 seconds before submitting dispute...");
  251. return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
  252. })
  253. .then(() => tryConfirmOwnership())
  254. .catch(error => {
  255. console.error("Error moving to next item:", error);
  256. });
  257. })
  258. .catch(error => {
  259. console.error("Error in checkAndClickConfirmOwnership:", error);
  260. });
  261. }
  262. tryConfirmOwnership();
  263. }
  264.  
  265.  
  266. function navigateAndLog() {
  267. GM_setValue('automationActive', true);
  268. window.location.href = 'https://business.facebook.com/latest/rights_manager/rights_manager_action_items?asset_id=114882491608990';
  269. }
  270.  
  271. // Function to wait for a non-empty first row in <tbody>
  272. function waitForNonEmptyFirstRow(selector, timeoutSeconds = 30) {
  273. return new Promise((resolve, reject) => {
  274. const intervalTime = 500; // Check every 500ms
  275. let elapsedTime = 0;
  276.  
  277. const interval = setInterval(() => {
  278. const firstRow = document.querySelector(selector);
  279. if (firstRow && firstRow.textContent.trim() !== '') {
  280. clearInterval(interval); // Stop checking when non-empty row is found
  281. resolve(firstRow);
  282. } else if (elapsedTime >= timeoutSeconds * 1000) {
  283. clearInterval(interval); // Stop checking after timeout
  284. reject(new Error(`Non-empty first row not found in: ${selector}`));
  285. }
  286. elapsedTime += intervalTime;
  287. }, intervalTime);
  288. });
  289. }
  290.  
  291. function resumeAutomation() {
  292. const automationActive = GM_getValue('automationActive', false);
  293.  
  294. if (automationActive) {
  295. console.log('Resuming automation...');
  296. waitForNonEmptyFirstRow('tbody tr:first-child', 30)
  297. .then((firstRow) => {
  298. console.log('First item in <tbody>:', firstRow.textContent.trim());
  299.  
  300. setTimeout(() => {
  301. const checkbox = firstRow.querySelector('input[type="checkbox"]');
  302. if (checkbox) {
  303. checkbox.click();
  304. console.log('Checkbox clicked!');
  305. setTimeout(() => {
  306. 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";
  307. const choose_action_xpath = "/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]";
  308.  
  309. clickElementByXPath(see_details_xpath)
  310. .then(() => {
  311. console.log("Clicked 'See Details', waiting for 'Choose Action'...");
  312. return waitForElement(() => getElementByXpath(choose_action_xpath), 15);
  313. })
  314. .then(() => {
  315. console.log("Waiting for 5 seconds before submitting dispute...");
  316. return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
  317. })
  318. .then(() => {
  319. console.log("'Choose Action' element found, clicking...");
  320. return clickElementByXPath(choose_action_xpath);
  321. })
  322. .then(() => {
  323. console.log("Waiting for 5 seconds before submitting dispute...");
  324. return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
  325. })
  326. .then(() => {
  327. console.log("Clicked 'Choose Action'");
  328. setTimeout(checkAndClickConfirmOwnership, 3000);
  329. })
  330. .then(() => {
  331. console.log("Waiting for 5 seconds before submitting dispute...");
  332. return new Promise(resolve => setTimeout(resolve, 5000)); // 5-second delay
  333. })
  334. .catch(error => {
  335. console.error("Error in automation sequence:", error);
  336. });
  337. }, 5000);
  338. } else {
  339. console.warn('Checkbox not found in the first row.');
  340. }
  341.  
  342. GM_setValue('automationActive', false);
  343. }, 7000);
  344. })
  345. .catch((error) => {
  346. console.error(error.message);
  347. console.log("Retrying to find a non-empty first row...");
  348. resumeAutomation(); // Retry until a non-empty row is found
  349. });
  350. }
  351. }
  352.  
  353. // Add Start button
  354. const startButton = document.createElement('button');
  355. startButton.textContent = 'Start';
  356. startButton.style.position = 'fixed';
  357. startButton.style.top = '20px';
  358. startButton.style.left = '20px';
  359. startButton.style.zIndex = '1000';
  360. startButton.style.backgroundColor = '#4267B2';
  361. startButton.style.color = 'white';
  362. startButton.style.border = 'none';
  363. startButton.style.padding = '10px 20px';
  364. startButton.style.cursor = 'pointer';
  365. startButton.style.borderRadius = '5px';
  366.  
  367. startButton.addEventListener('click', navigateAndLog);
  368. document.body.appendChild(startButton);
  369.  
  370. resumeAutomation();
  371. })()