diff --git a/.gitignore b/.gitignore index 4018bbe..64acc00 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ _desktop.ini .thumbnails # local env files +.env .env.local .env.*.local diff --git a/dcloud-renew/.gitignore b/dcloud-renew/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/dcloud-renew/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/dcloud-renew/package-lock.json b/dcloud-renew/package-lock.json new file mode 100644 index 0000000..6a7db7b --- /dev/null +++ b/dcloud-renew/package-lock.json @@ -0,0 +1,73 @@ +{ + "name": "dcloud-renew", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dcloud-renew", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^17.4.2", + "playwright": "^1.62.0" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.62.0", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.62.0.tgz", + "integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.0", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.62.0.tgz", + "integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + } + } +} diff --git a/dcloud-renew/package.json b/dcloud-renew/package.json new file mode 100644 index 0000000..4d72cb1 --- /dev/null +++ b/dcloud-renew/package.json @@ -0,0 +1,17 @@ +{ + "name": "dcloud-renew", + "version": "1.0.0", + "type": "module", + "description": "", + "main": "renew.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "dotenv": "^17.4.2", + "playwright": "^1.62.0" + } +} diff --git a/dcloud-renew/renew.mjs b/dcloud-renew/renew.mjs new file mode 100644 index 0000000..8b494d3 --- /dev/null +++ b/dcloud-renew/renew.mjs @@ -0,0 +1,110 @@ +import 'dotenv/config' +import { chromium } from 'playwright' + +const email = process.env.UNICLOUD_EMAIL +const password = process.env.UNICLOUD_PASSWORD + +if (!email || !password) { + throw new Error('Missing UNICLOUD_EMAIL or UNICLOUD_PASSWORD') +} + +async function waitForLoginFrame (page, timeoutMs = 30000) { + const start = Date.now() + while (Date.now() - start < timeoutMs) { + const f = page.frames().find(fr => fr.url().includes('account.dcloud.net.cn')) + if (f) return f + await page.waitForTimeout(500) + } + throw new Error('Timed out waiting for account.dcloud.net.cn login iframe') +} + +async function waitForUrl (page, predicate, timeoutMs = 30000) { + const start = Date.now() + while (Date.now() - start < timeoutMs) { + if (predicate(page.url())) return page.url() + await page.waitForTimeout(500) + } + throw new Error(`Timed out waiting for URL condition. Current: ${page.url()}`) +} + +;(async () => { + const browser = await chromium.launch({ + channel: 'msedge', + headless: false, + slowMo: 200 + }) + + const context = await browser.newContext() + const page = await context.newPage() + page.setDefaultTimeout(30000) + + await page.goto('https://unicloud.dcloud.net.cn/', { + waitUntil: 'domcontentloaded', + timeout: 60000 + }) + + console.log('Page loaded, waiting for login iframe...') + + // 1. Login via the account.dcloud.net.cn iframe. + const loginFrame = await waitForLoginFrame(page) + await loginFrame.waitForSelector('input.uni-input-input', { timeout: 20000 }) + await page.waitForTimeout(1500) + + const inputs = loginFrame.locator('input.uni-input-input') + if ((await inputs.count()) < 2) { + throw new Error('Expected 2 inputs in login iframe') + } + await inputs.nth(0).click({ clickCount: 3 }) + await inputs.nth(0).fill(email) + await inputs.nth(1).click({ clickCount: 3 }) + await inputs.nth(1).fill(password) + + await loginFrame.getByText('登录', { exact: true }).click() + + await waitForUrl(page, u => u.includes('unicloud.dcloud.net.cn') && !u.includes('/login/login')) + await page.waitForTimeout(5000) + console.log('Login successful. URL:', page.url()) + + // 2. Click "续费" — opens a NEW TAB that first goes through SSO + // (uni-trade.dcloud.net.cn/pages/login/login?oauthToken=...) then lands + // on the create-order page. + const renewBtn = page.getByText('续费', { exact: true }).first() + await renewBtn.waitFor({ timeout: 30000 }) + const newPagePromise = new Promise(resolve => context.once('page', resolve)) + await renewBtn.click() + const orderPage = await newPagePromise + + // Wait for the OAuth redirect to settle on create-order. + await waitForUrl( + orderPage, + u => u.includes('uni-trade.dcloud.net.cn') && u.includes('create-order'), + 45000 + ) + await orderPage.waitForTimeout(3000) + console.log('Order page ready:', orderPage.url()) + + // 3. Click "立即购买" — same tab navigates to /order-payment. + const buyBtn = orderPage.getByText('立即购买', { exact: true }).first() + await buyBtn.waitFor({ timeout: 30000 }) + await buyBtn.click() + + await waitForUrl( + orderPage, + u => u.includes('order-payment'), + 30000 + ) + await orderPage.waitForTimeout(3000) + console.log('Payment page ready:', orderPage.url()) + + // 4. Click "确认开通" to finalize. + const confirmBtn = orderPage.getByText('确认开通', { exact: true }).first() + await confirmBtn.waitFor({ timeout: 30000 }) + await confirmBtn.click() + console.log('Clicked 确认开通. Renewal submitted.') + + // Wait briefly for the confirmation redirect to complete, then exit. + await orderPage.waitForTimeout(5000) + + await browser.close() + process.exit(0) +})() diff --git a/nixhome/.gitignore_global b/nixhome/.gitignore_global index 4018bbe..64acc00 100644 --- a/nixhome/.gitignore_global +++ b/nixhome/.gitignore_global @@ -78,6 +78,7 @@ _desktop.ini .thumbnails # local env files +.env .env.local .env.*.local