feat: random password per account + accounts log
- generatePassword(): 14 chars with uppercase, lowercase, digits, symbols
- Each run generates a unique password, stored in state.password
- accounts array: { email, password, createdAt } persisted across resets
- No more hardcoded password
- seenCodes also preserved across resets
This commit is contained in:
+57
-13
@@ -16,7 +16,8 @@ const DEFAULT_STATE = {
|
|||||||
},
|
},
|
||||||
oauthUrl: null,
|
oauthUrl: null,
|
||||||
email: null,
|
email: null,
|
||||||
password: 'mimashisha0.0',
|
password: null,
|
||||||
|
accounts: [], // { email, password, createdAt }
|
||||||
lastEmailTimestamp: null,
|
lastEmailTimestamp: null,
|
||||||
localhostUrl: null,
|
localhostUrl: null,
|
||||||
flowStartTime: null,
|
flowStartTime: null,
|
||||||
@@ -38,8 +39,40 @@ async function setState(updates) {
|
|||||||
|
|
||||||
async function resetState() {
|
async function resetState() {
|
||||||
console.log(LOG_PREFIX, 'Resetting all state');
|
console.log(LOG_PREFIX, 'Resetting all state');
|
||||||
|
// Preserve seenCodes and accounts across resets
|
||||||
|
const prev = await chrome.storage.session.get(['seenCodes', 'accounts']);
|
||||||
await chrome.storage.session.clear();
|
await chrome.storage.session.clear();
|
||||||
await chrome.storage.session.set({ ...DEFAULT_STATE });
|
await chrome.storage.session.set({
|
||||||
|
...DEFAULT_STATE,
|
||||||
|
seenCodes: prev.seenCodes || [],
|
||||||
|
accounts: prev.accounts || [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random password: 14 chars, mix of uppercase, lowercase, digits, symbols.
|
||||||
|
*/
|
||||||
|
function generatePassword() {
|
||||||
|
const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
|
||||||
|
const lower = 'abcdefghjkmnpqrstuvwxyz';
|
||||||
|
const digits = '23456789';
|
||||||
|
const symbols = '!@#$%&*?';
|
||||||
|
const all = upper + lower + digits + symbols;
|
||||||
|
|
||||||
|
// Ensure at least one of each type
|
||||||
|
let pw = '';
|
||||||
|
pw += upper[Math.floor(Math.random() * upper.length)];
|
||||||
|
pw += lower[Math.floor(Math.random() * lower.length)];
|
||||||
|
pw += digits[Math.floor(Math.random() * digits.length)];
|
||||||
|
pw += symbols[Math.floor(Math.random() * symbols.length)];
|
||||||
|
|
||||||
|
// Fill remaining 10 chars
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
pw += all[Math.floor(Math.random() * all.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle
|
||||||
|
return pw.split('').sort(() => Math.random() - 0.5).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -406,17 +439,18 @@ async function autoRunLoop(totalRuns) {
|
|||||||
for (let run = 1; run <= totalRuns; run++) {
|
for (let run = 1; run <= totalRuns; run++) {
|
||||||
autoRunCurrentRun = run;
|
autoRunCurrentRun = run;
|
||||||
|
|
||||||
if (run > 1) {
|
// Reset everything at the start of each run (keep VPS/mail settings)
|
||||||
// Reset state for next run (keep vpsUrl, mailProvider settings)
|
const prevState = await getState();
|
||||||
await addLog(`=== Resetting for run ${run}/${totalRuns} ===`, 'info');
|
const keepSettings = {
|
||||||
const state = await getState();
|
vpsUrl: prevState.vpsUrl,
|
||||||
const keepSettings = { vpsUrl: state.vpsUrl, mailProvider: state.mailProvider };
|
mailProvider: prevState.mailProvider,
|
||||||
|
autoRunning: true,
|
||||||
|
};
|
||||||
await resetState();
|
await resetState();
|
||||||
await setState(keepSettings);
|
await setState(keepSettings);
|
||||||
// Broadcast reset to side panel
|
// Tell side panel to reset all UI
|
||||||
chrome.runtime.sendMessage({ type: 'AUTO_RUN_RESET' }).catch(() => {});
|
chrome.runtime.sendMessage({ type: 'AUTO_RUN_RESET' }).catch(() => {});
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 500));
|
||||||
}
|
|
||||||
|
|
||||||
await addLog(`=== Auto Run ${run}/${totalRuns} — Phase 1: Get OAuth link & open signup ===`, 'info');
|
await addLog(`=== Auto Run ${run}/${totalRuns} — Phase 1: Get OAuth link & open signup ===`, 'info');
|
||||||
const status = (phase) => ({ type: 'AUTO_RUN_STATUS', payload: { phase, currentRun: run, totalRuns } });
|
const status = (phase) => ({ type: 'AUTO_RUN_STATUS', payload: { phase, currentRun: run, totalRuns } });
|
||||||
@@ -558,12 +592,22 @@ async function executeStep3(state) {
|
|||||||
if (!state.email) {
|
if (!state.email) {
|
||||||
throw new Error('No email address. Paste email in Side Panel first.');
|
throw new Error('No email address. Paste email in Side Panel first.');
|
||||||
}
|
}
|
||||||
await addLog(`Step 3: Filling email ${state.email} and password`);
|
|
||||||
|
// Generate a unique password for this account
|
||||||
|
const password = generatePassword();
|
||||||
|
await setState({ password });
|
||||||
|
|
||||||
|
// Save account record
|
||||||
|
const accounts = state.accounts || [];
|
||||||
|
accounts.push({ email: state.email, password, createdAt: new Date().toISOString() });
|
||||||
|
await setState({ accounts });
|
||||||
|
|
||||||
|
await addLog(`Step 3: Filling email ${state.email}, password generated (${password.length} chars)`);
|
||||||
await sendToContentScript('signup-page', {
|
await sendToContentScript('signup-page', {
|
||||||
type: 'EXECUTE_STEP',
|
type: 'EXECUTE_STEP',
|
||||||
step: 3,
|
step: 3,
|
||||||
source: 'background',
|
source: 'background',
|
||||||
payload: { email: state.email },
|
payload: { email: state.email, password },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,7 +717,7 @@ async function executeStep6(state) {
|
|||||||
type: 'EXECUTE_STEP',
|
type: 'EXECUTE_STEP',
|
||||||
step: 6,
|
step: 6,
|
||||||
source: 'background',
|
source: 'background',
|
||||||
payload: { email: state.email, password: state.password || 'mimashisha0.0' },
|
payload: { email: state.email, password: state.password },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-15
@@ -17,8 +17,23 @@ if (!isTopFrame) {
|
|||||||
console.log(MAIL163_PREFIX, 'Skipping child frame');
|
console.log(MAIL163_PREFIX, 'Skipping child frame');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Track codes we've already seen across polls to avoid duplicates
|
// Track codes we've already seen — persisted in chrome.storage.session to survive script re-injection
|
||||||
const seenCodes = new Set();
|
let seenCodes = new Set();
|
||||||
|
|
||||||
|
// Load previously seen codes on startup
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const data = await chrome.storage.session.get('seenCodes');
|
||||||
|
if (data.seenCodes && Array.isArray(data.seenCodes)) {
|
||||||
|
seenCodes = new Set(data.seenCodes);
|
||||||
|
console.log(MAIL163_PREFIX, `Loaded ${seenCodes.size} previously seen codes`);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function persistSeenCodes() {
|
||||||
|
await chrome.storage.session.set({ seenCodes: [...seenCodes] });
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Message Handler (top frame only)
|
// Message Handler (top frame only)
|
||||||
@@ -130,11 +145,14 @@ async function handlePollEmail(step, payload) {
|
|||||||
const code = extractVerificationCode(subject + ' ' + ariaLabel);
|
const code = extractVerificationCode(subject + ' ' + ariaLabel);
|
||||||
if (code && !seenCodes.has(code)) {
|
if (code && !seenCodes.has(code)) {
|
||||||
seenCodes.add(code);
|
seenCodes.add(code);
|
||||||
|
persistSeenCodes();
|
||||||
const source = useFallback && existingMailIds.has(id) ? 'fallback' : 'new';
|
const source = useFallback && existingMailIds.has(id) ? 'fallback' : 'new';
|
||||||
log(`Step ${step}: Code found: ${code} (${source}, subject: ${subject.slice(0, 40)})`, 'ok');
|
log(`Step ${step}: Code found: ${code} (${source}, subject: ${subject.slice(0, 40)})`, 'ok');
|
||||||
|
|
||||||
// Delete this email via right-click menu
|
// Delete this email via right-click menu, WAIT for it to finish before returning
|
||||||
await deleteEmail(item, step);
|
await deleteEmail(item, step);
|
||||||
|
// Extra wait to ensure deletion is processed
|
||||||
|
await sleep(1000);
|
||||||
|
|
||||||
return { ok: true, code, emailTimestamp: Date.now(), mailId: id };
|
return { ok: true, code, emailTimestamp: Date.now(), mailId: id };
|
||||||
} else if (code && seenCodes.has(code)) {
|
} else if (code && seenCodes.has(code)) {
|
||||||
@@ -167,26 +185,36 @@ async function deleteEmail(item, step) {
|
|||||||
log(`Step ${step}: Deleting email...`);
|
log(`Step ${step}: Deleting email...`);
|
||||||
|
|
||||||
// Right-click on the mail item to trigger context menu
|
// Right-click on the mail item to trigger context menu
|
||||||
|
const rect = item.getBoundingClientRect();
|
||||||
item.dispatchEvent(new MouseEvent('contextmenu', {
|
item.dispatchEvent(new MouseEvent('contextmenu', {
|
||||||
bubbles: true, cancelable: true, button: 2,
|
bubbles: true, cancelable: true, button: 2,
|
||||||
clientX: item.getBoundingClientRect().x + 100,
|
clientX: rect.left + rect.width / 2,
|
||||||
clientY: item.getBoundingClientRect().y + 10,
|
clientY: rect.top + rect.height / 2,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await sleep(500);
|
// Wait for context menu to appear
|
||||||
|
let deleteMenuItem = null;
|
||||||
// Find the context menu and click "删除邮件"
|
for (let i = 0; i < 10; i++) {
|
||||||
|
await sleep(300);
|
||||||
const menuItems = document.querySelectorAll('.nui-menu-item .nui-menu-item-text');
|
const menuItems = document.querySelectorAll('.nui-menu-item .nui-menu-item-text');
|
||||||
for (const menuItem of menuItems) {
|
for (const mi of menuItems) {
|
||||||
if (menuItem.textContent.trim() === '删除邮件') {
|
if (mi.textContent.trim() === '删除邮件') {
|
||||||
menuItem.closest('.nui-menu-item').click();
|
deleteMenuItem = mi.closest('.nui-menu-item');
|
||||||
log(`Step ${step}: Email deleted`, 'ok');
|
break;
|
||||||
await sleep(500);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (deleteMenuItem) break;
|
||||||
|
}
|
||||||
|
|
||||||
log(`Step ${step}: Could not find "删除邮件" in context menu`, 'warn');
|
if (deleteMenuItem) {
|
||||||
|
deleteMenuItem.click();
|
||||||
|
log(`Step ${step}: Clicked "删除邮件"`, 'ok');
|
||||||
|
// Wait for the delete to process and item to disappear
|
||||||
|
await sleep(1000);
|
||||||
|
log(`Step ${step}: Email deleted successfully`);
|
||||||
|
} else {
|
||||||
|
log(`Step ${step}: Context menu "删除邮件" not found`, 'warn');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(`Step ${step}: Failed to delete email: ${err.message}`, 'warn');
|
log(`Step ${step}: Failed to delete email: ${err.message}`, 'warn');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ async function step3_fillEmailPassword(payload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fillInput(passwordInput, 'mimashisha0.0');
|
fillInput(passwordInput, payload.password || 'mimashisha0.0');
|
||||||
log('Step 3: Password filled');
|
log('Step 3: Password filled');
|
||||||
|
|
||||||
// Report complete BEFORE submit, because submit causes page navigation
|
// Report complete BEFORE submit, because submit causes page navigation
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ chrome.runtime.onMessage.addListener((message) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'AUTO_RUN_RESET': {
|
case 'AUTO_RUN_RESET': {
|
||||||
// Reset UI for next run (but keep buttons disabled since auto-run is still going)
|
// Full UI reset for next run
|
||||||
displayOauthUrl.textContent = 'Waiting...';
|
displayOauthUrl.textContent = 'Waiting...';
|
||||||
displayOauthUrl.classList.remove('has-value');
|
displayOauthUrl.classList.remove('has-value');
|
||||||
displayLocalhostUrl.textContent = 'Waiting...';
|
displayLocalhostUrl.textContent = 'Waiting...';
|
||||||
@@ -309,6 +309,7 @@ chrome.runtime.onMessage.addListener((message) => {
|
|||||||
inputEmail.value = '';
|
inputEmail.value = '';
|
||||||
displayStatus.textContent = 'Ready';
|
displayStatus.textContent = 'Ready';
|
||||||
statusBar.className = 'status-bar';
|
statusBar.className = 'status-bar';
|
||||||
|
logArea.innerHTML = '';
|
||||||
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
||||||
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
||||||
updateProgressCounter();
|
updateProgressCounter();
|
||||||
|
|||||||
Reference in New Issue
Block a user