feat: integrate SUB2API OAuth flow into dev

- absorb PR #19 by adding SUB2API mode for OAuth link generation and callback account creation
- keep dev-side Hotmail and Cloudflare sidepanel workflows while resolving sidepanel merge conflicts
- avoid duplicate SUB2API side effects by using explicit long single-response timeouts for step 1 and step 9 content-script execution
This commit is contained in:
QLHazyCoder
2026-04-12 20:55:41 +08:00
4 changed files with 717 additions and 11 deletions
+25 -2
View File
@@ -64,6 +64,13 @@
<section id="data-section">
<div class="data-card">
<div class="data-row">
<span class="data-label">来源</span>
<select id="select-panel-mode" class="data-select">
<option value="cpa">CPA 面板</option>
<option value="sub2api">SUB2API</option>
</select>
</div>
<div class="data-row" id="row-vps-url">
<span class="data-label">CPA</span>
<div class="input-with-icon">
<input type="password" id="input-vps-url" class="data-input data-input-with-icon"
@@ -72,10 +79,26 @@
title="显示 CPA 地址"></button>
</div>
</div>
<div class="data-row">
<div class="data-row" id="row-vps-password">
<span class="data-label">管理密钥</span>
<input type="password" id="input-vps-password" class="data-input" placeholder="请输入 CPA 管理密钥" />
</div>
<div class="data-row" id="row-sub2api-url" style="display:none;">
<span class="data-label">SUB2API</span>
<input type="text" id="input-sub2api-url" class="data-input" placeholder="https://sub2api.hisence.fun/admin/accounts" />
</div>
<div class="data-row" id="row-sub2api-email" style="display:none;">
<span class="data-label">账号</span>
<input type="text" id="input-sub2api-email" class="data-input" placeholder="请输入 SUB2API 登录邮箱" />
</div>
<div class="data-row" id="row-sub2api-password" style="display:none;">
<span class="data-label">密码</span>
<input type="password" id="input-sub2api-password" class="data-input" placeholder="请输入 SUB2API 登录密码" />
</div>
<div class="data-row" id="row-sub2api-group" style="display:none;">
<span class="data-label">分组</span>
<input type="text" id="input-sub2api-group" class="data-input" placeholder="默认 codex" />
</div>
<div class="data-row">
<span class="data-label">codex密码</span>
<div class="data-inline">
@@ -279,7 +302,7 @@
</div>
<div class="step-row" data-step="9">
<div class="step-indicator" data-step="9"><span class="step-num">9</span></div>
<button class="step-btn" data-step="9">CPA 回调验证</button>
<button class="step-btn" data-step="9">平台回调验证</button>
<span class="step-status" data-step="9"></span>
</div>
</div>
+87
View File
@@ -33,8 +33,19 @@ const autoScheduleMeta = document.getElementById('auto-schedule-meta');
const btnAutoRunNow = document.getElementById('btn-auto-run-now');
const btnAutoCancelSchedule = document.getElementById('btn-auto-cancel-schedule');
const btnClearLog = document.getElementById('btn-clear-log');
const selectPanelMode = document.getElementById('select-panel-mode');
const rowVpsUrl = document.getElementById('row-vps-url');
const inputVpsUrl = document.getElementById('input-vps-url');
const rowVpsPassword = document.getElementById('row-vps-password');
const inputVpsPassword = document.getElementById('input-vps-password');
const rowSub2ApiUrl = document.getElementById('row-sub2api-url');
const inputSub2ApiUrl = document.getElementById('input-sub2api-url');
const rowSub2ApiEmail = document.getElementById('row-sub2api-email');
const inputSub2ApiEmail = document.getElementById('input-sub2api-email');
const rowSub2ApiPassword = document.getElementById('row-sub2api-password');
const inputSub2ApiPassword = document.getElementById('input-sub2api-password');
const rowSub2ApiGroup = document.getElementById('row-sub2api-group');
const inputSub2ApiGroup = document.getElementById('input-sub2api-group');
const selectMailProvider = document.getElementById('select-mail-provider');
const selectEmailGenerator = document.getElementById('select-email-generator');
const hotmailSection = document.getElementById('hotmail-section');
@@ -478,8 +489,13 @@ function collectSettingsPayload() {
!cloudflareDomainEditMode ? selectCfDomain.value : activeDomain
) || activeDomain;
return {
panelMode: selectPanelMode.value,
vpsUrl: inputVpsUrl.value.trim(),
vpsPassword: inputVpsPassword.value,
sub2apiUrl: inputSub2ApiUrl.value.trim(),
sub2apiEmail: inputSub2ApiEmail.value.trim(),
sub2apiPassword: inputSub2ApiPassword.value,
sub2apiGroupName: inputSub2ApiGroup.value.trim(),
customPassword: inputPassword.value,
mailProvider: selectMailProvider.value,
emailGenerator: selectEmailGenerator.value,
@@ -535,6 +551,7 @@ async function saveSettings(options = {}) {
syncLatestState(payload);
markSettingsDirty(false);
updatePanelModeUI();
updateMailProviderUI();
updateButtonStates();
if (!silent) {
@@ -660,6 +677,21 @@ async function restoreState() {
if (state.vpsPassword) {
inputVpsPassword.value = state.vpsPassword;
}
if (state.panelMode) {
selectPanelMode.value = state.panelMode;
}
if (state.sub2apiUrl) {
inputSub2ApiUrl.value = state.sub2apiUrl;
}
if (state.sub2apiEmail) {
inputSub2ApiEmail.value = state.sub2apiEmail;
}
if (state.sub2apiPassword) {
inputSub2ApiPassword.value = state.sub2apiPassword;
}
if (state.sub2apiGroupName) {
inputSub2ApiGroup.value = state.sub2apiGroupName;
}
if (state.mailProvider) {
selectMailProvider.value = state.mailProvider;
}
@@ -698,6 +730,7 @@ async function restoreState() {
updateAutoDelayInputState();
updateStatusDisplay(latestState);
updateProgressCounter();
updatePanelModeUI();
updateMailProviderUI();
updateButtonStates();
} catch (err) {
@@ -1014,6 +1047,21 @@ async function saveCloudflareDomainSettings(domains, activeDomain, options = {})
}
}
function updatePanelModeUI() {
const useSub2Api = selectPanelMode.value === 'sub2api';
rowVpsUrl.style.display = useSub2Api ? 'none' : '';
rowVpsPassword.style.display = useSub2Api ? 'none' : '';
rowSub2ApiUrl.style.display = useSub2Api ? '' : 'none';
rowSub2ApiEmail.style.display = useSub2Api ? '' : 'none';
rowSub2ApiPassword.style.display = useSub2Api ? '' : 'none';
rowSub2ApiGroup.style.display = useSub2Api ? '' : 'none';
const step9Btn = document.querySelector('.step-btn[data-step="9"]');
if (step9Btn) {
step9Btn.textContent = useSub2Api ? 'SUB2API 回调验证' : 'CPA 回调验证';
}
}
// ============================================================
// UI Updates
// ============================================================
@@ -1841,6 +1889,12 @@ selectEmailGenerator.addEventListener('change', () => {
saveSettings({ silent: true }).catch(() => { });
});
selectPanelMode.addEventListener('change', () => {
updatePanelModeUI();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
selectCfDomain.addEventListener('change', () => {
if (selectCfDomain.disabled) {
return;
@@ -1877,6 +1931,38 @@ inputCfDomain.addEventListener('keydown', (event) => {
}
});
inputSub2ApiUrl.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
});
inputSub2ApiUrl.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => { });
});
inputSub2ApiEmail.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
});
inputSub2ApiEmail.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => { });
});
inputSub2ApiPassword.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
});
inputSub2ApiPassword.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => { });
});
inputSub2ApiGroup.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
});
inputSub2ApiGroup.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => { });
});
inputInbucketMailbox.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
@@ -2064,6 +2150,7 @@ updateSaveButtonState();
restoreState().then(() => {
syncPasswordToggleLabel();
syncVpsUrlToggleLabel();
updatePanelModeUI();
updateButtonStates();
updateStatusDisplay(latestState);
});