第三方支持
This commit is contained in:
@@ -52,7 +52,7 @@
|
|||||||
- 支持自定义密码;留空时自动生成强密码
|
- 支持自定义密码;留空时自动生成强密码
|
||||||
- 自动显示当前使用中的密码,便于后续保存
|
- 自动显示当前使用中的密码,便于后续保存
|
||||||
- 自动获取注册验证码与登录验证码
|
- 自动获取注册验证码与登录验证码
|
||||||
- 支持 `Hotmail`:直接使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)` 刷新微软令牌,并通过 Microsoft Graph 读取最新邮件
|
- 支持 `Hotmail`:直接使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)` 请求第三方邮件 API 读取最新邮件
|
||||||
- 支持 `QQ Mail`、`163 Mail`、`Inbucket mailbox`
|
- 支持 `QQ Mail`、`163 Mail`、`Inbucket mailbox`
|
||||||
- 支持从 DuckDuckGo Email Protection 自动生成新的 `@duck.com` 地址
|
- 支持从 DuckDuckGo Email Protection 自动生成新的 `@duck.com` 地址
|
||||||
- 支持基于 Cloudflare 自定义域名自动生成随机邮箱前缀
|
- 支持基于 Cloudflare 自定义域名自动生成随机邮箱前缀
|
||||||
@@ -137,7 +137,7 @@ Step 1 和 Step 9 都依赖这个地址。
|
|||||||
|
|
||||||
说明:
|
说明:
|
||||||
|
|
||||||
- `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,并直接访问 Microsoft Graph 邮件接口
|
- `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,并请求第三方邮件 API
|
||||||
- `QQ`、`163`、`163 VIP` 用于直接轮询网页邮箱
|
- `QQ`、`163`、`163 VIP` 用于直接轮询网页邮箱
|
||||||
- `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https://<your-inbucket-host>/m/<mailbox>/`
|
- `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https://<your-inbucket-host>/m/<mailbox>/`
|
||||||
|
|
||||||
@@ -145,6 +145,13 @@ Step 1 和 Step 9 都依赖这个地址。
|
|||||||
|
|
||||||
仅当 `Mail = Hotmail` 时使用。
|
仅当 `Mail = Hotmail` 时使用。
|
||||||
|
|
||||||
|
可配置项:
|
||||||
|
|
||||||
|
- `Hotmail API 地址`
|
||||||
|
- `响应类型`
|
||||||
|
- `收件箱参数`
|
||||||
|
- `垃圾箱参数`
|
||||||
|
|
||||||
每条账号支持保存:
|
每条账号支持保存:
|
||||||
|
|
||||||
- `email`
|
- `email`
|
||||||
@@ -154,7 +161,8 @@ Step 1 和 Step 9 都依赖这个地址。
|
|||||||
|
|
||||||
使用方式:
|
使用方式:
|
||||||
|
|
||||||
- 先新增账号
|
- 先配置 Hotmail API 相关设置
|
||||||
|
- 再新增账号
|
||||||
- 点击 `校验`
|
- 点击 `校验`
|
||||||
- 校验通过后,可点击 `测试收信`
|
- 校验通过后,可点击 `测试收信`
|
||||||
- Auto 模式每轮会自动选用一个可用账号
|
- Auto 模式每轮会自动选用一个可用账号
|
||||||
@@ -406,7 +414,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
|||||||
|
|
||||||
支持:
|
支持:
|
||||||
|
|
||||||
- `Hotmail`(Microsoft Graph 邮件接口)
|
- `Hotmail`(第三方邮件 API)
|
||||||
- `content/qq-mail.js`
|
- `content/qq-mail.js`
|
||||||
- `content/mail-163.js`
|
- `content/mail-163.js`
|
||||||
- `content/inbucket-mail.js`
|
- `content/inbucket-mail.js`
|
||||||
|
|||||||
+82
-201
@@ -3,11 +3,11 @@
|
|||||||
importScripts('data/names.js', 'hotmail-utils.js', 'content/activation-utils.js');
|
importScripts('data/names.js', 'hotmail-utils.js', 'content/activation-utils.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
buildHotmailGraphMessagesUrl,
|
buildHotmailMailApiLatestUrl,
|
||||||
extractVerificationCodeFromMessage,
|
extractVerificationCodeFromMessage,
|
||||||
filterHotmailAccountsByUsage,
|
filterHotmailAccountsByUsage,
|
||||||
getLatestHotmailMessage,
|
getLatestHotmailMessage,
|
||||||
getHotmailGraphRequestConfig,
|
getHotmailMailApiRequestConfig,
|
||||||
getHotmailVerificationPollConfig,
|
getHotmailVerificationPollConfig,
|
||||||
getHotmailVerificationRequestTimestamp,
|
getHotmailVerificationRequestTimestamp,
|
||||||
normalizeHotmailMailApiMessages,
|
normalizeHotmailMailApiMessages,
|
||||||
@@ -20,8 +20,6 @@ const {
|
|||||||
const {
|
const {
|
||||||
isRecoverableStep9AuthFailure,
|
isRecoverableStep9AuthFailure,
|
||||||
} = self.MultiPageActivationUtils;
|
} = self.MultiPageActivationUtils;
|
||||||
const buildHotmailMailApiLatestUrl = buildHotmailGraphMessagesUrl;
|
|
||||||
const getHotmailMailApiRequestConfig = getHotmailGraphRequestConfig;
|
|
||||||
|
|
||||||
const LOG_PREFIX = '[MultiPage:bg]';
|
const LOG_PREFIX = '[MultiPage:bg]';
|
||||||
const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
|
const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
|
||||||
@@ -40,6 +38,10 @@ const AUTO_RUN_ALARM_NAME = 'scheduled-auto-run';
|
|||||||
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
|
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
|
||||||
const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
|
const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
|
||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
|
const DEFAULT_HOTMAIL_MAIL_API_URL = 'https://apple.882263.xyz/api/mail-new';
|
||||||
|
const DEFAULT_HOTMAIL_MAIL_API_RESPONSE_TYPE = 'json';
|
||||||
|
const DEFAULT_HOTMAIL_MAIL_API_INBOX_MAILBOX = 'INBOX';
|
||||||
|
const DEFAULT_HOTMAIL_MAIL_API_JUNK_MAILBOX = 'Junk';
|
||||||
|
|
||||||
initializeSessionStorageAccess();
|
initializeSessionStorageAccess();
|
||||||
|
|
||||||
@@ -64,6 +66,10 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
emailGenerator: 'duck', // 注册邮箱生成方式:duck / cloudflare。
|
emailGenerator: 'duck', // 注册邮箱生成方式:duck / cloudflare。
|
||||||
inbucketHost: '', // 仅当 mailProvider 为 inbucket 时填写 Inbucket 地址,其他情况保持为空。
|
inbucketHost: '', // 仅当 mailProvider 为 inbucket 时填写 Inbucket 地址,其他情况保持为空。
|
||||||
inbucketMailbox: '', // 仅当 mailProvider 为 inbucket 时填写邮箱名,其他情况保持为空。
|
inbucketMailbox: '', // 仅当 mailProvider 为 inbucket 时填写邮箱名,其他情况保持为空。
|
||||||
|
hotmailApiUrl: DEFAULT_HOTMAIL_MAIL_API_URL, // Hotmail 第三方邮件 API 地址。
|
||||||
|
hotmailApiResponseType: DEFAULT_HOTMAIL_MAIL_API_RESPONSE_TYPE, // Hotmail 第三方邮件 API 的 response_type 参数。
|
||||||
|
hotmailApiInboxMailbox: DEFAULT_HOTMAIL_MAIL_API_INBOX_MAILBOX, // Hotmail API 收件箱参数值。
|
||||||
|
hotmailApiJunkMailbox: DEFAULT_HOTMAIL_MAIL_API_JUNK_MAILBOX, // Hotmail API 垃圾箱参数值。
|
||||||
cloudflareDomain: '', // 仅当 emailGenerator=cloudflare 时填写自定义域名。
|
cloudflareDomain: '', // 仅当 emailGenerator=cloudflare 时填写自定义域名。
|
||||||
cloudflareDomains: [], // Cloudflare 可选域名列表。
|
cloudflareDomains: [], // Cloudflare 可选域名列表。
|
||||||
hotmailAccounts: [],
|
hotmailAccounts: [],
|
||||||
@@ -191,6 +197,56 @@ function normalizeCloudflareDomains(values) {
|
|||||||
return normalizedDomains;
|
return normalizedDomains;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeHotmailMailApiUrl(rawValue = '') {
|
||||||
|
const value = String(rawValue || '').trim();
|
||||||
|
if (!value) return DEFAULT_HOTMAIL_MAIL_API_URL;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(value);
|
||||||
|
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||||
|
return DEFAULT_HOTMAIL_MAIL_API_URL;
|
||||||
|
}
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
return DEFAULT_HOTMAIL_MAIL_API_URL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHotmailMailApiResponseType(rawValue) {
|
||||||
|
if (rawValue === undefined || rawValue === null) {
|
||||||
|
return DEFAULT_HOTMAIL_MAIL_API_RESPONSE_TYPE;
|
||||||
|
}
|
||||||
|
return String(rawValue).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHotmailMailApiMailbox(rawValue, fallback) {
|
||||||
|
if (rawValue === undefined || rawValue === null) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
const value = String(rawValue).trim();
|
||||||
|
return value || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHotmailMailApiSettings(state = {}) {
|
||||||
|
return {
|
||||||
|
apiUrl: normalizeHotmailMailApiUrl(state.hotmailApiUrl),
|
||||||
|
responseType: normalizeHotmailMailApiResponseType(state.hotmailApiResponseType),
|
||||||
|
inboxMailbox: normalizeHotmailMailApiMailbox(state.hotmailApiInboxMailbox, DEFAULT_HOTMAIL_MAIL_API_INBOX_MAILBOX),
|
||||||
|
junkMailbox: normalizeHotmailMailApiMailbox(state.hotmailApiJunkMailbox, DEFAULT_HOTMAIL_MAIL_API_JUNK_MAILBOX),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveHotmailMailApiMailbox(mailbox, settings = {}) {
|
||||||
|
const normalized = String(mailbox || '').trim().toLowerCase();
|
||||||
|
if (normalized === 'junk' || normalized === 'junk email' || normalized === 'junkemail') {
|
||||||
|
return normalizeHotmailMailApiMailbox(settings.junkMailbox, DEFAULT_HOTMAIL_MAIL_API_JUNK_MAILBOX);
|
||||||
|
}
|
||||||
|
if (normalized === 'inbox' || !normalized) {
|
||||||
|
return normalizeHotmailMailApiMailbox(settings.inboxMailbox, DEFAULT_HOTMAIL_MAIL_API_INBOX_MAILBOX);
|
||||||
|
}
|
||||||
|
return String(mailbox || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePersistentSettingValue(key, value) {
|
function normalizePersistentSettingValue(key, value) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'panelMode':
|
case 'panelMode':
|
||||||
@@ -224,6 +280,14 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'inbucketMailbox':
|
case 'inbucketMailbox':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
|
case 'hotmailApiUrl':
|
||||||
|
return normalizeHotmailMailApiUrl(value);
|
||||||
|
case 'hotmailApiResponseType':
|
||||||
|
return normalizeHotmailMailApiResponseType(value);
|
||||||
|
case 'hotmailApiInboxMailbox':
|
||||||
|
return normalizeHotmailMailApiMailbox(value, DEFAULT_HOTMAIL_MAIL_API_INBOX_MAILBOX);
|
||||||
|
case 'hotmailApiJunkMailbox':
|
||||||
|
return normalizeHotmailMailApiMailbox(value, DEFAULT_HOTMAIL_MAIL_API_JUNK_MAILBOX);
|
||||||
case 'cloudflareDomain':
|
case 'cloudflareDomain':
|
||||||
return normalizeCloudflareDomain(value);
|
return normalizeCloudflareDomain(value);
|
||||||
case 'cloudflareDomains':
|
case 'cloudflareDomains':
|
||||||
@@ -449,16 +513,14 @@ function normalizeHotmailAccount(account = {}) {
|
|||||||
const normalizedLastAuthAt = Number.isFinite(Number(account.lastAuthAt)) ? Number(account.lastAuthAt) : 0;
|
const normalizedLastAuthAt = Number.isFinite(Number(account.lastAuthAt)) ? Number(account.lastAuthAt) : 0;
|
||||||
const normalizedStatus = String(
|
const normalizedStatus = String(
|
||||||
account.status
|
account.status
|
||||||
|| (normalizedLastAuthAt > 0 || account.accessToken ? 'authorized' : 'pending')
|
|| (normalizedLastAuthAt > 0 ? 'authorized' : 'pending')
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
id: String(account.id || crypto.randomUUID()),
|
id: String(account.id || crypto.randomUUID()),
|
||||||
email: String(account.email || '').trim(),
|
email: String(account.email || '').trim(),
|
||||||
password: String(account.password || ''),
|
password: String(account.password || ''),
|
||||||
clientId: String(account.clientId || '').trim(),
|
clientId: String(account.clientId || '').trim(),
|
||||||
accessToken: String(account.accessToken || ''),
|
|
||||||
refreshToken: String(account.refreshToken || ''),
|
refreshToken: String(account.refreshToken || ''),
|
||||||
expiresAt: Number.isFinite(Number(account.expiresAt)) ? Number(account.expiresAt) : 0,
|
|
||||||
status: normalizedStatus,
|
status: normalizedStatus,
|
||||||
enabled: account.enabled !== undefined ? Boolean(account.enabled) : true,
|
enabled: account.enabled !== undefined ? Boolean(account.enabled) : true,
|
||||||
used: Boolean(account.used),
|
used: Boolean(account.used),
|
||||||
@@ -513,8 +575,6 @@ async function upsertHotmailAccount(input) {
|
|||||||
const normalized = normalizeHotmailAccount({
|
const normalized = normalizeHotmailAccount({
|
||||||
...(existing || {}),
|
...(existing || {}),
|
||||||
...(credentialsChanged ? {
|
...(credentialsChanged ? {
|
||||||
accessToken: '',
|
|
||||||
expiresAt: 0,
|
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
lastAuthAt: 0,
|
lastAuthAt: 0,
|
||||||
lastError: '',
|
lastError: '',
|
||||||
@@ -650,7 +710,7 @@ async function ensureHotmailAccountForFlow(options = {}) {
|
|||||||
return setCurrentHotmailAccount(account.id, { markUsed, syncEmail: true });
|
return setCurrentHotmailAccount(account.id, { markUsed, syncEmail: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestHotmailMailApiLegacy(account, mailbox = 'INBOX') {
|
async function requestHotmailMailApi(account, mailbox = 'INBOX') {
|
||||||
if (!account?.email) {
|
if (!account?.email) {
|
||||||
throw new Error('Hotmail 账号缺少邮箱地址。');
|
throw new Error('Hotmail 账号缺少邮箱地址。');
|
||||||
}
|
}
|
||||||
@@ -661,12 +721,14 @@ async function requestHotmailMailApiLegacy(account, mailbox = 'INBOX') {
|
|||||||
throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`);
|
throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hotmailApiSettings = getHotmailMailApiSettings(await getState());
|
||||||
const url = buildHotmailMailApiLatestUrl({
|
const url = buildHotmailMailApiLatestUrl({
|
||||||
|
apiUrl: hotmailApiSettings.apiUrl,
|
||||||
clientId: account.clientId,
|
clientId: account.clientId,
|
||||||
email: account.email,
|
email: account.email,
|
||||||
refreshToken: account.refreshToken,
|
refreshToken: account.refreshToken,
|
||||||
mailbox,
|
mailbox: resolveHotmailMailApiMailbox(mailbox, hotmailApiSettings),
|
||||||
responseType: 'json',
|
responseType: hotmailApiSettings.responseType,
|
||||||
});
|
});
|
||||||
const { timeoutMs } = getHotmailMailApiRequestConfig();
|
const { timeoutMs } = getHotmailMailApiRequestConfig();
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@@ -710,12 +772,10 @@ async function requestHotmailMailApiLegacy(account, mailbox = 'INBOX') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyHotmailApiResultToAccountLegacy(account, apiResult) {
|
function applyHotmailApiResultToAccount(account, apiResult) {
|
||||||
const nextRefreshToken = String(apiResult?.nextRefreshToken || '').trim();
|
const nextRefreshToken = String(apiResult?.nextRefreshToken || '').trim();
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
accessToken: '',
|
|
||||||
expiresAt: 0,
|
|
||||||
refreshToken: nextRefreshToken || account.refreshToken,
|
refreshToken: nextRefreshToken || account.refreshToken,
|
||||||
status: 'authorized',
|
status: 'authorized',
|
||||||
lastAuthAt: Date.now(),
|
lastAuthAt: Date.now(),
|
||||||
@@ -723,168 +783,9 @@ function applyHotmailApiResultToAccountLegacy(account, apiResult) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchHotmailMailboxMessagesLegacy(account, mailboxes = HOTMAIL_MAILBOXES) {
|
function buildHotmailMailApiFailureAccount(account, errorMessage) {
|
||||||
let workingAccount = normalizeHotmailAccount(account);
|
|
||||||
const mailboxResults = [];
|
|
||||||
|
|
||||||
for (const mailbox of mailboxes) {
|
|
||||||
const result = await requestHotmailMailApiLegacy(workingAccount, mailbox);
|
|
||||||
workingAccount = applyHotmailApiResultToAccountLegacy(workingAccount, result);
|
|
||||||
mailboxResults.push({
|
|
||||||
mailbox,
|
|
||||||
count: result.messages.length,
|
|
||||||
messages: result.messages.map((message) => ({ ...message, mailbox })),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedAccount = await upsertHotmailAccount(workingAccount);
|
|
||||||
return {
|
|
||||||
account: savedAccount,
|
|
||||||
mailboxResults,
|
|
||||||
messages: mailboxResults.flatMap((item) => item.messages),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isHotmailAccessTokenUsable(account, now = Date.now()) {
|
|
||||||
return Boolean(account?.accessToken)
|
|
||||||
&& Number(account?.expiresAt || 0) > now + 60_000;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshHotmailAccessToken(account) {
|
|
||||||
if (!account?.email) {
|
|
||||||
throw new Error('Hotmail 账号缺少邮箱地址。');
|
|
||||||
}
|
|
||||||
if (!account?.clientId) {
|
|
||||||
throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少客户端 ID。`);
|
|
||||||
}
|
|
||||||
if (!account?.refreshToken) {
|
|
||||||
throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { timeoutMs, scopes, tokenUrl } = getHotmailGraphRequestConfig();
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
|
|
||||||
const formData = new URLSearchParams();
|
|
||||||
formData.set('client_id', account.clientId);
|
|
||||||
formData.set('grant_type', 'refresh_token');
|
|
||||||
formData.set('refresh_token', account.refreshToken);
|
|
||||||
formData.set('scope', scopes.join(' '));
|
|
||||||
formData.set('redirect_uri', 'https://login.microsoftonline.com/common/oauth2/nativeclient');
|
|
||||||
|
|
||||||
let response;
|
|
||||||
try {
|
|
||||||
response = await fetch(tokenUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: formData.toString(),
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const error = new Error(
|
|
||||||
err?.name === 'AbortError'
|
|
||||||
? `Hotmail 令牌刷新超时(>${Math.round(timeoutMs / 1000)} 秒)`
|
|
||||||
: `Hotmail 令牌刷新失败:${err.message}`
|
|
||||||
);
|
|
||||||
error.code = 'HOTMAIL_TOKEN_REFRESH_FAILED';
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
let payload = {};
|
|
||||||
try {
|
|
||||||
payload = text ? JSON.parse(text) : {};
|
|
||||||
} catch {
|
|
||||||
payload = { raw: text };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok || !payload?.access_token) {
|
|
||||||
const rawErrorText = payload?.error_description || payload?.error?.message || payload?.error || payload?.message || text || `HTTP ${response.status}`;
|
|
||||||
const isCrossOriginError = typeof rawErrorText === 'string' && rawErrorText.includes('AADSTS90023');
|
|
||||||
const errorText = isCrossOriginError
|
|
||||||
? `Azure AD 拒绝了跨域令牌请求(AADSTS90023)。请在 Azure AD 应用注册中将应用平台改为"单页应用程序(SPA)",并将重定向 URI 设置为 https://login.microsoftonline.com/common/oauth2/nativeclient,或将应用类型改为"移动和桌面应用程序(Native)"。`
|
|
||||||
: rawErrorText;
|
|
||||||
const error = new Error(`Hotmail 令牌刷新失败:${errorText}`);
|
|
||||||
error.code = 'HOTMAIL_TOKEN_REFRESH_FAILED';
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const expiresInSeconds = Math.max(60, Number(payload.expires_in || payload.expiresIn || 0) || 3600);
|
|
||||||
return normalizeHotmailAccount({
|
return normalizeHotmailAccount({
|
||||||
...account,
|
...account,
|
||||||
accessToken: String(payload.access_token || ''),
|
|
||||||
refreshToken: String(payload.refresh_token || '').trim() || account.refreshToken,
|
|
||||||
expiresAt: Date.now() + expiresInSeconds * 1000,
|
|
||||||
status: 'authorized',
|
|
||||||
lastAuthAt: Date.now(),
|
|
||||||
lastError: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requestHotmailGraphMessages(account, mailbox = 'INBOX') {
|
|
||||||
const { timeoutMs, pageSize, messageFields } = getHotmailGraphRequestConfig();
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
|
|
||||||
const url = buildHotmailGraphMessagesUrl({
|
|
||||||
mailbox,
|
|
||||||
top: pageSize,
|
|
||||||
selectFields: messageFields,
|
|
||||||
});
|
|
||||||
|
|
||||||
let response;
|
|
||||||
try {
|
|
||||||
response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${account.accessToken}`,
|
|
||||||
},
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const error = new Error(
|
|
||||||
err?.name === 'AbortError'
|
|
||||||
? `Hotmail 邮件请求超时(>${Math.round(timeoutMs / 1000)} 秒):${mailbox}`
|
|
||||||
: `Hotmail 邮件请求失败:${err.message}`
|
|
||||||
);
|
|
||||||
error.code = 'HOTMAIL_GRAPH_REQUEST_FAILED';
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
let payload = {};
|
|
||||||
try {
|
|
||||||
payload = text ? JSON.parse(text) : {};
|
|
||||||
} catch {
|
|
||||||
payload = { raw: text };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = payload?.error?.message || payload?.error_description || payload?.message || text || `HTTP ${response.status}`;
|
|
||||||
const error = new Error(`Hotmail 邮件请求失败:${errorText}`);
|
|
||||||
error.code = response.status === 401 || response.status === 403
|
|
||||||
? 'HOTMAIL_GRAPH_AUTH_FAILED'
|
|
||||||
: 'HOTMAIL_GRAPH_REQUEST_FAILED';
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
mailbox,
|
|
||||||
payload,
|
|
||||||
messages: normalizeHotmailMailApiMessages(payload?.value),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHotmailAuthFailureAccount(account, errorMessage) {
|
|
||||||
return normalizeHotmailAccount({
|
|
||||||
...account,
|
|
||||||
accessToken: '',
|
|
||||||
expiresAt: 0,
|
|
||||||
status: 'error',
|
status: 'error',
|
||||||
lastError: String(errorMessage || ''),
|
lastError: String(errorMessage || ''),
|
||||||
});
|
});
|
||||||
@@ -895,27 +796,9 @@ async function fetchHotmailMailboxMessages(account, mailboxes = HOTMAIL_MAILBOXE
|
|||||||
const mailboxResults = [];
|
const mailboxResults = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!isHotmailAccessTokenUsable(workingAccount)) {
|
|
||||||
workingAccount = await refreshHotmailAccessToken(workingAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const mailbox of mailboxes) {
|
for (const mailbox of mailboxes) {
|
||||||
let result;
|
const result = await requestHotmailMailApi(workingAccount, mailbox);
|
||||||
try {
|
workingAccount = applyHotmailApiResultToAccount(workingAccount, result);
|
||||||
result = await requestHotmailGraphMessages(workingAccount, mailbox);
|
|
||||||
} catch (err) {
|
|
||||||
if (err?.code !== 'HOTMAIL_GRAPH_AUTH_FAILED') {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
workingAccount = await refreshHotmailAccessToken({
|
|
||||||
...workingAccount,
|
|
||||||
accessToken: '',
|
|
||||||
expiresAt: 0,
|
|
||||||
});
|
|
||||||
result = await requestHotmailGraphMessages(workingAccount, mailbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
mailboxResults.push({
|
mailboxResults.push({
|
||||||
mailbox,
|
mailbox,
|
||||||
count: result.messages.length,
|
count: result.messages.length,
|
||||||
@@ -923,10 +806,8 @@ async function fetchHotmailMailboxMessages(account, mailboxes = HOTMAIL_MAILBOXE
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err?.code === 'HOTMAIL_TOKEN_REFRESH_FAILED' || err?.code === 'HOTMAIL_GRAPH_AUTH_FAILED') {
|
const failedAccount = buildHotmailMailApiFailureAccount(workingAccount, err.message);
|
||||||
const failedAccount = buildHotmailAuthFailureAccount(workingAccount, err.message);
|
|
||||||
await upsertHotmailAccount(failedAccount);
|
await upsertHotmailAccount(failedAccount);
|
||||||
}
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1874,7 +1755,7 @@ function getSourceLabel(source) {
|
|||||||
'mail-163': '163 邮箱',
|
'mail-163': '163 邮箱',
|
||||||
'inbucket-mail': 'Inbucket 邮箱',
|
'inbucket-mail': 'Inbucket 邮箱',
|
||||||
'duck-mail': 'Duck 邮箱',
|
'duck-mail': 'Duck 邮箱',
|
||||||
'hotmail-api': 'Hotmail(微软 Graph)',
|
'hotmail-api': 'Hotmail(第三方邮件 API)',
|
||||||
};
|
};
|
||||||
return labels[source] || source || '未知来源';
|
return labels[source] || source || '未知来源';
|
||||||
}
|
}
|
||||||
@@ -2650,7 +2531,7 @@ async function handleMessage(message, sender) {
|
|||||||
const updates = buildPersistentSettingsPayload(message.payload || {});
|
const updates = buildPersistentSettingsPayload(message.payload || {});
|
||||||
await setPersistentSettings(updates);
|
await setPersistentSettings(updates);
|
||||||
await setState(updates);
|
await setState(updates);
|
||||||
return { ok: true };
|
return { ok: true, state: await getState() };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'EXPORT_SETTINGS': {
|
case 'EXPORT_SETTINGS': {
|
||||||
@@ -3738,7 +3619,7 @@ async function executeStep3(state) {
|
|||||||
function getMailConfig(state) {
|
function getMailConfig(state) {
|
||||||
const provider = state.mailProvider || 'qq';
|
const provider = state.mailProvider || 'qq';
|
||||||
if (provider === HOTMAIL_PROVIDER) {
|
if (provider === HOTMAIL_PROVIDER) {
|
||||||
return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(微软 Graph)' };
|
return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(第三方邮件 API)' };
|
||||||
}
|
}
|
||||||
if (provider === '163') {
|
if (provider === '163') {
|
||||||
return { source: 'mail-163', url: 'https://mail.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 邮箱' };
|
return { source: 'mail-163', url: 'https://mail.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 邮箱' };
|
||||||
|
|||||||
+16
-37
@@ -6,22 +6,7 @@
|
|||||||
|
|
||||||
root.HotmailUtils = factory();
|
root.HotmailUtils = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createHotmailUtils() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createHotmailUtils() {
|
||||||
const HOTMAIL_MICROSOFT_TOKEN_URL = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token';
|
const HOTMAIL_MAIL_API_URL = 'https://apple.882263.xyz/api/mail-new';
|
||||||
const HOTMAIL_GRAPH_API_ORIGIN = 'https://graph.microsoft.com';
|
|
||||||
const HOTMAIL_GRAPH_PAGE_SIZE = 10;
|
|
||||||
const HOTMAIL_GRAPH_MESSAGE_FIELDS = [
|
|
||||||
'id',
|
|
||||||
'internetMessageId',
|
|
||||||
'subject',
|
|
||||||
'from',
|
|
||||||
'bodyPreview',
|
|
||||||
'receivedDateTime',
|
|
||||||
];
|
|
||||||
const HOTMAIL_GRAPH_SCOPES = [
|
|
||||||
'offline_access',
|
|
||||||
'https://graph.microsoft.com/Mail.Read',
|
|
||||||
'https://graph.microsoft.com/User.Read',
|
|
||||||
];
|
|
||||||
|
|
||||||
function normalizeText(value) {
|
function normalizeText(value) {
|
||||||
return String(value || '')
|
return String(value || '')
|
||||||
@@ -291,20 +276,19 @@
|
|||||||
return list.map((message) => normalizeHotmailMailApiMessage(message));
|
return list.map((message) => normalizeHotmailMailApiMessage(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeHotmailMailboxId(mailbox = 'INBOX') {
|
function buildHotmailMailApiLatestUrl(options) {
|
||||||
const normalized = normalizeText(mailbox);
|
const apiUrl = String(options?.apiUrl || '').trim() || HOTMAIL_MAIL_API_URL;
|
||||||
if (normalized === 'junk' || normalized === 'junk email' || normalized === 'junkemail') {
|
const url = new URL(apiUrl);
|
||||||
return 'junkemail';
|
url.searchParams.set('refresh_token', String(options?.refreshToken || ''));
|
||||||
|
url.searchParams.set('client_id', String(options?.clientId || ''));
|
||||||
|
url.searchParams.set('email', String(options?.email || ''));
|
||||||
|
url.searchParams.set('mailbox', String(options?.mailbox || 'INBOX'));
|
||||||
|
const responseType = options?.responseType === undefined || options?.responseType === null
|
||||||
|
? 'json'
|
||||||
|
: String(options.responseType).trim();
|
||||||
|
if (responseType) {
|
||||||
|
url.searchParams.set('response_type', responseType);
|
||||||
}
|
}
|
||||||
return 'inbox';
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHotmailGraphMessagesUrl(options) {
|
|
||||||
const folderId = normalizeHotmailMailboxId(options?.mailbox);
|
|
||||||
const url = new URL(`${HOTMAIL_GRAPH_API_ORIGIN}/v1.0/me/mailFolders/${folderId}/messages`);
|
|
||||||
url.searchParams.set('$top', String(options?.top || HOTMAIL_GRAPH_PAGE_SIZE));
|
|
||||||
url.searchParams.set('$select', (options?.selectFields || HOTMAIL_GRAPH_MESSAGE_FIELDS).join(','));
|
|
||||||
url.searchParams.set('$orderby', String(options?.orderBy || 'receivedDateTime desc'));
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,13 +332,9 @@
|
|||||||
: (flowStartTime || 0);
|
: (flowStartTime || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHotmailGraphRequestConfig() {
|
function getHotmailMailApiRequestConfig() {
|
||||||
return {
|
return {
|
||||||
timeoutMs: 15000,
|
timeoutMs: 15000,
|
||||||
pageSize: HOTMAIL_GRAPH_PAGE_SIZE,
|
|
||||||
scopes: HOTMAIL_GRAPH_SCOPES.slice(),
|
|
||||||
tokenUrl: HOTMAIL_MICROSOFT_TOKEN_URL,
|
|
||||||
messageFields: HOTMAIL_GRAPH_MESSAGE_FIELDS.slice(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,17 +357,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildHotmailGraphMessagesUrl,
|
buildHotmailMailApiLatestUrl,
|
||||||
extractVerificationCodeFromMessage,
|
extractVerificationCodeFromMessage,
|
||||||
filterHotmailAccountsByUsage,
|
filterHotmailAccountsByUsage,
|
||||||
extractVerificationCode,
|
extractVerificationCode,
|
||||||
getLatestHotmailMessage,
|
getLatestHotmailMessage,
|
||||||
getHotmailBulkActionLabel,
|
getHotmailBulkActionLabel,
|
||||||
getHotmailListToggleLabel,
|
getHotmailListToggleLabel,
|
||||||
getHotmailGraphRequestConfig,
|
getHotmailMailApiRequestConfig,
|
||||||
getHotmailVerificationPollConfig,
|
getHotmailVerificationPollConfig,
|
||||||
getHotmailVerificationRequestTimestamp,
|
getHotmailVerificationRequestTimestamp,
|
||||||
normalizeHotmailMailboxId,
|
|
||||||
isAuthorizedHotmailAccount,
|
isAuthorizedHotmailAccount,
|
||||||
normalizeHotmailMailApiMessages,
|
normalizeHotmailMailApiMessages,
|
||||||
normalizeTimestamp,
|
normalizeTimestamp,
|
||||||
|
|||||||
@@ -130,7 +130,7 @@
|
|||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">邮箱服务</span>
|
<span class="data-label">邮箱服务</span>
|
||||||
<select id="select-mail-provider" class="data-select">
|
<select id="select-mail-provider" class="data-select">
|
||||||
<option value="hotmail-api">Hotmail(微软 Graph)</option>
|
<option value="hotmail-api">Hotmail(第三方邮件 API)</option>
|
||||||
<option value="163">163 邮箱 (mail.163.com)</option>
|
<option value="163">163 邮箱 (mail.163.com)</option>
|
||||||
<option value="163-vip">163 VIP 邮箱 (webmail.vip.163.com)</option>
|
<option value="163-vip">163 VIP 邮箱 (webmail.vip.163.com)</option>
|
||||||
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
|
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
<div class="section-mini-header">
|
<div class="section-mini-header">
|
||||||
<div class="section-mini-copy">
|
<div class="section-mini-copy">
|
||||||
<span class="section-label">Hotmail 账号池</span>
|
<span class="section-label">Hotmail 账号池</span>
|
||||||
<span class="section-hint">每轮自动分配一个可用账号</span>
|
<span class="section-hint">先配置 API,再维护账号池并校验</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="section-mini-actions">
|
<div class="section-mini-actions">
|
||||||
<button id="btn-clear-used-hotmail-accounts" class="btn btn-ghost btn-xs" type="button">清空已用</button>
|
<button id="btn-clear-used-hotmail-accounts" class="btn btn-ghost btn-xs" type="button">清空已用</button>
|
||||||
@@ -213,6 +213,22 @@
|
|||||||
<button id="btn-toggle-hotmail-list" class="btn btn-ghost btn-xs" type="button" aria-expanded="false">展开列表</button>
|
<button id="btn-toggle-hotmail-list" class="btn btn-ghost btn-xs" type="button" aria-expanded="false">展开列表</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">API 地址</span>
|
||||||
|
<input type="text" id="input-hotmail-api-url" class="data-input mono" placeholder="https://apple.882263.xyz/api/mail-new" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">响应类型</span>
|
||||||
|
<input type="text" id="input-hotmail-api-response-type" class="data-input mono" placeholder="json;留空则不传 response_type" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">收件箱参数</span>
|
||||||
|
<input type="text" id="input-hotmail-api-inbox-mailbox" class="data-input mono" placeholder="INBOX" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">垃圾箱参数</span>
|
||||||
|
<input type="text" id="input-hotmail-api-junk-mailbox" class="data-input mono" placeholder="Junk" />
|
||||||
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">邮箱</span>
|
<span class="data-label">邮箱</span>
|
||||||
<input type="text" id="input-hotmail-email" class="data-input" placeholder="name@hotmail.com" />
|
<input type="text" id="input-hotmail-email" class="data-input" placeholder="name@hotmail.com" />
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ const selectMailProvider = document.getElementById('select-mail-provider');
|
|||||||
const rowEmailGenerator = document.getElementById('row-email-generator');
|
const rowEmailGenerator = document.getElementById('row-email-generator');
|
||||||
const selectEmailGenerator = document.getElementById('select-email-generator');
|
const selectEmailGenerator = document.getElementById('select-email-generator');
|
||||||
const hotmailSection = document.getElementById('hotmail-section');
|
const hotmailSection = document.getElementById('hotmail-section');
|
||||||
|
const inputHotmailApiUrl = document.getElementById('input-hotmail-api-url');
|
||||||
|
const inputHotmailApiResponseType = document.getElementById('input-hotmail-api-response-type');
|
||||||
|
const inputHotmailApiInboxMailbox = document.getElementById('input-hotmail-api-inbox-mailbox');
|
||||||
|
const inputHotmailApiJunkMailbox = document.getElementById('input-hotmail-api-junk-mailbox');
|
||||||
const inputHotmailEmail = document.getElementById('input-hotmail-email');
|
const inputHotmailEmail = document.getElementById('input-hotmail-email');
|
||||||
const inputHotmailClientId = document.getElementById('input-hotmail-client-id');
|
const inputHotmailClientId = document.getElementById('input-hotmail-client-id');
|
||||||
const inputHotmailPassword = document.getElementById('input-hotmail-password');
|
const inputHotmailPassword = document.getElementById('input-hotmail-password');
|
||||||
@@ -597,6 +601,10 @@ function collectSettingsPayload() {
|
|||||||
emailGenerator: selectEmailGenerator.value,
|
emailGenerator: selectEmailGenerator.value,
|
||||||
inbucketHost: inputInbucketHost.value.trim(),
|
inbucketHost: inputInbucketHost.value.trim(),
|
||||||
inbucketMailbox: inputInbucketMailbox.value.trim(),
|
inbucketMailbox: inputInbucketMailbox.value.trim(),
|
||||||
|
hotmailApiUrl: inputHotmailApiUrl.value.trim(),
|
||||||
|
hotmailApiResponseType: inputHotmailApiResponseType.value.trim(),
|
||||||
|
hotmailApiInboxMailbox: inputHotmailApiInboxMailbox.value.trim(),
|
||||||
|
hotmailApiJunkMailbox: inputHotmailApiJunkMailbox.value.trim(),
|
||||||
cloudflareDomain: selectedCloudflareDomain,
|
cloudflareDomain: selectedCloudflareDomain,
|
||||||
cloudflareDomains: domains,
|
cloudflareDomains: domains,
|
||||||
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
||||||
@@ -708,11 +716,15 @@ async function saveSettings(options = {}) {
|
|||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response?.state) {
|
||||||
|
applySettingsState(response.state);
|
||||||
|
} else {
|
||||||
syncLatestState(payload);
|
syncLatestState(payload);
|
||||||
markSettingsDirty(false);
|
markSettingsDirty(false);
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
updateMailProviderUI();
|
updateMailProviderUI();
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
|
}
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
showToast('配置已保存', 'success', 1800);
|
showToast('配置已保存', 'success', 1800);
|
||||||
}
|
}
|
||||||
@@ -838,6 +850,10 @@ function applySettingsState(state) {
|
|||||||
selectEmailGenerator.value = state?.emailGenerator || 'duck';
|
selectEmailGenerator.value = state?.emailGenerator || 'duck';
|
||||||
inputInbucketHost.value = state?.inbucketHost || '';
|
inputInbucketHost.value = state?.inbucketHost || '';
|
||||||
inputInbucketMailbox.value = state?.inbucketMailbox || '';
|
inputInbucketMailbox.value = state?.inbucketMailbox || '';
|
||||||
|
inputHotmailApiUrl.value = state?.hotmailApiUrl || '';
|
||||||
|
inputHotmailApiResponseType.value = state?.hotmailApiResponseType ?? '';
|
||||||
|
inputHotmailApiInboxMailbox.value = state?.hotmailApiInboxMailbox || '';
|
||||||
|
inputHotmailApiJunkMailbox.value = state?.hotmailApiJunkMailbox || '';
|
||||||
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
|
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
|
||||||
setCloudflareDomainEditMode(false, { clearInput: true });
|
setCloudflareDomainEditMode(false, { clearInput: true });
|
||||||
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
||||||
@@ -2200,6 +2216,16 @@ inputVpsPassword.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[inputHotmailApiUrl, inputHotmailApiResponseType, inputHotmailApiInboxMailbox, inputHotmailApiJunkMailbox].forEach((input) => {
|
||||||
|
input?.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
input?.addEventListener('blur', () => {
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
inputPassword.addEventListener('input', () => {
|
inputPassword.addEventListener('input', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
|
|||||||
+31
-31
@@ -2,17 +2,16 @@ const test = require('node:test');
|
|||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
buildHotmailGraphMessagesUrl,
|
buildHotmailMailApiLatestUrl,
|
||||||
extractVerificationCodeFromMessage,
|
extractVerificationCodeFromMessage,
|
||||||
filterHotmailAccountsByUsage,
|
filterHotmailAccountsByUsage,
|
||||||
extractVerificationCode,
|
extractVerificationCode,
|
||||||
getLatestHotmailMessage,
|
getLatestHotmailMessage,
|
||||||
getHotmailBulkActionLabel,
|
getHotmailBulkActionLabel,
|
||||||
getHotmailListToggleLabel,
|
getHotmailListToggleLabel,
|
||||||
getHotmailGraphRequestConfig,
|
getHotmailMailApiRequestConfig,
|
||||||
getHotmailVerificationPollConfig,
|
getHotmailVerificationPollConfig,
|
||||||
getHotmailVerificationRequestTimestamp,
|
getHotmailVerificationRequestTimestamp,
|
||||||
normalizeHotmailMailboxId,
|
|
||||||
normalizeHotmailMailApiMessages,
|
normalizeHotmailMailApiMessages,
|
||||||
parseHotmailImportText,
|
parseHotmailImportText,
|
||||||
pickHotmailAccountForRun,
|
pickHotmailAccountForRun,
|
||||||
@@ -346,22 +345,38 @@ test('pickVerificationMessageWithTimeFallback can ignore afterTimestamp while ke
|
|||||||
assert.equal(result.usedTimeFallback, true);
|
assert.equal(result.usedTimeFallback, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('buildHotmailGraphMessagesUrl targets the official Microsoft Graph mailbox endpoint', () => {
|
test('buildHotmailMailApiLatestUrl includes email, client id, refresh token, and mailbox', () => {
|
||||||
const url = new URL(buildHotmailGraphMessagesUrl({
|
const url = new URL(buildHotmailMailApiLatestUrl({
|
||||||
|
clientId: 'client-123',
|
||||||
|
email: 'user@hotmail.com',
|
||||||
|
refreshToken: 'refresh-token-xyz',
|
||||||
mailbox: 'Junk',
|
mailbox: 'Junk',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
assert.equal(url.origin + url.pathname, 'https://graph.microsoft.com/v1.0/me/mailFolders/junkemail/messages');
|
assert.equal(url.origin + url.pathname, 'https://apple.882263.xyz/api/mail-new');
|
||||||
assert.equal(url.searchParams.get('$top'), '10');
|
assert.equal(url.searchParams.get('client_id'), 'client-123');
|
||||||
assert.equal(url.searchParams.get('$orderby'), 'receivedDateTime desc');
|
assert.equal(url.searchParams.get('email'), 'user@hotmail.com');
|
||||||
assert.match(url.searchParams.get('$select'), /subject/);
|
assert.equal(url.searchParams.get('refresh_token'), 'refresh-token-xyz');
|
||||||
assert.match(url.searchParams.get('$select'), /receivedDateTime/);
|
assert.equal(url.searchParams.get('mailbox'), 'Junk');
|
||||||
|
assert.equal(url.searchParams.get('response_type'), 'json');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normalizeHotmailMailboxId maps supported mailbox labels to Graph folder ids', () => {
|
test('buildHotmailMailApiLatestUrl supports custom api url and can omit response_type', () => {
|
||||||
assert.equal(normalizeHotmailMailboxId('INBOX'), 'inbox');
|
const url = new URL(buildHotmailMailApiLatestUrl({
|
||||||
assert.equal(normalizeHotmailMailboxId('Junk'), 'junkemail');
|
apiUrl: 'https://example.com/custom-mail-api',
|
||||||
assert.equal(normalizeHotmailMailboxId('junkemail'), 'junkemail');
|
clientId: 'client-789',
|
||||||
|
email: 'custom@hotmail.com',
|
||||||
|
refreshToken: 'refresh-token-custom',
|
||||||
|
mailbox: 'Spam',
|
||||||
|
responseType: '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert.equal(url.origin + url.pathname, 'https://example.com/custom-mail-api');
|
||||||
|
assert.equal(url.searchParams.get('client_id'), 'client-789');
|
||||||
|
assert.equal(url.searchParams.get('email'), 'custom@hotmail.com');
|
||||||
|
assert.equal(url.searchParams.get('refresh_token'), 'refresh-token-custom');
|
||||||
|
assert.equal(url.searchParams.get('mailbox'), 'Spam');
|
||||||
|
assert.equal(url.searchParams.has('response_type'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normalizeHotmailMailApiMessages maps third-party payload fields into verification message shape', () => {
|
test('normalizeHotmailMailApiMessages maps third-party payload fields into verification message shape', () => {
|
||||||
@@ -440,24 +455,9 @@ test('getHotmailVerificationRequestTimestamp prefers actual request timestamps w
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getHotmailGraphRequestConfig defines Microsoft Graph request defaults', () => {
|
test('getHotmailMailApiRequestConfig defines third-party mail API request defaults', () => {
|
||||||
assert.deepEqual(getHotmailGraphRequestConfig(), {
|
assert.deepEqual(getHotmailMailApiRequestConfig(), {
|
||||||
timeoutMs: 15000,
|
timeoutMs: 15000,
|
||||||
pageSize: 10,
|
|
||||||
scopes: [
|
|
||||||
'offline_access',
|
|
||||||
'https://graph.microsoft.com/Mail.Read',
|
|
||||||
'https://graph.microsoft.com/User.Read',
|
|
||||||
],
|
|
||||||
tokenUrl: 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token',
|
|
||||||
messageFields: [
|
|
||||||
'id',
|
|
||||||
'internetMessageId',
|
|
||||||
'subject',
|
|
||||||
'from',
|
|
||||||
'bodyPreview',
|
|
||||||
'receivedDateTime',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user