generated-email-helpers.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. (function attachGeneratedEmailHelpers(root, factory) {
  2. root.MultiPageGeneratedEmailHelpers = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createGeneratedEmailHelpersModule() {
  4. const A4SKY_PROVIDER = 'a4sky';
  5. function createGeneratedEmailHelpers(deps = {}) {
  6. const {
  7. addLog,
  8. buildGeneratedAliasEmail,
  9. buildCloudflareTempEmailHeaders,
  10. CLOUDFLARE_TEMP_EMAIL_GENERATOR,
  11. DUCK_AUTOFILL_URL,
  12. fetch,
  13. fetchIcloudHideMyEmail,
  14. getCloudflareTempEmailAddressFromResponse,
  15. getCloudflareTempEmailConfig,
  16. getState,
  17. joinCloudflareTempEmailUrl,
  18. normalizeCloudflareDomain,
  19. normalizeCloudflareTempEmailAddress,
  20. normalizeEmailGenerator,
  21. isGeneratedAliasProvider,
  22. reuseOrCreateTab,
  23. sendToContentScript,
  24. setEmailState,
  25. throwIfStopped,
  26. } = deps;
  27. function padNumber(value) {
  28. return String(value).padStart(2, '0');
  29. }
  30. function buildA4skyTimestamp(date = new Date()) {
  31. return [
  32. date.getFullYear(),
  33. padNumber(date.getMonth() + 1),
  34. padNumber(date.getDate()),
  35. padNumber(date.getHours()),
  36. padNumber(date.getMinutes()),
  37. padNumber(date.getSeconds()),
  38. ].join('');
  39. }
  40. async function fetchA4skyEmail() {
  41. throwIfStopped();
  42. const email = `n${buildA4skyTimestamp()}@edu.a4sky.com`;
  43. await setEmailState(email);
  44. await addLog(`A4Sky 邮箱:已生成 ${email}`, 'ok');
  45. return email;
  46. }
  47. function generateCloudflareAliasLocalPart() {
  48. const letters = 'abcdefghijklmnopqrstuvwxyz';
  49. const digits = '0123456789';
  50. const chars = [];
  51. for (let i = 0; i < 6; i++) {
  52. chars.push(letters[Math.floor(Math.random() * letters.length)]);
  53. }
  54. for (let i = 0; i < 4; i++) {
  55. chars.push(digits[Math.floor(Math.random() * digits.length)]);
  56. }
  57. for (let i = chars.length - 1; i > 0; i--) {
  58. const j = Math.floor(Math.random() * (i + 1));
  59. [chars[i], chars[j]] = [chars[j], chars[i]];
  60. }
  61. return chars.join('');
  62. }
  63. async function fetchCloudflareEmail(state, options = {}) {
  64. throwIfStopped();
  65. const latestState = state || await getState();
  66. const domain = normalizeCloudflareDomain(latestState.cloudflareDomain);
  67. if (!domain) {
  68. throw new Error('Cloudflare 域名为空或格式无效。');
  69. }
  70. const localPart = String(options.localPart || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
  71. const aliasEmail = `${localPart}@${domain}`;
  72. await setEmailState(aliasEmail);
  73. await addLog(`Cloudflare 邮箱:已生成 ${aliasEmail}`, 'ok');
  74. return aliasEmail;
  75. }
  76. function ensureCloudflareTempEmailConfig(state, options = {}) {
  77. const {
  78. requireAdminAuth = false,
  79. requireDomain = false,
  80. } = options;
  81. const config = getCloudflareTempEmailConfig(state);
  82. if (!config.baseUrl) {
  83. throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
  84. }
  85. if (requireAdminAuth && !config.adminAuth) {
  86. throw new Error('Cloudflare Temp Email 缺少 Admin Auth。');
  87. }
  88. if (requireDomain && !config.domain) {
  89. throw new Error('Cloudflare Temp Email 域名为空或格式无效。');
  90. }
  91. return config;
  92. }
  93. async function requestCloudflareTempEmailJson(config, path, options = {}) {
  94. const {
  95. method = 'GET',
  96. payload,
  97. searchParams,
  98. timeoutMs = 20000,
  99. } = options;
  100. const url = new URL(joinCloudflareTempEmailUrl(config.baseUrl, path));
  101. if (searchParams && typeof searchParams === 'object') {
  102. for (const [key, value] of Object.entries(searchParams)) {
  103. if (value === undefined || value === null || value === '') continue;
  104. url.searchParams.set(key, String(value));
  105. }
  106. }
  107. const controller = new AbortController();
  108. const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
  109. let response;
  110. try {
  111. response = await fetch(url.toString(), {
  112. method,
  113. headers: buildCloudflareTempEmailHeaders(config, {
  114. json: payload !== undefined,
  115. }),
  116. body: payload !== undefined ? JSON.stringify(payload) : undefined,
  117. signal: controller.signal,
  118. });
  119. } catch (err) {
  120. const errorMessage = err?.name === 'AbortError'
  121. ? `Cloudflare Temp Email 请求超时(>${Math.round(timeoutMs / 1000)} 秒)`
  122. : `Cloudflare Temp Email 请求失败:${err.message}`;
  123. throw new Error(errorMessage);
  124. } finally {
  125. clearTimeout(timeoutId);
  126. }
  127. const text = await response.text();
  128. let parsed;
  129. try {
  130. parsed = text ? JSON.parse(text) : {};
  131. } catch {
  132. parsed = text;
  133. }
  134. if (!response.ok) {
  135. const payloadError = typeof parsed === 'object' && parsed
  136. ? (parsed.message || parsed.error || parsed.msg)
  137. : '';
  138. throw new Error(`Cloudflare Temp Email 请求失败:${payloadError || text || `HTTP ${response.status}`}`);
  139. }
  140. return parsed;
  141. }
  142. async function fetchCloudflareTempEmailAddress(state, options = {}) {
  143. throwIfStopped();
  144. const latestState = state || await getState();
  145. const config = ensureCloudflareTempEmailConfig(latestState, {
  146. requireAdminAuth: true,
  147. requireDomain: true,
  148. });
  149. const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
  150. const payload = {
  151. enablePrefix: true,
  152. name: requestedName,
  153. domain: config.domain,
  154. };
  155. const result = await requestCloudflareTempEmailJson(config, '/admin/new_address', {
  156. method: 'POST',
  157. payload,
  158. });
  159. const address = normalizeCloudflareTempEmailAddress(getCloudflareTempEmailAddressFromResponse(result));
  160. if (!address) {
  161. throw new Error('Cloudflare Temp Email 未返回可用邮箱地址。');
  162. }
  163. await setEmailState(address);
  164. await addLog(`Cloudflare Temp Email:已生成 ${address}`, 'ok');
  165. return address;
  166. }
  167. async function fetchDuckEmail(options = {}) {
  168. throwIfStopped();
  169. const { generateNew = true } = options;
  170. await addLog(`Duck 邮箱:正在打开自动填充设置(${generateNew ? '生成新地址' : '复用当前地址'})...`);
  171. await reuseOrCreateTab('duck-mail', DUCK_AUTOFILL_URL);
  172. const result = await sendToContentScript('duck-mail', {
  173. type: 'FETCH_DUCK_EMAIL',
  174. source: 'background',
  175. payload: { generateNew },
  176. });
  177. if (result?.error) {
  178. throw new Error(result.error);
  179. }
  180. if (!result?.email) {
  181. throw new Error('未返回 Duck 邮箱地址。');
  182. }
  183. await setEmailState(result.email);
  184. await addLog(`Duck 邮箱:${result.generated ? '已生成' : '已读取'} ${result.email}`, 'ok');
  185. return result.email;
  186. }
  187. async function fetchManagedAliasEmail(state, options = {}) {
  188. throwIfStopped();
  189. const provider = String(options.mailProvider || state?.mailProvider || '').trim().toLowerCase();
  190. const mergedState = {
  191. ...(state || {}),
  192. mailProvider: provider,
  193. };
  194. if (options.gmailBaseEmail !== undefined) {
  195. mergedState.gmailBaseEmail = String(options.gmailBaseEmail || '').trim();
  196. }
  197. if (options.mail2925BaseEmail !== undefined) {
  198. mergedState.mail2925BaseEmail = String(options.mail2925BaseEmail || '').trim();
  199. }
  200. const email = buildGeneratedAliasEmail(mergedState);
  201. await setEmailState(email);
  202. await addLog(`${provider === 'gmail' ? 'Gmail +tag' : '2925'}:已生成 ${email}`, 'ok');
  203. return email;
  204. }
  205. async function fetchGeneratedEmail(state, options = {}) {
  206. const currentState = state || await getState();
  207. const provider = String(options.mailProvider || currentState.mailProvider || '').trim().toLowerCase();
  208. if (provider === A4SKY_PROVIDER) {
  209. return fetchA4skyEmail();
  210. }
  211. if (isGeneratedAliasProvider?.(provider)) {
  212. return fetchManagedAliasEmail(currentState, options);
  213. }
  214. const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
  215. if (generator === 'custom') {
  216. throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');
  217. }
  218. if (generator === 'icloud') {
  219. return fetchIcloudHideMyEmail();
  220. }
  221. if (generator === 'cloudflare') {
  222. return fetchCloudflareEmail(currentState, options);
  223. }
  224. if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) {
  225. return fetchCloudflareTempEmailAddress(currentState, options);
  226. }
  227. return fetchDuckEmail(options);
  228. }
  229. return {
  230. ensureCloudflareTempEmailConfig,
  231. fetchA4skyEmail,
  232. fetchCloudflareEmail,
  233. fetchCloudflareTempEmailAddress,
  234. fetchDuckEmail,
  235. fetchGeneratedEmail,
  236. buildA4skyTimestamp,
  237. generateCloudflareAliasLocalPart,
  238. requestCloudflareTempEmailJson,
  239. };
  240. }
  241. return {
  242. createGeneratedEmailHelpers,
  243. };
  244. });