hotmail-utils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. (function hotmailUtilsModule(root, factory) {
  2. if (typeof module !== 'undefined' && module.exports) {
  3. module.exports = factory();
  4. return;
  5. }
  6. root.HotmailUtils = factory();
  7. })(typeof self !== 'undefined' ? self : globalThis, function createHotmailUtils() {
  8. const HOTMAIL_MAIL_API_URL = 'https://apple.882263.xyz/api/mail-new';
  9. function normalizeText(value) {
  10. return String(value || '')
  11. .replace(/\s+/g, ' ')
  12. .trim()
  13. .toLowerCase();
  14. }
  15. function normalizeTimestamp(value) {
  16. if (!value) return 0;
  17. if (typeof value === 'number' && Number.isFinite(value)) {
  18. return value > 0 ? value : 0;
  19. }
  20. const timestamp = Date.parse(value);
  21. return Number.isFinite(timestamp) ? timestamp : 0;
  22. }
  23. function extractVerificationCode(text) {
  24. const source = String(text || '');
  25. const matchCn = source.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/i);
  26. if (matchCn) return matchCn[1];
  27. const matchEn = source.match(/code(?:\s+is|[\s:])+(\d{6})/i);
  28. if (matchEn) return matchEn[1];
  29. const matchStandalone = source.match(/\b(\d{6})\b/);
  30. return matchStandalone ? matchStandalone[1] : null;
  31. }
  32. function extractVerificationCodeFromMessage(message = {}) {
  33. const sender = firstNonEmptyString([
  34. message?.from?.emailAddress?.address,
  35. message?.sender,
  36. message?.from,
  37. ]);
  38. const subject = firstNonEmptyString([message?.subject]);
  39. const preview = firstNonEmptyString([message?.bodyPreview, message?.preview, message?.text]);
  40. return extractVerificationCode([subject, preview, sender].filter(Boolean).join(' '));
  41. }
  42. function getLatestHotmailMessage(messages) {
  43. return (Array.isArray(messages) ? messages : [])
  44. .slice()
  45. .sort((left, right) => {
  46. const leftTime = normalizeTimestamp(left?.receivedDateTime);
  47. const rightTime = normalizeTimestamp(right?.receivedDateTime);
  48. return rightTime - leftTime;
  49. })[0] || null;
  50. }
  51. function getHotmailListToggleLabel(expanded, count = 0) {
  52. const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
  53. const suffix = normalizedCount > 0 ? `(${normalizedCount})` : '';
  54. return `${expanded ? '收起列表' : '展开列表'}${suffix}`;
  55. }
  56. function filterHotmailAccountsByUsage(accounts, mode = 'all') {
  57. const list = Array.isArray(accounts) ? accounts.slice() : [];
  58. if (mode === 'used') {
  59. return list.filter((account) => Boolean(account?.used));
  60. }
  61. return list;
  62. }
  63. function getHotmailBulkActionLabel(mode = 'all', count = 0) {
  64. const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
  65. const prefix = mode === 'used' ? '清空已用' : '全部删除';
  66. const suffix = normalizedCount > 0 ? `(${normalizedCount})` : '';
  67. return `${prefix}${suffix}`;
  68. }
  69. function isAuthorizedHotmailAccount(account) {
  70. return Boolean(account)
  71. && account.status === 'authorized'
  72. && !account.used
  73. && Boolean(account.refreshToken);
  74. }
  75. function shouldClearHotmailCurrentSelection(account) {
  76. return Boolean(account) && account.used === true;
  77. }
  78. function upsertHotmailAccountInList(accounts, nextAccount) {
  79. const list = Array.isArray(accounts) ? accounts.slice() : [];
  80. if (!nextAccount?.id) return list;
  81. const existingIndex = list.findIndex((account) => account?.id === nextAccount.id);
  82. if (existingIndex === -1) {
  83. list.push(nextAccount);
  84. return list;
  85. }
  86. list[existingIndex] = nextAccount;
  87. return list;
  88. }
  89. function pickHotmailAccountForRun(accounts, options = {}) {
  90. const candidates = Array.isArray(accounts) ? accounts.filter(isAuthorizedHotmailAccount) : [];
  91. if (!candidates.length) return null;
  92. const excludeIds = new Set((options.excludeIds || []).filter(Boolean));
  93. const filtered = candidates.filter((account) => !excludeIds.has(account.id));
  94. const pool = filtered.length ? filtered : candidates;
  95. return pool
  96. .slice()
  97. .sort((left, right) => {
  98. const leftUsedAt = normalizeTimestamp(left.lastUsedAt);
  99. const rightUsedAt = normalizeTimestamp(right.lastUsedAt);
  100. if (leftUsedAt !== rightUsedAt) {
  101. return leftUsedAt - rightUsedAt;
  102. }
  103. return String(left.email || '').localeCompare(String(right.email || ''));
  104. })[0] || null;
  105. }
  106. function messageMatchesFilters(message, filters = {}) {
  107. const senderFilters = (filters.senderFilters || []).map(normalizeText).filter(Boolean);
  108. const subjectFilters = (filters.subjectFilters || []).map(normalizeText).filter(Boolean);
  109. const afterTimestamp = normalizeTimestamp(filters.afterTimestamp);
  110. const receivedAt = normalizeTimestamp(message?.receivedDateTime);
  111. if (afterTimestamp && receivedAt && receivedAt < afterTimestamp) {
  112. return null;
  113. }
  114. const sender = normalizeText(message?.from?.emailAddress?.address);
  115. const subject = normalizeText(message?.subject);
  116. const preview = String(message?.bodyPreview || '');
  117. const combinedText = [subject, sender, preview].filter(Boolean).join(' ');
  118. const code = extractVerificationCode(combinedText);
  119. const excludedCodes = new Set((filters.excludeCodes || []).filter(Boolean));
  120. if (code && excludedCodes.has(code)) {
  121. return null;
  122. }
  123. const senderMatch = senderFilters.length === 0
  124. ? true
  125. : senderFilters.some((item) => sender.includes(item) || normalizeText(preview).includes(item));
  126. const subjectMatch = subjectFilters.length === 0
  127. ? true
  128. : subjectFilters.some((item) => subject.includes(item) || normalizeText(preview).includes(item));
  129. if (!senderMatch && !subjectMatch) {
  130. return null;
  131. }
  132. if (!code) {
  133. return null;
  134. }
  135. return {
  136. code,
  137. message,
  138. receivedAt,
  139. };
  140. }
  141. function pickVerificationMessage(messages, filters = {}) {
  142. const matches = (Array.isArray(messages) ? messages : [])
  143. .map((message) => messageMatchesFilters(message, filters))
  144. .filter(Boolean)
  145. .sort((left, right) => right.receivedAt - left.receivedAt);
  146. return matches[0] || null;
  147. }
  148. function pickVerificationMessageWithFallback(messages, filters = {}) {
  149. const strictMatch = pickVerificationMessage(messages, filters);
  150. return {
  151. match: strictMatch || null,
  152. usedRelaxedFilters: false,
  153. usedTimeFallback: false,
  154. };
  155. }
  156. function pickVerificationMessageWithTimeFallback(messages, filters = {}) {
  157. const strictOrRelaxedResult = pickVerificationMessageWithFallback(messages, filters);
  158. if (strictOrRelaxedResult.match) {
  159. return strictOrRelaxedResult;
  160. }
  161. const timeFallbackMatch = pickVerificationMessage(messages, {
  162. afterTimestamp: 0,
  163. excludeCodes: filters.excludeCodes,
  164. senderFilters: filters.senderFilters,
  165. subjectFilters: filters.subjectFilters,
  166. });
  167. return {
  168. match: timeFallbackMatch || null,
  169. usedRelaxedFilters: false,
  170. usedTimeFallback: Boolean(timeFallbackMatch),
  171. };
  172. /* c8 ignore stop */
  173. }
  174. function firstNonEmptyString(values) {
  175. for (const value of values) {
  176. if (value === undefined || value === null) continue;
  177. const normalized = String(value).trim();
  178. if (normalized) return normalized;
  179. }
  180. return '';
  181. }
  182. function normalizeMailAddress(rawValue) {
  183. if (!rawValue) return '';
  184. if (typeof rawValue === 'string') {
  185. return rawValue.trim();
  186. }
  187. if (typeof rawValue === 'object') {
  188. return firstNonEmptyString([
  189. rawValue.emailAddress?.address,
  190. rawValue.address,
  191. rawValue.email,
  192. rawValue.sender,
  193. rawValue.from,
  194. ]);
  195. }
  196. return '';
  197. }
  198. function stripHtmlTags(text) {
  199. return String(text || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
  200. }
  201. function normalizeHotmailMailApiMessage(message = {}) {
  202. return {
  203. id: firstNonEmptyString([message.id, message.message_id, message.messageId, message.internetMessageId]),
  204. subject: firstNonEmptyString([message.subject, message.title]),
  205. from: {
  206. emailAddress: {
  207. address: normalizeMailAddress(
  208. message.from_email
  209. || message.sender_email
  210. || message.from
  211. || message.sender
  212. || message.emailAddress
  213. ),
  214. },
  215. },
  216. bodyPreview: firstNonEmptyString([
  217. message.bodyPreview,
  218. message.preview,
  219. message.snippet,
  220. message.text,
  221. message.body,
  222. stripHtmlTags(message.html || message.content || ''),
  223. ]),
  224. receivedDateTime: firstNonEmptyString([
  225. message.receivedDateTime,
  226. message.received_at,
  227. message.receivedAt,
  228. message.date,
  229. message.created_at,
  230. message.time,
  231. ]),
  232. };
  233. }
  234. function normalizeHotmailMailApiMessages(messages) {
  235. const list = Array.isArray(messages)
  236. ? messages
  237. : (messages ? [messages] : []);
  238. return list.map((message) => normalizeHotmailMailApiMessage(message));
  239. }
  240. function buildHotmailMailApiLatestUrl(options) {
  241. const apiUrl = String(options?.apiUrl || '').trim() || HOTMAIL_MAIL_API_URL;
  242. const url = new URL(apiUrl);
  243. url.searchParams.set('refresh_token', String(options?.refreshToken || ''));
  244. url.searchParams.set('client_id', String(options?.clientId || ''));
  245. url.searchParams.set('email', String(options?.email || ''));
  246. url.searchParams.set('mailbox', String(options?.mailbox || 'INBOX'));
  247. const responseType = options?.responseType === undefined || options?.responseType === null
  248. ? 'json'
  249. : String(options.responseType).trim();
  250. if (responseType) {
  251. url.searchParams.set('response_type', responseType);
  252. }
  253. return url.toString();
  254. }
  255. function getHotmailVerificationPollConfig(step) {
  256. if (step === 4 || step === 7) {
  257. return {
  258. initialDelayMs: 5000,
  259. maxAttempts: 12,
  260. intervalMs: 5000,
  261. requestFreshCodeFirst: false,
  262. ignorePersistedLastCode: true,
  263. };
  264. }
  265. return {
  266. initialDelayMs: 5000,
  267. maxAttempts: 8,
  268. intervalMs: 4000,
  269. requestFreshCodeFirst: false,
  270. ignorePersistedLastCode: true,
  271. };
  272. }
  273. function getHotmailVerificationRequestTimestamp(step, state = {}, options = {}) {
  274. const bufferMs = Number(options.bufferMs) || 15_000;
  275. const signupRequestedAt = normalizeTimestamp(state.signupVerificationRequestedAt);
  276. const loginRequestedAt = normalizeTimestamp(state.loginVerificationRequestedAt);
  277. const lastEmailTimestamp = normalizeTimestamp(state.lastEmailTimestamp);
  278. const flowStartTime = normalizeTimestamp(state.flowStartTime);
  279. if (step === 4 && signupRequestedAt) {
  280. return Math.max(0, signupRequestedAt - bufferMs);
  281. }
  282. if (step === 7 && loginRequestedAt) {
  283. return Math.max(0, loginRequestedAt - bufferMs);
  284. }
  285. return step === 7
  286. ? (lastEmailTimestamp || flowStartTime || 0)
  287. : (flowStartTime || 0);
  288. }
  289. function getHotmailMailApiRequestConfig() {
  290. return {
  291. timeoutMs: 15000,
  292. };
  293. }
  294. function parseHotmailImportText(rawText) {
  295. const lines = String(rawText || '')
  296. .split(/\r?\n/)
  297. .map((line) => line.trim())
  298. .filter(Boolean);
  299. return lines
  300. .filter((line, index) => !(index === 0 && /^账号----密码----ID----Token$/i.test(line)))
  301. .map((line) => line.split('----').map((part) => part.trim()))
  302. .filter((parts) => parts.length >= 4 && parts[0] && parts[2])
  303. .map(([email, password, clientId, refreshToken]) => ({
  304. email,
  305. password,
  306. clientId,
  307. refreshToken,
  308. }));
  309. }
  310. return {
  311. buildHotmailMailApiLatestUrl,
  312. extractVerificationCodeFromMessage,
  313. filterHotmailAccountsByUsage,
  314. extractVerificationCode,
  315. getLatestHotmailMessage,
  316. getHotmailBulkActionLabel,
  317. getHotmailListToggleLabel,
  318. getHotmailMailApiRequestConfig,
  319. getHotmailVerificationPollConfig,
  320. getHotmailVerificationRequestTimestamp,
  321. isAuthorizedHotmailAccount,
  322. normalizeHotmailMailApiMessages,
  323. normalizeTimestamp,
  324. parseHotmailImportText,
  325. pickHotmailAccountForRun,
  326. pickVerificationMessage,
  327. pickVerificationMessageWithFallback,
  328. pickVerificationMessageWithTimeFallback,
  329. shouldClearHotmailCurrentSelection,
  330. upsertHotmailAccountInList,
  331. };
  332. });