init repo

This commit is contained in:
2026-04-25 21:50:03 +08:00
commit ada92373c2
124 changed files with 5292 additions and 0 deletions

285
auto-login-v5.mjs Normal file
View File

@@ -0,0 +1,285 @@
/**
* 店小秘全自动登录 v5
* 用 jQuery 设值 + 点击按钮 + 监听 API
*/
import { chromium } from 'playwright';
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
import { execSync } from 'child_process';
import sharp from 'sharp';
const COOKIE_FILE = './cookies.json';
const SCREENSHOTS_DIR = './screenshots';
const DOWNLOAD_DIR = './downloads';
const MAX_RETRIES = 15;
mkdirSync(SCREENSHOTS_DIR, { recursive: true });
mkdirSync(DOWNLOAD_DIR, { recursive: true });
async function ocrCaptcha(page) {
const captchaImg = await page.$('#verifyImgCode');
if (!captchaImg) return null;
const rawPath = `${SCREENSHOTS_DIR}/captcha_raw.png`;
await captchaImg.screenshot({ path: rawPath });
const presets = [
async (s, d) => sharp(s).grayscale().resize({ width: 468 }).normalize().sharpen({ sigma: 1.5 }).threshold(130).toFile(d),
async (s, d) => sharp(s).grayscale().resize({ width: 468 }).normalize().threshold(100).toFile(d),
async (s, d) => sharp(s).grayscale().resize({ width: 468 }).normalize().threshold(160).toFile(d),
async (s, d) => sharp(s).resize({ width: 468 }).toFile(d),
async (s, d) => sharp(s).grayscale().resize({ width: 468 }).negate().normalize().threshold(128).toFile(d),
];
for (let i = 0; i < presets.length; i++) {
const p = `${SCREENSHOTS_DIR}/cap_p${i}.png`;
try { await presets[i](rawPath, p); } catch { continue; }
for (const psm of ['7', '8']) {
try {
const r = execSync(
`tesseract "${p}" stdout --psm ${psm} -c tessedit_char_whitelist=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`,
{ encoding: 'utf-8', timeout: 10000 }
).trim().replace(/[\s\n\r]/g, '');
if (r && r.length === 4) return r;
} catch {}
}
}
// fallback: 任何 >=3 字符
for (let i = 0; i < presets.length; i++) {
const p = `${SCREENSHOTS_DIR}/cap_p${i}.png`;
if (!existsSync(p)) continue;
try {
const r = execSync(
`tesseract "${p}" stdout --psm 7 -c tessedit_char_whitelist=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`,
{ encoding: 'utf-8', timeout: 10000 }
).trim().replace(/[\s\n\r]/g, '');
if (r && r.length >= 3) return r.substring(0, 4);
} catch {}
}
return null;
}
async function main() {
console.log('====== 店小秘全自动登录 v5 ======\n');
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
locale: 'zh-CN',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
});
const page = await context.newPage();
// 先试 Cookie
if (existsSync(COOKIE_FILE)) {
console.log('>> 尝试复用 Cookie...');
const cookies = JSON.parse(readFileSync(COOKIE_FILE, 'utf-8'));
await context.addCookies(cookies);
await page.goto('https://www.dianxiaomi.com/saleManage/index.htm', {
waitUntil: 'domcontentloaded', timeout: 20000
}).catch(() => {});
const title = await page.title();
if (!title.includes('Error') && !page.url().includes('/home.htm') && !page.url().includes('/index.htm')) {
console.log('>> Cookie 有效!');
return { success: true, page, context, browser };
}
console.log('>> Cookie 无效\n');
await context.clearCookies();
}
// 打开登录页
await page.goto('https://www.dianxiaomi.com/home.htm', { waitUntil: 'networkidle', timeout: 30000 });
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
console.log(`\n>> 第 ${attempt}/${MAX_RETRIES} 次尝试...`);
// 确保在登录页
if (!page.url().includes('home.htm') && !page.url().includes('index.htm')) {
await page.goto('https://www.dianxiaomi.com/home.htm', { waitUntil: 'networkidle', timeout: 30000 });
}
// 刷新验证码
if (attempt > 1) {
await page.evaluate(() => {
const img = document.getElementById('verifyImgCode');
if (img) img.src = '/verify/code.htm?t=' + Date.now();
});
await page.waitForTimeout(1500);
}
// 用 jQuery 设置表单值(确保 .val() 能读到)
await page.evaluate(() => {
if (typeof $ !== 'undefined') {
$('#exampleInputName').val('MiLe-kf01').trigger('change').trigger('input');
$('#exampleInputPassword').val('Vxdas@302').trigger('change').trigger('input');
}
});
// OCR 验证码
const code = await ocrCaptcha(page);
if (!code) { console.log(' OCR 失败'); continue; }
console.log(` 验证码: "${code}"`);
// 用 jQuery 设验证码
await page.evaluate((c) => {
if (typeof $ !== 'undefined') {
$('#verifyCode').val(c).trigger('change').trigger('input');
}
}, code);
// 截图确认
await page.screenshot({ path: `${SCREENSHOTS_DIR}/attempt-${attempt}.png` });
// 验证 jQuery 读到的值
const formValues = await page.evaluate(() => {
if (typeof $ === 'undefined') return { error: 'no jQuery' };
return {
account: $.trim($('#exampleInputName').val()),
password: $.trim($('#exampleInputPassword').val()),
verifyCode: $.trim($('#verifyCode').val()),
};
});
console.log(` 表单值: ${JSON.stringify(formValues)}`);
// 监听 API 响应
const apiResponsePromise = page.waitForResponse(
resp => resp.url().includes('userLoginNew2.json'),
{ timeout: 15000 }
).catch(() => null);
// 点击登录按钮(按钮 onclick="login()"
console.log(' 点击登录按钮...');
await page.click('#loginBtn');
// 等待 API 响应
const apiResp = await apiResponsePromise;
if (apiResp) {
let data;
try {
data = await apiResp.json();
} catch {
const text = await apiResp.text().catch(() => '');
console.log(` API 原始响应: ${text.substring(0, 200)}`);
data = {};
}
console.log(` API 响应: ${JSON.stringify(data)}`);
if (data.code === 0 || (data.url && data.url !== '')) {
console.log('\n>> ★★★ 登录成功!★★★');
// 等待页面跳转
await page.waitForTimeout(3000);
const afterUrl = page.url();
console.log('>> 自动跳转到:', afterUrl);
// 如果没跳转,手动去
if (afterUrl.includes('/home.htm') || afterUrl.includes('/index.htm')) {
const target = data.url ? ('https://www.dianxiaomi.com' + data.url) : 'https://www.dianxiaomi.com/saleManage/index.htm';
await page.goto(target, { waitUntil: 'networkidle', timeout: 30000 }).catch(() => {});
}
// 保存 Cookie
const cookies = await context.cookies();
writeFileSync(COOKIE_FILE, JSON.stringify(cookies, null, 2));
console.log(`>> Cookie 已保存(${cookies.length} 条)`);
console.log('>> 当前 URL:', page.url());
return { success: true, page, context, browser };
}
const errMsg = data.error || JSON.stringify(data);
console.log(` 失败: ${errMsg}`);
if (errMsg.includes('账号') || errMsg.includes('密码错误') || errMsg.includes('不存在') || errMsg.includes('锁定')) {
console.log('>> 账号/密码问题,停止');
break;
}
} else {
console.log(' 未收到 API 响应');
await page.waitForTimeout(2000);
// 也许已经登录跳转了
const url = page.url();
if (!url.includes('/home.htm') && !url.includes('/index.htm')) {
console.log('>> 已跳转到:', url);
const cookies = await context.cookies();
writeFileSync(COOKIE_FILE, JSON.stringify(cookies, null, 2));
return { success: true, page, context, browser };
}
}
}
await page.screenshot({ path: `${SCREENSHOTS_DIR}/final-fail.png`, fullPage: true });
await browser.close();
return { success: false };
}
// ====== 探索后台 ======
async function explore(page) {
console.log('\n>> ===== 探索后台 =====');
console.log(`>> URL: ${page.url()}`);
console.log(`>> 标题: ${await page.title()}`);
await page.screenshot({ path: `${SCREENSHOTS_DIR}/backend-main.png`, fullPage: true });
const text = await page.evaluate(() => document.body?.innerText?.substring(0, 5000));
console.log('>> 页面文本:\n', text);
const links = await page.$$eval('a', els =>
els.map(el => ({
text: el.textContent.trim().replace(/\s+/g, ' ').substring(0, 60),
href: el.href,
})).filter(e => e.text && e.href?.includes('dianxiaomi'))
.filter((e, i, arr) => arr.findIndex(a => a.href === e.href) === i)
);
console.log(`\n>> 链接(${links.length} 个):`);
for (const l of links) {
const star = ['采购', '仓库', '导出', '库存', '备货', '建议'].some(k => l.text.includes(k)) ? '★' : ' ';
console.log(` ${star} [${l.text}] ${l.href}`);
}
// 访问几个关键页面截图
const paths = [
'/saleManage/index.htm',
'/purchaseManage/purchaseSuggestion.htm',
'/purchaseManage/purchaseOrder.htm',
'/stockManage/stockList.htm',
];
for (const p of paths) {
try {
await page.goto(`https://www.dianxiaomi.com${p}`, { waitUntil: 'domcontentloaded', timeout: 10000 });
const t = await page.title();
const ok = !t.includes('Error');
console.log(`\n ${ok ? '✓' : '✗'} ${p} [${t}]`);
if (ok) {
await page.screenshot({ path: `${SCREENSHOTS_DIR}/page${p.replace(/\//g, '_')}.png`, fullPage: true });
// 打印页面所有按钮
const btns = await page.$$eval('button, a.btn, .btn, [class*="export"], [class*="download"]', els =>
els.map(el => ({ tag: el.tagName, text: el.textContent.trim().substring(0, 50), id: el.id, cls: (el.className || '').substring(0, 60) }))
.filter(e => e.text)
.slice(0, 30)
);
if (btns.length) {
console.log(' 按钮:');
btns.forEach(b => console.log(` ${b.tag}#${b.id} .${b.cls}: "${b.text}"`));
}
// 特别查找导出
const exportBtns = btns.filter(b => b.text.includes('导出'));
if (exportBtns.length) {
console.log(' ★ 导出按钮:');
exportBtns.forEach(b => console.log(` ${b.tag}#${b.id}: "${b.text}"`));
}
}
} catch { console.log(`${p} 超时`); }
}
}
const result = await main();
if (result.success) {
await explore(result.page);
await result.browser.close();
} else {
process.exit(1);
}