Add LuckMail openai mailbox management

This commit is contained in:
Q3CC
2026-04-14 22:07:16 +08:00
parent be98e05780
commit 84412c7ea4
7 changed files with 3083 additions and 17 deletions
+445
View File
@@ -0,0 +1,445 @@
(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 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,
};
});