mirror of
https://github.com/adminlove520/AI-Account-Toolkit.git
synced 2026-05-10 12:29:33 +08:00
feat: add codex-oauth-automation-extension and update documents to v2.3.0
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -2,6 +2,20 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2.3.0] - 2026-04-10
|
||||
|
||||
### Added
|
||||
- codex-oauth-automation-extension 子仓库 - Codex OAuth 批量自动化 Chrome 扩展
|
||||
- codex-register-V2 目录 - Codex 远程注册机 V2 (Browserbase + DDG)
|
||||
- Extensions 目录 - 浏览器扩展插件集 (含 2925 自动化)
|
||||
- FreeSMS 目录 - 免费在线接码平台资料
|
||||
- mailhub 目录 - 邮箱分享资料
|
||||
- grok 目录 - Grok 相关研究资料
|
||||
- openai_register 目录 - OpenAI 自动化注册脚本
|
||||
|
||||
### Changed
|
||||
- README 全面更新:添加新增项目到项目结构与导航,重新索引所有项目章节
|
||||
|
||||
## [2.2.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
|
||||
17
Extensions/autoRegisterPlugins/README.md
Normal file
17
Extensions/autoRegisterPlugins/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Auto Register 2925
|
||||
|
||||
当前版本已经接入:
|
||||
|
||||
- Step 1:通过 VPS 页面自动获取 OAuth
|
||||
- Step 2 / 3 / 4 / 5:复用 `multiPagePlugins` 的 OpenAI 注册流程
|
||||
- 2925 邮箱轮询地址:`https://www.2925.com/#/mailList`
|
||||
- 2925 无限别名:`主邮箱 local_part + _suffix @2925.com`
|
||||
|
||||
## 说明
|
||||
|
||||
- 如果填写了 `VPS`,默认按 1 -> 5 全流程执行
|
||||
- 如果没填 `VPS` 但填了 `OAuth`,会跳过 Step 1,直接从 Step 2 开始
|
||||
- 2925 邮箱当前优先直接从页面 DOM 表格读取邮件列表,并在轮询时主动点击“刷新”
|
||||
- 邮箱验证码轮询策略统一为:3 秒一次、每轮 10 次、总共 5 轮(含首次),轮询失败后会点击重试/重发
|
||||
- `Password` 留空时会自动生成 OpenAI 账号密码
|
||||
- `Email` 字段为运行时生成结果,只读展示
|
||||
2954
Extensions/autoRegisterPlugins/background.js
Normal file
2954
Extensions/autoRegisterPlugins/background.js
Normal file
File diff suppressed because it is too large
Load Diff
77
Extensions/autoRegisterPlugins/content/duck-mail.js
Normal file
77
Extensions/autoRegisterPlugins/content/duck-mail.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// content/duck-mail.js — Content script for DuckDuckGo Email Protection autofill settings
|
||||
|
||||
console.log('[AutoRegister:duck-mail] Content script loaded on', location.href);
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'FETCH_DUCK_EMAIL') {
|
||||
resetStopState();
|
||||
fetchDuckEmail(message.payload).then(result => {
|
||||
sendResponse(result);
|
||||
}).catch(err => {
|
||||
if (isStopError(err)) {
|
||||
log('Duck Mail: Stopped by user.', 'warn');
|
||||
sendResponse({ stopped: true, error: err.message });
|
||||
return;
|
||||
}
|
||||
sendResponse({ error: err.message });
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchDuckEmail(payload = {}) {
|
||||
const { generateNew = true } = payload;
|
||||
|
||||
log(`Duck Mail: ${generateNew ? 'Generating' : 'Reading'} private address...`);
|
||||
|
||||
await waitForElement(
|
||||
'input.AutofillSettingsPanel__PrivateDuckAddressValue, button.AutofillSettingsPanel__GeneratorButton',
|
||||
15000,
|
||||
);
|
||||
|
||||
await sleep(1500);
|
||||
|
||||
const getAddressInput = () => document.querySelector('input.AutofillSettingsPanel__PrivateDuckAddressValue');
|
||||
const getGeneratorButton = () => document.querySelector('button.AutofillSettingsPanel__GeneratorButton')
|
||||
|| Array.from(document.querySelectorAll('button')).find(btn => /generate private duck address/i.test(btn.textContent || ''));
|
||||
const readEmail = () => {
|
||||
const value = getAddressInput()?.value?.trim() || '';
|
||||
return value.includes('@duck.com') ? value : '';
|
||||
};
|
||||
|
||||
const waitForEmailValue = async (previousValue = '') => {
|
||||
for (let index = 0; index < 100; index++) {
|
||||
throwIfStopped();
|
||||
const nextValue = readEmail();
|
||||
if (nextValue && nextValue !== previousValue) {
|
||||
return nextValue;
|
||||
}
|
||||
await sleep(150);
|
||||
}
|
||||
throw new Error('Timed out waiting for Duck address to appear.');
|
||||
};
|
||||
|
||||
const currentEmail = readEmail();
|
||||
if (currentEmail && !generateNew) {
|
||||
log(`Duck Mail: Found existing address ${currentEmail}`);
|
||||
return { email: currentEmail, generated: false };
|
||||
}
|
||||
|
||||
await humanPause(500, 1300);
|
||||
const generatorButton = getGeneratorButton();
|
||||
if (!generatorButton) {
|
||||
if (currentEmail) {
|
||||
log(`Duck Mail: Reusing existing address ${currentEmail}`, 'warn');
|
||||
return { email: currentEmail, generated: false };
|
||||
}
|
||||
throw new Error('Could not find "Generate Private Duck Address" button.');
|
||||
}
|
||||
|
||||
generatorButton.click();
|
||||
log('Duck Mail: Clicked "Generate Private Duck Address"');
|
||||
|
||||
const nextEmail = await waitForEmailValue(currentEmail);
|
||||
log(`Duck Mail: Ready address ${nextEmail}`, 'ok');
|
||||
return { email: nextEmail, generated: true };
|
||||
}
|
||||
309
Extensions/autoRegisterPlugins/content/mail-163.js
Normal file
309
Extensions/autoRegisterPlugins/content/mail-163.js
Normal file
@@ -0,0 +1,309 @@
|
||||
// content/mail-163.js — Content script for 163 Mail (steps 4, 7)
|
||||
|
||||
const MAIL163_PREFIX = '[AutoRegister:mail-163]';
|
||||
const isTopFrame = window === window.top;
|
||||
|
||||
console.log(MAIL163_PREFIX, 'Content script loaded on', location.href, 'frame:', isTopFrame ? 'top' : 'child');
|
||||
|
||||
if (!isTopFrame) {
|
||||
console.log(MAIL163_PREFIX, 'Skipping child frame');
|
||||
} else {
|
||||
|
||||
let seenCodes = new Set();
|
||||
|
||||
async function loadSeenCodes() {
|
||||
try {
|
||||
const data = await chrome.storage.session.get('seenCodes');
|
||||
if (data.seenCodes && Array.isArray(data.seenCodes)) {
|
||||
seenCodes = new Set(data.seenCodes);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
loadSeenCodes();
|
||||
|
||||
async function persistSeenCodes() {
|
||||
try {
|
||||
await chrome.storage.session.set({ seenCodes: [...seenCodes] });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let pollSendResponse = null;
|
||||
|
||||
(async function checkAndResume() {
|
||||
try {
|
||||
const data = await chrome.storage.session.get('mail163PollState');
|
||||
if (data.mail163PollState) {
|
||||
console.log(MAIL163_PREFIX, `Resuming poll from attempt ${data.mail163PollState.attempt}...`);
|
||||
await chrome.storage.session.remove('mail163PollState');
|
||||
|
||||
if (data.mail163PollState.seenCodes) {
|
||||
seenCodes = new Set(data.mail163PollState.seenCodes);
|
||||
}
|
||||
|
||||
await sleep(3000);
|
||||
|
||||
const state = data.mail163PollState;
|
||||
const result = await continuePoll(state);
|
||||
|
||||
if (pollSendResponse) {
|
||||
pollSendResponse(result);
|
||||
} else {
|
||||
chrome.runtime.sendMessage({ type: 'MAIL163_RESULT', payload: result }).catch(() => {});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(MAIL163_PREFIX, 'Resume check failed:', err.message);
|
||||
}
|
||||
})();
|
||||
|
||||
async function continuePoll(state) {
|
||||
for (let attempt = state.attempt; attempt <= state.maxAttempts; attempt++) {
|
||||
if (attempt > state.attempt) {
|
||||
if ((attempt - 1) % 5 === 0 && attempt > 1) {
|
||||
console.log(MAIL163_PREFIX, `Refreshing page at attempt ${attempt}...`);
|
||||
await chrome.storage.session.set({
|
||||
mail163PollState: { ...state, attempt, seenCodes: [...seenCodes] },
|
||||
});
|
||||
window.location.reload();
|
||||
return new Promise(() => {});
|
||||
}
|
||||
await refreshInbox();
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
const allItems = findMailItems();
|
||||
const existingMailIds = new Set(state.existingMailIds || []);
|
||||
const useFallback = attempt > 3;
|
||||
|
||||
for (const item of allItems) {
|
||||
const id = item.getAttribute('id') || '';
|
||||
if (existingMailIds.has(id)) continue;
|
||||
|
||||
const code = getMatchingCodeForItem(item, state);
|
||||
if (!code) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const duplicateIds = collectDuplicateItemIds(allItems, state, code);
|
||||
const recentUsedCodes = new Set((state.recentUsedCodes || []).map((value) => String(value || '').trim()).filter(Boolean));
|
||||
if (recentUsedCodes.has(code)) {
|
||||
log(`Step ${state.step}: Skipping recently used code: ${code}`, 'info');
|
||||
continue;
|
||||
}
|
||||
if (seenCodes.has(code)) {
|
||||
log(`Step ${state.step}: Skipping seen code: ${code}`, 'info');
|
||||
await deleteEmailsByIds(duplicateIds, state.step);
|
||||
continue;
|
||||
}
|
||||
|
||||
seenCodes.add(code);
|
||||
await persistSeenCodes();
|
||||
log(`Step ${state.step}: Code found: ${code}`, 'ok');
|
||||
await deleteEmailsByIds(duplicateIds, state.step);
|
||||
await sleep(1000);
|
||||
return { ok: true, code, emailTimestamp: Date.now(), mailId: id };
|
||||
}
|
||||
|
||||
if (attempt < state.maxAttempts) {
|
||||
await sleep(state.intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
log(`Step ${state.step}: No code found after ${state.maxAttempts} attempts`, 'warn');
|
||||
return { ok: true, code: null, error: null };
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'POLL_EMAIL') {
|
||||
resetStopState();
|
||||
pollSendResponse = sendResponse;
|
||||
|
||||
handlePollEmail(message.step, message.payload).then(result => {
|
||||
sendResponse(result);
|
||||
}).catch(err => {
|
||||
if (isStopError(err)) {
|
||||
sendResponse({ stopped: true, error: err.message });
|
||||
return;
|
||||
}
|
||||
sendResponse({ error: err.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
function findMailItems() {
|
||||
return document.querySelectorAll('div[sign="letter"]');
|
||||
}
|
||||
|
||||
function getCurrentMailIds() {
|
||||
const ids = new Set();
|
||||
findMailItems().forEach(item => {
|
||||
const id = item.getAttribute('id') || '';
|
||||
if (id) ids.add(id);
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function getMatchingCodeForItem(item, state) {
|
||||
const sender = (item.querySelector('.nui-user')?.textContent || '').toLowerCase();
|
||||
const subject = item.querySelector('span.da0')?.textContent || '';
|
||||
const ariaLabel = (item.getAttribute('aria-label') || '').toLowerCase();
|
||||
|
||||
const senderMatch = state.senderFilters.some(filter => sender.includes(filter.toLowerCase()) || ariaLabel.includes(filter.toLowerCase()));
|
||||
const subjectMatch = state.subjectFilters.some(filter => subject.toLowerCase().includes(filter.toLowerCase()) || ariaLabel.includes(filter.toLowerCase()));
|
||||
if (!senderMatch && !subjectMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return extractVerificationCode(subject + ' ' + ariaLabel);
|
||||
}
|
||||
|
||||
function collectDuplicateItemIds(items, state, code) {
|
||||
const ids = [];
|
||||
for (const item of items) {
|
||||
const itemId = item.getAttribute('id') || '';
|
||||
if (!itemId) {
|
||||
continue;
|
||||
}
|
||||
if (getMatchingCodeForItem(item, state) === code) {
|
||||
ids.push(itemId);
|
||||
}
|
||||
}
|
||||
return [...new Set(ids)];
|
||||
}
|
||||
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const { senderFilters, subjectFilters, maxAttempts, intervalMs, newOnly = false } = payload;
|
||||
const recentUsedCodes = Array.isArray(payload.recentUsedCodes) ? payload.recentUsedCodes.map((value) => String(value || '').trim()).filter(Boolean) : [];
|
||||
|
||||
log(`Step ${step}: Starting email poll on 163 Mail (max ${maxAttempts} attempts)`);
|
||||
|
||||
try {
|
||||
const inboxLink = await waitForElement('.nui-tree-item-text[title="收件箱"]', 5000);
|
||||
inboxLink.click();
|
||||
} catch {
|
||||
log(`Step ${step}: Inbox link not found, proceeding...`, 'warn');
|
||||
}
|
||||
|
||||
let items = [];
|
||||
for (let index = 0; index < 20; index++) {
|
||||
items = findMailItems();
|
||||
if (items.length > 0) break;
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
await refreshInbox();
|
||||
await sleep(2000);
|
||||
items = findMailItems();
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
throw new Error('163 Mail list did not load.');
|
||||
}
|
||||
|
||||
log(`Step ${step}: Mail list loaded, ${items.length} items`);
|
||||
|
||||
const existingMailIds = newOnly ? [...getCurrentMailIds()] : [];
|
||||
if (newOnly) {
|
||||
log(`Step ${step}: Snapshotted ${existingMailIds.length} existing emails as old before this resend round`);
|
||||
}
|
||||
|
||||
const state = {
|
||||
step,
|
||||
senderFilters,
|
||||
subjectFilters,
|
||||
maxAttempts,
|
||||
intervalMs,
|
||||
attempt: 1,
|
||||
existingMailIds,
|
||||
recentUsedCodes,
|
||||
};
|
||||
|
||||
return continuePoll(state);
|
||||
}
|
||||
|
||||
async function deleteEmailsByIds(itemIds, step) {
|
||||
const uniqueIds = [...new Set((itemIds || []).filter(Boolean))];
|
||||
if (!uniqueIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uniqueIds.length > 1) {
|
||||
log(`Step ${step}: Deleting ${uniqueIds.length} emails with the same verification code`, 'info');
|
||||
}
|
||||
|
||||
for (const itemId of uniqueIds) {
|
||||
const currentItem = document.getElementById(itemId);
|
||||
if (!currentItem) {
|
||||
continue;
|
||||
}
|
||||
await deleteEmail(currentItem, step);
|
||||
await sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteEmail(item, step) {
|
||||
try {
|
||||
item.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
|
||||
item.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
||||
await sleep(300);
|
||||
|
||||
const trashIcon = item.querySelector('[sign="trash"], .nui-ico-delete, [title="删除邮件"]');
|
||||
if (trashIcon) {
|
||||
trashIcon.click();
|
||||
await sleep(1500);
|
||||
return;
|
||||
}
|
||||
|
||||
const checkbox = item.querySelector('[sign="checkbox"], .nui-chk');
|
||||
if (checkbox) {
|
||||
checkbox.click();
|
||||
await sleep(300);
|
||||
const toolbarBtns = document.querySelectorAll('.nui-btn .nui-btn-text');
|
||||
for (const btn of toolbarBtns) {
|
||||
if (btn.textContent.replace(/\s/g, '').includes('删除')) {
|
||||
btn.closest('.nui-btn').click();
|
||||
await sleep(1500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log(`Step ${step}: Failed to delete email: ${err.message}`, 'warn');
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshInbox() {
|
||||
const toolbarBtns = document.querySelectorAll('.nui-btn .nui-btn-text');
|
||||
for (const btn of toolbarBtns) {
|
||||
if (btn.textContent.replace(/\s/g, '') === '刷新') {
|
||||
btn.closest('.nui-btn').click();
|
||||
await sleep(800);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const shouXinBtns = document.querySelectorAll('.ra0');
|
||||
for (const btn of shouXinBtns) {
|
||||
if (btn.textContent.replace(/\s/g, '').includes('收信')) {
|
||||
btn.click();
|
||||
await sleep(800);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
const match6 = text.match(/\b(\d{6})\b/);
|
||||
if (match6) return match6[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
150
Extensions/autoRegisterPlugins/content/mail-2925-bridge.js
Normal file
150
Extensions/autoRegisterPlugins/content/mail-2925-bridge.js
Normal file
@@ -0,0 +1,150 @@
|
||||
(() => {
|
||||
if (window.__AUTO_REGISTER_2925_BRIDGE_INSTALLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.__AUTO_REGISTER_2925_BRIDGE_INSTALLED = true;
|
||||
|
||||
const BRIDGE_SOURCE = 'auto-register-2925-bridge';
|
||||
|
||||
window.addEventListener('message', async (event) => {
|
||||
if (event.source !== window) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.data;
|
||||
if (!data || data.source !== BRIDGE_SOURCE || data.direction !== 'content-to-page') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let result;
|
||||
switch (data.action) {
|
||||
case 'FETCH_MAIL_LIST':
|
||||
result = await fetchMailList(data.payload || {});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown bridge action: ${data.action}`);
|
||||
}
|
||||
|
||||
window.postMessage({
|
||||
source: BRIDGE_SOURCE,
|
||||
direction: 'page-to-content',
|
||||
requestId: data.requestId,
|
||||
ok: true,
|
||||
result,
|
||||
}, '*');
|
||||
} catch (error) {
|
||||
window.postMessage({
|
||||
source: BRIDGE_SOURCE,
|
||||
direction: 'page-to-content',
|
||||
requestId: data.requestId,
|
||||
ok: false,
|
||||
error: error?.message || String(error),
|
||||
}, '*');
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchMailList(payload) {
|
||||
if (!payload.mainEmail) {
|
||||
throw new Error('2925 主邮箱缺失。');
|
||||
}
|
||||
|
||||
if (isLoginPage()) {
|
||||
throw new Error('2925 邮箱未登录,请先在浏览器中登录主邮箱账号。');
|
||||
}
|
||||
|
||||
const store = findVueStore();
|
||||
if (!store?.dispatch) {
|
||||
throw new Error('未找到 2925 页面 store,请确认页面已进入收件箱。');
|
||||
}
|
||||
|
||||
const mailboxCandidates = [...new Set([
|
||||
String(payload.mainEmail || '').trim(),
|
||||
String(payload.mainEmail || '').trim().split('@')[0],
|
||||
].filter(Boolean))];
|
||||
|
||||
let lastError = null;
|
||||
|
||||
for (const mailBox of mailboxCandidates) {
|
||||
try {
|
||||
const response = await store.dispatch('getMailListApi', {
|
||||
Folder: 'Inbox',
|
||||
MailBox: mailBox,
|
||||
FilterType: 0,
|
||||
PageIndex: 1,
|
||||
PageCount: payload.pageCount || 50,
|
||||
});
|
||||
|
||||
if (response?.code === 200) {
|
||||
return {
|
||||
mailbox: mailBox,
|
||||
messages: normalizeMessages(response.result?.pageData || []),
|
||||
};
|
||||
}
|
||||
|
||||
lastError = new Error(response?.message || `2925 邮件列表接口返回异常 code=${response?.code}`);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error('2925 邮件列表接口调用失败。');
|
||||
}
|
||||
|
||||
function isLoginPage() {
|
||||
return location.pathname.startsWith('/login')
|
||||
|| Boolean(document.querySelector('input[placeholder*="邮箱账号"], input[placeholder*="手机号"]'));
|
||||
}
|
||||
|
||||
function findVueStore() {
|
||||
const root = document.querySelector('#app') || document.body;
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const queue = [root];
|
||||
const visited = new Set();
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift();
|
||||
if (!node || visited.has(node)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(node);
|
||||
|
||||
const vueInstance = node.__vue__;
|
||||
if (vueInstance?.$store) {
|
||||
return vueInstance.$store;
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeMessages(pageData) {
|
||||
return pageData.map((item) => ({
|
||||
messageId: item.messageId,
|
||||
folder: item.folder,
|
||||
subject: item.subject || '',
|
||||
text: item.text || '',
|
||||
bodyContent: item.text || '',
|
||||
fromName: item.fromName || '',
|
||||
from: item.from || '',
|
||||
toAddress: Array.isArray(item.to)
|
||||
? item.to
|
||||
: String(item.to || '')
|
||||
.split(/\s+/)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean),
|
||||
date: item.date,
|
||||
isAttachment: item.isAttachment,
|
||||
}));
|
||||
}
|
||||
})();
|
||||
398
Extensions/autoRegisterPlugins/content/mail-2925.js
Normal file
398
Extensions/autoRegisterPlugins/content/mail-2925.js
Normal file
@@ -0,0 +1,398 @@
|
||||
const MAIL2925_PREFIX = '[AutoRegister:mail-2925]';
|
||||
const SEEN_CODES_KEY = 'seen2925Codes';
|
||||
const isTopFrame = window === window.top;
|
||||
|
||||
console.log(MAIL2925_PREFIX, 'Content script loaded on', location.href, 'frame:', isTopFrame ? 'top' : 'child');
|
||||
|
||||
if (!isTopFrame) {
|
||||
console.log(MAIL2925_PREFIX, 'Skipping child frame');
|
||||
} else {
|
||||
let seenCodes = new Set();
|
||||
loadSeenCodes();
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (message.type === 'POLL_EMAIL') {
|
||||
resetStopState();
|
||||
handlePollEmail(message.step, message.payload)
|
||||
.then(sendResponse)
|
||||
.catch((error) => {
|
||||
if (isStopError(error)) {
|
||||
sendResponse({ stopped: true, error: error.message });
|
||||
return;
|
||||
}
|
||||
sendResponse({ error: error.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const config = payload || {};
|
||||
const recentUsedCodes = new Set((config.recentUsedCodes || []).map((value) => String(value || '').trim()).filter(Boolean));
|
||||
const maxAttempts = Number(config.maxAttempts);
|
||||
const intervalMs = Number(config.intervalMs);
|
||||
|
||||
if (!Number.isFinite(maxAttempts) || !Number.isFinite(intervalMs)) {
|
||||
throw new Error('2925 mailbox poll config is invalid.');
|
||||
}
|
||||
|
||||
log(`Step ${step}: Waiting for 2925 mailbox UI...`);
|
||||
await waitForMailboxReady(30000);
|
||||
await ensureInboxSelected();
|
||||
|
||||
log(`Step ${step}: Starting email poll on 2925 mailbox (${maxAttempts} attempts)`);
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
throwIfStopped();
|
||||
|
||||
if (attempt > 1) {
|
||||
await refreshMailbox(step, attempt);
|
||||
}
|
||||
|
||||
let messages = extractMessagesFromDom();
|
||||
if (!messages.length) {
|
||||
log(`Step ${step}: Mail rows not ready, waiting once more...`, 'warn');
|
||||
await waitForMailboxReady(10000);
|
||||
messages = extractMessagesFromDom();
|
||||
}
|
||||
|
||||
const matched = pickMatchingMessage(messages, config);
|
||||
if (matched?.code) {
|
||||
const duplicateMessages = findMessagesByCode(messages, matched.code);
|
||||
if (recentUsedCodes.has(matched.code)) {
|
||||
log(`Step ${step}: Skipping recently used code ${matched.code}`, 'info');
|
||||
await deleteMatchedMessages(duplicateMessages, step);
|
||||
continue;
|
||||
}
|
||||
if (seenCodes.has(matched.code)) {
|
||||
log(`Step ${step}: Skipping seen code ${matched.code}`, 'info');
|
||||
await deleteMatchedMessages(duplicateMessages, step);
|
||||
continue;
|
||||
}
|
||||
|
||||
seenCodes.add(matched.code);
|
||||
await persistSeenCodes();
|
||||
log(`Step ${step}: Code found: ${matched.code}`, 'ok');
|
||||
await deleteMatchedMessages(duplicateMessages, step);
|
||||
return {
|
||||
ok: true,
|
||||
code: matched.code,
|
||||
emailTimestamp: matched.timestamp || Date.now(),
|
||||
messageId: matched.messageId,
|
||||
};
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts) {
|
||||
log(`Step ${step}: No matching code yet, retrying (${attempt}/${maxAttempts})...`, 'warn');
|
||||
await sleep(intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
log(`Step ${step}: No code found after ${maxAttempts} attempts`, 'warn');
|
||||
return { ok: true, code: null, error: null };
|
||||
}
|
||||
|
||||
async function waitForMailboxReady(timeout = 30000) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
throwIfStopped();
|
||||
|
||||
if (isLoginPage()) {
|
||||
await sleep(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
const table = document.querySelector('.maillist-table');
|
||||
const container = document.querySelector('.list-container');
|
||||
const header = document.querySelector('.mail-header');
|
||||
if (table || (container && header)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sleep(300);
|
||||
}
|
||||
|
||||
throw new Error('2925 邮箱列表页面加载超时,请确认已经进入收件箱。');
|
||||
}
|
||||
|
||||
function isLoginPage() {
|
||||
return location.pathname.startsWith('/login')
|
||||
|| Boolean(document.querySelector('input[placeholder*="邮箱账号"], input[placeholder*="手机号"]'));
|
||||
}
|
||||
|
||||
async function ensureInboxSelected() {
|
||||
const inboxItem = [...document.querySelectorAll('li')].find((node) => (node.textContent || '').trim() === '收件箱');
|
||||
if (!inboxItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inboxItem.classList.contains('select')) {
|
||||
simulateClick(inboxItem);
|
||||
await sleep(1200);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshMailbox(step, attempt) {
|
||||
const refreshButton = findRefreshButton();
|
||||
if (!refreshButton) {
|
||||
log(`Step ${step}: Refresh button not found, using passive wait (${attempt})`, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
simulateClick(refreshButton);
|
||||
log(`Step ${step}: Clicked refresh (${attempt})`, 'info');
|
||||
await sleep(1500);
|
||||
}
|
||||
|
||||
function findRefreshButton() {
|
||||
return [...document.querySelectorAll('.mail-header .tool-common, .mail-header button, .mail-header div')]
|
||||
.find((node) => /刷新/.test(node.textContent || '')) || null;
|
||||
}
|
||||
|
||||
async function deleteMatchedMessages(messages, step) {
|
||||
const rows = [...new Set((messages || []).map((message) => message?.rowElement).filter(Boolean))]
|
||||
.filter((row) => row.isConnected);
|
||||
if (!rows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let selectedCount = 0;
|
||||
for (const row of rows) {
|
||||
const checkboxTarget = findRowCheckboxTarget(row);
|
||||
if (!checkboxTarget) {
|
||||
continue;
|
||||
}
|
||||
simulateClick(checkboxTarget);
|
||||
selectedCount += 1;
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
if (!selectedCount) {
|
||||
log(`Step ${step}: Checkbox not found for matched email`, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
selectedCount > 1
|
||||
? `Step ${step}: Selected ${selectedCount} matched emails by checkbox`
|
||||
: `Step ${step}: Selected matched email by checkbox`,
|
||||
'info',
|
||||
);
|
||||
await sleep(500);
|
||||
|
||||
const deleteButton = findDeleteButton();
|
||||
if (!deleteButton) {
|
||||
log(`Step ${step}: Delete button not found, keeping matched email`, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
simulateClick(deleteButton);
|
||||
log(`Step ${step}: Clicked delete for matched email`, 'info');
|
||||
await sleep(500);
|
||||
|
||||
const confirmButton = findDeleteConfirmButton();
|
||||
if (confirmButton) {
|
||||
simulateClick(confirmButton);
|
||||
log(`Step ${step}: Confirmed email deletion`, 'info');
|
||||
await sleep(900);
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Step ${step}: Failed to delete matched email: ${error.message}`, 'warn');
|
||||
}
|
||||
}
|
||||
|
||||
function findRowCheckboxTarget(row) {
|
||||
return row.querySelector('.check label')
|
||||
|| row.querySelector('.check .ivu-checkbox')
|
||||
|| row.querySelector('.check .ivu-checkbox-inner')
|
||||
|| row.querySelector('.check input[type="checkbox"]');
|
||||
}
|
||||
|
||||
function findDeleteButton() {
|
||||
return document.querySelector('.mail-header .delete.tool-common[title="删除"]')
|
||||
|| [...document.querySelectorAll('.mail-header .tool-common, .mail-header button, .mail-header div, button, [role="button"]')]
|
||||
.find((node) => {
|
||||
const text = cleanText(node.textContent || '');
|
||||
return /删除|彻底删除|trash|delete/i.test(text) && !/刷新/.test(text);
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function findDeleteConfirmButton() {
|
||||
return [...document.querySelectorAll('.el-message-box__btns button, .ant-modal-footer button, .dialog-footer button, button, [role="button"]')]
|
||||
.find((node) => /确定|确认|删除|是|yes/i.test(cleanText(node.textContent || '')));
|
||||
}
|
||||
|
||||
function extractMessagesFromDom() {
|
||||
const rows = [...document.querySelectorAll('.maillist-table tbody tr, .maillist-table tr')]
|
||||
.filter((row) => row.querySelector('.sender, .mail-content-title, .mail-content'));
|
||||
|
||||
return rows.map((row, index) => {
|
||||
const sender = cleanText(row.querySelector('.sender')?.textContent || '');
|
||||
const subject = cleanText(row.querySelector('.mail-content-title')?.textContent || row.querySelector('.mail-content')?.textContent || '');
|
||||
const preview = cleanText(row.querySelector('.mail-content-text')?.textContent || '');
|
||||
const dateText = cleanText(row.querySelector('.date-time-text, .date-time')?.textContent || '');
|
||||
const rowText = cleanText(row.innerText || '');
|
||||
const timestamp = normalizeTimestamp(dateText);
|
||||
|
||||
return {
|
||||
messageId: row.dataset.id || row.getAttribute('data-id') || `${sender}-${subject}-${dateText}-${index}`,
|
||||
fromName: sender,
|
||||
from: sender,
|
||||
subject,
|
||||
text: preview,
|
||||
bodyContent: rowText,
|
||||
toAddress: [rowText],
|
||||
date: timestamp || dateText,
|
||||
rowElement: row,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function cleanText(value) {
|
||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function findMessagesByCode(messages, code) {
|
||||
return (messages || []).filter((message) => {
|
||||
const extracted = extractVerificationCode(`${message.subject || ''} ${message.text || ''} ${message.bodyContent || ''}`);
|
||||
return extracted && extracted === code;
|
||||
});
|
||||
}
|
||||
|
||||
function pickMatchingMessage(messages, config) {
|
||||
const filterAfterTimestamp = Number(config.filterAfterTimestamp || 0);
|
||||
const senderFilters = (config.senderFilters || []).map((value) => String(value).toLowerCase());
|
||||
const subjectFilters = (config.subjectFilters || []).map((value) => String(value).toLowerCase());
|
||||
const targetEmail = String(config.targetEmail || '').toLowerCase();
|
||||
const mainEmail = String(config.mainEmail || '').toLowerCase();
|
||||
|
||||
const candidates = messages
|
||||
.map((message) => {
|
||||
const senderText = `${message.fromName || ''} ${message.from || ''}`.toLowerCase();
|
||||
const subjectText = String(message.subject || '').toLowerCase();
|
||||
const bodyText = `${message.text || ''} ${message.bodyContent || ''}`.toLowerCase();
|
||||
const toList = Array.isArray(message.toAddress) ? message.toAddress : [message.toAddress].filter(Boolean);
|
||||
const toText = toList.join(' ').toLowerCase();
|
||||
const timestamp = normalizeTimestamp(message.date);
|
||||
const combined = `${senderText}\n${subjectText}\n${bodyText}\n${toText}`;
|
||||
const code = extractVerificationCode(`${message.subject || ''} ${message.text || ''} ${message.bodyContent || ''}`);
|
||||
|
||||
let score = 0;
|
||||
if (matchesAny(senderText, senderFilters)) score += 6;
|
||||
if (matchesAny(subjectText, subjectFilters)) score += 6;
|
||||
if (/openai|chatgpt/.test(combined)) score += 4;
|
||||
if (targetEmail && combined.includes(targetEmail)) score += 6;
|
||||
if (mainEmail && combined.includes(mainEmail)) score += 2;
|
||||
if (code) score += 4;
|
||||
if (timestamp && filterAfterTimestamp && timestamp < filterAfterTimestamp) {
|
||||
return null;
|
||||
}
|
||||
if (timestamp && filterAfterTimestamp) {
|
||||
score += 2;
|
||||
}
|
||||
|
||||
return {
|
||||
...message,
|
||||
timestamp,
|
||||
code,
|
||||
score,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter((message) => message.score > 0)
|
||||
.sort((left, right) => right.score - left.score || (right.timestamp || 0) - (left.timestamp || 0));
|
||||
|
||||
return candidates.find((message) => message.code) || null;
|
||||
}
|
||||
|
||||
function normalizeTimestamp(value) {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return value > 1e12 ? value : value * 1000;
|
||||
}
|
||||
|
||||
const text = cleanText(value);
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (/^\d{10,13}$/.test(text)) {
|
||||
const numeric = Number(text);
|
||||
return text.length === 13 ? numeric : numeric * 1000;
|
||||
}
|
||||
|
||||
if (text === '刚刚') {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
let match = text.match(/(\d+)\s*分钟前/);
|
||||
if (match) {
|
||||
return Date.now() - Number(match[1]) * 60 * 1000;
|
||||
}
|
||||
|
||||
match = text.match(/(\d+)\s*小时前/);
|
||||
if (match) {
|
||||
return Date.now() - Number(match[1]) * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
match = text.match(/昨天\s*(\d{1,2}:\d{2})?/);
|
||||
if (match) {
|
||||
const base = new Date();
|
||||
base.setDate(base.getDate() - 1);
|
||||
if (match[1]) {
|
||||
const [hours, minutes] = match[1].split(':').map(Number);
|
||||
base.setHours(hours || 0, minutes || 0, 0, 0);
|
||||
}
|
||||
return base.getTime();
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
match = text.match(/^(\d{1,2})-(\d{1,2})(?:\s+(\d{1,2}:\d{2}))?$/);
|
||||
if (match) {
|
||||
const base = new Date(now.getFullYear(), Number(match[1]) - 1, Number(match[2]), 0, 0, 0, 0);
|
||||
if (match[3]) {
|
||||
const [hours, minutes] = match[3].split(':').map(Number);
|
||||
base.setHours(hours || 0, minutes || 0, 0, 0);
|
||||
}
|
||||
return base.getTime();
|
||||
}
|
||||
|
||||
const parsed = Date.parse(text.replace(/-/g, '/'));
|
||||
return Number.isFinite(parsed) ? parsed : 0;
|
||||
}
|
||||
|
||||
function matchesAny(text, patterns) {
|
||||
return patterns.some((pattern) => pattern && text.includes(pattern));
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
const match6 = text.match(/\b(\d{6})\b/);
|
||||
if (match6) return match6[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loadSeenCodes() {
|
||||
try {
|
||||
const data = await chrome.storage.session.get(SEEN_CODES_KEY);
|
||||
if (Array.isArray(data[SEEN_CODES_KEY])) {
|
||||
seenCodes = new Set(data[SEEN_CODES_KEY]);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function persistSeenCodes() {
|
||||
try {
|
||||
await chrome.storage.session.set({ [SEEN_CODES_KEY]: [...seenCodes] });
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
140
Extensions/autoRegisterPlugins/content/qq-mail.js
Normal file
140
Extensions/autoRegisterPlugins/content/qq-mail.js
Normal file
@@ -0,0 +1,140 @@
|
||||
// content/qq-mail.js — Content script for QQ Mail (steps 4, 7)
|
||||
|
||||
const QQ_MAIL_PREFIX = '[AutoRegister:qq-mail]';
|
||||
const isTopFrame = window === window.top;
|
||||
|
||||
console.log(QQ_MAIL_PREFIX, 'Content script loaded on', location.href, 'frame:', isTopFrame ? 'top' : 'child');
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'POLL_EMAIL') {
|
||||
if (!isTopFrame) {
|
||||
sendResponse({ ok: false, reason: 'wrong-frame' });
|
||||
return;
|
||||
}
|
||||
resetStopState();
|
||||
handlePollEmail(message.step, message.payload).then(result => {
|
||||
sendResponse(result);
|
||||
}).catch(err => {
|
||||
if (isStopError(err)) {
|
||||
log(`Step ${message.step}: Stopped by user.`, 'warn');
|
||||
sendResponse({ stopped: true, error: err.message });
|
||||
return;
|
||||
}
|
||||
reportError(message.step, err.message);
|
||||
sendResponse({ error: err.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
function getCurrentMailIds() {
|
||||
const ids = new Set();
|
||||
document.querySelectorAll('.mail-list-page-item[data-mailid]').forEach(item => {
|
||||
ids.add(item.getAttribute('data-mailid'));
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const { senderFilters, subjectFilters, maxAttempts, intervalMs, newOnly = false } = payload;
|
||||
const recentUsedCodes = new Set((payload.recentUsedCodes || []).map((value) => String(value || '').trim()).filter(Boolean));
|
||||
|
||||
log(`Step ${step}: Starting email poll (max ${maxAttempts} attempts, every ${intervalMs / 1000}s)`);
|
||||
|
||||
try {
|
||||
await waitForElement('.mail-list-page-item', 10000);
|
||||
log(`Step ${step}: Mail list loaded`);
|
||||
} catch {
|
||||
throw new Error('Mail list did not load. Make sure QQ Mail inbox is open.');
|
||||
}
|
||||
|
||||
const existingMailIds = newOnly ? getCurrentMailIds() : new Set();
|
||||
if (newOnly) {
|
||||
log(`Step ${step}: Snapshotted ${existingMailIds.size} existing emails as old before this resend round`);
|
||||
}
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
log(`Polling QQ Mail... attempt ${attempt}/${maxAttempts}`);
|
||||
|
||||
if (attempt > 1) {
|
||||
await refreshInbox();
|
||||
await sleep(800);
|
||||
}
|
||||
|
||||
const allItems = document.querySelectorAll('.mail-list-page-item[data-mailid]');
|
||||
|
||||
for (const item of allItems) {
|
||||
const mailId = item.getAttribute('data-mailid');
|
||||
|
||||
if (newOnly && existingMailIds.has(mailId)) continue;
|
||||
|
||||
const sender = (item.querySelector('.cmp-account-nick')?.textContent || '').toLowerCase();
|
||||
const subject = (item.querySelector('.mail-subject')?.textContent || '').toLowerCase();
|
||||
const digest = item.querySelector('.mail-digest')?.textContent || '';
|
||||
|
||||
const senderMatch = senderFilters.some(filter => sender.includes(filter.toLowerCase()));
|
||||
const subjectMatch = subjectFilters.some(filter => subject.includes(filter.toLowerCase()));
|
||||
|
||||
if (senderMatch || subjectMatch) {
|
||||
const code = extractVerificationCode(subject + ' ' + digest);
|
||||
if (!code) {
|
||||
continue;
|
||||
}
|
||||
if (recentUsedCodes.has(code)) {
|
||||
log(`Step ${step}: Skipping recently used code ${code}`, 'info');
|
||||
continue;
|
||||
}
|
||||
log(`Step ${step}: Code found: ${code} (subject: ${subject.slice(0, 40)})`, 'ok');
|
||||
return { ok: true, code, emailTimestamp: Date.now(), mailId };
|
||||
}
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts) {
|
||||
await sleep(intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
log(`Step ${step}: No new matching email found after ${maxAttempts} attempts, returning no code`, 'warn');
|
||||
return { ok: true, code: null, error: null };
|
||||
}
|
||||
|
||||
async function refreshInbox() {
|
||||
const refreshBtn = document.querySelector('[class*="refresh"], [title*="刷新"]');
|
||||
if (refreshBtn) {
|
||||
simulateClick(refreshBtn);
|
||||
console.log(QQ_MAIL_PREFIX, 'Clicked refresh button');
|
||||
await sleep(500);
|
||||
return;
|
||||
}
|
||||
|
||||
const sidebarInbox = document.querySelector('a[href*="inbox"], [class*="folder-item"][class*="inbox"], [title="收件箱"]');
|
||||
if (sidebarInbox) {
|
||||
simulateClick(sidebarInbox);
|
||||
console.log(QQ_MAIL_PREFIX, 'Clicked sidebar inbox');
|
||||
await sleep(500);
|
||||
return;
|
||||
}
|
||||
|
||||
const folderName = document.querySelector('.toolbar-folder-name');
|
||||
if (folderName) {
|
||||
simulateClick(folderName);
|
||||
console.log(QQ_MAIL_PREFIX, 'Clicked toolbar folder name');
|
||||
await sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
|
||||
const match6 = text.match(/\b(\d{6})\b/);
|
||||
if (match6) return match6[1];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1135
Extensions/autoRegisterPlugins/content/signup-page.js
Normal file
1135
Extensions/autoRegisterPlugins/content/signup-page.js
Normal file
File diff suppressed because it is too large
Load Diff
281
Extensions/autoRegisterPlugins/content/utils.js
Normal file
281
Extensions/autoRegisterPlugins/content/utils.js
Normal file
@@ -0,0 +1,281 @@
|
||||
const SCRIPT_SOURCE = (() => {
|
||||
if (window.__AUTO_REGISTER_SOURCE) return window.__AUTO_REGISTER_SOURCE;
|
||||
const url = location.href;
|
||||
if (url.includes('auth0.openai.com') || url.includes('auth.openai.com') || url.includes('accounts.openai.com')) return 'signup-page';
|
||||
if (url.includes('mail.qq.com')) return 'qq-mail';
|
||||
if (url.includes('mail.163.com')) return 'mail-163';
|
||||
if (url.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
|
||||
if (url.includes('2925.com')) return 'mail-2925';
|
||||
return 'unknown';
|
||||
})();
|
||||
|
||||
const LOG_PREFIX = `[AutoRegister:${SCRIPT_SOURCE}]`;
|
||||
const STOP_ERROR_MESSAGE = 'Flow stopped by user.';
|
||||
let flowStopped = false;
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (message.type === 'STOP_FLOW') {
|
||||
flowStopped = true;
|
||||
console.warn(LOG_PREFIX, STOP_ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'AUTO_REGISTER_PING') {
|
||||
sendResponse({ ok: true, source: SCRIPT_SOURCE, url: location.href });
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
function resetStopState() {
|
||||
flowStopped = false;
|
||||
}
|
||||
|
||||
function isStopError(error) {
|
||||
const message = typeof error === 'string' ? error : error?.message;
|
||||
return message === STOP_ERROR_MESSAGE;
|
||||
}
|
||||
|
||||
function throwIfStopped() {
|
||||
if (flowStopped) {
|
||||
throw new Error(STOP_ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
function waitForElement(selector, timeout = 10000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
throwIfStopped();
|
||||
|
||||
const existing = document.querySelector(selector);
|
||||
if (existing) {
|
||||
console.log(LOG_PREFIX, `Found immediately: ${selector}`);
|
||||
log(`Found element: ${selector}`);
|
||||
resolve(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(LOG_PREFIX, `Waiting for: ${selector} (timeout: ${timeout}ms)`);
|
||||
log(`Waiting for selector: ${selector}...`);
|
||||
|
||||
let settled = false;
|
||||
let stopTimer = null;
|
||||
const cleanup = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
observer.disconnect();
|
||||
clearTimeout(timer);
|
||||
clearTimeout(stopTimer);
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (flowStopped) {
|
||||
cleanup();
|
||||
reject(new Error(STOP_ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
cleanup();
|
||||
console.log(LOG_PREFIX, `Found after wait: ${selector}`);
|
||||
log(`Found element: ${selector}`);
|
||||
resolve(el);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body || document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error(`Timeout waiting for ${selector} after ${timeout}ms on ${location.href}`));
|
||||
}, timeout);
|
||||
|
||||
const pollStop = () => {
|
||||
if (settled) return;
|
||||
if (flowStopped) {
|
||||
cleanup();
|
||||
reject(new Error(STOP_ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
stopTimer = setTimeout(pollStop, 100);
|
||||
};
|
||||
pollStop();
|
||||
});
|
||||
}
|
||||
|
||||
function waitForElementByText(containerSelector, textPattern, timeout = 10000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
throwIfStopped();
|
||||
|
||||
function search() {
|
||||
const candidates = document.querySelectorAll(containerSelector);
|
||||
for (const el of candidates) {
|
||||
if (textPattern.test(el.textContent || '')) {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const existing = search();
|
||||
if (existing) {
|
||||
console.log(LOG_PREFIX, `Found by text immediately: ${containerSelector} matching ${textPattern}`);
|
||||
log(`Found element by text: ${textPattern}`);
|
||||
resolve(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(LOG_PREFIX, `Waiting for text match: ${containerSelector} / ${textPattern}`);
|
||||
log(`Waiting for element with text: ${textPattern}...`);
|
||||
|
||||
let settled = false;
|
||||
let stopTimer = null;
|
||||
const cleanup = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
observer.disconnect();
|
||||
clearTimeout(timer);
|
||||
clearTimeout(stopTimer);
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (flowStopped) {
|
||||
cleanup();
|
||||
reject(new Error(STOP_ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
const el = search();
|
||||
if (el) {
|
||||
cleanup();
|
||||
console.log(LOG_PREFIX, `Found by text after wait: ${textPattern}`);
|
||||
log(`Found element by text: ${textPattern}`);
|
||||
resolve(el);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body || document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error(`Timeout waiting for text ${textPattern} in ${containerSelector} after ${timeout}ms on ${location.href}`));
|
||||
}, timeout);
|
||||
|
||||
const pollStop = () => {
|
||||
if (settled) return;
|
||||
if (flowStopped) {
|
||||
cleanup();
|
||||
reject(new Error(STOP_ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
stopTimer = setTimeout(pollStop, 100);
|
||||
};
|
||||
pollStop();
|
||||
});
|
||||
}
|
||||
|
||||
function fillInput(el, value) {
|
||||
throwIfStopped();
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
'value'
|
||||
).set;
|
||||
nativeInputValueSetter.call(el, value);
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
console.log(LOG_PREFIX, `Filled input ${el.name || el.id || el.type} with: ${value}`);
|
||||
log(`Filled input [${el.name || el.id || el.type || 'unknown'}]`);
|
||||
}
|
||||
|
||||
function fillSelect(el, value) {
|
||||
throwIfStopped();
|
||||
el.value = value;
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
console.log(LOG_PREFIX, `Selected value ${value} in ${el.name || el.id}`);
|
||||
log(`Selected [${el.name || el.id || 'unknown'}] = ${value}`);
|
||||
}
|
||||
|
||||
function log(message, level = 'info') {
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'LOG',
|
||||
source: SCRIPT_SOURCE,
|
||||
step: null,
|
||||
payload: { message, level, timestamp: Date.now() },
|
||||
error: null,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function reportReady() {
|
||||
console.log(LOG_PREFIX, 'Content script ready');
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'CONTENT_SCRIPT_READY',
|
||||
source: SCRIPT_SOURCE,
|
||||
step: null,
|
||||
payload: {},
|
||||
error: null,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function reportComplete(step, data = {}) {
|
||||
console.log(LOG_PREFIX, `Step ${step} completed`, data);
|
||||
log(`Step ${step} completed successfully`, 'ok');
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'STEP_COMPLETE',
|
||||
source: SCRIPT_SOURCE,
|
||||
step,
|
||||
payload: data,
|
||||
error: null,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function reportError(step, errorMessage) {
|
||||
console.error(LOG_PREFIX, `Step ${step} failed: ${errorMessage}`);
|
||||
log(`Step ${step} failed: ${errorMessage}`, 'error');
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'STEP_ERROR',
|
||||
source: SCRIPT_SOURCE,
|
||||
step,
|
||||
payload: {},
|
||||
error: errorMessage,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function simulateClick(el) {
|
||||
throwIfStopped();
|
||||
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
console.log(LOG_PREFIX, `Clicked: ${el.tagName} ${el.textContent?.slice(0, 30) || ''}`);
|
||||
log(`Clicked [${el.tagName}] ${el.textContent?.trim().slice(0, 30) || ''}`);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = Date.now();
|
||||
|
||||
function tick() {
|
||||
if (flowStopped) {
|
||||
reject(new Error(STOP_ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
if (Date.now() - start >= ms) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
setTimeout(tick, Math.min(100, Math.max(25, ms - (Date.now() - start))));
|
||||
}
|
||||
|
||||
tick();
|
||||
});
|
||||
}
|
||||
|
||||
async function humanPause(min = 250, max = 850) {
|
||||
const duration = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
await sleep(duration);
|
||||
}
|
||||
|
||||
const isMailChildFrame = (SCRIPT_SOURCE === 'mail-2925' || SCRIPT_SOURCE === 'qq-mail' || SCRIPT_SOURCE === 'mail-163') && window !== window.top;
|
||||
if (!isMailChildFrame) {
|
||||
reportReady();
|
||||
}
|
||||
140
Extensions/autoRegisterPlugins/content/vps-panel.js
Normal file
140
Extensions/autoRegisterPlugins/content/vps-panel.js
Normal file
@@ -0,0 +1,140 @@
|
||||
// content/vps-panel.js — Content script for VPS panel (step 1)
|
||||
// Injected on: VPS panel (user-configured URL)
|
||||
//
|
||||
// Actual DOM structure (after login click):
|
||||
// <div class="card">
|
||||
// <div class="card-header">
|
||||
// <span class="OAuthPage-module__cardTitle___yFaP0">Codex OAuth</span>
|
||||
// <button class="btn btn-primary"><span>登录</span></button>
|
||||
// </div>
|
||||
// <div class="OAuthPage-module__cardContent___1sXLA">
|
||||
// <div class="OAuthPage-module__authUrlBox___Iu1d4">
|
||||
// <div class="OAuthPage-module__authUrlLabel___mYFJB">授权链接:</div>
|
||||
// <div class="OAuthPage-module__authUrlValue___axvUJ">https://auth.openai.com/...</div>
|
||||
// <div class="OAuthPage-module__authUrlActions___venPj">
|
||||
// <button class="btn btn-secondary btn-sm"><span>复制链接</span></button>
|
||||
// <button class="btn btn-secondary btn-sm"><span>打开链接</span></button>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div class="OAuthPage-module__callbackSection___8kA31">
|
||||
// <input class="input" placeholder="http://localhost:1455/auth/callback?code=...&state=...">
|
||||
// <button class="btn btn-secondary btn-sm"><span>提交回调 URL</span></button>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
console.log('[MultiPage:vps-panel] Content script loaded on', location.href);
|
||||
|
||||
// Listen for commands from Background
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'EXECUTE_STEP' || message.type === 'FETCH_OAUTH_URL') {
|
||||
resetStopState();
|
||||
|
||||
const task = message.type === 'FETCH_OAUTH_URL'
|
||||
? fetchOAuthUrl(message.payload || {})
|
||||
: handleStep(message.step, message.payload);
|
||||
|
||||
task.then((result) => {
|
||||
sendResponse({ ok: true, ...(result || {}) });
|
||||
}).catch(err => {
|
||||
const step = Number(message.payload?.step) || Number(message.step) || 1;
|
||||
if (isStopError(err)) {
|
||||
log(`Step ${step}: Stopped by user.`, 'warn');
|
||||
sendResponse({ stopped: true, error: err.message });
|
||||
return;
|
||||
}
|
||||
if (message.type === 'EXECUTE_STEP') {
|
||||
reportError(message.step, err.message);
|
||||
} else {
|
||||
log(`Step ${step}: ${err.message}`, 'error');
|
||||
}
|
||||
sendResponse({ error: err.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleStep(step) {
|
||||
switch (step) {
|
||||
case 1: return await step1_getOAuthLink();
|
||||
default:
|
||||
throw new Error(`vps-panel.js does not handle step ${step}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchOAuthUrl(payload = {}) {
|
||||
return getOAuthLinkFromPanel({
|
||||
step: Number(payload.step) || 1,
|
||||
reportCompletion: false,
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Step 1: Get OAuth Link
|
||||
// ============================================================
|
||||
|
||||
async function step1_getOAuthLink() {
|
||||
return getOAuthLinkFromPanel({
|
||||
step: 1,
|
||||
reportCompletion: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getOAuthLinkFromPanel(options = {}) {
|
||||
const step = Number(options.step) || 1;
|
||||
const stepLabel = `Step ${step}`;
|
||||
const reportCompletion = Boolean(options.reportCompletion);
|
||||
|
||||
log(`${stepLabel}: Waiting for VPS panel to load (auto-login may take a moment)...`);
|
||||
|
||||
// The page may start at #/login and auto-redirect to #/oauth.
|
||||
// Wait for the Codex OAuth card to appear (up to 30s for auto-login + redirect).
|
||||
let loginBtn = null;
|
||||
try {
|
||||
// Wait for any card-header containing "Codex" to appear
|
||||
const header = await waitForElementByText('.card-header', /codex/i, 30000);
|
||||
loginBtn = header.querySelector('button.btn.btn-primary, button.btn');
|
||||
log(`${stepLabel}: Found Codex OAuth card`);
|
||||
} catch {
|
||||
throw new Error(
|
||||
'Codex OAuth card did not appear after 30s. Page may still be loading or not logged in. ' +
|
||||
'Current URL: ' + location.href
|
||||
);
|
||||
}
|
||||
|
||||
if (!loginBtn) {
|
||||
throw new Error('Found Codex OAuth card but no login button inside it. URL: ' + location.href);
|
||||
}
|
||||
|
||||
// Check if button is disabled (already clicked / loading)
|
||||
if (loginBtn.disabled) {
|
||||
log(`${stepLabel}: Login button is disabled (already loading), waiting for auth URL...`);
|
||||
} else {
|
||||
await humanPause(500, 1400);
|
||||
simulateClick(loginBtn);
|
||||
log(`${stepLabel}: Clicked login button, waiting for auth URL...`);
|
||||
}
|
||||
|
||||
// Wait for the auth URL to appear in the specific div
|
||||
let authUrlEl = null;
|
||||
try {
|
||||
authUrlEl = await waitForElement('[class*="authUrlValue"]', 15000);
|
||||
} catch {
|
||||
throw new Error(
|
||||
'Auth URL did not appear after clicking login. ' +
|
||||
'Check if VPS panel is logged in and Codex service is running. URL: ' + location.href
|
||||
);
|
||||
}
|
||||
|
||||
const oauthUrl = (authUrlEl.textContent || '').trim();
|
||||
if (!oauthUrl || !oauthUrl.startsWith('http')) {
|
||||
throw new Error(`Invalid OAuth URL found: "${oauthUrl.slice(0, 50)}". Expected URL starting with http.`);
|
||||
}
|
||||
|
||||
log(`${stepLabel}: OAuth URL obtained: ${oauthUrl.slice(0, 80)}...`, 'ok');
|
||||
if (reportCompletion) {
|
||||
reportComplete(step, { oauthUrl });
|
||||
}
|
||||
return { oauthUrl };
|
||||
}
|
||||
|
||||
38
Extensions/autoRegisterPlugins/data/names.js
Normal file
38
Extensions/autoRegisterPlugins/data/names.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// data/names.js — English name lists for random generation
|
||||
|
||||
const FIRST_NAMES = [
|
||||
'James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Joseph', 'Thomas', 'Christopher',
|
||||
'Mary', 'Patricia', 'Jennifer', 'Linda', 'Barbara', 'Elizabeth', 'Susan', 'Jessica', 'Sarah', 'Karen',
|
||||
'Daniel', 'Matthew', 'Anthony', 'Mark', 'Donald', 'Steven', 'Andrew', 'Paul', 'Joshua', 'Kenneth',
|
||||
'Emma', 'Olivia', 'Ava', 'Isabella', 'Sophia', 'Mia', 'Charlotte', 'Amelia', 'Harper', 'Evelyn',
|
||||
];
|
||||
|
||||
const LAST_NAMES = [
|
||||
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez',
|
||||
'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin',
|
||||
'Lee', 'Perez', 'Thompson', 'White', 'Harris', 'Sanchez', 'Clark', 'Ramirez', 'Lewis', 'Robinson',
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate a random full name.
|
||||
* @returns {{ firstName: string, lastName: string }}
|
||||
*/
|
||||
function generateRandomName() {
|
||||
const firstName = FIRST_NAMES[Math.floor(Math.random() * FIRST_NAMES.length)];
|
||||
const lastName = LAST_NAMES[Math.floor(Math.random() * LAST_NAMES.length)];
|
||||
return { firstName, lastName };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random birthday (age 19-25).
|
||||
* @returns {{ year: number, month: number, day: number }}
|
||||
*/
|
||||
function generateRandomBirthday() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const age = 19 + Math.floor(Math.random() * 7); // 19 to 25
|
||||
const year = currentYear - age;
|
||||
const month = 1 + Math.floor(Math.random() * 12); // 1 to 12
|
||||
const maxDay = new Date(year, month, 0).getDate(); // days in that month
|
||||
const day = 1 + Math.floor(Math.random() * maxDay);
|
||||
return { year, month, day };
|
||||
}
|
||||
37
Extensions/autoRegisterPlugins/lib/mail-polling.js
Normal file
37
Extensions/autoRegisterPlugins/lib/mail-polling.js
Normal file
@@ -0,0 +1,37 @@
|
||||
(function attachMailPollingApi(globalScope) {
|
||||
const MAIL_POLL_INTERVAL_MS = 3000;
|
||||
const MAIL_POLL_ATTEMPTS_PER_ROUND = 10;
|
||||
const MAIL_POLL_TOTAL_ROUNDS = 5;
|
||||
|
||||
function getMailPollConfig() {
|
||||
return {
|
||||
intervalMs: MAIL_POLL_INTERVAL_MS,
|
||||
maxAttempts: MAIL_POLL_ATTEMPTS_PER_ROUND,
|
||||
totalRounds: MAIL_POLL_TOTAL_ROUNDS,
|
||||
};
|
||||
}
|
||||
|
||||
function shouldRetryMailPollRound(round, totalRounds = MAIL_POLL_TOTAL_ROUNDS) {
|
||||
return Number(round) < Number(totalRounds);
|
||||
}
|
||||
|
||||
function formatMailPollingLog(step, mailboxLabel, round, config = getMailPollConfig()) {
|
||||
const intervalSeconds = Number(config.intervalMs) / 1000;
|
||||
return `Step ${step}: Polling ${mailboxLabel} (${round}/${config.totalRounds}, ${config.maxAttempts} checks x ${intervalSeconds}s)...`;
|
||||
}
|
||||
|
||||
const api = {
|
||||
MAIL_POLL_INTERVAL_MS,
|
||||
MAIL_POLL_ATTEMPTS_PER_ROUND,
|
||||
MAIL_POLL_TOTAL_ROUNDS,
|
||||
getMailPollConfig,
|
||||
shouldRetryMailPollRound,
|
||||
formatMailPollingLog,
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = api;
|
||||
}
|
||||
|
||||
Object.assign(globalScope, api);
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : self);
|
||||
98
Extensions/autoRegisterPlugins/lib/step-waiters.js
Normal file
98
Extensions/autoRegisterPlugins/lib/step-waiters.js
Normal file
@@ -0,0 +1,98 @@
|
||||
(function attachStepWaiterApi(globalScope) {
|
||||
function normalizeError(error, fallbackMessage) {
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
}
|
||||
if (typeof error === 'string' && error) {
|
||||
return new Error(error);
|
||||
}
|
||||
return new Error(fallbackMessage);
|
||||
}
|
||||
|
||||
function createStepWaiterRegistry(options = {}) {
|
||||
const schedule = options.schedule || setTimeout;
|
||||
const cancel = options.cancel || clearTimeout;
|
||||
const waiters = new Map();
|
||||
|
||||
function clearOwnedWaiter(step, waiter) {
|
||||
if (waiters.get(step) === waiter) {
|
||||
waiters.delete(step);
|
||||
}
|
||||
}
|
||||
|
||||
function waitForStepComplete(step, timeoutMs = 120000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const existing = waiters.get(step);
|
||||
if (existing) {
|
||||
existing.reject(new Error(`Step ${step} waiter was replaced by a newer execution.`));
|
||||
}
|
||||
|
||||
let settled = false;
|
||||
const waiter = {
|
||||
resolve: (payload) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
cancel(waiter.timer);
|
||||
clearOwnedWaiter(step, waiter);
|
||||
resolve(payload);
|
||||
},
|
||||
reject: (error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
cancel(waiter.timer);
|
||||
clearOwnedWaiter(step, waiter);
|
||||
reject(normalizeError(error, `Step ${step} failed.`));
|
||||
},
|
||||
timer: null,
|
||||
};
|
||||
|
||||
waiter.timer = schedule(() => {
|
||||
waiter.reject(new Error(`Step ${step} timed out after ${timeoutMs / 1000}s.`));
|
||||
}, timeoutMs);
|
||||
|
||||
waiters.set(step, waiter);
|
||||
});
|
||||
}
|
||||
|
||||
function notifyStepComplete(step, payload) {
|
||||
const waiter = waiters.get(step);
|
||||
if (waiter) {
|
||||
waiter.resolve(payload);
|
||||
}
|
||||
}
|
||||
|
||||
function notifyStepError(step, error) {
|
||||
const waiter = waiters.get(step);
|
||||
if (waiter) {
|
||||
waiter.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
function rejectAll(error) {
|
||||
const pending = Array.from(waiters.values());
|
||||
waiters.clear();
|
||||
for (const waiter of pending) {
|
||||
waiter.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
waitForStepComplete,
|
||||
notifyStepComplete,
|
||||
notifyStepError,
|
||||
rejectAll,
|
||||
};
|
||||
}
|
||||
|
||||
const api = { createStepWaiterRegistry };
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = api;
|
||||
}
|
||||
|
||||
Object.assign(globalScope, api);
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : self);
|
||||
96
Extensions/autoRegisterPlugins/manifest.json
Normal file
96
Extensions/autoRegisterPlugins/manifest.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Auto Register 2925",
|
||||
"version": "0.3.0",
|
||||
"description": "OpenAI account creation helper with 2925 and DDG mailbox polling",
|
||||
"permissions": [
|
||||
"sidePanel",
|
||||
"tabs",
|
||||
"webNavigation",
|
||||
"debugger",
|
||||
"browsingData",
|
||||
"storage",
|
||||
"scripting",
|
||||
"activeTab"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"side_panel": {
|
||||
"default_path": "sidepanel/sidepanel.html"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"https://auth0.openai.com/*",
|
||||
"https://auth.openai.com/*",
|
||||
"https://accounts.openai.com/*"
|
||||
],
|
||||
"js": [
|
||||
"content/utils.js",
|
||||
"content/signup-page.js"
|
||||
],
|
||||
"run_at": "document_idle"
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"https://www.2925.com/*"
|
||||
],
|
||||
"js": [
|
||||
"content/utils.js",
|
||||
"content/mail-2925.js"
|
||||
],
|
||||
"all_frames": true,
|
||||
"run_at": "document_idle"
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"https://mail.qq.com/*",
|
||||
"https://wx.mail.qq.com/*"
|
||||
],
|
||||
"js": [
|
||||
"content/utils.js",
|
||||
"content/qq-mail.js"
|
||||
],
|
||||
"all_frames": true,
|
||||
"run_at": "document_idle"
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"https://mail.163.com/*"
|
||||
],
|
||||
"js": [
|
||||
"content/utils.js",
|
||||
"content/mail-163.js"
|
||||
],
|
||||
"all_frames": true,
|
||||
"run_at": "document_idle"
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"https://duckduckgo.com/email/settings/autofill*"
|
||||
],
|
||||
"js": [
|
||||
"content/utils.js",
|
||||
"content/duck-mail.js"
|
||||
],
|
||||
"run_at": "document_idle"
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"content/mail-2925-bridge.js"
|
||||
],
|
||||
"matches": [
|
||||
"https://www.2925.com/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"default_title": "Open Auto Register"
|
||||
}
|
||||
}
|
||||
803
Extensions/autoRegisterPlugins/sidepanel/sidepanel.css
Normal file
803
Extensions/autoRegisterPlugins/sidepanel/sidepanel.css
Normal file
@@ -0,0 +1,803 @@
|
||||
/* ============================================================
|
||||
MultiPage Automation — Side Panel
|
||||
Design: Swiss Modernism + Developer Tool
|
||||
Font: Inter (UI) + JetBrains Mono (code)
|
||||
Themes: Light (default) + Dark (toggle)
|
||||
============================================================ */
|
||||
|
||||
/* ---- Light Theme (default) ---- */
|
||||
:root {
|
||||
--bg-base: #ffffff;
|
||||
--bg-surface: #f7f8fa;
|
||||
--bg-elevated: #eef0f4;
|
||||
--bg-hover: #e4e7ec;
|
||||
--bg-active: #dce0e8;
|
||||
|
||||
--border: #d8dce3;
|
||||
--border-subtle: #e8ecf1;
|
||||
|
||||
--text-primary: #1a1d24;
|
||||
--text-secondary: #5c6370;
|
||||
--text-muted: #9ca3af;
|
||||
|
||||
--blue: #2563eb;
|
||||
--blue-soft: rgba(37, 99, 235, 0.08);
|
||||
--blue-glow: rgba(37, 99, 235, 0.12);
|
||||
--green: #16a34a;
|
||||
--green-soft: rgba(22, 163, 74, 0.08);
|
||||
--orange: #ea580c;
|
||||
--orange-soft: rgba(234, 88, 12, 0.08);
|
||||
--red: #dc2626;
|
||||
--red-soft: rgba(220, 38, 38, 0.08);
|
||||
--cyan: #0891b2;
|
||||
--purple: #7c3aed;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
|
||||
--shadow-md: 0 2px 6px rgba(0,0,0,0.06);
|
||||
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 8px;
|
||||
--transition: 150ms ease;
|
||||
}
|
||||
|
||||
/* ---- Dark Theme ---- */
|
||||
[data-theme="dark"] {
|
||||
--bg-base: #0f1117;
|
||||
--bg-surface: #181a21;
|
||||
--bg-elevated: #21242d;
|
||||
--bg-hover: #2a2e38;
|
||||
--bg-active: #323844;
|
||||
|
||||
--border: #2a2e38;
|
||||
--border-subtle: #21242d;
|
||||
|
||||
--text-primary: #e4e6eb;
|
||||
--text-secondary: #8b919e;
|
||||
--text-muted: #565c6a;
|
||||
|
||||
--blue: #3b82f6;
|
||||
--blue-soft: rgba(59, 130, 246, 0.12);
|
||||
--blue-glow: rgba(59, 130, 246, 0.18);
|
||||
--green: #22c55e;
|
||||
--green-soft: rgba(34, 197, 94, 0.12);
|
||||
--orange: #f97316;
|
||||
--orange-soft: rgba(249, 115, 22, 0.12);
|
||||
--red: #ef4444;
|
||||
--red-soft: rgba(239, 68, 68, 0.12);
|
||||
--cyan: #06b6d4;
|
||||
--purple: #a78bfa;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
|
||||
--shadow-md: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-base);
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
transition: background var(--transition), color var(--transition);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Header
|
||||
============================================================ */
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-left svg { color: var(--blue); }
|
||||
|
||||
.header-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-account-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-surface);
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.header-left h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.header-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Theme Toggle
|
||||
============================================================ */
|
||||
|
||||
.theme-toggle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
.theme-toggle:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||
.theme-toggle .icon-moon { display: block; }
|
||||
.theme-toggle .icon-sun { display: none; }
|
||||
[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
|
||||
[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
|
||||
|
||||
/* ============================================================
|
||||
Buttons
|
||||
============================================================ */
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 14px;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--blue);
|
||||
color: #fff;
|
||||
}
|
||||
.btn-primary:hover { opacity: 0.9; box-shadow: 0 2px 8px var(--blue-glow); }
|
||||
|
||||
.btn-success {
|
||||
background: var(--green);
|
||||
color: #fff;
|
||||
}
|
||||
.btn-success:hover { opacity: 0.9; box-shadow: 0 2px 8px var(--green-soft); }
|
||||
|
||||
.btn-danger {
|
||||
background: var(--red);
|
||||
color: #fff;
|
||||
}
|
||||
.btn-danger:hover { opacity: 0.9; box-shadow: 0 2px 8px var(--red-soft); }
|
||||
|
||||
.run-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.run-count-input {
|
||||
width: 42px;
|
||||
padding: 6px 4px;
|
||||
text-align: center;
|
||||
background: var(--bg-base);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
}
|
||||
.run-count-input:focus { border-color: var(--blue); }
|
||||
.run-count-input::-webkit-inner-spin-button { opacity: 0.5; }
|
||||
.btn-success:disabled, .btn-primary:disabled, .btn-danger:disabled { background: var(--bg-elevated); color: var(--text-muted); cursor: not-allowed; box-shadow: none; }
|
||||
.run-count-input:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
padding: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.btn-ghost:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.btn-outline:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); }
|
||||
|
||||
.btn-sm { padding: 5px 12px; font-size: 12px; }
|
||||
.btn-xs { padding: 4px 10px; font-size: 11px; }
|
||||
|
||||
/* ============================================================
|
||||
Data Card
|
||||
============================================================ */
|
||||
|
||||
#data-section { margin-bottom: 14px; }
|
||||
|
||||
.data-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 12px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 9px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.data-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.data-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
width: 78px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.data-value.has-value { color: var(--text-primary); }
|
||||
|
||||
.mono {
|
||||
font-family: 'JetBrains Mono', 'Consolas', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-input {
|
||||
flex: 1;
|
||||
padding: 7px 10px;
|
||||
background: var(--bg-base);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
transition: border-color var(--transition), box-shadow var(--transition);
|
||||
min-width: 0;
|
||||
}
|
||||
.data-input::placeholder { color: var(--text-muted); }
|
||||
.data-input:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
|
||||
|
||||
#btn-fetch-email {
|
||||
padding-inline: 10px;
|
||||
}
|
||||
|
||||
#btn-toggle-password {
|
||||
min-width: 58px;
|
||||
padding-inline: 10px;
|
||||
}
|
||||
|
||||
.data-select {
|
||||
flex: 1;
|
||||
padding: 7px 10px;
|
||||
background: var(--bg-base);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: border-color var(--transition);
|
||||
min-width: 0;
|
||||
}
|
||||
.data-select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
|
||||
[data-theme="dark"] .data-select { color-scheme: dark; }
|
||||
|
||||
/* Status Bar */
|
||||
.status-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.status-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-stat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-base);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.status-stat.success {
|
||||
color: var(--green);
|
||||
background: var(--green-soft);
|
||||
border-color: rgba(22, 163, 74, 0.16);
|
||||
}
|
||||
|
||||
.status-stat.failure {
|
||||
color: var(--red);
|
||||
background: var(--red-soft);
|
||||
border-color: rgba(220, 38, 38, 0.16);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.status-bar.running .status-dot {
|
||||
background: var(--orange);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
.status-bar.running { color: var(--orange); }
|
||||
|
||||
.status-bar.completed .status-dot { background: var(--green); }
|
||||
.status-bar.completed { color: var(--green); }
|
||||
|
||||
.status-bar.failed .status-dot { background: var(--red); }
|
||||
.status-bar.failed { color: var(--red); }
|
||||
|
||||
.status-bar.stopped .status-dot { background: var(--cyan); }
|
||||
.status-bar.stopped { color: var(--cyan); }
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.4; transform: scale(0.85); }
|
||||
}
|
||||
|
||||
/* Auto Continue Bar */
|
||||
.auto-continue-bar {
|
||||
margin-top: 8px;
|
||||
padding: 8px 10px;
|
||||
background: var(--orange-soft);
|
||||
border: 1px solid rgba(234, 88, 12, 0.2);
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.auto-continue-bar svg { color: var(--orange); flex-shrink: 0; }
|
||||
.auto-hint { font-size: 13px; color: var(--orange); flex: 1; font-weight: 500; }
|
||||
|
||||
/* ============================================================
|
||||
Steps Section
|
||||
============================================================ */
|
||||
|
||||
#steps-section { margin-bottom: 14px; }
|
||||
|
||||
.steps-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.steps-progress {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-surface);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.steps-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.step-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 1px 0;
|
||||
transition: opacity var(--transition);
|
||||
}
|
||||
|
||||
/* Step Number Indicator */
|
||||
.step-indicator {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-surface);
|
||||
border: 1.5px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.step-num {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
transition: color var(--transition);
|
||||
}
|
||||
|
||||
.step-row.running .step-indicator { border-color: var(--orange); background: var(--orange-soft); }
|
||||
.step-row.running .step-num { color: var(--orange); }
|
||||
.step-row.running .step-indicator { animation: pulse 1.5s ease-in-out infinite; }
|
||||
|
||||
.step-row.completed .step-indicator { border-color: var(--green); background: var(--green-soft); }
|
||||
.step-row.completed .step-num { color: var(--green); }
|
||||
|
||||
.step-row.failed .step-indicator { border-color: var(--red); background: var(--red-soft); }
|
||||
.step-row.failed .step-num { color: var(--red); }
|
||||
|
||||
.step-row.stopped .step-indicator { border-color: var(--cyan); background: rgba(8, 145, 178, 0.08); }
|
||||
.step-row.stopped .step-num { color: var(--cyan); }
|
||||
|
||||
/* Step Button */
|
||||
.step-btn {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: all var(--transition);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.step-btn:hover:not(:disabled) { background: var(--bg-hover); border-color: var(--blue); }
|
||||
.step-btn:disabled { color: var(--text-muted); background: var(--bg-base); border-color: var(--border-subtle); cursor: not-allowed; opacity: 0.45; box-shadow: none; }
|
||||
|
||||
.step-row.running .step-btn { border-color: var(--orange); color: var(--orange); }
|
||||
.step-row.completed .step-btn { border-color: var(--border-subtle); color: var(--text-secondary); opacity: 0.7; }
|
||||
.step-row.failed .step-btn { border-color: var(--red); color: var(--red); }
|
||||
.step-row.stopped .step-btn { border-color: var(--cyan); color: var(--cyan); }
|
||||
|
||||
.step-status {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.step-row.completed .step-status { color: var(--green); }
|
||||
.step-row.failed .step-status { color: var(--red); }
|
||||
.step-row.stopped .step-status { color: var(--cyan); }
|
||||
|
||||
/* ============================================================
|
||||
Log / Console Section
|
||||
============================================================ */
|
||||
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
#log-area {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 10px 12px;
|
||||
height: 220px;
|
||||
overflow-y: auto;
|
||||
font-family: 'JetBrains Mono', 'Consolas', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
#log-area::-webkit-scrollbar { width: 5px; }
|
||||
#log-area::-webkit-scrollbar-track { background: transparent; }
|
||||
#log-area::-webkit-scrollbar-thumb { background: var(--bg-elevated); border-radius: 4px; }
|
||||
#log-area::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
||||
|
||||
.log-line { padding: 2.5px 0; }
|
||||
.log-line + .log-line { border-top: 1px solid var(--border-subtle); }
|
||||
|
||||
.log-time { color: var(--text-muted); }
|
||||
|
||||
.log-level { font-weight: 700; margin: 0 2px; }
|
||||
.log-level-info { color: var(--blue); }
|
||||
.log-level-ok { color: var(--green); }
|
||||
.log-level-warn { color: var(--orange); }
|
||||
.log-level-error { color: var(--red); }
|
||||
|
||||
.log-step-tag {
|
||||
display: inline-block;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.log-step-tag.step-1 { color: var(--cyan); background: rgba(8, 145, 178, 0.08); }
|
||||
.log-step-tag.step-2 { color: var(--purple); background: rgba(124, 58, 237, 0.08); }
|
||||
.log-step-tag.step-3 { color: #b45309; background: rgba(180, 83, 9, 0.06); }
|
||||
[data-theme="dark"] .log-step-tag.step-3 { color: #fbbf24; background: rgba(251, 191, 36, 0.1); }
|
||||
.log-step-tag.step-4 { color: var(--orange); background: var(--orange-soft); }
|
||||
.log-step-tag.step-5 { color: var(--green); background: var(--green-soft); }
|
||||
.log-step-tag.step-6 { color: var(--cyan); background: rgba(8, 145, 178, 0.08); }
|
||||
.log-step-tag.step-7 { color: var(--orange); background: var(--orange-soft); }
|
||||
.log-step-tag.step-8 { color: var(--purple); background: rgba(124, 58, 237, 0.08); }
|
||||
.log-step-tag.step-9 { color: var(--green); background: var(--green-soft); }
|
||||
|
||||
.log-msg { color: var(--text-secondary); }
|
||||
.log-line.log-ok .log-msg { color: var(--green); font-weight: 500; }
|
||||
.log-line.log-error .log-msg { color: var(--red); font-weight: 500; }
|
||||
.log-line.log-warn .log-msg { color: var(--orange); }
|
||||
|
||||
/* ============================================================
|
||||
Animations
|
||||
============================================================ */
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(3px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.log-line { animation: fadeIn 120ms ease-out; }
|
||||
|
||||
/* ============================================================
|
||||
Toast Notifications
|
||||
============================================================ */
|
||||
|
||||
#toast-container {
|
||||
position: fixed;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-md), 0 4px 12px rgba(0,0,0,0.1);
|
||||
pointer-events: auto;
|
||||
animation: toastIn 250ms ease-out;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.toast.toast-exit {
|
||||
animation: toastOut 200ms ease-in forwards;
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
background: var(--red-soft);
|
||||
border-color: var(--red);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.toast-warn {
|
||||
background: var(--orange-soft);
|
||||
border-color: var(--orange);
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
background: var(--green-soft);
|
||||
border-color: var(--green);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
background: var(--blue-soft);
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.toast svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toast-msg {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.toast-close:hover { opacity: 1; }
|
||||
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateY(-10px) scale(0.96); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
@keyframes toastOut {
|
||||
from { opacity: 1; transform: translateY(0) scale(1); }
|
||||
to { opacity: 0; transform: translateY(-10px) scale(0.96); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions,
|
||||
.steps-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.data-help code {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
padding: 1px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.step-btn {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-subtitle {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.step-status {
|
||||
min-width: 86px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.step-status-icon {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.step-status.pending {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.step-row.resume-target {
|
||||
box-shadow: 0 0 0 3px var(--blue-soft), var(--shadow-sm);
|
||||
}
|
||||
120
Extensions/autoRegisterPlugins/sidepanel/sidepanel.html
Normal file
120
Extensions/autoRegisterPlugins/sidepanel/sidepanel.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Auto Register 2925</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="sidepanel.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<div class="header-title-row">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M8 6h13"></path>
|
||||
<path d="M8 12h13"></path>
|
||||
<path d="M8 18h13"></path>
|
||||
<path d="M3 6h.01"></path>
|
||||
<path d="M3 12h.01"></path>
|
||||
<path d="M3 18h.01"></path>
|
||||
</svg>
|
||||
<h1>AutoRegister</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div class="run-group">
|
||||
<input type="number" id="input-run-count" class="run-count-input" value="1" min="1" max="999" title="次数">
|
||||
<button id="btn-toggle-run" class="btn btn-success" type="button">
|
||||
<span class="btn-icon" aria-hidden="true">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
</span>
|
||||
<span id="btn-toggle-run-label">开始</span>
|
||||
</button>
|
||||
</div>
|
||||
<button id="btn-refresh" class="btn btn-ghost" type="button" title="刷新状态">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="1 4 1 10 7 10"></polyline>
|
||||
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="data-section">
|
||||
<div class="data-card">
|
||||
<div class="data-row">
|
||||
<span class="data-label">VPS</span>
|
||||
<input type="text" id="input-vps-url" class="data-input" placeholder="http://ip:port/management.html#/oauth">
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">Mail 类型</span>
|
||||
<select id="select-mail-provider" class="data-select">
|
||||
<option value="2925">2925 邮箱</option>
|
||||
<option value="ddg">DDG</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="data-row" id="row-main-email">
|
||||
<span class="data-label">主邮箱</span>
|
||||
<input type="text" id="input-main-email" class="data-input" placeholder="例如 jariviswang@2925.com">
|
||||
<select id="select-ddg-mailbox-provider" class="data-select hidden">
|
||||
<option value="qq">QQ邮箱</option>
|
||||
<option value="163">163邮箱</option>
|
||||
<option value="2925">2925邮箱</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">Email</span>
|
||||
<input type="text" id="input-email" class="data-input mono" placeholder="运行时自动生成注册邮箱" readonly>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">Password</span>
|
||||
<div class="data-inline">
|
||||
<input type="password" id="input-password" class="data-input mono" placeholder="留空则自动生成 OpenAI 密码">
|
||||
<button id="btn-toggle-password" class="btn btn-outline btn-sm" type="button">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">OAuth</span>
|
||||
<input type="text" id="input-oauth-url" class="data-input mono" placeholder="可手动填写 OAuth 链接,也可由 Step 5 自动获取">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="status-bar" class="status-bar idle">
|
||||
<div class="status-main">
|
||||
<div class="status-dot"></div>
|
||||
<span id="display-status">就绪</span>
|
||||
</div>
|
||||
<div id="display-run-summary" class="status-summary hidden">
|
||||
<span id="display-run-success" class="status-stat success">成功 0</span>
|
||||
<span id="display-run-failure" class="status-stat failure">失败 0</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="steps-section">
|
||||
<div class="steps-header">
|
||||
<span class="section-label">Workflow</span>
|
||||
<div class="steps-actions">
|
||||
<button id="btn-workflow-continue" class="btn btn-primary btn-xs workflow-continue hidden" type="button">继续</button>
|
||||
<span id="steps-progress" class="steps-progress">0 / 8</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="steps-list" class="steps-list"></div>
|
||||
</section>
|
||||
|
||||
<section id="log-section">
|
||||
<div class="log-header">
|
||||
<span class="section-label">Console</span>
|
||||
<button id="btn-clear-log" class="btn btn-ghost btn-xs" type="button">Clear</button>
|
||||
</div>
|
||||
<div id="log-area"></div>
|
||||
</section>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
<script src="sidepanel.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
405
Extensions/autoRegisterPlugins/sidepanel/sidepanel.js
Normal file
405
Extensions/autoRegisterPlugins/sidepanel/sidepanel.js
Normal file
@@ -0,0 +1,405 @@
|
||||
const PLAY_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>';
|
||||
const PAUSE_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg>';
|
||||
|
||||
const STEPS = [
|
||||
{ id: 1, title: 'Open Signup', subtitle: '打开注册授权页' },
|
||||
{ id: 2, title: 'Fill Email / Password', subtitle: '填入注册邮箱和密码' },
|
||||
{ id: 3, title: 'Get Signup Code', subtitle: '轮询注册验证码并回填' },
|
||||
{ id: 4, title: 'Fill Name / Birthday', subtitle: '填写姓名和生日' },
|
||||
{ id: 5, title: 'Get OAuth Link', subtitle: '获取登录所需的最新授权链接' },
|
||||
{ id: 6, title: 'Login via OAuth', subtitle: '使用已注册账号重新登录' },
|
||||
{ id: 7, title: 'Get Login Code', subtitle: '轮询登录验证码并回填' },
|
||||
{ id: 8, title: 'OAuth Auto Confirm', subtitle: '自动点击继续并捕获 localhost 回调' },
|
||||
];
|
||||
|
||||
const STATUS_LABELS = {
|
||||
pending: '待执行',
|
||||
running: '执行中',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
stopped: '已停止',
|
||||
};
|
||||
|
||||
const STATUS_ICONS = {
|
||||
pending: '',
|
||||
running: '...',
|
||||
completed: '✓',
|
||||
failed: '✕',
|
||||
stopped: '■',
|
||||
};
|
||||
|
||||
const uiState = {
|
||||
runState: 'idle',
|
||||
currentStep: 0,
|
||||
currentRunIndex: 0,
|
||||
runCount: 1,
|
||||
runSuccessCount: 0,
|
||||
runFailureCount: 0,
|
||||
mainEmail: '',
|
||||
email: '',
|
||||
password: '',
|
||||
customPassword: '',
|
||||
oauthUrl: '',
|
||||
vpsUrl: '',
|
||||
mailProvider: '2925',
|
||||
ddgMailboxProvider: 'qq',
|
||||
manualContinueVisible: false,
|
||||
manualResumeFromStep: null,
|
||||
lastManualStep: null,
|
||||
stepStatuses: createDefaultStepStatuses(),
|
||||
};
|
||||
|
||||
const logArea = document.getElementById('log-area');
|
||||
const inputVpsUrl = document.getElementById('input-vps-url');
|
||||
const selectMailProvider = document.getElementById('select-mail-provider');
|
||||
const rowMainEmail = document.getElementById('row-main-email');
|
||||
const inputMainEmail = document.getElementById('input-main-email');
|
||||
const selectDdgMailboxProvider = document.getElementById('select-ddg-mailbox-provider');
|
||||
const inputEmail = document.getElementById('input-email');
|
||||
const inputPassword = document.getElementById('input-password');
|
||||
const btnTogglePassword = document.getElementById('btn-toggle-password');
|
||||
const inputOauthUrl = document.getElementById('input-oauth-url');
|
||||
const inputRunCount = document.getElementById('input-run-count');
|
||||
const btnToggleRun = document.getElementById('btn-toggle-run');
|
||||
const btnToggleRunLabel = document.getElementById('btn-toggle-run-label');
|
||||
const btnRefresh = document.getElementById('btn-refresh');
|
||||
const btnClearLog = document.getElementById('btn-clear-log');
|
||||
const statusBar = document.getElementById('status-bar');
|
||||
const displayStatus = document.getElementById('display-status');
|
||||
const displayRunSummary = document.getElementById('display-run-summary');
|
||||
const displayRunSuccess = document.getElementById('display-run-success');
|
||||
const displayRunFailure = document.getElementById('display-run-failure');
|
||||
const stepsList = document.getElementById('steps-list');
|
||||
const stepsProgress = document.getElementById('steps-progress');
|
||||
const btnWorkflowContinue = document.getElementById('btn-workflow-continue');
|
||||
const toastContainer = document.getElementById('toast-container');
|
||||
|
||||
renderWorkflowRows();
|
||||
bindEvents();
|
||||
restoreState();
|
||||
|
||||
function createDefaultStepStatuses() {
|
||||
return STEPS.reduce((statuses, step) => {
|
||||
statuses[step.id] = 'pending';
|
||||
return statuses;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function renderWorkflowRows() {
|
||||
stepsList.innerHTML = STEPS.map((step) => `
|
||||
<div class="step-row" data-step="${step.id}">
|
||||
<div class="step-indicator"><span class="step-num">${step.id}</span></div>
|
||||
<button class="step-btn" data-step="${step.id}" type="button">
|
||||
<span class="step-title">${escapeHtml(step.title)}</span>
|
||||
<span class="step-subtitle">${escapeHtml(step.subtitle)}</span>
|
||||
</button>
|
||||
<span class="step-status pending" data-step="${step.id}">
|
||||
<span class="step-status-icon"></span>
|
||||
<span class="step-status-label">${STATUS_LABELS.pending}</span>
|
||||
</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
inputVpsUrl.addEventListener('change', () => saveSettings({ vpsUrl: inputVpsUrl.value }));
|
||||
selectMailProvider.addEventListener('change', () => {
|
||||
updateMailProviderUI();
|
||||
saveSettings({ mailProvider: selectMailProvider.value });
|
||||
});
|
||||
inputMainEmail.addEventListener('change', () => saveSettings({ mainEmail: inputMainEmail.value }));
|
||||
selectDdgMailboxProvider.addEventListener('change', () => saveSettings({ ddgMailboxProvider: selectDdgMailboxProvider.value }));
|
||||
inputPassword.addEventListener('change', () => saveSettings({ customPassword: inputPassword.value }));
|
||||
inputOauthUrl.addEventListener('change', () => saveSettings({ oauthUrl: inputOauthUrl.value }));
|
||||
inputRunCount.addEventListener('change', handleRunCountChange);
|
||||
|
||||
btnTogglePassword.addEventListener('click', () => {
|
||||
inputPassword.type = inputPassword.type === 'password' ? 'text' : 'password';
|
||||
btnTogglePassword.textContent = inputPassword.type === 'password' ? 'Show' : 'Hide';
|
||||
});
|
||||
|
||||
btnToggleRun.addEventListener('click', async () => {
|
||||
btnToggleRun.disabled = true;
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ type: 'TOGGLE_RUN' });
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
} finally {
|
||||
btnToggleRun.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
btnRefresh.addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ type: 'REFRESH_RUNTIME' });
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
showToast('运行状态已刷新', 'info');
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
btnClearLog.addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ type: 'CLEAR_LOGS' });
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
btnWorkflowContinue.addEventListener('click', async () => {
|
||||
btnWorkflowContinue.disabled = true;
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ type: 'CONTINUE_FROM_MANUAL' });
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
} finally {
|
||||
btnWorkflowContinue.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
stepsList.addEventListener('click', async (event) => {
|
||||
const button = event.target.closest('.step-btn');
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
const step = Number(button.dataset.step);
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
type: 'MANUAL_RUN_STEP',
|
||||
payload: { step },
|
||||
});
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((message) => {
|
||||
switch (message.type) {
|
||||
case 'STATE_UPDATED':
|
||||
case 'DATA_UPDATED':
|
||||
applyPartialState(message.payload || {});
|
||||
break;
|
||||
case 'LOG_ENTRY':
|
||||
appendLog(message.payload);
|
||||
if (message.payload?.level === 'error') {
|
||||
showToast(message.payload.message, 'error');
|
||||
}
|
||||
break;
|
||||
case 'LOGS_CLEARED':
|
||||
logArea.innerHTML = '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function restoreState() {
|
||||
try {
|
||||
const state = await chrome.runtime.sendMessage({ type: 'GET_STATE' });
|
||||
if (state?.error) {
|
||||
throw new Error(state.error);
|
||||
}
|
||||
|
||||
Object.assign(uiState, state, {
|
||||
stepStatuses: {
|
||||
...createDefaultStepStatuses(),
|
||||
...(state.stepStatuses || {}),
|
||||
},
|
||||
});
|
||||
|
||||
renderState();
|
||||
logArea.innerHTML = '';
|
||||
for (const entry of state.logs || []) {
|
||||
appendLog(entry);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function applyPartialState(payload) {
|
||||
const nextState = { ...payload };
|
||||
if (payload.stepStatuses) {
|
||||
nextState.stepStatuses = {
|
||||
...uiState.stepStatuses,
|
||||
...payload.stepStatuses,
|
||||
};
|
||||
}
|
||||
Object.assign(uiState, nextState);
|
||||
renderState();
|
||||
}
|
||||
|
||||
function renderState() {
|
||||
inputVpsUrl.value = uiState.vpsUrl || '';
|
||||
selectMailProvider.value = uiState.mailProvider || '2925';
|
||||
inputMainEmail.value = uiState.mainEmail || '';
|
||||
selectDdgMailboxProvider.value = uiState.ddgMailboxProvider || 'qq';
|
||||
inputEmail.value = uiState.email || '';
|
||||
inputPassword.value = uiState.customPassword || uiState.password || '';
|
||||
inputOauthUrl.value = uiState.oauthUrl || '';
|
||||
inputRunCount.value = String(uiState.runCount || 1);
|
||||
updateMailProviderUI();
|
||||
updateRunState();
|
||||
renderWorkflow();
|
||||
}
|
||||
|
||||
function updateRunState() {
|
||||
const runState = uiState.runState || 'idle';
|
||||
statusBar.className = `status-bar ${runState}`;
|
||||
displayStatus.textContent = buildStatusText();
|
||||
renderRunSummary();
|
||||
|
||||
const isRunning = runState === 'running';
|
||||
inputRunCount.disabled = isRunning;
|
||||
inputVpsUrl.disabled = isRunning;
|
||||
selectMailProvider.disabled = isRunning;
|
||||
inputMainEmail.disabled = isRunning;
|
||||
selectDdgMailboxProvider.disabled = isRunning;
|
||||
inputOauthUrl.disabled = isRunning;
|
||||
inputPassword.disabled = isRunning;
|
||||
|
||||
btnToggleRun.className = isRunning ? 'btn btn-warning' : 'btn btn-success';
|
||||
btnToggleRun.querySelector('.btn-icon').innerHTML = isRunning ? PAUSE_ICON : PLAY_ICON;
|
||||
btnToggleRunLabel.textContent = isRunning ? '暂停' : '开始';
|
||||
}
|
||||
|
||||
function renderRunSummary() {
|
||||
const totalRuns = Number(uiState.runCount || 1);
|
||||
const successCount = Number(uiState.runSuccessCount || 0);
|
||||
const failureCount = Number(uiState.runFailureCount || 0);
|
||||
const shouldShow = totalRuns > 1 && (uiState.currentRunIndex > 0 || successCount > 0 || failureCount > 0);
|
||||
|
||||
displayRunSummary.classList.toggle('hidden', !shouldShow);
|
||||
if (!shouldShow) {
|
||||
return;
|
||||
}
|
||||
|
||||
displayRunSuccess.textContent = `成功 ${successCount}`;
|
||||
displayRunFailure.textContent = `失败 ${failureCount}`;
|
||||
}
|
||||
function updateMailProviderUI() {
|
||||
const useDdg = (selectMailProvider.value || '2925') === 'ddg';
|
||||
rowMainEmail.dataset.mode = useDdg ? 'ddg' : '2925';
|
||||
inputMainEmail.classList.toggle('hidden', useDdg);
|
||||
selectDdgMailboxProvider.classList.toggle('hidden', !useDdg);
|
||||
}
|
||||
|
||||
function buildStatusText() {
|
||||
const runState = uiState.runState || 'idle';
|
||||
if (runState === 'running') {
|
||||
const totalRuns = uiState.runCount || 1;
|
||||
const runIndex = uiState.currentRunIndex || 1;
|
||||
const step = uiState.currentStep || 1;
|
||||
return totalRuns > 1 ? `第 ${runIndex}/${totalRuns} 轮 · Step ${step}` : `Step ${step} 执行中`;
|
||||
}
|
||||
|
||||
if (runState === 'paused' && uiState.manualContinueVisible && uiState.manualResumeFromStep) {
|
||||
return `已暂停 · 可从 Step ${uiState.manualResumeFromStep} 继续`;
|
||||
}
|
||||
|
||||
return runState === 'paused' ? '已暂停' : '就绪';
|
||||
}
|
||||
|
||||
function renderWorkflow() {
|
||||
const completedCount = STEPS.filter((step) => uiState.stepStatuses[step.id] === 'completed').length;
|
||||
stepsProgress.textContent = `${completedCount} / ${STEPS.length}`;
|
||||
|
||||
const showContinue = Boolean(uiState.manualContinueVisible && uiState.manualResumeFromStep);
|
||||
btnWorkflowContinue.classList.toggle('hidden', !showContinue);
|
||||
btnWorkflowContinue.textContent = showContinue ? `继续 Step ${uiState.manualResumeFromStep}` : '继续';
|
||||
|
||||
for (const step of STEPS) {
|
||||
const status = uiState.stepStatuses[step.id] || 'pending';
|
||||
const row = stepsList.querySelector(`.step-row[data-step="${step.id}"]`);
|
||||
const statusEl = stepsList.querySelector(`.step-status[data-step="${step.id}"]`);
|
||||
if (!row || !statusEl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
row.className = `step-row ${status}${showContinue && uiState.manualResumeFromStep === step.id ? ' resume-target' : ''}`;
|
||||
statusEl.className = `step-status ${status}`;
|
||||
statusEl.innerHTML = [
|
||||
`<span class="step-status-icon">${escapeHtml(STATUS_ICONS[status] || '')}</span>`,
|
||||
`<span class="step-status-label">${escapeHtml(STATUS_LABELS[status] || STATUS_LABELS.pending)}</span>`,
|
||||
].join('');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings(payload) {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
type: 'SAVE_SETTINGS',
|
||||
payload,
|
||||
});
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function handleRunCountChange() {
|
||||
const numeric = Number.parseInt(inputRunCount.value, 10);
|
||||
const safeCount = Number.isFinite(numeric) && numeric > 0 ? Math.min(numeric, 999) : 1;
|
||||
inputRunCount.value = String(safeCount);
|
||||
saveSettings({ runCount: safeCount });
|
||||
}
|
||||
|
||||
function appendLog(entry) {
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const line = document.createElement('div');
|
||||
line.className = `log-line log-${entry.level || 'info'}`;
|
||||
|
||||
const time = new Date(entry.timestamp || Date.now()).toLocaleTimeString('en-US', { hour12: false });
|
||||
const level = String(entry.level || 'info').toUpperCase();
|
||||
|
||||
line.innerHTML = [
|
||||
`<span class="log-time">${escapeHtml(time)}</span>`,
|
||||
`<span class="log-level log-level-${escapeHtml((entry.level || 'info').toLowerCase())}">${escapeHtml(level)}</span>`,
|
||||
`<span class="log-msg">${escapeHtml(entry.message || '')}</span>`,
|
||||
].join(' ');
|
||||
|
||||
logArea.appendChild(line);
|
||||
logArea.scrollTop = logArea.scrollHeight;
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.innerHTML = `<span class="toast-msg">${escapeHtml(message)}</span><button class="toast-close" type="button" aria-label="关闭">×</button>`;
|
||||
|
||||
toast.querySelector('.toast-close').addEventListener('click', () => toast.remove());
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
window.setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 2600);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
3
Extensions/autoRegisterPlugins/sidepanel/test.css
Normal file
3
Extensions/autoRegisterPlugins/sidepanel/test.css
Normal file
@@ -0,0 +1,3 @@
|
||||
:root {
|
||||
--a: 1;
|
||||
}
|
||||
1
Extensions/autoRegisterPlugins/sidepanel/test.txt
Normal file
1
Extensions/autoRegisterPlugins/sidepanel/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
test
|
||||
96
README.md
96
README.md
@@ -43,6 +43,12 @@ AI-Account-Toolkit/
|
||||
├── GPT_register+duckmail+CPA+autouploadsub2api/ # DuckMail + OAuth + Sub2Api 注册工具
|
||||
├── team_all-in-one/ # ChatGPT Team 一键注册工具
|
||||
├── codex/ # Codex 相关工具
|
||||
├── codex-oauth-automation-extension/ # Codex OAuth 批量自动化 Chrome 扩展
|
||||
├── codex-register-V2/ # Codex 远程注册机 V2 (Browserbase + DDG)
|
||||
├── Extensions/ # 浏览器扩展插件集 (含 2925 自动化)
|
||||
├── FreeSMS/ # 免费在线接码平台资料
|
||||
├── mailhub/ # 邮箱分享资料
|
||||
├── grok/ # SuperGrok 相关资料
|
||||
├── freemail/ # 临时邮箱服务
|
||||
├── merge-mailtm-share/ # MailTM 邮箱合并工具
|
||||
├── ob12api/ # OB12 API 服务
|
||||
@@ -165,11 +171,45 @@ AI-Account-Toolkit/
|
||||
|
||||
**使用指南**:[ClashVerge_/README.md](ClashVerge_/README.md)
|
||||
|
||||
#### 13. codex-oauth-automation-extension - Codex OAuth 批量自动化 Chrome 扩展
|
||||
|
||||
**功能**:批量跑通 ChatGPT OAuth 注册/登录流程。支持单步/整套自动执行、DDG 邮箱别名生成、验证码自动获取等功能。
|
||||
|
||||
**使用指南**:[codex-oauth-automation-extension/README.md](codex-oauth-automation-extension/README.md)
|
||||
|
||||
#### 14. codex-register-V2 - Codex 远程注册机 V2
|
||||
|
||||
**功能**:基于 Browserbase 远程浏览器和 DDG 邮箱别名的 Codex Token 自动注册工具,分两阶段完成注册和 OAuth 授权。
|
||||
|
||||
**使用指南**:[codex-register-V2/eefdb42dd6dfcbb5acee9fa2efeb03d775a509df/README.md](codex-register-V2/eefdb42dd6dfcbb5acee9fa2efeb03d775a509df/README.md)
|
||||
|
||||
#### 15. Extensions - 浏览器扩展插件集
|
||||
|
||||
**功能**:包含用于自动注册的浏览器插件,如 `autoRegisterPlugins` 针对 2925 邮箱实现的无限别名自动化注册流程。
|
||||
|
||||
**使用指南**:[Extensions/autoRegisterPlugins/README.md](Extensions/autoRegisterPlugins/README.md)
|
||||
|
||||
#### 16. FreeSMS - 免费在线接码平台资料
|
||||
|
||||
**功能**:收集整理全球多个主流免费接码平台资料,用于注册辅助。
|
||||
|
||||
#### 17. mailhub - 邮箱分享资料
|
||||
|
||||
**功能**:邮箱账号分享及相关资源汇集。
|
||||
|
||||
#### 18. grok - Grok 相关资料
|
||||
|
||||
**功能**:包含 SuperGrok 等 Grok 平台的相关研究资料。
|
||||
|
||||
#### 19. openai_register - OpenAI 注册脚本
|
||||
|
||||
**功能**:用于 OpenAI 账号注册的自动化 Python 脚本。
|
||||
|
||||
---
|
||||
|
||||
### OpenAI 相关 (packages/openai)
|
||||
|
||||
#### 13. ab-card - ChatGPT Business/Plus 自动开通工具
|
||||
#### 20. ab-card - ChatGPT Business/Plus 自动开通工具
|
||||
|
||||
**功能**:全自动注册 ChatGPT 账号 + 开通 Business 或 Plus 套餐(首月免费),支持 Web UI 操作。
|
||||
|
||||
@@ -177,13 +217,13 @@ AI-Account-Toolkit/
|
||||
|
||||
**使用指南**:[packages/openai/ab-card/README.md](packages/openai/ab-card/README.md)
|
||||
|
||||
#### 14. chatgpt-creator - ChatGPT 账号创建工具
|
||||
#### 21. chatgpt-creator - ChatGPT 账号创建工具
|
||||
|
||||
**功能**:ChatGPT 账号自动创建工具,支持批量注册和自动化流程。
|
||||
|
||||
**使用指南**:[packages/openai/chatgpt-creator/README.md](packages/openai/chatgpt-creator/README.md)
|
||||
|
||||
#### 15. openai-oauth - OpenAI OAuth 认证工具
|
||||
#### 22. openai-oauth - OpenAI OAuth 认证工具
|
||||
|
||||
**功能**:OpenAI OAuth 认证工具,提供 OAuth 自动化认证和 Token 获取功能。
|
||||
|
||||
@@ -193,9 +233,9 @@ AI-Account-Toolkit/
|
||||
|
||||
### Claude 相关 (packages/claude)
|
||||
|
||||
#### 16. claude-key-switch - Claude 密钥切换工具
|
||||
#### 23. claude-key-switch - Claude 密钥切换工具
|
||||
|
||||
**功能**:Claude API 密钥管理和切换工具,支持多密钥轮换和负载均衡。
|
||||
**功能**:Claude API 密钥管理 and 切换工具,支持多密钥轮换和负载均衡。
|
||||
|
||||
**使用指南**:[packages/claude/claude-key-switch/README.md](packages/claude/claude-key-switch/README.md)
|
||||
|
||||
@@ -203,7 +243,7 @@ AI-Account-Toolkit/
|
||||
|
||||
### Gemini 相关 (packages/gemini)
|
||||
|
||||
#### 17. gemini-balance-do - Gemini 余额查询工具
|
||||
#### 24. gemini-balance-do - Gemini 余额查询工具
|
||||
|
||||
**功能**:Gemini API 余额查询和管理工具。
|
||||
|
||||
@@ -213,19 +253,19 @@ AI-Account-Toolkit/
|
||||
|
||||
### Codex 相关 (packages/codex)
|
||||
|
||||
#### 18. codex-lb - Codex 负载均衡工具
|
||||
#### 25. codex-lb - Codex 负载均衡工具
|
||||
|
||||
**功能**:Codex API 负载均衡工具,支持多实例分发和健康检查。
|
||||
|
||||
**使用指南**:[packages/codex/codex-lb/README.md](packages/codex/codex-lb/README.md)
|
||||
|
||||
#### 19. codex-register - Codex 注册脚本
|
||||
#### 26. codex-register - Codex 注册脚本
|
||||
|
||||
**功能**:基于 Python 的 HTTP 自动化脚本,通过接口执行账号注册/登录相关步骤,并通过 MailAPI 轮询邮箱验证码,注册完成后自动上传到 CPA。
|
||||
|
||||
**使用指南**:[packages/codex/codex-register/README.md](packages/codex/codex-register/README.md)
|
||||
|
||||
#### 20. codex-register-fix - Codex 注册修复版本
|
||||
#### 27. codex-register-fix - Codex 注册修复版本
|
||||
|
||||
**功能**:基于 codex-manager 二次开发,修复了原项目因 OpenAI 授权流程变更导致的注册失败问题。
|
||||
|
||||
@@ -235,7 +275,7 @@ AI-Account-Toolkit/
|
||||
|
||||
### Cursor 相关 (packages/cursor)
|
||||
|
||||
#### 21. cursor-auto-register - Cursor 自动注册工具
|
||||
#### 28. cursor-auto-register - Cursor 自动注册工具
|
||||
|
||||
**功能**:Cursor 编辑器账号自动注册和管理工具。
|
||||
|
||||
@@ -245,7 +285,7 @@ AI-Account-Toolkit/
|
||||
|
||||
### Grok 相关 (packages/grok)
|
||||
|
||||
#### 22. grok-register - x.ai 注册批处理工具
|
||||
#### 29. grok-register - x.ai 注册批处理工具
|
||||
|
||||
**功能**:面向 x.ai 注册批处理的一体化项目,提供控制台、注册执行器、WARP 网络出口、grok2api token 落池和运行时环境。
|
||||
|
||||
@@ -253,7 +293,7 @@ AI-Account-Toolkit/
|
||||
|
||||
**使用指南**:[packages/grok/grok-register/README.md](packages/grok/grok-register/README.md)
|
||||
|
||||
#### 23. grok2api - Grok API 转换服务
|
||||
#### 30. grok2api - Grok API 转换服务
|
||||
|
||||
**功能**:将 Grok 服务的接口转换为标准 API 格式,支持多账号管理和 Token 池化。
|
||||
|
||||
@@ -263,13 +303,13 @@ AI-Account-Toolkit/
|
||||
|
||||
### 邮箱服务 (packages/email)
|
||||
|
||||
#### 24. cloudflare-temp-email - Cloudflare 临时邮箱服务
|
||||
#### 31. cloudflare-temp-email - Cloudflare 临时邮箱服务
|
||||
|
||||
**功能**:基于 Cloudflare 免费服务构建的临时邮箱服务,支持邮件收发、附件处理等功能。
|
||||
|
||||
**使用指南**:[packages/email/cloudflare-temp-email/README.md](packages/email/cloudflare-temp-email/README.md)
|
||||
|
||||
#### 25. tempmail - 自托管临时邮箱服务
|
||||
#### 32. tempmail - 自托管临时邮箱服务
|
||||
|
||||
**功能**:自托管临时邮件服务平台,支持多域名池、用户自助提交域名、MX 自动验证与自动禁用、API Key 鉴权及 Web 管理后台。基于 Docker 部署,包含 PostgreSQL、PgBouncer、Redis、Postfix 等完整组件。
|
||||
|
||||
@@ -277,19 +317,19 @@ AI-Account-Toolkit/
|
||||
|
||||
**使用指南**:[packages/email/tempmail/README.md](packages/email/tempmail/README.md)
|
||||
|
||||
#### 26. ms-oauth2-api - 微软 OAuth2 邮件取件 API
|
||||
#### 33. ms-oauth2-api - 微软 OAuth2 邮件取件 API
|
||||
|
||||
**功能**:将微软 OAuth2 认证取件流程封装成一个简单的 API,部署在 Vercel 无服务器平台上。
|
||||
|
||||
**使用指南**:[packages/email/ms-oauth2-api/README.md](packages/email/ms-oauth2-api/README.md)
|
||||
|
||||
#### 27. hotmail-outlook-auto-register - Hotmail/Outlook 自动注册
|
||||
#### 34. hotmail-outlook-auto-register - Hotmail/Outlook 自动注册
|
||||
|
||||
**功能**:高级 Hotmail / Outlook 账号创建和自动化工具,支持验证码绕过、代理轮换、指纹伪装和逼真的人类行为模拟。
|
||||
|
||||
**使用指南**:[packages/email/hotmail-outlook-auto-register/README.md](packages/email/hotmail-outlook-auto-register/README.md)
|
||||
|
||||
#### 28. outlook-auto-register - Outlook 邮箱注册工具集
|
||||
#### 35. outlook-auto-register - Outlook 邮箱注册工具集
|
||||
|
||||
**功能**:基于 Outlook 邮箱 OAuth2 认证的批量自动注册工具集,支持多个目标平台共享同一套邮箱接码模块。
|
||||
|
||||
@@ -299,37 +339,37 @@ AI-Account-Toolkit/
|
||||
|
||||
### 通用工具 (packages/general)
|
||||
|
||||
#### 29. any-auto-register - 多平台账号自动注册工具
|
||||
#### 36. any-auto-register - 多平台账号自动注册工具
|
||||
|
||||
**功能**:多平台账号自动注册工具,支持 ChatGPT、Cursor、Kiro 等多个平台。
|
||||
**功能**:多平台账号自动注册工具,支持 ChatGPT、Cursor、Kiro 等多个平台.
|
||||
|
||||
**主要文件**:`main.py` · `api/` · `core/` · `platforms/`
|
||||
|
||||
**使用指南**:[packages/general/any-auto-register/README.md](packages/general/any-auto-register/README.md)
|
||||
|
||||
#### 30. api-key-scraper - 多平台 API 密钥抓取工具
|
||||
#### 37. api-key-scraper - 多平台 API 密钥抓取工具
|
||||
|
||||
**功能**:多平台 API 密钥抓取工具,支持从多个来源自动化获取 OpenAI、Gemini、Claude 的 API 密钥。
|
||||
**功能**:多平台 API 密钥抓取工具,支持从多个来源自动化获取 OpenAI、Gemini、Claude 的 API 密钥.
|
||||
|
||||
**使用指南**:[packages/general/api-key-scraper/README.md](packages/general/api-key-scraper/README.md)
|
||||
|
||||
#### 31. mregister - ChatGPT 注册机 Web UI
|
||||
#### 38. mregister - ChatGPT 注册机 Web UI
|
||||
|
||||
**功能**:基于 FastAPI 的控制台,用来统一管理 chatgpt_register_v2 和 grok-register 两个注册脚本。它把原本偏命令行的执行方式包装成可持久化、可排队、可下载结果、可通过 API 调用的任务系统。
|
||||
**功能**:基于 FastAPI 的控制台,用来统一管理 chatgpt_register_v2 和 grok-register 两个注册脚本. 它把原本偏命令行的执行方式包装成可持久化、可排队、可下载结果、可通过 API 调用的任务系统.
|
||||
|
||||
**主要文件**:`web_console/` · `chatgpt_register_v2/` · `docker-compose.yml`
|
||||
|
||||
**使用指南**:[packages/general/mregister/README.md](packages/general/mregister/README.md)
|
||||
|
||||
#### 32. exa-free - Exa 免费使用工具
|
||||
#### 39. exa-free - Exa 免费使用工具
|
||||
|
||||
**功能**:Exa 免费使用工具,提供 Exa 相关服务的免费访问。
|
||||
**功能**:Exa 免费使用工具,提供 Exa 相关服务的免费访问.
|
||||
|
||||
**使用指南**:[packages/general/exa-free/README.md](packages/general/exa-free/README.md)
|
||||
|
||||
#### 33. real-random-taxfree-address - 真实随机免税地址生成
|
||||
#### 40. real-random-taxfree-address - 真实随机免税地址生成
|
||||
|
||||
**功能**:生成真实的美国随机免税地址,用于账号注册等场景。
|
||||
**功能**:生成真实的美国随机免税地址,用于账号注册等场景.
|
||||
|
||||
**使用指南**:[packages/general/real-random-taxfree-address/README.md](packages/general/real-random-taxfree-address/README.md)
|
||||
|
||||
@@ -445,4 +485,4 @@ python packages/general/any-auto-register/main.py
|
||||
|
||||
---
|
||||
|
||||
**License**: [MIT](LICENSE) | **更新日期**:2026-04-01 | **版本**:2.2.0
|
||||
**License**: [MIT](LICENSE) | **更新日期**:2026-04-10 | **版本**:2.3.0
|
||||
|
||||
1
codex-oauth-automation-extension
Submodule
1
codex-oauth-automation-extension
Submodule
Submodule codex-oauth-automation-extension added at 9768cfafcc
Reference in New Issue
Block a user