449 lines
16 KiB
JavaScript
449 lines
16 KiB
JavaScript
(function luckmailUtilsModule(root, factory) {
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = factory();
|
||
return;
|
||
}
|
||
|
||
root.LuckMailUtils = factory();
|
||
})(typeof self !== 'undefined' ? self : globalThis, function createLuckmailUtils() {
|
||
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
|
||
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
|
||
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
|
||
const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = '保留';
|
||
const LUCKMAIL_EMAIL_TYPES = ['self_built', 'ms_imap', 'ms_graph', 'google_variant'];
|
||
|
||
function firstNonEmptyString(values) {
|
||
for (const value of values) {
|
||
if (value === undefined || value === null) continue;
|
||
const normalized = String(value).trim();
|
||
if (normalized) return normalized;
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function normalizeText(value) {
|
||
return String(value || '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
.toLowerCase();
|
||
}
|
||
|
||
function normalizeTimestamp(value) {
|
||
if (!value) return 0;
|
||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||
return value > 0 ? value : 0;
|
||
}
|
||
|
||
const rawValue = String(value || '').trim();
|
||
const utcLikeMatch = rawValue.match(
|
||
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?$/
|
||
);
|
||
if (utcLikeMatch && !/[zZ]|[+\-]\d{2}:?\d{2}$/.test(rawValue)) {
|
||
const [, year, month, day, hour, minute, second = '0'] = utcLikeMatch;
|
||
return Date.UTC(
|
||
Number(year),
|
||
Number(month) - 1,
|
||
Number(day),
|
||
Number(hour),
|
||
Number(minute),
|
||
Number(second),
|
||
0
|
||
);
|
||
}
|
||
|
||
const timestamp = Date.parse(rawValue);
|
||
return Number.isFinite(timestamp) ? timestamp : 0;
|
||
}
|
||
|
||
function normalizeLuckmailBaseUrl(rawValue = '') {
|
||
const value = String(rawValue || '').trim();
|
||
if (!value) return DEFAULT_LUCKMAIL_BASE_URL;
|
||
|
||
try {
|
||
const parsed = new URL(value);
|
||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||
return DEFAULT_LUCKMAIL_BASE_URL;
|
||
}
|
||
parsed.pathname = parsed.pathname.replace(/\/+$/, '');
|
||
parsed.search = '';
|
||
parsed.hash = '';
|
||
return parsed.toString().replace(/\/$/, '');
|
||
} catch {
|
||
return DEFAULT_LUCKMAIL_BASE_URL;
|
||
}
|
||
}
|
||
|
||
function normalizeLuckmailEmailType(rawValue = '') {
|
||
const normalized = String(rawValue || '').trim().toLowerCase();
|
||
return LUCKMAIL_EMAIL_TYPES.includes(normalized)
|
||
? normalized
|
||
: DEFAULT_LUCKMAIL_EMAIL_TYPE;
|
||
}
|
||
|
||
function normalizeLuckmailProjectName(rawValue = '') {
|
||
return normalizeText(rawValue);
|
||
}
|
||
|
||
function extractLuckmailVerificationCode(text) {
|
||
const source = String(text || '');
|
||
const matchCn = source.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/i);
|
||
if (matchCn) return matchCn[1];
|
||
|
||
const matchOpenAiLogin = source.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||
|
||
const matchChatGPT = source.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||
if (matchChatGPT) return matchChatGPT[1];
|
||
|
||
const matchEn = source.match(/code(?:\s+is|[\s:])+(\d{6})/i);
|
||
if (matchEn) return matchEn[1];
|
||
|
||
const matchStandalone = source.match(/\b(\d{6})\b/);
|
||
return matchStandalone ? matchStandalone[1] : null;
|
||
}
|
||
|
||
function normalizeLuckmailTag(item = {}) {
|
||
const safeItem = item && typeof item === 'object' ? item : {};
|
||
return {
|
||
id: Number(safeItem.id) || 0,
|
||
name: firstNonEmptyString([safeItem.name, safeItem.tag_name]),
|
||
remark: firstNonEmptyString([safeItem.remark]),
|
||
limit_type: Number(safeItem.limit_type) || 0,
|
||
purchase_count: Number(safeItem.purchase_count) || 0,
|
||
created_at: firstNonEmptyString([safeItem.created_at]) || null,
|
||
};
|
||
}
|
||
|
||
function normalizeLuckmailTags(input) {
|
||
const list = Array.isArray(input?.list)
|
||
? input.list
|
||
: (Array.isArray(input) ? input : []);
|
||
return list
|
||
.map((item) => normalizeLuckmailTag(item))
|
||
.filter((item) => item.id > 0 || item.name);
|
||
}
|
||
|
||
function normalizeLuckmailPurchase(item = {}) {
|
||
const safeItem = item && typeof item === 'object' ? item : {};
|
||
const projectName = firstNonEmptyString([safeItem.project_name, safeItem.project]);
|
||
return {
|
||
id: Number(safeItem.id) || 0,
|
||
email_address: firstNonEmptyString([safeItem.email_address, safeItem.address]),
|
||
token: firstNonEmptyString([safeItem.token]),
|
||
project_name: projectName,
|
||
project_code: normalizeLuckmailProjectName(projectName),
|
||
price: firstNonEmptyString([safeItem.price]) || '0.0000',
|
||
status: Number(safeItem.status) || 0,
|
||
tag_id: Number(safeItem.tag_id) || 0,
|
||
tag_name: firstNonEmptyString([safeItem.tag_name]),
|
||
user_disabled: Number(safeItem.user_disabled) || 0,
|
||
warranty_hours: Number(safeItem.warranty_hours) || 0,
|
||
warranty_until: firstNonEmptyString([safeItem.warranty_until]) || null,
|
||
created_at: firstNonEmptyString([safeItem.created_at]) || null,
|
||
};
|
||
}
|
||
|
||
function normalizeLuckmailPurchases(result) {
|
||
const list = Array.isArray(result?.purchases)
|
||
? result.purchases
|
||
: (Array.isArray(result) ? result : []);
|
||
return list.map((item) => normalizeLuckmailPurchase(item));
|
||
}
|
||
|
||
function normalizeLuckmailPurchaseListPage(result = {}) {
|
||
const safeResult = result && typeof result === 'object' ? result : {};
|
||
const list = Array.isArray(safeResult.list)
|
||
? safeResult.list
|
||
: (Array.isArray(safeResult.purchases) ? safeResult.purchases : []);
|
||
const total = Number(safeResult.total);
|
||
return {
|
||
list: list.map((item) => normalizeLuckmailPurchase(item)),
|
||
total: Number.isFinite(total) && total >= 0 ? total : 0,
|
||
page: Math.max(1, Number(safeResult.page) || 1),
|
||
page_size: Math.max(1, Number(safeResult.page_size || safeResult.pageSize) || list.length || 1),
|
||
};
|
||
}
|
||
|
||
function normalizeLuckmailPurchaseId(value) {
|
||
const numeric = Number(value);
|
||
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||
return '';
|
||
}
|
||
return String(Math.floor(numeric));
|
||
}
|
||
|
||
function normalizeLuckmailUsedPurchases(rawValue = {}) {
|
||
if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
|
||
return {};
|
||
}
|
||
|
||
return Object.entries(rawValue).reduce((result, [key, value]) => {
|
||
const normalizedKey = normalizeLuckmailPurchaseId(key);
|
||
if (!normalizedKey) {
|
||
return result;
|
||
}
|
||
result[normalizedKey] = Boolean(value);
|
||
return result;
|
||
}, {});
|
||
}
|
||
|
||
function isLuckmailPurchaseForProject(purchase, projectCode = DEFAULT_LUCKMAIL_PROJECT_CODE) {
|
||
const normalizedPurchase = normalizeLuckmailPurchase(purchase);
|
||
return normalizeLuckmailProjectName(normalizedPurchase.project_name) === normalizeLuckmailProjectName(projectCode);
|
||
}
|
||
|
||
function normalizeLuckmailTokenMail(mail = {}) {
|
||
const safeMail = mail && typeof mail === 'object' ? mail : {};
|
||
const subject = firstNonEmptyString([safeMail.subject, safeMail.title]);
|
||
const body = firstNonEmptyString([safeMail.body, safeMail.body_text, safeMail.text]);
|
||
const htmlBody = firstNonEmptyString([safeMail.html_body, safeMail.body_html, safeMail.html]);
|
||
const from = firstNonEmptyString([safeMail.from, safeMail.sender]);
|
||
const verificationCode = firstNonEmptyString([safeMail.verification_code])
|
||
|| extractLuckmailVerificationCode([subject, body, htmlBody, from].filter(Boolean).join(' '));
|
||
|
||
return {
|
||
message_id: firstNonEmptyString([safeMail.message_id, safeMail.id]),
|
||
from,
|
||
subject,
|
||
body,
|
||
html_body: htmlBody,
|
||
received_at: firstNonEmptyString([safeMail.received_at, safeMail.receivedAt, safeMail.created_at]),
|
||
verification_code: verificationCode || '',
|
||
};
|
||
}
|
||
|
||
function normalizeLuckmailTokenMails(input) {
|
||
const list = Array.isArray(input?.mails)
|
||
? input.mails
|
||
: (Array.isArray(input) ? input : []);
|
||
return list.map((mail) => normalizeLuckmailTokenMail(mail));
|
||
}
|
||
|
||
function normalizeLuckmailTokenCode(result = {}) {
|
||
return {
|
||
email_address: firstNonEmptyString([result.email_address, result.address]),
|
||
project: firstNonEmptyString([result.project]),
|
||
has_new_mail: Boolean(result.has_new_mail),
|
||
verification_code: firstNonEmptyString([result.verification_code]) || null,
|
||
mail: result.mail ? normalizeLuckmailTokenMail(result.mail) : null,
|
||
};
|
||
}
|
||
|
||
function normalizeLuckmailMailCursor(cursor = {}) {
|
||
const safeCursor = cursor && typeof cursor === 'object' ? cursor : {};
|
||
return {
|
||
messageId: firstNonEmptyString([safeCursor.messageId, safeCursor.message_id]),
|
||
receivedAt: firstNonEmptyString([safeCursor.receivedAt, safeCursor.received_at]),
|
||
};
|
||
}
|
||
|
||
function buildLuckmailMailCursor(mail = {}) {
|
||
const normalizedMail = normalizeLuckmailTokenMail(mail);
|
||
return normalizeLuckmailMailCursor({
|
||
messageId: normalizedMail.message_id,
|
||
receivedAt: normalizedMail.received_at,
|
||
});
|
||
}
|
||
|
||
function buildLuckmailBaselineCursor(mails) {
|
||
const latestMail = normalizeLuckmailTokenMails(mails)
|
||
.sort((left, right) => {
|
||
const leftTimestamp = normalizeTimestamp(left.received_at);
|
||
const rightTimestamp = normalizeTimestamp(right.received_at);
|
||
if (leftTimestamp !== rightTimestamp) {
|
||
return rightTimestamp - leftTimestamp;
|
||
}
|
||
return String(right.message_id || '').localeCompare(String(left.message_id || ''));
|
||
})[0] || null;
|
||
|
||
return latestMail ? buildLuckmailMailCursor(latestMail) : null;
|
||
}
|
||
|
||
function isLuckmailMailNewerThanCursor(mail = {}, cursor = {}) {
|
||
const normalizedMail = normalizeLuckmailTokenMail(mail);
|
||
const normalizedCursor = normalizeLuckmailMailCursor(cursor);
|
||
|
||
if (!normalizedCursor.messageId && !normalizedCursor.receivedAt) {
|
||
return true;
|
||
}
|
||
|
||
if (normalizedMail.message_id && normalizedCursor.messageId && normalizedMail.message_id === normalizedCursor.messageId) {
|
||
return false;
|
||
}
|
||
|
||
const mailTimestamp = normalizeTimestamp(normalizedMail.received_at);
|
||
const cursorTimestamp = normalizeTimestamp(normalizedCursor.receivedAt);
|
||
|
||
if (mailTimestamp && cursorTimestamp) {
|
||
if (mailTimestamp > cursorTimestamp) return true;
|
||
if (mailTimestamp < cursorTimestamp) return false;
|
||
return Boolean(
|
||
normalizedMail.message_id
|
||
&& normalizedCursor.messageId
|
||
&& normalizedMail.message_id !== normalizedCursor.messageId
|
||
);
|
||
}
|
||
|
||
if (normalizedMail.message_id && normalizedCursor.messageId) {
|
||
return normalizedMail.message_id !== normalizedCursor.messageId;
|
||
}
|
||
|
||
return !cursorTimestamp || Boolean(mailTimestamp && mailTimestamp > cursorTimestamp);
|
||
}
|
||
|
||
function isLuckmailPurchaseExpired(purchase, now = Date.now()) {
|
||
const normalizedPurchase = normalizeLuckmailPurchase(purchase);
|
||
const expiresAt = normalizeTimestamp(normalizedPurchase.warranty_until);
|
||
return Boolean(expiresAt && expiresAt <= Number(now || 0));
|
||
}
|
||
|
||
function isLuckmailPurchaseDisabled(purchase) {
|
||
return Number(normalizeLuckmailPurchase(purchase).user_disabled) === 1;
|
||
}
|
||
|
||
function isLuckmailPurchasePreserved(purchase, options = {}) {
|
||
const normalizedPurchase = normalizeLuckmailPurchase(purchase);
|
||
const expectedTagId = Number(options.preserveTagId) || 0;
|
||
const expectedTagName = normalizeText(options.preserveTagName || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME);
|
||
|
||
if (expectedTagId > 0 && normalizedPurchase.tag_id === expectedTagId) {
|
||
return true;
|
||
}
|
||
|
||
return Boolean(normalizedPurchase.tag_name && normalizeText(normalizedPurchase.tag_name) === expectedTagName);
|
||
}
|
||
|
||
function isLuckmailPurchaseReusable(purchase, options = {}) {
|
||
const normalizedPurchase = normalizeLuckmailPurchase(purchase);
|
||
const usedPurchases = normalizeLuckmailUsedPurchases(options.usedPurchases);
|
||
const purchaseId = normalizeLuckmailPurchaseId(normalizedPurchase.id);
|
||
|
||
if (!isLuckmailPurchaseForProject(normalizedPurchase, options.projectCode || DEFAULT_LUCKMAIL_PROJECT_CODE)) {
|
||
return false;
|
||
}
|
||
if (!normalizedPurchase.email_address || !normalizedPurchase.token) {
|
||
return false;
|
||
}
|
||
if (isLuckmailPurchaseDisabled(normalizedPurchase)) {
|
||
return false;
|
||
}
|
||
if (purchaseId && usedPurchases[purchaseId]) {
|
||
return false;
|
||
}
|
||
if (isLuckmailPurchasePreserved(normalizedPurchase, options)) {
|
||
return false;
|
||
}
|
||
if (isLuckmailPurchaseExpired(normalizedPurchase, options.now || Date.now())) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function filterReusableLuckmailPurchases(purchases, options = {}) {
|
||
const list = Array.isArray(purchases)
|
||
? purchases
|
||
: normalizeLuckmailPurchaseListPage(purchases).list;
|
||
return list
|
||
.map((purchase) => normalizeLuckmailPurchase(purchase))
|
||
.filter((purchase) => isLuckmailPurchaseReusable(purchase, options));
|
||
}
|
||
|
||
function pickReusableLuckmailPurchase(purchases, options = {}) {
|
||
return filterReusableLuckmailPurchases(purchases, options)[0] || null;
|
||
}
|
||
|
||
function mailMatchesLuckmailFilters(mail, filters = {}) {
|
||
const normalizedMail = normalizeLuckmailTokenMail(mail);
|
||
const afterTimestamp = normalizeTimestamp(filters.afterTimestamp);
|
||
const receivedAt = normalizeTimestamp(normalizedMail.received_at);
|
||
if (afterTimestamp && receivedAt && receivedAt < afterTimestamp) {
|
||
return null;
|
||
}
|
||
|
||
const senderFilters = (filters.senderFilters || []).map(normalizeText).filter(Boolean);
|
||
const subjectFilters = (filters.subjectFilters || []).map(normalizeText).filter(Boolean);
|
||
const excludedCodes = new Set((filters.excludeCodes || []).filter(Boolean));
|
||
const combinedText = [
|
||
normalizedMail.subject,
|
||
normalizedMail.from,
|
||
normalizedMail.body,
|
||
normalizedMail.html_body,
|
||
].filter(Boolean).join(' ');
|
||
const combinedTextNormalized = normalizeText(combinedText);
|
||
const senderNormalized = normalizeText(normalizedMail.from);
|
||
const subjectNormalized = normalizeText(normalizedMail.subject);
|
||
const code = normalizedMail.verification_code || extractLuckmailVerificationCode(combinedText);
|
||
|
||
if (!code || excludedCodes.has(code)) {
|
||
return null;
|
||
}
|
||
|
||
const senderMatch = senderFilters.length === 0
|
||
? true
|
||
: senderFilters.some((item) => senderNormalized.includes(item) || combinedTextNormalized.includes(item));
|
||
const subjectMatch = subjectFilters.length === 0
|
||
? true
|
||
: subjectFilters.some((item) => subjectNormalized.includes(item) || combinedTextNormalized.includes(item));
|
||
|
||
if (!senderMatch && !subjectMatch) {
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
code,
|
||
mail: normalizedMail,
|
||
receivedAt,
|
||
};
|
||
}
|
||
|
||
function pickLuckmailVerificationMail(mails, filters = {}) {
|
||
const matches = normalizeLuckmailTokenMails(mails)
|
||
.map((mail) => mailMatchesLuckmailFilters(mail, filters))
|
||
.filter(Boolean)
|
||
.sort((left, right) => {
|
||
if (left.receivedAt !== right.receivedAt) {
|
||
return right.receivedAt - left.receivedAt;
|
||
}
|
||
return String(right.mail.message_id || '').localeCompare(String(left.mail.message_id || ''));
|
||
});
|
||
|
||
return matches[0] || null;
|
||
}
|
||
|
||
return {
|
||
DEFAULT_LUCKMAIL_BASE_URL,
|
||
DEFAULT_LUCKMAIL_EMAIL_TYPE,
|
||
DEFAULT_LUCKMAIL_PROJECT_CODE,
|
||
DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||
LUCKMAIL_EMAIL_TYPES,
|
||
buildLuckmailBaselineCursor,
|
||
buildLuckmailMailCursor,
|
||
extractLuckmailVerificationCode,
|
||
filterReusableLuckmailPurchases,
|
||
isLuckmailMailNewerThanCursor,
|
||
isLuckmailPurchaseDisabled,
|
||
isLuckmailPurchaseExpired,
|
||
isLuckmailPurchaseForProject,
|
||
isLuckmailPurchasePreserved,
|
||
isLuckmailPurchaseReusable,
|
||
normalizeLuckmailBaseUrl,
|
||
normalizeLuckmailEmailType,
|
||
normalizeLuckmailMailCursor,
|
||
normalizeLuckmailProjectName,
|
||
normalizeLuckmailPurchase,
|
||
normalizeLuckmailPurchaseId,
|
||
normalizeLuckmailPurchaseListPage,
|
||
normalizeLuckmailPurchases,
|
||
normalizeLuckmailTag,
|
||
normalizeLuckmailTags,
|
||
normalizeLuckmailTokenCode,
|
||
normalizeLuckmailTokenMail,
|
||
normalizeLuckmailTokenMails,
|
||
normalizeLuckmailUsedPurchases,
|
||
normalizeText,
|
||
normalizeTimestamp,
|
||
pickLuckmailVerificationMail,
|
||
pickReusableLuckmailPurchase,
|
||
};
|
||
});
|