utils.js 6.6 KB

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