utils.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // content/utils.js — Shared utilities for all content scripts
  2. const SCRIPT_SOURCE = (() => {
  3. const url = location.href;
  4. if (url.includes('154.26.182.181')) return 'vps-panel';
  5. if (url.includes('auth0.openai.com') || url.includes('auth.openai.com') || url.includes('accounts.openai.com')) return 'signup-page';
  6. if (url.includes('mail.qq.com')) return 'qq-mail';
  7. if (url.includes('chatgpt.com')) return 'chatgpt';
  8. return 'unknown';
  9. })();
  10. const LOG_PREFIX = `[MultiPage:${SCRIPT_SOURCE}]`;
  11. /**
  12. * Wait for a DOM element to appear.
  13. * @param {string} selector - CSS selector
  14. * @param {number} timeout - Max wait time in ms (default 10000)
  15. * @returns {Promise<Element>}
  16. */
  17. function waitForElement(selector, timeout = 10000) {
  18. return new Promise((resolve, reject) => {
  19. const existing = document.querySelector(selector);
  20. if (existing) {
  21. console.log(LOG_PREFIX, `Found immediately: ${selector}`);
  22. log(`Found element: ${selector}`);
  23. resolve(existing);
  24. return;
  25. }
  26. console.log(LOG_PREFIX, `Waiting for: ${selector} (timeout: ${timeout}ms)`);
  27. log(`Waiting for selector: ${selector}...`);
  28. const observer = new MutationObserver(() => {
  29. const el = document.querySelector(selector);
  30. if (el) {
  31. observer.disconnect();
  32. clearTimeout(timer);
  33. console.log(LOG_PREFIX, `Found after wait: ${selector}`);
  34. log(`Found element: ${selector}`);
  35. resolve(el);
  36. }
  37. });
  38. observer.observe(document.body || document.documentElement, {
  39. childList: true,
  40. subtree: true,
  41. });
  42. const timer = setTimeout(() => {
  43. observer.disconnect();
  44. const msg = `Timeout waiting for ${selector} after ${timeout}ms on ${location.href}`;
  45. console.error(LOG_PREFIX, msg);
  46. reject(new Error(msg));
  47. }, timeout);
  48. });
  49. }
  50. /**
  51. * Wait for an element matching a text pattern among multiple candidates.
  52. * @param {string} containerSelector - Selector for candidate elements
  53. * @param {RegExp} textPattern - Regex to match against textContent
  54. * @param {number} timeout - Max wait time in ms
  55. * @returns {Promise<Element>}
  56. */
  57. function waitForElementByText(containerSelector, textPattern, timeout = 10000) {
  58. return new Promise((resolve, reject) => {
  59. function search() {
  60. const candidates = document.querySelectorAll(containerSelector);
  61. for (const el of candidates) {
  62. if (textPattern.test(el.textContent)) {
  63. return el;
  64. }
  65. }
  66. return null;
  67. }
  68. const existing = search();
  69. if (existing) {
  70. console.log(LOG_PREFIX, `Found by text immediately: ${containerSelector} matching ${textPattern}`);
  71. log(`Found element by text: ${textPattern}`);
  72. resolve(existing);
  73. return;
  74. }
  75. console.log(LOG_PREFIX, `Waiting for text match: ${containerSelector} / ${textPattern}`);
  76. log(`Waiting for element with text: ${textPattern}...`);
  77. const observer = new MutationObserver(() => {
  78. const el = search();
  79. if (el) {
  80. observer.disconnect();
  81. clearTimeout(timer);
  82. console.log(LOG_PREFIX, `Found by text after wait: ${textPattern}`);
  83. log(`Found element by text: ${textPattern}`);
  84. resolve(el);
  85. }
  86. });
  87. observer.observe(document.body || document.documentElement, {
  88. childList: true,
  89. subtree: true,
  90. });
  91. const timer = setTimeout(() => {
  92. observer.disconnect();
  93. const msg = `Timeout waiting for text "${textPattern}" in "${containerSelector}" after ${timeout}ms on ${location.href}`;
  94. console.error(LOG_PREFIX, msg);
  95. reject(new Error(msg));
  96. }, timeout);
  97. });
  98. }
  99. /**
  100. * React-compatible form filling.
  101. * Sets value via native setter and dispatches input + change events.
  102. * @param {HTMLInputElement} el
  103. * @param {string} value
  104. */
  105. function fillInput(el, value) {
  106. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
  107. window.HTMLInputElement.prototype,
  108. 'value'
  109. ).set;
  110. nativeInputValueSetter.call(el, value);
  111. el.dispatchEvent(new Event('input', { bubbles: true }));
  112. el.dispatchEvent(new Event('change', { bubbles: true }));
  113. console.log(LOG_PREFIX, `Filled input ${el.name || el.id || el.type} with: ${value}`);
  114. log(`Filled input [${el.name || el.id || el.type || 'unknown'}]`);
  115. }
  116. /**
  117. * Fill a select element by setting its value and triggering change.
  118. * @param {HTMLSelectElement} el
  119. * @param {string} value
  120. */
  121. function fillSelect(el, value) {
  122. el.value = value;
  123. el.dispatchEvent(new Event('change', { bubbles: true }));
  124. console.log(LOG_PREFIX, `Selected value ${value} in ${el.name || el.id}`);
  125. log(`Selected [${el.name || el.id || 'unknown'}] = ${value}`);
  126. }
  127. /**
  128. * Send a log message to Side Panel via Background.
  129. * @param {string} message
  130. * @param {string} level - 'info' | 'ok' | 'warn' | 'error'
  131. */
  132. function log(message, level = 'info') {
  133. chrome.runtime.sendMessage({
  134. type: 'LOG',
  135. source: SCRIPT_SOURCE,
  136. step: null,
  137. payload: { message, level, timestamp: Date.now() },
  138. error: null,
  139. });
  140. }
  141. /**
  142. * Report that this content script is loaded and ready.
  143. */
  144. function reportReady() {
  145. console.log(LOG_PREFIX, 'Content script ready');
  146. chrome.runtime.sendMessage({
  147. type: 'CONTENT_SCRIPT_READY',
  148. source: SCRIPT_SOURCE,
  149. step: null,
  150. payload: {},
  151. error: null,
  152. });
  153. }
  154. /**
  155. * Report step completion.
  156. * @param {number} step
  157. * @param {Object} data - Step output data
  158. */
  159. function reportComplete(step, data = {}) {
  160. console.log(LOG_PREFIX, `Step ${step} completed`, data);
  161. log(`Step ${step} completed successfully`, 'ok');
  162. chrome.runtime.sendMessage({
  163. type: 'STEP_COMPLETE',
  164. source: SCRIPT_SOURCE,
  165. step,
  166. payload: data,
  167. error: null,
  168. });
  169. }
  170. /**
  171. * Report step error.
  172. * @param {number} step
  173. * @param {string} errorMessage
  174. */
  175. function reportError(step, errorMessage) {
  176. console.error(LOG_PREFIX, `Step ${step} failed: ${errorMessage}`);
  177. log(`Step ${step} failed: ${errorMessage}`, 'error');
  178. chrome.runtime.sendMessage({
  179. type: 'STEP_ERROR',
  180. source: SCRIPT_SOURCE,
  181. step,
  182. payload: {},
  183. error: errorMessage,
  184. });
  185. }
  186. /**
  187. * Simulate a click with proper event dispatching.
  188. * @param {Element} el
  189. */
  190. function simulateClick(el) {
  191. el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
  192. console.log(LOG_PREFIX, `Clicked: ${el.tagName} ${el.textContent?.slice(0, 30) || ''}`);
  193. log(`Clicked [${el.tagName}] "${el.textContent?.trim().slice(0, 30) || ''}"`);
  194. }
  195. /**
  196. * Wait a specified number of milliseconds.
  197. * @param {number} ms
  198. * @returns {Promise<void>}
  199. */
  200. function sleep(ms) {
  201. return new Promise(r => setTimeout(r, ms));
  202. }
  203. // Auto-report ready on load
  204. reportReady();