Merge pull request #73 from q3cc/codex-luckmail
feat: 接入 LuckMail openai 邮箱购买、复用与管理
This commit is contained in:
+952
-4
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -698,6 +698,164 @@ header {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.luckmail-card {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-manager-card {
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: color-mix(in srgb, var(--bg-base) 84%, transparent);
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-summary {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-search {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-filter {
|
||||||
|
flex: 0 0 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-bulkbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-select-all {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-bulk-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-base);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 10px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item.is-current {
|
||||||
|
border-color: var(--blue);
|
||||||
|
box-shadow: 0 0 0 1px var(--blue-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-check {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-main {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-email-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-email {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-meta,
|
||||||
|
.luckmail-item-details {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-details {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-item-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-tag.used {
|
||||||
|
background: var(--orange-soft);
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-tag.active {
|
||||||
|
background: var(--green-soft);
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-tag.current {
|
||||||
|
background: var(--blue-soft);
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-tag.disabled {
|
||||||
|
background: var(--red-soft);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luckmail-empty {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.hotmail-actions-row .data-label {
|
.hotmail-actions-row .data-label {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,6 +154,7 @@
|
|||||||
<select id="select-mail-provider" class="data-select">
|
<select id="select-mail-provider" class="data-select">
|
||||||
<option value="custom">自定义邮箱</option>
|
<option value="custom">自定义邮箱</option>
|
||||||
<option value="hotmail-api">Hotmail(本地助手)</option>
|
<option value="hotmail-api">Hotmail(本地助手)</option>
|
||||||
|
<option value="luckmail-api">LuckMail(API 购邮)</option>
|
||||||
<option value="163">163 邮箱 (mail.163.com)</option>
|
<option value="163">163 邮箱 (mail.163.com)</option>
|
||||||
<option value="163-vip">163 VIP 邮箱 (vip.163.com)</option>
|
<option value="163-vip">163 VIP 邮箱 (vip.163.com)</option>
|
||||||
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
|
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
|
||||||
@@ -347,6 +348,77 @@
|
|||||||
<div id="hotmail-accounts-list" class="hotmail-accounts-list"></div>
|
<div id="hotmail-accounts-list" class="hotmail-accounts-list"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="luckmail-section" class="data-card luckmail-card" style="display:none;">
|
||||||
|
<div class="section-mini-header">
|
||||||
|
<div class="section-mini-copy">
|
||||||
|
<span class="section-label">LuckMail 配置</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">API Key</span>
|
||||||
|
<input type="password" id="input-luckmail-api-key" class="data-input mono" placeholder="请输入 LuckMail API Key" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">Base URL</span>
|
||||||
|
<input type="text" id="input-luckmail-base-url" class="data-input mono" placeholder="https://mails.luckyous.com" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">邮箱类型</span>
|
||||||
|
<select id="select-luckmail-email-type" class="data-select">
|
||||||
|
<option value="self_built">自建邮箱</option>
|
||||||
|
<option value="ms_imap">微软 IMAP</option>
|
||||||
|
<option value="ms_graph">微软 Graph</option>
|
||||||
|
<option value="google_variant">谷歌变种</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">域名</span>
|
||||||
|
<input type="text" id="input-luckmail-domain" class="data-input mono" placeholder="可选,留空则由 LuckMail 默认处理" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">项目</span>
|
||||||
|
<span class="data-value mono">openai</span>
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-manager-card">
|
||||||
|
<div class="section-mini-header">
|
||||||
|
<div class="section-mini-copy">
|
||||||
|
<span class="section-label">LuckMail 邮箱列表</span>
|
||||||
|
<span class="data-value">仅显示项目为 <span class="mono">openai</span> 的邮箱</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-mini-actions">
|
||||||
|
<button id="btn-luckmail-refresh" class="btn btn-ghost btn-xs" type="button">刷新</button>
|
||||||
|
<button id="btn-luckmail-disable-used" class="btn btn-ghost btn-xs" type="button">禁用已用</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="luckmail-summary" class="luckmail-summary">加载已购邮箱后可在这里管理 openai 项目的 LuckMail 邮箱。</div>
|
||||||
|
<div class="luckmail-toolbar">
|
||||||
|
<input id="input-luckmail-search" class="data-input luckmail-search" type="text" placeholder="搜索邮箱 / 标签 / 项目" />
|
||||||
|
<select id="select-luckmail-filter" class="data-select luckmail-filter">
|
||||||
|
<option value="all">全部</option>
|
||||||
|
<option value="reusable">可复用</option>
|
||||||
|
<option value="used">已用</option>
|
||||||
|
<option value="unused">未用</option>
|
||||||
|
<option value="preserved">保留</option>
|
||||||
|
<option value="disabled">已禁用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-bulkbar">
|
||||||
|
<label class="option-toggle luckmail-select-all" for="checkbox-luckmail-select-all">
|
||||||
|
<input type="checkbox" id="checkbox-luckmail-select-all" />
|
||||||
|
<span id="luckmail-selection-summary">已选 0 个</span>
|
||||||
|
</label>
|
||||||
|
<div class="luckmail-bulk-actions">
|
||||||
|
<button id="btn-luckmail-bulk-used" class="btn btn-outline btn-xs" type="button">标记已用</button>
|
||||||
|
<button id="btn-luckmail-bulk-unused" class="btn btn-outline btn-xs" type="button">标记未用</button>
|
||||||
|
<button id="btn-luckmail-bulk-preserve" class="btn btn-outline btn-xs" type="button">保留</button>
|
||||||
|
<button id="btn-luckmail-bulk-unpreserve" class="btn btn-outline btn-xs" type="button">取消保留</button>
|
||||||
|
<button id="btn-luckmail-bulk-disable" class="btn btn-outline btn-xs" type="button">禁用</button>
|
||||||
|
<button id="btn-luckmail-bulk-enable" class="btn btn-outline btn-xs" type="button">启用</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="luckmail-list" class="luckmail-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="icloud-section" class="data-card hotmail-card" style="display:none;">
|
<div id="icloud-section" class="data-card hotmail-card" style="display:none;">
|
||||||
<div class="section-mini-header">
|
<div class="section-mini-header">
|
||||||
<div class="section-mini-copy">
|
<div class="section-mini-copy">
|
||||||
@@ -523,6 +595,7 @@
|
|||||||
<div id="toast-container"></div>
|
<div id="toast-container"></div>
|
||||||
<input id="input-import-settings-file" type="file" accept=".json,application/json" hidden />
|
<input id="input-import-settings-file" type="file" accept=".json,application/json" hidden />
|
||||||
<script src="../hotmail-utils.js"></script>
|
<script src="../hotmail-utils.js"></script>
|
||||||
|
<script src="../luckmail-utils.js"></script>
|
||||||
<script src="update-service.js"></script>
|
<script src="update-service.js"></script>
|
||||||
<script src="sidepanel.js"></script>
|
<script src="sidepanel.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+626
-13
@@ -81,6 +81,7 @@ const selectTempEmailDomain = document.getElementById('select-temp-email-domain'
|
|||||||
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
|
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
|
||||||
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
|
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
|
||||||
const hotmailSection = document.getElementById('hotmail-section');
|
const hotmailSection = document.getElementById('hotmail-section');
|
||||||
|
const luckmailSection = document.getElementById('luckmail-section');
|
||||||
const icloudSection = document.getElementById('icloud-section');
|
const icloudSection = document.getElementById('icloud-section');
|
||||||
const icloudSummary = document.getElementById('icloud-summary');
|
const icloudSummary = document.getElementById('icloud-summary');
|
||||||
const icloudList = document.getElementById('icloud-list');
|
const icloudList = document.getElementById('icloud-list');
|
||||||
@@ -120,6 +121,24 @@ const btnDeleteAllHotmailAccounts = document.getElementById('btn-delete-all-hotm
|
|||||||
const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list');
|
const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list');
|
||||||
const hotmailListShell = document.getElementById('hotmail-list-shell');
|
const hotmailListShell = document.getElementById('hotmail-list-shell');
|
||||||
const hotmailAccountsList = document.getElementById('hotmail-accounts-list');
|
const hotmailAccountsList = document.getElementById('hotmail-accounts-list');
|
||||||
|
const inputLuckmailApiKey = document.getElementById('input-luckmail-api-key');
|
||||||
|
const inputLuckmailBaseUrl = document.getElementById('input-luckmail-base-url');
|
||||||
|
const selectLuckmailEmailType = document.getElementById('select-luckmail-email-type');
|
||||||
|
const inputLuckmailDomain = document.getElementById('input-luckmail-domain');
|
||||||
|
const btnLuckmailRefresh = document.getElementById('btn-luckmail-refresh');
|
||||||
|
const btnLuckmailDisableUsed = document.getElementById('btn-luckmail-disable-used');
|
||||||
|
const luckmailSummary = document.getElementById('luckmail-summary');
|
||||||
|
const inputLuckmailSearch = document.getElementById('input-luckmail-search');
|
||||||
|
const selectLuckmailFilter = document.getElementById('select-luckmail-filter');
|
||||||
|
const checkboxLuckmailSelectAll = document.getElementById('checkbox-luckmail-select-all');
|
||||||
|
const luckmailSelectionSummary = document.getElementById('luckmail-selection-summary');
|
||||||
|
const btnLuckmailBulkUsed = document.getElementById('btn-luckmail-bulk-used');
|
||||||
|
const btnLuckmailBulkUnused = document.getElementById('btn-luckmail-bulk-unused');
|
||||||
|
const btnLuckmailBulkPreserve = document.getElementById('btn-luckmail-bulk-preserve');
|
||||||
|
const btnLuckmailBulkUnpreserve = document.getElementById('btn-luckmail-bulk-unpreserve');
|
||||||
|
const btnLuckmailBulkDisable = document.getElementById('btn-luckmail-bulk-disable');
|
||||||
|
const btnLuckmailBulkEnable = document.getElementById('btn-luckmail-bulk-enable');
|
||||||
|
const luckmailList = document.getElementById('luckmail-list');
|
||||||
const rowEmailPrefix = document.getElementById('row-email-prefix');
|
const rowEmailPrefix = document.getElementById('row-email-prefix');
|
||||||
const labelEmailPrefix = document.getElementById('label-email-prefix');
|
const labelEmailPrefix = document.getElementById('label-email-prefix');
|
||||||
const inputEmailPrefix = document.getElementById('input-email-prefix');
|
const inputEmailPrefix = document.getElementById('input-email-prefix');
|
||||||
@@ -180,6 +199,9 @@ const AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS = 15;
|
|||||||
const AUTO_RUN_FALLBACK_RISK_RECOMMENDED_THREAD_INTERVAL_MINUTES = 5;
|
const AUTO_RUN_FALLBACK_RISK_RECOMMENDED_THREAD_INTERVAL_MINUTES = 5;
|
||||||
const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
|
const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
|
||||||
const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
|
const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
|
||||||
|
const LUCKMAIL_PROVIDER = 'luckmail-api';
|
||||||
|
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
|
||||||
|
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
|
||||||
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
|
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
|
||||||
|
|
||||||
let latestState = null;
|
let latestState = null;
|
||||||
@@ -223,8 +245,20 @@ const upsertHotmailAccountInList = window.HotmailUtils?.upsertHotmailAccountInLi
|
|||||||
const filterHotmailAccountsByUsage = window.HotmailUtils?.filterHotmailAccountsByUsage;
|
const filterHotmailAccountsByUsage = window.HotmailUtils?.filterHotmailAccountsByUsage;
|
||||||
const getHotmailBulkActionLabel = window.HotmailUtils?.getHotmailBulkActionLabel;
|
const getHotmailBulkActionLabel = window.HotmailUtils?.getHotmailBulkActionLabel;
|
||||||
const getHotmailListToggleLabel = window.HotmailUtils?.getHotmailListToggleLabel;
|
const getHotmailListToggleLabel = window.HotmailUtils?.getHotmailListToggleLabel;
|
||||||
|
const normalizeLuckmailTimestampValue = window.LuckMailUtils?.normalizeTimestamp
|
||||||
|
|| ((value) => {
|
||||||
|
const timestamp = Date.parse(String(value || ''));
|
||||||
|
return Number.isFinite(timestamp) ? timestamp : 0;
|
||||||
|
});
|
||||||
const HOTMAIL_LIST_EXPANDED_STORAGE_KEY = 'multipage-hotmail-list-expanded';
|
const HOTMAIL_LIST_EXPANDED_STORAGE_KEY = 'multipage-hotmail-list-expanded';
|
||||||
const sidepanelUpdateService = window.SidepanelUpdateService;
|
const sidepanelUpdateService = window.SidepanelUpdateService;
|
||||||
|
const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = window.LuckMailUtils?.DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME || '保留';
|
||||||
|
|
||||||
|
let lastRenderedLuckmailPurchases = [];
|
||||||
|
let luckmailSelectedPurchaseIds = new Set();
|
||||||
|
let luckmailSearchTerm = '';
|
||||||
|
let luckmailFilterMode = 'all';
|
||||||
|
let luckmailRefreshQueued = false;
|
||||||
|
|
||||||
btnAutoCancelSchedule?.remove();
|
btnAutoCancelSchedule?.remove();
|
||||||
const MAIL_PROVIDER_LOGIN_CONFIGS = {
|
const MAIL_PROVIDER_LOGIN_CONFIGS = {
|
||||||
@@ -1072,6 +1106,10 @@ function collectSettingsPayload() {
|
|||||||
hotmailServiceMode: getSelectedHotmailServiceMode(),
|
hotmailServiceMode: getSelectedHotmailServiceMode(),
|
||||||
hotmailRemoteBaseUrl: inputHotmailRemoteBaseUrl.value.trim(),
|
hotmailRemoteBaseUrl: inputHotmailRemoteBaseUrl.value.trim(),
|
||||||
hotmailLocalBaseUrl: inputHotmailLocalBaseUrl.value.trim(),
|
hotmailLocalBaseUrl: inputHotmailLocalBaseUrl.value.trim(),
|
||||||
|
luckmailApiKey: inputLuckmailApiKey.value,
|
||||||
|
luckmailBaseUrl: normalizeLuckmailBaseUrl(inputLuckmailBaseUrl.value),
|
||||||
|
luckmailEmailType: normalizeLuckmailEmailType(selectLuckmailEmailType.value),
|
||||||
|
luckmailDomain: inputLuckmailDomain.value.trim(),
|
||||||
cloudflareDomain: selectedCloudflareDomain,
|
cloudflareDomain: selectedCloudflareDomain,
|
||||||
cloudflareDomains: domains,
|
cloudflareDomains: domains,
|
||||||
cloudflareTempEmailBaseUrl: normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl.value),
|
cloudflareTempEmailBaseUrl: normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl.value),
|
||||||
@@ -1370,7 +1408,7 @@ function applySettingsState(state) {
|
|||||||
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
||||||
inputSub2ApiGroup.value = state?.sub2apiGroupName || '';
|
inputSub2ApiGroup.value = state?.sub2apiGroupName || '';
|
||||||
const restoredMailProvider = isCustomMailProvider(state?.mailProvider)
|
const restoredMailProvider = isCustomMailProvider(state?.mailProvider)
|
||||||
|| ['hotmail-api', '163', '163-vip', 'qq', 'inbucket', '2925', 'cloudflare-temp-email'].includes(String(state?.mailProvider || '').trim())
|
|| ['hotmail-api', 'luckmail-api', '163', '163-vip', 'qq', 'inbucket', '2925', 'cloudflare-temp-email'].includes(String(state?.mailProvider || '').trim())
|
||||||
? String(state?.mailProvider || '163').trim()
|
? String(state?.mailProvider || '163').trim()
|
||||||
: (String(state?.emailGenerator || '').trim().toLowerCase() === 'custom'
|
: (String(state?.emailGenerator || '').trim().toLowerCase() === 'custom'
|
||||||
|| String(state?.emailGenerator || '').trim().toLowerCase() === 'manual'
|
|| String(state?.emailGenerator || '').trim().toLowerCase() === 'manual'
|
||||||
@@ -1404,6 +1442,10 @@ function applySettingsState(state) {
|
|||||||
setHotmailServiceMode(state?.hotmailServiceMode);
|
setHotmailServiceMode(state?.hotmailServiceMode);
|
||||||
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
|
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
|
||||||
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
|
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
|
||||||
|
inputLuckmailApiKey.value = state?.luckmailApiKey || '';
|
||||||
|
inputLuckmailBaseUrl.value = normalizeLuckmailBaseUrl(state?.luckmailBaseUrl);
|
||||||
|
selectLuckmailEmailType.value = normalizeLuckmailEmailType(state?.luckmailEmailType);
|
||||||
|
inputLuckmailDomain.value = state?.luckmailDomain || '';
|
||||||
inputTempEmailBaseUrl.value = state?.cloudflareTempEmailBaseUrl || '';
|
inputTempEmailBaseUrl.value = state?.cloudflareTempEmailBaseUrl || '';
|
||||||
inputTempEmailAdminAuth.value = state?.cloudflareTempEmailAdminAuth || '';
|
inputTempEmailAdminAuth.value = state?.cloudflareTempEmailAdminAuth || '';
|
||||||
inputTempEmailCustomAuth.value = state?.cloudflareTempEmailCustomAuth || '';
|
inputTempEmailCustomAuth.value = state?.cloudflareTempEmailCustomAuth || '';
|
||||||
@@ -1426,6 +1468,9 @@ function applySettingsState(state) {
|
|||||||
updateFallbackThreadIntervalInputState();
|
updateFallbackThreadIntervalInputState();
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
updateMailProviderUI();
|
updateMailProviderUI();
|
||||||
|
if (isLuckmailProvider(state?.mailProvider)) {
|
||||||
|
queueLuckmailPurchaseRefresh();
|
||||||
|
}
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1678,6 +1723,37 @@ function isCustomMailProvider(provider = selectMailProvider.value) {
|
|||||||
return String(provider || '').trim().toLowerCase() === 'custom';
|
return String(provider || '').trim().toLowerCase() === 'custom';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLuckmailProvider(provider = selectMailProvider.value) {
|
||||||
|
return String(provider || '').trim().toLowerCase() === LUCKMAIL_PROVIDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLuckmailBaseUrl(value = '') {
|
||||||
|
const trimmed = String(value || '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return DEFAULT_LUCKMAIL_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(trimmed);
|
||||||
|
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(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(normalized)
|
||||||
|
? normalized
|
||||||
|
: DEFAULT_LUCKMAIL_EMAIL_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedEmailGenerator() {
|
function getSelectedEmailGenerator() {
|
||||||
const generator = String(selectEmailGenerator.value || '').trim().toLowerCase();
|
const generator = String(selectEmailGenerator.value || '').trim().toLowerCase();
|
||||||
if (generator === 'custom' || generator === 'manual') {
|
if (generator === 'custom' || generator === 'manual') {
|
||||||
@@ -1762,6 +1838,417 @@ function getCurrentHotmailEmail(state = latestState) {
|
|||||||
return String(getCurrentHotmailAccount(state)?.email || '').trim();
|
return String(getCurrentHotmailAccount(state)?.email || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCurrentLuckmailPurchase(state = latestState) {
|
||||||
|
return state?.currentLuckmailPurchase || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLuckmailEmail(state = latestState) {
|
||||||
|
return String(getCurrentLuckmailPurchase(state)?.email_address || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLuckmailUsedPurchases(state = latestState) {
|
||||||
|
const rawValue = state?.luckmailUsedPurchases;
|
||||||
|
if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(rawValue).reduce((result, [key, value]) => {
|
||||||
|
const numeric = Number(key);
|
||||||
|
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result[String(Math.floor(numeric))] = Boolean(value);
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLuckmailProjectName(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLuckmailPreserveTagName(state = latestState) {
|
||||||
|
return String(state?.luckmailPreserveTagName || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLuckmailDateTime(value) {
|
||||||
|
const timestamp = normalizeLuckmailTimestampValue(value);
|
||||||
|
if (!timestamp) {
|
||||||
|
return String(value || '').trim() || '未知';
|
||||||
|
}
|
||||||
|
return new Date(timestamp).toLocaleString('zh-CN', {
|
||||||
|
hour12: false,
|
||||||
|
timeZone: DISPLAY_TIMEZONE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLuckmailSearchText(value) {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredLuckmailPurchases(purchases = lastRenderedLuckmailPurchases) {
|
||||||
|
const searchTerm = normalizeLuckmailSearchText(luckmailSearchTerm);
|
||||||
|
return (Array.isArray(purchases) ? purchases : []).filter((purchase) => {
|
||||||
|
const matchesFilter = (() => {
|
||||||
|
switch (luckmailFilterMode) {
|
||||||
|
case 'reusable': return Boolean(purchase.reusable);
|
||||||
|
case 'used': return Boolean(purchase.used);
|
||||||
|
case 'unused': return !purchase.used;
|
||||||
|
case 'preserved': return Boolean(purchase.preserved);
|
||||||
|
case 'disabled': return Boolean(purchase.disabled);
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!matchesFilter) return false;
|
||||||
|
if (!searchTerm) return true;
|
||||||
|
|
||||||
|
const haystack = [
|
||||||
|
purchase.email_address,
|
||||||
|
purchase.project_name,
|
||||||
|
purchase.tag_name,
|
||||||
|
purchase.used ? '已用 used' : '未用 unused',
|
||||||
|
purchase.preserved ? '保留 preserved' : '',
|
||||||
|
purchase.disabled ? '已禁用 disabled' : '',
|
||||||
|
purchase.reusable ? '可复用 reusable' : '',
|
||||||
|
].join(' ').toLowerCase();
|
||||||
|
|
||||||
|
return haystack.includes(searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneLuckmailSelection(purchases = lastRenderedLuckmailPurchases) {
|
||||||
|
const existingIds = new Set((Array.isArray(purchases) ? purchases : []).map((purchase) => String(purchase.id)));
|
||||||
|
luckmailSelectedPurchaseIds = new Set([...luckmailSelectedPurchaseIds].filter((id) => existingIds.has(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLuckmailBulkUI(visiblePurchases = getFilteredLuckmailPurchases()) {
|
||||||
|
if (!checkboxLuckmailSelectAll || !luckmailSelectionSummary) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleIds = visiblePurchases.map((purchase) => String(purchase.id));
|
||||||
|
const selectedVisibleCount = visibleIds.filter((id) => luckmailSelectedPurchaseIds.has(id)).length;
|
||||||
|
const hasVisible = visibleIds.length > 0;
|
||||||
|
const hasSelection = luckmailSelectedPurchaseIds.size > 0;
|
||||||
|
|
||||||
|
checkboxLuckmailSelectAll.checked = hasVisible && selectedVisibleCount === visibleIds.length;
|
||||||
|
checkboxLuckmailSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleIds.length;
|
||||||
|
checkboxLuckmailSelectAll.disabled = !hasVisible;
|
||||||
|
luckmailSelectionSummary.textContent = `已选 ${luckmailSelectedPurchaseIds.size} 个(当前显示 ${visibleIds.length} 个)`;
|
||||||
|
|
||||||
|
if (btnLuckmailBulkUsed) btnLuckmailBulkUsed.disabled = !hasSelection;
|
||||||
|
if (btnLuckmailBulkUnused) btnLuckmailBulkUnused.disabled = !hasSelection;
|
||||||
|
if (btnLuckmailBulkPreserve) btnLuckmailBulkPreserve.disabled = !hasSelection;
|
||||||
|
if (btnLuckmailBulkUnpreserve) btnLuckmailBulkUnpreserve.disabled = !hasSelection;
|
||||||
|
if (btnLuckmailBulkDisable) btnLuckmailBulkDisable.disabled = !hasSelection;
|
||||||
|
if (btnLuckmailBulkEnable) btnLuckmailBulkEnable.disabled = !hasSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLuckmailLoadingState(loading, summary = '') {
|
||||||
|
if (btnLuckmailRefresh) btnLuckmailRefresh.disabled = loading;
|
||||||
|
if (btnLuckmailDisableUsed) btnLuckmailDisableUsed.disabled = loading;
|
||||||
|
if (inputLuckmailSearch) inputLuckmailSearch.disabled = loading;
|
||||||
|
if (selectLuckmailFilter) selectLuckmailFilter.disabled = loading;
|
||||||
|
if (checkboxLuckmailSelectAll) checkboxLuckmailSelectAll.disabled = loading || getFilteredLuckmailPurchases().length === 0;
|
||||||
|
if (btnLuckmailBulkUsed) btnLuckmailBulkUsed.disabled = loading || luckmailSelectedPurchaseIds.size === 0;
|
||||||
|
if (btnLuckmailBulkUnused) btnLuckmailBulkUnused.disabled = loading || luckmailSelectedPurchaseIds.size === 0;
|
||||||
|
if (btnLuckmailBulkPreserve) btnLuckmailBulkPreserve.disabled = loading || luckmailSelectedPurchaseIds.size === 0;
|
||||||
|
if (btnLuckmailBulkUnpreserve) btnLuckmailBulkUnpreserve.disabled = loading || luckmailSelectedPurchaseIds.size === 0;
|
||||||
|
if (btnLuckmailBulkDisable) btnLuckmailBulkDisable.disabled = loading || luckmailSelectedPurchaseIds.size === 0;
|
||||||
|
if (btnLuckmailBulkEnable) btnLuckmailBulkEnable.disabled = loading || luckmailSelectedPurchaseIds.size === 0;
|
||||||
|
if (summary && luckmailSummary) {
|
||||||
|
luckmailSummary.textContent = summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLuckmailPurchases(purchases = []) {
|
||||||
|
if (!luckmailList || !luckmailSummary) return;
|
||||||
|
|
||||||
|
lastRenderedLuckmailPurchases = Array.isArray(purchases) ? purchases : [];
|
||||||
|
pruneLuckmailSelection(lastRenderedLuckmailPurchases);
|
||||||
|
luckmailList.innerHTML = '';
|
||||||
|
|
||||||
|
if (!lastRenderedLuckmailPurchases.length) {
|
||||||
|
luckmailSelectedPurchaseIds.clear();
|
||||||
|
luckmailList.innerHTML = '<div class="luckmail-empty">未找到 openai 项目的 LuckMail 邮箱。</div>';
|
||||||
|
luckmailSummary.textContent = '加载已购邮箱后可在这里管理 openai 项目的 LuckMail 邮箱。';
|
||||||
|
if (btnLuckmailDisableUsed) btnLuckmailDisableUsed.disabled = true;
|
||||||
|
updateLuckmailBulkUI([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usedCount = lastRenderedLuckmailPurchases.filter((purchase) => purchase.used).length;
|
||||||
|
const reusableCount = lastRenderedLuckmailPurchases.filter((purchase) => purchase.reusable).length;
|
||||||
|
const disableUsedCount = lastRenderedLuckmailPurchases.filter((purchase) => purchase.used && !purchase.preserved && !purchase.disabled).length;
|
||||||
|
luckmailSummary.textContent = `已加载 ${lastRenderedLuckmailPurchases.length} 个 openai 邮箱,其中 ${reusableCount} 个可复用,${usedCount} 个已本地标记为已用。`;
|
||||||
|
if (btnLuckmailDisableUsed) {
|
||||||
|
btnLuckmailDisableUsed.textContent = `禁用已用${disableUsedCount > 0 ? `(${disableUsedCount})` : ''}`;
|
||||||
|
btnLuckmailDisableUsed.disabled = disableUsedCount === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visiblePurchases = getFilteredLuckmailPurchases(lastRenderedLuckmailPurchases);
|
||||||
|
if (!visiblePurchases.length) {
|
||||||
|
luckmailList.innerHTML = '<div class="luckmail-empty">没有匹配当前筛选条件的 LuckMail 邮箱。</div>';
|
||||||
|
updateLuckmailBulkUI([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const purchase of visiblePurchases) {
|
||||||
|
const purchaseId = String(purchase.id);
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = `luckmail-item${purchase.current ? ' is-current' : ''}`;
|
||||||
|
item.innerHTML = `
|
||||||
|
<input class="luckmail-item-check" type="checkbox" data-action="select" ${luckmailSelectedPurchaseIds.has(purchaseId) ? 'checked' : ''} />
|
||||||
|
<div class="luckmail-item-main">
|
||||||
|
<div class="luckmail-item-email-row">
|
||||||
|
<div class="luckmail-item-email">${escapeHtml(purchase.email_address || '(未知邮箱)')}</div>
|
||||||
|
<button
|
||||||
|
class="hotmail-copy-btn"
|
||||||
|
type="button"
|
||||||
|
data-action="copy-email"
|
||||||
|
title="复制邮箱"
|
||||||
|
aria-label="复制邮箱 ${escapeHtml(purchase.email_address || '')}"
|
||||||
|
>${COPY_ICON}</button>
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-item-meta">
|
||||||
|
<span class="luckmail-tag">${escapeHtml(normalizeLuckmailProjectName(purchase.project_name) || 'openai')}</span>
|
||||||
|
${purchase.reusable ? '<span class="luckmail-tag active">可复用</span>' : ''}
|
||||||
|
${purchase.current ? '<span class="luckmail-tag current">当前</span>' : ''}
|
||||||
|
${purchase.used ? '<span class="luckmail-tag used">已用</span>' : ''}
|
||||||
|
${purchase.preserved ? '<span class="luckmail-tag">保留</span>' : ''}
|
||||||
|
${purchase.disabled ? '<span class="luckmail-tag disabled">已禁用</span>' : ''}
|
||||||
|
${purchase.tag_name && normalizeLuckmailSearchText(purchase.tag_name) !== normalizeLuckmailSearchText(getLuckmailPreserveTagName())
|
||||||
|
? `<span class="luckmail-tag">${escapeHtml(purchase.tag_name)}</span>`
|
||||||
|
: ''}
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-item-details">
|
||||||
|
<span>ID:${escapeHtml(String(purchase.id || ''))}</span>
|
||||||
|
<span>保修至:${escapeHtml(formatLuckmailDateTime(purchase.warranty_until))}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-item-actions">
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="use">使用此邮箱</button>
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-used">${escapeHtml(purchase.used ? '标记未用' : '标记已用')}</button>
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-preserved">${escapeHtml(purchase.preserved ? '取消保留' : '保留')}</button>
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-disabled">${escapeHtml(purchase.disabled ? '启用' : '禁用')}</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
item.querySelector('[data-action="select"]').addEventListener('change', (event) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
luckmailSelectedPurchaseIds.add(purchaseId);
|
||||||
|
} else {
|
||||||
|
luckmailSelectedPurchaseIds.delete(purchaseId);
|
||||||
|
}
|
||||||
|
updateLuckmailBulkUI(visiblePurchases);
|
||||||
|
});
|
||||||
|
item.querySelector('[data-action="copy-email"]').addEventListener('click', async () => {
|
||||||
|
await copyTextToClipboard(purchase.email_address || '');
|
||||||
|
showToast('邮箱已复制', 'success', 1600);
|
||||||
|
});
|
||||||
|
item.querySelector('[data-action="use"]').addEventListener('click', async () => {
|
||||||
|
await selectSingleLuckmailPurchase(purchase);
|
||||||
|
});
|
||||||
|
item.querySelector('[data-action="toggle-used"]').addEventListener('click', async () => {
|
||||||
|
await setSingleLuckmailPurchaseUsedState(purchase, !purchase.used);
|
||||||
|
});
|
||||||
|
item.querySelector('[data-action="toggle-preserved"]').addEventListener('click', async () => {
|
||||||
|
await setSingleLuckmailPurchasePreservedState(purchase, !purchase.preserved);
|
||||||
|
});
|
||||||
|
item.querySelector('[data-action="toggle-disabled"]').addEventListener('click', async () => {
|
||||||
|
await setSingleLuckmailPurchaseDisabledState(purchase, !purchase.disabled);
|
||||||
|
});
|
||||||
|
luckmailList.appendChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLuckmailBulkUI(visiblePurchases);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshLuckmailPurchases(options = {}) {
|
||||||
|
const { silent = false } = options;
|
||||||
|
if (!luckmailSection || luckmailSection.style.display === 'none') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silent) setLuckmailLoadingState(true, '正在加载 LuckMail openai 邮箱...');
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'LIST_LUCKMAIL_PURCHASES',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
renderLuckmailPurchases(response?.purchases || []);
|
||||||
|
} catch (err) {
|
||||||
|
luckmailSelectedPurchaseIds.clear();
|
||||||
|
if (luckmailList) {
|
||||||
|
luckmailList.innerHTML = '<div class="luckmail-empty">无法加载 LuckMail 邮箱列表。</div>';
|
||||||
|
}
|
||||||
|
if (luckmailSummary) {
|
||||||
|
luckmailSummary.textContent = err.message;
|
||||||
|
}
|
||||||
|
updateLuckmailBulkUI([]);
|
||||||
|
if (!silent) {
|
||||||
|
showToast(`LuckMail 邮箱列表加载失败:${err.message}`, 'error');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueLuckmailPurchaseRefresh() {
|
||||||
|
if (luckmailRefreshQueued) return;
|
||||||
|
luckmailRefreshQueued = true;
|
||||||
|
setTimeout(async () => {
|
||||||
|
luckmailRefreshQueued = false;
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectSingleLuckmailPurchase(purchase) {
|
||||||
|
setLuckmailLoadingState(true, `正在切换到 ${purchase.email_address} ...`);
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'SELECT_LUCKMAIL_PURCHASE',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { purchaseId: purchase.id },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
inputEmail.value = response?.purchase?.email_address || purchase.email_address || '';
|
||||||
|
showToast(`已切换当前 LuckMail 邮箱为 ${purchase.email_address}`, 'success', 2200);
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
if (luckmailSummary) luckmailSummary.textContent = err.message;
|
||||||
|
showToast(`切换 LuckMail 邮箱失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSingleLuckmailPurchaseUsedState(purchase, used) {
|
||||||
|
setLuckmailLoadingState(true, `正在更新 ${purchase.email_address} 的已用状态...`);
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'SET_LUCKMAIL_PURCHASE_USED_STATE',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { purchaseId: purchase.id, used },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
showToast(`${purchase.email_address} 已${used ? '标记为已用' : '恢复为未用'}`, 'success', 2200);
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
if (luckmailSummary) luckmailSummary.textContent = err.message;
|
||||||
|
showToast(`更新 LuckMail 已用状态失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSingleLuckmailPurchasePreservedState(purchase, preserved) {
|
||||||
|
setLuckmailLoadingState(true, `正在更新 ${purchase.email_address} 的保留状态...`);
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'SET_LUCKMAIL_PURCHASE_PRESERVED_STATE',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { purchaseId: purchase.id, preserved },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
showToast(`${purchase.email_address} 已${preserved ? '设为保留' : '取消保留'}`, 'success', 2200);
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
if (luckmailSummary) luckmailSummary.textContent = err.message;
|
||||||
|
showToast(`更新 LuckMail 保留状态失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSingleLuckmailPurchaseDisabledState(purchase, disabled) {
|
||||||
|
setLuckmailLoadingState(true, `正在${disabled ? '禁用' : '启用'} ${purchase.email_address} ...`);
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'SET_LUCKMAIL_PURCHASE_DISABLED_STATE',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { purchaseId: purchase.id, disabled },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
showToast(`${purchase.email_address} 已${disabled ? '禁用' : '启用'}`, 'success', 2200);
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
if (luckmailSummary) luckmailSummary.textContent = err.message;
|
||||||
|
showToast(`更新 LuckMail 禁用状态失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runBulkLuckmailAction(action) {
|
||||||
|
const selectedIds = lastRenderedLuckmailPurchases
|
||||||
|
.filter((purchase) => luckmailSelectedPurchaseIds.has(String(purchase.id)))
|
||||||
|
.map((purchase) => purchase.id);
|
||||||
|
if (!selectedIds.length) {
|
||||||
|
updateLuckmailBulkUI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionLabelMap = {
|
||||||
|
used: '标记已用',
|
||||||
|
unused: '标记未用',
|
||||||
|
preserve: '保留',
|
||||||
|
unpreserve: '取消保留',
|
||||||
|
disable: '禁用',
|
||||||
|
enable: '启用',
|
||||||
|
};
|
||||||
|
|
||||||
|
setLuckmailLoadingState(true, `正在批量${actionLabelMap[action] || '处理'} LuckMail 邮箱...`);
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'BATCH_UPDATE_LUCKMAIL_PURCHASES',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { action, ids: selectedIds },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
showToast(`已批量${actionLabelMap[action] || '处理'} ${selectedIds.length} 个 LuckMail 邮箱`, 'success', 2400);
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
if (luckmailSummary) luckmailSummary.textContent = err.message;
|
||||||
|
showToast(`批量处理 LuckMail 邮箱失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
updateLuckmailBulkUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableUsedLuckmailPurchases() {
|
||||||
|
const confirmed = await openConfirmModal({
|
||||||
|
title: '禁用已用 LuckMail 邮箱',
|
||||||
|
message: '确认禁用所有本地已用且未保留的 openai LuckMail 邮箱吗?',
|
||||||
|
confirmLabel: '确认禁用',
|
||||||
|
confirmVariant: 'btn-danger',
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLuckmailLoadingState(true, '正在禁用已用 LuckMail 邮箱...');
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'DISABLE_USED_LUCKMAIL_PURCHASES',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
const disabledCount = Array.isArray(response?.disabledIds) ? response.disabledIds.length : 0;
|
||||||
|
showToast(`已禁用 ${disabledCount} 个 LuckMail 邮箱`, disabledCount > 0 ? 'success' : 'info', 2400);
|
||||||
|
await refreshLuckmailPurchases({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
if (luckmailSummary) luckmailSummary.textContent = err.message;
|
||||||
|
showToast(`禁用已用 LuckMail 邮箱失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLuckmailLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getMailProviderLoginConfig(provider = selectMailProvider.value) {
|
function getMailProviderLoginConfig(provider = selectMailProvider.value) {
|
||||||
return MAIL_PROVIDER_LOGIN_CONFIGS[String(provider || '').trim()] || null;
|
return MAIL_PROVIDER_LOGIN_CONFIGS[String(provider || '').trim()] || null;
|
||||||
}
|
}
|
||||||
@@ -1783,6 +2270,17 @@ function isCurrentEmailManagedByHotmail(state = latestState) {
|
|||||||
return inputEmailValue === hotmailEmail || stateEmailValue === hotmailEmail;
|
return inputEmailValue === hotmailEmail || stateEmailValue === hotmailEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCurrentEmailManagedByLuckmail(state = latestState) {
|
||||||
|
const luckmailEmail = getCurrentLuckmailEmail(state);
|
||||||
|
if (!luckmailEmail) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputEmailValue = String(inputEmail.value || '').trim();
|
||||||
|
const stateEmailValue = String(state?.email || '').trim();
|
||||||
|
return inputEmailValue === luckmailEmail || stateEmailValue === luckmailEmail;
|
||||||
|
}
|
||||||
|
|
||||||
function isCurrentEmailManagedByGeneratedAlias(
|
function isCurrentEmailManagedByGeneratedAlias(
|
||||||
provider = latestState?.mailProvider,
|
provider = latestState?.mailProvider,
|
||||||
state = latestState,
|
state = latestState,
|
||||||
@@ -2028,8 +2526,9 @@ function updateMailProviderUI() {
|
|||||||
const useGeneratedAlias = use2925 && mail2925Mode === MAIL_2925_MODE_PROVIDE;
|
const useGeneratedAlias = use2925 && mail2925Mode === MAIL_2925_MODE_PROVIDE;
|
||||||
const useInbucket = selectMailProvider.value === 'inbucket';
|
const useInbucket = selectMailProvider.value === 'inbucket';
|
||||||
const useHotmail = selectMailProvider.value === 'hotmail-api';
|
const useHotmail = selectMailProvider.value === 'hotmail-api';
|
||||||
|
const useLuckmail = isLuckmailProvider();
|
||||||
const useCustomEmail = isCustomMailProvider();
|
const useCustomEmail = isCustomMailProvider();
|
||||||
const useEmailGenerator = !useHotmail && !useGeneratedAlias && !useCustomEmail;
|
const useEmailGenerator = !useHotmail && !useLuckmail && !useGeneratedAlias && !useCustomEmail;
|
||||||
const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
|
const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
|
||||||
updateMailLoginButtonState();
|
updateMailLoginButtonState();
|
||||||
if (rowMail2925Mode) {
|
if (rowMail2925Mode) {
|
||||||
@@ -2080,9 +2579,12 @@ function updateMailProviderUI() {
|
|||||||
if (hotmailSection) {
|
if (hotmailSection) {
|
||||||
hotmailSection.style.display = useHotmail ? '' : 'none';
|
hotmailSection.style.display = useHotmail ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
if (luckmailSection) {
|
||||||
|
luckmailSection.style.display = useLuckmail ? '' : 'none';
|
||||||
|
}
|
||||||
labelEmailPrefix.textContent = '邮箱前缀';
|
labelEmailPrefix.textContent = '邮箱前缀';
|
||||||
inputEmailPrefix.placeholder = '例如 abc';
|
inputEmailPrefix.placeholder = '例如 abc';
|
||||||
selectEmailGenerator.disabled = useHotmail || useGeneratedAlias || useCustomEmail;
|
selectEmailGenerator.disabled = useHotmail || useLuckmail || useGeneratedAlias || useCustomEmail;
|
||||||
if (rowHotmailServiceMode) {
|
if (rowHotmailServiceMode) {
|
||||||
rowHotmailServiceMode.style.display = useHotmail ? '' : 'none';
|
rowHotmailServiceMode.style.display = useHotmail ? '' : 'none';
|
||||||
}
|
}
|
||||||
@@ -2092,27 +2594,36 @@ function updateMailProviderUI() {
|
|||||||
if (rowHotmailLocalBaseUrl) {
|
if (rowHotmailLocalBaseUrl) {
|
||||||
rowHotmailLocalBaseUrl.style.display = useHotmail && hotmailServiceMode === HOTMAIL_SERVICE_MODE_LOCAL ? '' : 'none';
|
rowHotmailLocalBaseUrl.style.display = useHotmail && hotmailServiceMode === HOTMAIL_SERVICE_MODE_LOCAL ? '' : 'none';
|
||||||
}
|
}
|
||||||
btnFetchEmail.hidden = useHotmail || useCustomEmail;
|
btnFetchEmail.hidden = useHotmail || useLuckmail || useCustomEmail;
|
||||||
inputEmail.readOnly = useHotmail || useGeneratedAlias;
|
inputEmail.readOnly = useHotmail || useLuckmail || useGeneratedAlias;
|
||||||
const uiCopy = useCustomEmail ? getCustomMailProviderUiCopy() : getEmailGeneratorUiCopy();
|
const uiCopy = useCustomEmail ? getCustomMailProviderUiCopy() : getEmailGeneratorUiCopy();
|
||||||
inputEmail.placeholder = useHotmail
|
inputEmail.placeholder = useHotmail
|
||||||
? '由 Hotmail 账号池自动分配'
|
? '由 Hotmail 账号池自动分配'
|
||||||
: (useGeneratedAlias ? '步骤 3 自动生成 2925 邮箱并回填' : uiCopy.placeholder);
|
: (useLuckmail
|
||||||
btnFetchEmail.disabled = useGeneratedAlias || useCustomEmail || isAutoRunLockedPhase();
|
? '步骤 3 自动购买 LuckMail 邮箱并回填'
|
||||||
|
: (useGeneratedAlias ? '步骤 3 自动生成 2925 邮箱并回填' : uiCopy.placeholder));
|
||||||
|
btnFetchEmail.disabled = useGeneratedAlias || useLuckmail || useCustomEmail || isAutoRunLockedPhase();
|
||||||
if (!btnFetchEmail.disabled) {
|
if (!btnFetchEmail.disabled) {
|
||||||
btnFetchEmail.textContent = uiCopy.buttonLabel;
|
btnFetchEmail.textContent = uiCopy.buttonLabel;
|
||||||
}
|
}
|
||||||
if (autoHintText) {
|
if (autoHintText) {
|
||||||
autoHintText.textContent = useHotmail
|
autoHintText.textContent = useHotmail
|
||||||
? '请先校验并选择一个 Hotmail 账号'
|
? '请先校验并选择一个 Hotmail 账号'
|
||||||
|
: (useLuckmail
|
||||||
|
? '步骤 3 会自动购买 LuckMail 邮箱并用于收码'
|
||||||
: (useGeneratedAlias
|
: (useGeneratedAlias
|
||||||
? '步骤 3 会自动生成邮箱,无需手动获取'
|
? '步骤 3 会自动生成邮箱,无需手动获取'
|
||||||
: (useCustomEmail ? '请先填写自定义注册邮箱,成功一轮后会自动清空' : `先自动获取${uiCopy.label},或手动粘贴邮箱后再继续`));
|
: (useCustomEmail ? '请先填写自定义注册邮箱,成功一轮后会自动清空' : `先自动获取${uiCopy.label},或手动粘贴邮箱后再继续`)));
|
||||||
}
|
}
|
||||||
if (useHotmail) {
|
if (useHotmail) {
|
||||||
inputEmail.value = getCurrentHotmailEmail();
|
inputEmail.value = getCurrentHotmailEmail();
|
||||||
|
} else if (useLuckmail) {
|
||||||
|
inputEmail.value = getCurrentLuckmailEmail();
|
||||||
}
|
}
|
||||||
renderHotmailAccounts();
|
renderHotmailAccounts();
|
||||||
|
if (useLuckmail) {
|
||||||
|
renderLuckmailPurchases(lastRenderedLuckmailPurchases);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveCloudflareDomainSettings(domains, activeDomain, options = {}) {
|
async function saveCloudflareDomainSettings(domains, activeDomain, options = {}) {
|
||||||
@@ -3057,7 +3568,7 @@ document.querySelectorAll('.step-btn').forEach(btn => {
|
|||||||
syncLatestState({ customPassword: inputPassword.value });
|
syncLatestState({ customPassword: inputPassword.value });
|
||||||
}
|
}
|
||||||
let email = inputEmail.value.trim();
|
let email = inputEmail.value.trim();
|
||||||
if (selectMailProvider.value === 'hotmail-api') {
|
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
|
||||||
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -3104,7 +3615,7 @@ document.querySelectorAll('.step-btn').forEach(btn => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
btnFetchEmail.addEventListener('click', async () => {
|
btnFetchEmail.addEventListener('click', async () => {
|
||||||
if (selectMailProvider.value === 'hotmail-api' || isCustomMailProvider()) {
|
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider() || isCustomMailProvider()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await fetchGeneratedEmail().catch(() => { });
|
await fetchGeneratedEmail().catch(() => { });
|
||||||
@@ -3637,7 +4148,13 @@ btnReset.addEventListener('click', async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await chrome.runtime.sendMessage({ type: 'RESET', source: 'sidepanel' });
|
await chrome.runtime.sendMessage({ type: 'RESET', source: 'sidepanel' });
|
||||||
syncLatestState({ stepStatuses: STEP_DEFAULT_STATUSES, currentHotmailAccountId: null, email: null });
|
syncLatestState({
|
||||||
|
stepStatuses: STEP_DEFAULT_STATUSES,
|
||||||
|
currentHotmailAccountId: null,
|
||||||
|
currentLuckmailPurchase: null,
|
||||||
|
currentLuckmailMailCursor: null,
|
||||||
|
email: null,
|
||||||
|
});
|
||||||
syncAutoRunState({
|
syncAutoRunState({
|
||||||
autoRunning: false,
|
autoRunning: false,
|
||||||
autoRunPhase: 'idle',
|
autoRunPhase: 'idle',
|
||||||
@@ -3675,6 +4192,10 @@ btnReset.addEventListener('click', async () => {
|
|||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
updateProgressCounter();
|
updateProgressCounter();
|
||||||
renderHotmailAccounts();
|
renderHotmailAccounts();
|
||||||
|
renderLuckmailPurchases(lastRenderedLuckmailPurchases);
|
||||||
|
if (isLuckmailProvider()) {
|
||||||
|
queueLuckmailPurchaseRefresh();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear log
|
// Clear log
|
||||||
@@ -3684,7 +4205,7 @@ btnClearLog.addEventListener('click', () => {
|
|||||||
|
|
||||||
// Save settings on change
|
// Save settings on change
|
||||||
inputEmail.addEventListener('change', async () => {
|
inputEmail.addEventListener('change', async () => {
|
||||||
if (selectMailProvider.value === 'hotmail-api') {
|
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const email = inputEmail.value.trim();
|
const email = inputEmail.value.trim();
|
||||||
@@ -3729,6 +4250,73 @@ inputVpsPassword.addEventListener('blur', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[inputLuckmailApiKey, inputLuckmailBaseUrl, inputLuckmailDomain].forEach((input) => {
|
||||||
|
input?.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
input?.addEventListener('blur', () => {
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
selectLuckmailEmailType?.addEventListener('change', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
inputLuckmailSearch?.addEventListener('input', (event) => {
|
||||||
|
luckmailSearchTerm = event.target.value || '';
|
||||||
|
renderLuckmailPurchases(lastRenderedLuckmailPurchases);
|
||||||
|
});
|
||||||
|
|
||||||
|
selectLuckmailFilter?.addEventListener('change', (event) => {
|
||||||
|
luckmailFilterMode = String(event.target.value || 'all').trim() || 'all';
|
||||||
|
renderLuckmailPurchases(lastRenderedLuckmailPurchases);
|
||||||
|
});
|
||||||
|
|
||||||
|
checkboxLuckmailSelectAll?.addEventListener('change', () => {
|
||||||
|
const visiblePurchases = getFilteredLuckmailPurchases();
|
||||||
|
if (checkboxLuckmailSelectAll.checked) {
|
||||||
|
visiblePurchases.forEach((purchase) => luckmailSelectedPurchaseIds.add(String(purchase.id)));
|
||||||
|
} else {
|
||||||
|
visiblePurchases.forEach((purchase) => luckmailSelectedPurchaseIds.delete(String(purchase.id)));
|
||||||
|
}
|
||||||
|
renderLuckmailPurchases(lastRenderedLuckmailPurchases);
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailRefresh?.addEventListener('click', async () => {
|
||||||
|
await refreshLuckmailPurchases();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailDisableUsed?.addEventListener('click', async () => {
|
||||||
|
await disableUsedLuckmailPurchases();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailBulkUsed?.addEventListener('click', async () => {
|
||||||
|
await runBulkLuckmailAction('used');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailBulkUnused?.addEventListener('click', async () => {
|
||||||
|
await runBulkLuckmailAction('unused');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailBulkPreserve?.addEventListener('click', async () => {
|
||||||
|
await runBulkLuckmailAction('preserve');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailBulkUnpreserve?.addEventListener('click', async () => {
|
||||||
|
await runBulkLuckmailAction('unpreserve');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailBulkDisable?.addEventListener('click', async () => {
|
||||||
|
await runBulkLuckmailAction('disable');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLuckmailBulkEnable?.addEventListener('click', async () => {
|
||||||
|
await runBulkLuckmailAction('enable');
|
||||||
|
});
|
||||||
|
|
||||||
inputPassword.addEventListener('input', () => {
|
inputPassword.addEventListener('input', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
@@ -3746,15 +4334,21 @@ selectMailProvider.addEventListener('change', async () => {
|
|||||||
const leavingHotmail = previousProvider === 'hotmail-api'
|
const leavingHotmail = previousProvider === 'hotmail-api'
|
||||||
&& nextProvider !== 'hotmail-api'
|
&& nextProvider !== 'hotmail-api'
|
||||||
&& isCurrentEmailManagedByHotmail();
|
&& isCurrentEmailManagedByHotmail();
|
||||||
|
const leavingLuckmail = previousProvider === LUCKMAIL_PROVIDER
|
||||||
|
&& nextProvider !== LUCKMAIL_PROVIDER
|
||||||
|
&& isCurrentEmailManagedByLuckmail();
|
||||||
const leavingGeneratedAlias = (
|
const leavingGeneratedAlias = (
|
||||||
previousProvider !== nextProvider
|
previousProvider !== nextProvider
|
||||||
|| (previousProvider === '2925' && normalizeMail2925Mode(previousMail2925Mode) !== getSelectedMail2925Mode())
|
|| (previousProvider === '2925' && normalizeMail2925Mode(previousMail2925Mode) !== getSelectedMail2925Mode())
|
||||||
) && previousProvider === '2925'
|
) && previousProvider === '2925'
|
||||||
&& normalizeMail2925Mode(previousMail2925Mode) === MAIL_2925_MODE_PROVIDE
|
&& normalizeMail2925Mode(previousMail2925Mode) === MAIL_2925_MODE_PROVIDE
|
||||||
&& isCurrentEmailManagedByGeneratedAlias(previousProvider, latestState, previousMail2925Mode);
|
&& isCurrentEmailManagedByGeneratedAlias(previousProvider, latestState, previousMail2925Mode);
|
||||||
if (leavingHotmail || leavingGeneratedAlias) {
|
if (leavingHotmail || leavingLuckmail || leavingGeneratedAlias) {
|
||||||
await clearRegistrationEmail({ silent: true }).catch(() => { });
|
await clearRegistrationEmail({ silent: true }).catch(() => { });
|
||||||
}
|
}
|
||||||
|
if (nextProvider === LUCKMAIL_PROVIDER) {
|
||||||
|
queueLuckmailPurchaseRefresh();
|
||||||
|
}
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
@@ -4165,6 +4759,25 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
inputEmail.value = getCurrentHotmailEmail();
|
inputEmail.value = getCurrentHotmailEmail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (message.payload.luckmailApiKey !== undefined) {
|
||||||
|
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
|
||||||
|
}
|
||||||
|
if (message.payload.luckmailBaseUrl !== undefined) {
|
||||||
|
inputLuckmailBaseUrl.value = normalizeLuckmailBaseUrl(message.payload.luckmailBaseUrl);
|
||||||
|
}
|
||||||
|
if (message.payload.luckmailEmailType !== undefined) {
|
||||||
|
selectLuckmailEmailType.value = normalizeLuckmailEmailType(message.payload.luckmailEmailType);
|
||||||
|
}
|
||||||
|
if (message.payload.luckmailDomain !== undefined) {
|
||||||
|
inputLuckmailDomain.value = message.payload.luckmailDomain || '';
|
||||||
|
}
|
||||||
|
if (message.payload.luckmailUsedPurchases !== undefined && isLuckmailProvider()) {
|
||||||
|
queueLuckmailPurchaseRefresh();
|
||||||
|
}
|
||||||
|
if (message.payload.currentLuckmailPurchase !== undefined && isLuckmailProvider()) {
|
||||||
|
inputEmail.value = getCurrentLuckmailEmail();
|
||||||
|
queueLuckmailPurchaseRefresh();
|
||||||
|
}
|
||||||
if (message.payload.autoDeleteUsedIcloudAlias !== undefined && checkboxAutoDeleteIcloud) {
|
if (message.payload.autoDeleteUsedIcloudAlias !== undefined && checkboxAutoDeleteIcloud) {
|
||||||
checkboxAutoDeleteIcloud.checked = Boolean(message.payload.autoDeleteUsedIcloudAlias);
|
checkboxAutoDeleteIcloud.checked = Boolean(message.payload.autoDeleteUsedIcloudAlias);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,572 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(name) {
|
||||||
|
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||||
|
const start = markers
|
||||||
|
.map((marker) => source.indexOf(marker))
|
||||||
|
.find((index) => index >= 0);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new Error(`missing function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parenDepth = 0;
|
||||||
|
let signatureEnded = false;
|
||||||
|
let braceStart = -1;
|
||||||
|
for (let i = start; i < source.length; i += 1) {
|
||||||
|
const ch = source[i];
|
||||||
|
if (ch === '(') {
|
||||||
|
parenDepth += 1;
|
||||||
|
} else if (ch === ')') {
|
||||||
|
parenDepth -= 1;
|
||||||
|
if (parenDepth === 0) {
|
||||||
|
signatureEnded = true;
|
||||||
|
}
|
||||||
|
} else if (ch === '{' && signatureEnded) {
|
||||||
|
braceStart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (braceStart < 0) {
|
||||||
|
throw new Error(`missing body for function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let depth = 0;
|
||||||
|
let end = braceStart;
|
||||||
|
for (; end < source.length; end += 1) {
|
||||||
|
const ch = source[end];
|
||||||
|
if (ch === '{') depth += 1;
|
||||||
|
if (ch === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('ensureLuckmailPurchaseForFlow buys openai mailbox and defaults email type to ms_graph', async () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('getLuckmailSessionConfig'),
|
||||||
|
extractFunction('getCurrentLuckmailPurchase'),
|
||||||
|
extractFunction('ensureLuckmailPurchaseForFlow'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const factory = new Function('initialState', `
|
||||||
|
let currentState = { ...initialState };
|
||||||
|
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
|
||||||
|
const purchaseCalls = [];
|
||||||
|
const activateCalls = [];
|
||||||
|
|
||||||
|
function normalizeLuckmailBaseUrl(value) {
|
||||||
|
return String(value || '').trim() || 'https://mails.luckyous.com';
|
||||||
|
}
|
||||||
|
function normalizeLuckmailEmailType(value) {
|
||||||
|
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
|
||||||
|
? String(value || '').trim()
|
||||||
|
: 'ms_graph';
|
||||||
|
}
|
||||||
|
function normalizeLuckmailPurchase(value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function normalizeLuckmailPurchases(value) {
|
||||||
|
return value.purchases || [];
|
||||||
|
}
|
||||||
|
async function getState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
function createLuckmailClient() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
async purchaseEmails(projectCode, quantity, options) {
|
||||||
|
purchaseCalls.push({ projectCode, quantity, options });
|
||||||
|
return {
|
||||||
|
purchases: [{ id: 15, email_address: 'demo@outlook.com', token: 'tok-1' }],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function findReusableLuckmailPurchaseForFlow() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function activateLuckmailPurchaseForFlow(state, client, purchase, options) {
|
||||||
|
activateCalls.push({ state, purchase, options });
|
||||||
|
currentState.currentLuckmailPurchase = purchase;
|
||||||
|
currentState.email = purchase.email_address;
|
||||||
|
return purchase;
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ensureLuckmailPurchaseForFlow,
|
||||||
|
snapshot() {
|
||||||
|
return { currentState, purchaseCalls, activateCalls };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory({
|
||||||
|
luckmailApiKey: 'sk-test',
|
||||||
|
luckmailBaseUrl: '',
|
||||||
|
luckmailEmailType: '',
|
||||||
|
luckmailDomain: '',
|
||||||
|
currentLuckmailPurchase: null,
|
||||||
|
email: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const purchase = await api.ensureLuckmailPurchaseForFlow();
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
|
||||||
|
assert.equal(purchase.email_address, 'demo@outlook.com');
|
||||||
|
assert.deepStrictEqual(snapshot.purchaseCalls, [{
|
||||||
|
projectCode: 'openai',
|
||||||
|
quantity: 1,
|
||||||
|
options: {
|
||||||
|
emailType: 'ms_graph',
|
||||||
|
domain: undefined,
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
assert.equal(snapshot.activateCalls[0].options.initializeCursor, false);
|
||||||
|
assert.equal(snapshot.currentState.email, 'demo@outlook.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ensureLuckmailPurchaseForFlow reuses reusable openai mailbox before buying a new one', async () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('getLuckmailSessionConfig'),
|
||||||
|
extractFunction('getCurrentLuckmailPurchase'),
|
||||||
|
extractFunction('ensureLuckmailPurchaseForFlow'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const factory = new Function('initialState', `
|
||||||
|
let currentState = { ...initialState };
|
||||||
|
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
|
||||||
|
const purchaseCalls = [];
|
||||||
|
const activateCalls = [];
|
||||||
|
|
||||||
|
function normalizeLuckmailBaseUrl(value) {
|
||||||
|
return String(value || '').trim() || 'https://mails.luckyous.com';
|
||||||
|
}
|
||||||
|
function normalizeLuckmailEmailType(value) {
|
||||||
|
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
|
||||||
|
? String(value || '').trim()
|
||||||
|
: 'ms_graph';
|
||||||
|
}
|
||||||
|
function normalizeLuckmailPurchase(value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function normalizeLuckmailPurchases(value) {
|
||||||
|
return value.purchases || [];
|
||||||
|
}
|
||||||
|
async function getState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
function createLuckmailClient() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
async purchaseEmails(projectCode, quantity, options) {
|
||||||
|
purchaseCalls.push({ projectCode, quantity, options });
|
||||||
|
return { purchases: [] };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function findReusableLuckmailPurchaseForFlow() {
|
||||||
|
return {
|
||||||
|
id: 99,
|
||||||
|
email_address: 'reuse@outlook.com',
|
||||||
|
token: 'tok-reuse',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function activateLuckmailPurchaseForFlow(state, client, purchase, options) {
|
||||||
|
activateCalls.push({ state, purchase, options });
|
||||||
|
currentState.currentLuckmailPurchase = purchase;
|
||||||
|
currentState.email = purchase.email_address;
|
||||||
|
return purchase;
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ensureLuckmailPurchaseForFlow,
|
||||||
|
snapshot() {
|
||||||
|
return { currentState, purchaseCalls, activateCalls };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory({
|
||||||
|
luckmailApiKey: 'sk-test',
|
||||||
|
luckmailBaseUrl: 'https://mails.luckyous.com',
|
||||||
|
luckmailEmailType: 'ms_imap',
|
||||||
|
luckmailDomain: 'outlook.com',
|
||||||
|
currentLuckmailPurchase: null,
|
||||||
|
email: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const purchase = await api.ensureLuckmailPurchaseForFlow();
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
|
||||||
|
assert.equal(purchase.id, 99);
|
||||||
|
assert.deepStrictEqual(snapshot.purchaseCalls, []);
|
||||||
|
assert.equal(snapshot.activateCalls[0].options.initializeCursor, true);
|
||||||
|
assert.match(snapshot.activateCalls[0].options.logMessage, /已复用 openai 邮箱/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activateLuckmailPurchaseForFlow builds baseline cursor from existing mails when reusing mailbox', async () => {
|
||||||
|
const bundle = extractFunction('activateLuckmailPurchaseForFlow');
|
||||||
|
|
||||||
|
const factory = new Function(`
|
||||||
|
let currentPurchase = null;
|
||||||
|
let currentCursor = null;
|
||||||
|
let currentEmail = null;
|
||||||
|
const buildCalls = [];
|
||||||
|
|
||||||
|
function normalizeLuckmailPurchase(value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
async function setLuckmailPurchaseState(value) {
|
||||||
|
currentPurchase = value;
|
||||||
|
}
|
||||||
|
async function setLuckmailMailCursorState(value) {
|
||||||
|
currentCursor = value;
|
||||||
|
}
|
||||||
|
async function setEmailState(value) {
|
||||||
|
currentEmail = value;
|
||||||
|
}
|
||||||
|
async function addLog() {}
|
||||||
|
function buildLuckmailBaselineCursor(mails) {
|
||||||
|
buildCalls.push(mails);
|
||||||
|
return { messageId: 'mail-new', receivedAt: '2026-04-14 13:32:05' };
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
activateLuckmailPurchaseForFlow,
|
||||||
|
snapshot() {
|
||||||
|
return { currentPurchase, currentCursor, currentEmail, buildCalls };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory();
|
||||||
|
const client = {
|
||||||
|
user: {
|
||||||
|
async getTokenMails() {
|
||||||
|
return {
|
||||||
|
mails: [
|
||||||
|
{ message_id: 'mail-old', received_at: '2026-04-14 13:31:15' },
|
||||||
|
{ message_id: 'mail-new', received_at: '2026-04-14 13:32:05' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await api.activateLuckmailPurchaseForFlow({}, client, {
|
||||||
|
id: 5,
|
||||||
|
email_address: 'reuse@outlook.com',
|
||||||
|
token: 'tok-reuse',
|
||||||
|
}, {
|
||||||
|
initializeCursor: true,
|
||||||
|
logMessage: 'reuse',
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
assert.equal(snapshot.currentPurchase.id, 5);
|
||||||
|
assert.deepStrictEqual(snapshot.currentCursor, {
|
||||||
|
messageId: 'mail-new',
|
||||||
|
receivedAt: '2026-04-14 13:32:05',
|
||||||
|
});
|
||||||
|
assert.equal(snapshot.currentEmail, 'reuse@outlook.com');
|
||||||
|
assert.equal(snapshot.buildCalls.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listLuckmailPurchasesByProject only keeps openai purchases', async () => {
|
||||||
|
const bundle = extractFunction('listLuckmailPurchasesByProject');
|
||||||
|
|
||||||
|
const factory = new Function(`
|
||||||
|
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
|
||||||
|
function normalizeLuckmailProjectName(value) {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
function isLuckmailPurchaseForProject(purchase, projectCode) {
|
||||||
|
return normalizeLuckmailProjectName(purchase.project_name || purchase.project) === normalizeLuckmailProjectName(projectCode);
|
||||||
|
}
|
||||||
|
async function getAllLuckmailPurchases() {
|
||||||
|
return [
|
||||||
|
{ id: 1, project_name: 'OpenAi' },
|
||||||
|
{ id: 2, project_name: 'other' },
|
||||||
|
{ id: 3, project: 'openai' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return { listLuckmailPurchasesByProject };
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory();
|
||||||
|
const result = await api.listLuckmailPurchasesByProject({}, { projectCode: 'openai' });
|
||||||
|
assert.deepStrictEqual(result.map((item) => item.id), [1, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disableUsedLuckmailPurchases only disables locally used and non-preserved openai mailboxes', async () => {
|
||||||
|
const bundle = extractFunction('disableUsedLuckmailPurchases');
|
||||||
|
|
||||||
|
const factory = new Function(`
|
||||||
|
let clearedOptions = null;
|
||||||
|
const disabledCalls = [];
|
||||||
|
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
|
||||||
|
|
||||||
|
function normalizeLuckmailPurchaseId(value) {
|
||||||
|
const numeric = Number(value);
|
||||||
|
return Number.isFinite(numeric) && numeric > 0 ? String(Math.floor(numeric)) : '';
|
||||||
|
}
|
||||||
|
async function ensureManualInteractionAllowed() {
|
||||||
|
return {
|
||||||
|
luckmailUsedPurchases: { 1: true, 2: true, 3: true },
|
||||||
|
luckmailPreserveTagId: 9,
|
||||||
|
luckmailPreserveTagName: '保留',
|
||||||
|
mailProvider: 'luckmail-api',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function getLuckmailUsedPurchases(state) {
|
||||||
|
return state.luckmailUsedPurchases;
|
||||||
|
}
|
||||||
|
function getLuckmailPreserveTagInfo(state) {
|
||||||
|
return {
|
||||||
|
id: state.luckmailPreserveTagId,
|
||||||
|
name: state.luckmailPreserveTagName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function isLuckmailPurchasePreserved(purchase, options) {
|
||||||
|
return purchase.tag_id === options.preserveTagId || purchase.tag_name === options.preserveTagName;
|
||||||
|
}
|
||||||
|
function createLuckmailClient() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
async batchSetPurchaseDisabled(ids, disabled) {
|
||||||
|
disabledCalls.push({ ids, disabled });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function listLuckmailPurchasesByProject() {
|
||||||
|
return [
|
||||||
|
{ id: 1, email_address: 'used-1@outlook.com', user_disabled: 0, tag_id: 0, tag_name: '' },
|
||||||
|
{ id: 2, email_address: 'preserved@outlook.com', user_disabled: 0, tag_id: 9, tag_name: '保留' },
|
||||||
|
{ id: 3, email_address: 'already-disabled@outlook.com', user_disabled: 1, tag_id: 0, tag_name: '' },
|
||||||
|
{ id: 4, email_address: 'unused@outlook.com', user_disabled: 0, tag_id: 0, tag_name: '' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
async function getState() {
|
||||||
|
return {
|
||||||
|
currentLuckmailPurchase: { id: 1 },
|
||||||
|
mailProvider: 'luckmail-api',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function getCurrentLuckmailPurchase(state) {
|
||||||
|
return state.currentLuckmailPurchase;
|
||||||
|
}
|
||||||
|
function isLuckmailProvider(state) {
|
||||||
|
return state.mailProvider === 'luckmail-api';
|
||||||
|
}
|
||||||
|
async function clearLuckmailRuntimeState(options) {
|
||||||
|
clearedOptions = options;
|
||||||
|
}
|
||||||
|
async function addLog() {}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
disableUsedLuckmailPurchases,
|
||||||
|
snapshot() {
|
||||||
|
return { disabledCalls, clearedOptions };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory();
|
||||||
|
const result = await api.disableUsedLuckmailPurchases();
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result.disabledIds, [1]);
|
||||||
|
assert.deepStrictEqual(snapshot.disabledCalls, [{ ids: [1], disabled: 1 }]);
|
||||||
|
assert.deepStrictEqual(snapshot.clearedOptions, { clearEmail: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => {
|
||||||
|
const bundle = extractFunction('resetState');
|
||||||
|
|
||||||
|
const factory = new Function([
|
||||||
|
'let cleared = false;',
|
||||||
|
'let storedPayload = null;',
|
||||||
|
"const LOG_PREFIX = '[test]';",
|
||||||
|
"const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = '保留';",
|
||||||
|
'const DEFAULT_STATE = {',
|
||||||
|
" luckmailApiKey: '',",
|
||||||
|
" luckmailBaseUrl: 'https://mails.luckyous.com',",
|
||||||
|
" luckmailEmailType: 'ms_graph',",
|
||||||
|
" luckmailDomain: '',",
|
||||||
|
' luckmailUsedPurchases: {},',
|
||||||
|
' luckmailPreserveTagId: 0,',
|
||||||
|
" luckmailPreserveTagName: '保留',",
|
||||||
|
" currentLuckmailPurchase: { token: 'stale' },",
|
||||||
|
" currentLuckmailMailCursor: { messageId: 'stale' },",
|
||||||
|
' email: null,',
|
||||||
|
'};',
|
||||||
|
'function normalizeLuckmailBaseUrl(value) {',
|
||||||
|
" const normalized = String(value || '').trim() || 'https://mails.luckyous.com';",
|
||||||
|
" return normalized.replace(/\\/$/, '');",
|
||||||
|
'}',
|
||||||
|
'function normalizeLuckmailEmailType(value) {',
|
||||||
|
" return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())",
|
||||||
|
" ? String(value || '').trim()",
|
||||||
|
" : 'ms_graph';",
|
||||||
|
'}',
|
||||||
|
'function normalizeLuckmailUsedPurchases(value) {',
|
||||||
|
' return value || {};',
|
||||||
|
'}',
|
||||||
|
'async function getPersistedSettings() {',
|
||||||
|
" return { mailProvider: '163' };",
|
||||||
|
'}',
|
||||||
|
'async function getPersistedAliasState() {',
|
||||||
|
' return {};',
|
||||||
|
'}',
|
||||||
|
'const chrome = {',
|
||||||
|
' storage: {',
|
||||||
|
' session: {',
|
||||||
|
' async get() {',
|
||||||
|
' return {',
|
||||||
|
" seenCodes: ['seen-1'],",
|
||||||
|
" seenInbucketMailIds: ['mail-1'],",
|
||||||
|
" accounts: [{ email: 'saved@example.com' }],",
|
||||||
|
" tabRegistry: { foo: { tabId: 1 } },",
|
||||||
|
" sourceLastUrls: { foo: 'https://example.com' },",
|
||||||
|
" luckmailApiKey: 'sk-session',",
|
||||||
|
" luckmailBaseUrl: 'https://demo.example.com/',",
|
||||||
|
" luckmailEmailType: 'ms_imap',",
|
||||||
|
" luckmailDomain: 'outlook.com',",
|
||||||
|
" luckmailUsedPurchases: { 88: true },",
|
||||||
|
' luckmailPreserveTagId: 9,',
|
||||||
|
" luckmailPreserveTagName: '保留',",
|
||||||
|
' };',
|
||||||
|
' },',
|
||||||
|
' async clear() {',
|
||||||
|
' cleared = true;',
|
||||||
|
' },',
|
||||||
|
' async set(payload) {',
|
||||||
|
' storedPayload = payload;',
|
||||||
|
' },',
|
||||||
|
' },',
|
||||||
|
' },',
|
||||||
|
'};',
|
||||||
|
bundle,
|
||||||
|
'return {',
|
||||||
|
' resetState,',
|
||||||
|
' snapshot() {',
|
||||||
|
' return { cleared, storedPayload };',
|
||||||
|
' },',
|
||||||
|
'};',
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
const api = factory();
|
||||||
|
await api.resetState();
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
|
||||||
|
assert.equal(snapshot.cleared, true);
|
||||||
|
assert.equal(snapshot.storedPayload.luckmailApiKey, 'sk-session');
|
||||||
|
assert.equal(snapshot.storedPayload.luckmailBaseUrl, 'https://demo.example.com');
|
||||||
|
assert.equal(snapshot.storedPayload.luckmailEmailType, 'ms_imap');
|
||||||
|
assert.equal(snapshot.storedPayload.luckmailDomain, 'outlook.com');
|
||||||
|
assert.deepStrictEqual(snapshot.storedPayload.luckmailUsedPurchases, { 88: true });
|
||||||
|
assert.equal(snapshot.storedPayload.luckmailPreserveTagId, 9);
|
||||||
|
assert.equal(snapshot.storedPayload.luckmailPreserveTagName, '保留');
|
||||||
|
assert.equal(snapshot.storedPayload.currentLuckmailPurchase, null);
|
||||||
|
assert.equal(snapshot.storedPayload.currentLuckmailMailCursor, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleStepData step 9 marks current LuckMail purchase as used and clears runtime state', async () => {
|
||||||
|
const bundle = extractFunction('handleStepData');
|
||||||
|
|
||||||
|
const factory = new Function(`
|
||||||
|
let clearedOptions = null;
|
||||||
|
let usedMarker = null;
|
||||||
|
const logs = [];
|
||||||
|
|
||||||
|
async function closeLocalhostCallbackTabs() {}
|
||||||
|
async function getState() {
|
||||||
|
return {
|
||||||
|
mailProvider: 'luckmail-api',
|
||||||
|
currentHotmailAccountId: null,
|
||||||
|
currentLuckmailPurchase: {
|
||||||
|
id: 123,
|
||||||
|
email_address: 'demo@outlook.com',
|
||||||
|
},
|
||||||
|
email: 'demo@outlook.com',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function getCurrentLuckmailPurchase(state) {
|
||||||
|
return state.currentLuckmailPurchase;
|
||||||
|
}
|
||||||
|
function isHotmailProvider() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
async function patchHotmailAccount() {}
|
||||||
|
function isLuckmailProvider(state) {
|
||||||
|
return state.mailProvider === 'luckmail-api';
|
||||||
|
}
|
||||||
|
async function setLuckmailPurchaseUsedState(purchaseId, used) {
|
||||||
|
usedMarker = { purchaseId, used };
|
||||||
|
}
|
||||||
|
async function clearLuckmailRuntimeState(options) {
|
||||||
|
clearedOptions = options;
|
||||||
|
}
|
||||||
|
async function addLog(message, level) {
|
||||||
|
logs.push({ message, level });
|
||||||
|
}
|
||||||
|
function buildLocalhostCleanupPrefix() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
async function closeTabsByUrlPrefix() {}
|
||||||
|
function shouldUseCustomRegistrationEmail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
async function setEmailStateSilently() {}
|
||||||
|
async function setState() {}
|
||||||
|
function broadcastDataUpdate() {}
|
||||||
|
function isLocalhostOAuthCallbackUrl() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleStepData,
|
||||||
|
snapshot() {
|
||||||
|
return { clearedOptions, usedMarker, logs };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory();
|
||||||
|
await api.handleStepData(9, {
|
||||||
|
localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
assert.deepStrictEqual(snapshot.usedMarker, { purchaseId: 123, used: true });
|
||||||
|
assert.deepStrictEqual(snapshot.clearedOptions, { clearEmail: true });
|
||||||
|
assert.equal(snapshot.logs.at(-1).message, '当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。');
|
||||||
|
});
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
|
const {
|
||||||
|
DEFAULT_LUCKMAIL_BASE_URL,
|
||||||
|
DEFAULT_LUCKMAIL_EMAIL_TYPE,
|
||||||
|
DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||||
|
buildLuckmailBaselineCursor,
|
||||||
|
buildLuckmailMailCursor,
|
||||||
|
extractLuckmailVerificationCode,
|
||||||
|
filterReusableLuckmailPurchases,
|
||||||
|
isLuckmailMailNewerThanCursor,
|
||||||
|
isLuckmailPurchaseForProject,
|
||||||
|
normalizeLuckmailBaseUrl,
|
||||||
|
normalizeLuckmailEmailType,
|
||||||
|
normalizeLuckmailProjectName,
|
||||||
|
normalizeLuckmailPurchaseListPage,
|
||||||
|
normalizeLuckmailTags,
|
||||||
|
normalizeLuckmailUsedPurchases,
|
||||||
|
normalizeTimestamp,
|
||||||
|
normalizeLuckmailTokenCode,
|
||||||
|
normalizeLuckmailTokenMail,
|
||||||
|
pickReusableLuckmailPurchase,
|
||||||
|
pickLuckmailVerificationMail,
|
||||||
|
} = require('../luckmail-utils.js');
|
||||||
|
|
||||||
|
test('normalizeLuckmailEmailType keeps supported values and falls back to ms_graph', () => {
|
||||||
|
assert.equal(normalizeLuckmailEmailType('self_built'), 'self_built');
|
||||||
|
assert.equal(normalizeLuckmailEmailType('ms_imap'), 'ms_imap');
|
||||||
|
assert.equal(normalizeLuckmailEmailType('ms_graph'), 'ms_graph');
|
||||||
|
assert.equal(normalizeLuckmailEmailType('google_variant'), 'google_variant');
|
||||||
|
assert.equal(normalizeLuckmailEmailType(''), DEFAULT_LUCKMAIL_EMAIL_TYPE);
|
||||||
|
assert.equal(normalizeLuckmailEmailType('unknown'), DEFAULT_LUCKMAIL_EMAIL_TYPE);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeLuckmailBaseUrl trims invalid input to default base url', () => {
|
||||||
|
assert.equal(normalizeLuckmailBaseUrl(''), DEFAULT_LUCKMAIL_BASE_URL);
|
||||||
|
assert.equal(normalizeLuckmailBaseUrl('https://mails.luckyous.com/'), DEFAULT_LUCKMAIL_BASE_URL);
|
||||||
|
assert.equal(normalizeLuckmailBaseUrl('https://demo.example.com/api/'), 'https://demo.example.com/api');
|
||||||
|
assert.equal(normalizeLuckmailBaseUrl('notaurl'), DEFAULT_LUCKMAIL_BASE_URL);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeLuckmailTokenCode and normalizeLuckmailTokenMail extract verification code', () => {
|
||||||
|
const tokenCode = normalizeLuckmailTokenCode({
|
||||||
|
email_address: 'demo@outlook.com',
|
||||||
|
project: 'openai',
|
||||||
|
has_new_mail: true,
|
||||||
|
verification_code: '123456',
|
||||||
|
mail: {
|
||||||
|
message_id: 'mail-1',
|
||||||
|
from: 'noreply@openai.com',
|
||||||
|
subject: 'Your ChatGPT code is 123456',
|
||||||
|
received_at: '2026-04-14T10:00:00Z',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(tokenCode.verification_code, '123456');
|
||||||
|
assert.equal(tokenCode.mail.message_id, 'mail-1');
|
||||||
|
assert.equal(tokenCode.mail.verification_code, '123456');
|
||||||
|
|
||||||
|
const normalizedMail = normalizeLuckmailTokenMail({
|
||||||
|
message_id: 'mail-2',
|
||||||
|
from: 'noreply@openai.com',
|
||||||
|
subject: 'OpenAI security message',
|
||||||
|
body: 'Your verification code is 654321.',
|
||||||
|
received_at: '2026-04-14T10:01:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(normalizedMail.verification_code, '654321');
|
||||||
|
assert.equal(extractLuckmailVerificationCode('你的验证码为 778899'), '778899');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeLuckmailProjectName and isLuckmailPurchaseForProject match openai case-insensitively', () => {
|
||||||
|
assert.equal(normalizeLuckmailProjectName(' OpenAi '), 'openai');
|
||||||
|
assert.equal(isLuckmailPurchaseForProject({
|
||||||
|
id: 1,
|
||||||
|
email_address: 'demo@outlook.com',
|
||||||
|
token: 'tok-1',
|
||||||
|
project_name: 'OpenAi',
|
||||||
|
}, 'openai'), true);
|
||||||
|
assert.equal(isLuckmailPurchaseForProject({
|
||||||
|
id: 2,
|
||||||
|
email_address: 'other@example.com',
|
||||||
|
token: 'tok-2',
|
||||||
|
project: 'OtherProject',
|
||||||
|
}, 'openai'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeLuckmailPurchaseListPage and normalizeLuckmailTags normalize list payloads', () => {
|
||||||
|
const page = normalizeLuckmailPurchaseListPage({
|
||||||
|
list: [{
|
||||||
|
id: 3,
|
||||||
|
email_address: 'demo@outlook.com',
|
||||||
|
token: 'tok-3',
|
||||||
|
project_name: 'OpenAi',
|
||||||
|
}],
|
||||||
|
total: 4,
|
||||||
|
page: 2,
|
||||||
|
page_size: 1,
|
||||||
|
});
|
||||||
|
assert.equal(page.total, 4);
|
||||||
|
assert.equal(page.page, 2);
|
||||||
|
assert.equal(page.page_size, 1);
|
||||||
|
assert.equal(page.list[0].project_code, 'openai');
|
||||||
|
|
||||||
|
const tags = normalizeLuckmailTags([{
|
||||||
|
id: 9,
|
||||||
|
name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||||
|
limit_type: 0,
|
||||||
|
}]);
|
||||||
|
assert.deepEqual(tags[0], {
|
||||||
|
id: 9,
|
||||||
|
name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||||
|
remark: '',
|
||||||
|
limit_type: 0,
|
||||||
|
purchase_count: 0,
|
||||||
|
created_at: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeLuckmailUsedPurchases keeps positive numeric keys only', () => {
|
||||||
|
assert.deepEqual(normalizeLuckmailUsedPurchases({
|
||||||
|
1: true,
|
||||||
|
foo: true,
|
||||||
|
'-2': true,
|
||||||
|
3: false,
|
||||||
|
}), {
|
||||||
|
1: true,
|
||||||
|
3: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pickReusableLuckmailPurchase only returns reusable openai purchase', () => {
|
||||||
|
const purchases = [{
|
||||||
|
id: 10,
|
||||||
|
email_address: 'used@outlook.com',
|
||||||
|
token: 'tok-used',
|
||||||
|
project_name: 'openai',
|
||||||
|
}, {
|
||||||
|
id: 11,
|
||||||
|
email_address: 'preserved@outlook.com',
|
||||||
|
token: 'tok-preserved',
|
||||||
|
project_name: 'OpenAi',
|
||||||
|
tag_name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||||
|
}, {
|
||||||
|
id: 12,
|
||||||
|
email_address: 'disabled@outlook.com',
|
||||||
|
token: 'tok-disabled',
|
||||||
|
project_name: 'openai',
|
||||||
|
user_disabled: 1,
|
||||||
|
}, {
|
||||||
|
id: 13,
|
||||||
|
email_address: 'expired@outlook.com',
|
||||||
|
token: 'tok-expired',
|
||||||
|
project_name: 'openai',
|
||||||
|
warranty_until: '2026-04-14T09:00:00Z',
|
||||||
|
}, {
|
||||||
|
id: 14,
|
||||||
|
email_address: 'other@example.com',
|
||||||
|
token: 'tok-other',
|
||||||
|
project_name: 'other',
|
||||||
|
}, {
|
||||||
|
id: 15,
|
||||||
|
email_address: 'ready@outlook.com',
|
||||||
|
token: 'tok-ready',
|
||||||
|
project_name: 'OpenAi',
|
||||||
|
warranty_until: '2026-04-15T09:00:00Z',
|
||||||
|
}];
|
||||||
|
|
||||||
|
const reusable = filterReusableLuckmailPurchases(purchases, {
|
||||||
|
projectCode: 'openai',
|
||||||
|
usedPurchases: { 10: true },
|
||||||
|
preserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||||
|
now: Date.parse('2026-04-14T10:00:00Z'),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(reusable.map((purchase) => purchase.id), [15]);
|
||||||
|
assert.equal(pickReusableLuckmailPurchase(purchases, {
|
||||||
|
projectCode: 'openai',
|
||||||
|
usedPurchases: { 10: true },
|
||||||
|
preserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||||
|
now: Date.parse('2026-04-14T10:00:00Z'),
|
||||||
|
}).id, 15);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pickLuckmailVerificationMail respects sender filters, time filters, and excluded codes', () => {
|
||||||
|
const match = pickLuckmailVerificationMail([
|
||||||
|
{
|
||||||
|
message_id: 'old-mail',
|
||||||
|
from: 'noreply@openai.com',
|
||||||
|
subject: 'Your code is 111111',
|
||||||
|
received_at: '2026-04-14T09:59:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message_id: 'new-mail',
|
||||||
|
from: 'noreply@openai.com',
|
||||||
|
subject: 'Your code is 222222',
|
||||||
|
received_at: '2026-04-14T10:05:00Z',
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
senderFilters: ['openai'],
|
||||||
|
subjectFilters: ['code'],
|
||||||
|
excludeCodes: ['111111'],
|
||||||
|
afterTimestamp: Date.parse('2026-04-14T10:00:00Z'),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(match.code, '222222');
|
||||||
|
assert.equal(match.mail.message_id, 'new-mail');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isLuckmailMailNewerThanCursor compares message id and timestamp safely', () => {
|
||||||
|
const cursor = buildLuckmailMailCursor({
|
||||||
|
message_id: 'mail-1',
|
||||||
|
received_at: '2026-04-14T10:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(isLuckmailMailNewerThanCursor({
|
||||||
|
message_id: 'mail-1',
|
||||||
|
received_at: '2026-04-14T10:00:00Z',
|
||||||
|
}, cursor), false);
|
||||||
|
|
||||||
|
assert.equal(isLuckmailMailNewerThanCursor({
|
||||||
|
message_id: 'mail-2',
|
||||||
|
received_at: '2026-04-14T10:01:00Z',
|
||||||
|
}, cursor), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeLuckmailMailCursor tolerates null cursor input', () => {
|
||||||
|
const { normalizeLuckmailMailCursor } = require('../luckmail-utils.js');
|
||||||
|
assert.deepEqual(normalizeLuckmailMailCursor(null), {
|
||||||
|
messageId: '',
|
||||||
|
receivedAt: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizeTimestamp treats LuckMail naive datetime strings as UTC', () => {
|
||||||
|
assert.equal(
|
||||||
|
normalizeTimestamp('2026-04-14 13:32:05'),
|
||||||
|
Date.UTC(2026, 3, 14, 13, 32, 5, 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildLuckmailBaselineCursor tracks newest existing mail as baseline', () => {
|
||||||
|
const cursor = buildLuckmailBaselineCursor([
|
||||||
|
{
|
||||||
|
message_id: 'mail-old',
|
||||||
|
received_at: '2026-04-14 13:31:15',
|
||||||
|
subject: '你的 ChatGPT 代码为 111111',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message_id: 'mail-new',
|
||||||
|
received_at: '2026-04-14 13:32:05',
|
||||||
|
subject: '你的 ChatGPT 代码为 222222',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(cursor, {
|
||||||
|
messageId: 'mail-new',
|
||||||
|
receivedAt: '2026-04-14 13:32:05',
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -53,6 +53,8 @@ function extractFunction(name) {
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('getTabRegistry'),
|
extractFunction('getTabRegistry'),
|
||||||
extractFunction('normalizeEmailGenerator'),
|
extractFunction('normalizeEmailGenerator'),
|
||||||
|
extractFunction('normalizeMail2925Mode'),
|
||||||
|
extractFunction('getMail2925Mode'),
|
||||||
extractFunction('parseUrlSafely'),
|
extractFunction('parseUrlSafely'),
|
||||||
extractFunction('isHotmailProvider'),
|
extractFunction('isHotmailProvider'),
|
||||||
extractFunction('isCustomMailProvider'),
|
extractFunction('isCustomMailProvider'),
|
||||||
@@ -70,6 +72,9 @@ const api = new Function(`
|
|||||||
const HOTMAIL_PROVIDER = 'hotmail-api';
|
const HOTMAIL_PROVIDER = 'hotmail-api';
|
||||||
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
||||||
const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
|
const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
|
||||||
|
const MAIL_2925_MODE_PROVIDE = 'provide';
|
||||||
|
const MAIL_2925_MODE_RECEIVE = 'receive';
|
||||||
|
const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
|
||||||
let currentState = {
|
let currentState = {
|
||||||
tabRegistry: {
|
tabRegistry: {
|
||||||
'signup-page': { tabId: 1, ready: true },
|
'signup-page': { tabId: 1, ready: true },
|
||||||
@@ -104,6 +109,26 @@ async function setEmailState(email) {
|
|||||||
currentState = { ...currentState, email };
|
currentState = { ...currentState, email };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setEmailStateSilently(email) {
|
||||||
|
currentState = { ...currentState, email };
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHotmailProvider() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLuckmailProvider() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchHotmailAccount() {}
|
||||||
|
|
||||||
|
async function clearLuckmailRuntimeState() {}
|
||||||
|
|
||||||
|
function shouldUseCustomRegistrationEmail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function broadcastDataUpdate() {}
|
function broadcastDataUpdate() {}
|
||||||
|
|
||||||
async function addLog(message) {
|
async function addLog(message) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ const api = new Function(`
|
|||||||
let stopRequested = false;
|
let stopRequested = false;
|
||||||
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
||||||
const HOTMAIL_PROVIDER = 'hotmail-api';
|
const HOTMAIL_PROVIDER = 'hotmail-api';
|
||||||
|
const LUCKMAIL_PROVIDER = 'luckmail-api';
|
||||||
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
||||||
const VERIFICATION_POLL_MAX_ROUNDS = 5;
|
const VERIFICATION_POLL_MAX_ROUNDS = 5;
|
||||||
const logs = [];
|
const logs = [];
|
||||||
@@ -72,6 +73,9 @@ function getHotmailVerificationPollConfig() {
|
|||||||
async function pollHotmailVerificationCode() {
|
async function pollHotmailVerificationCode() {
|
||||||
throw new Error('hotmail path should not run in this test');
|
throw new Error('hotmail path should not run in this test');
|
||||||
}
|
}
|
||||||
|
async function pollLuckmailVerificationCode() {
|
||||||
|
throw new Error('luckmail path should not run in this test');
|
||||||
|
}
|
||||||
function getVerificationCodeStateKey(step) {
|
function getVerificationCodeStateKey(step) {
|
||||||
return step === 4 ? 'lastSignupCode' : 'lastLoginCode';
|
return step === 4 ? 'lastSignupCode' : 'lastLoginCode';
|
||||||
}
|
}
|
||||||
@@ -123,6 +127,7 @@ async function testResolveVerificationStepRethrowsStopFromFreshRequest() {
|
|||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
||||||
const HOTMAIL_PROVIDER = 'hotmail-api';
|
const HOTMAIL_PROVIDER = 'hotmail-api';
|
||||||
|
const LUCKMAIL_PROVIDER = 'luckmail-api';
|
||||||
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
||||||
const logs = [];
|
const logs = [];
|
||||||
let pollCalls = 0;
|
let pollCalls = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user