Add LuckMail openai mailbox management
This commit is contained in:
@@ -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,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user