aliexpress: collect daily coins via desktop website
All load in webview in android app. Other games have no desktop version and mobile version uses canvas and only refers to download app with both stealth and fingerprint-injector. Also stuck on loading screen when using firefox instead of chrome.
This commit is contained in:
parent
b8f7068a87
commit
a39d737999
2 changed files with 108 additions and 0 deletions
105
aliexpress.js
Normal file
105
aliexpress.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
|
||||||
|
import { datetime, filenamify, prompt, handleSIGINT, stealth } from './src/util.js';
|
||||||
|
import { cfg } from './src/config.js';
|
||||||
|
|
||||||
|
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
|
||||||
|
headless: cfg.headless,
|
||||||
|
viewport: { width: cfg.width, height: cfg.height },
|
||||||
|
locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL
|
||||||
|
recordVideo: cfg.record ? { dir: 'data/record/', size: { width: cfg.width, height: cfg.height } } : undefined, // will record a .webm video for each page navigated; without size, video would be scaled down to fit 800x800
|
||||||
|
recordHar: cfg.record ? { path: `data/record/gog-${filenamify(datetime())}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools
|
||||||
|
handleSIGINT: false, // have to handle ourselves and call context.close(), otherwise recordings from above won't be saved
|
||||||
|
});
|
||||||
|
handleSIGINT(context);
|
||||||
|
await stealth(context);
|
||||||
|
|
||||||
|
context.setDefaultTimeout(cfg.debug ? 0 : cfg.timeout);
|
||||||
|
|
||||||
|
const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist
|
||||||
|
|
||||||
|
const auth = async (url) => {
|
||||||
|
console.log('auth', url);
|
||||||
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
||||||
|
// redirects to https://login.aliexpress.com/?return_url=https%3A%2F%2Fwww.aliexpress.com%2Fp%2Fcoin-pc-index%2Findex.html
|
||||||
|
await Promise.any([page.waitForURL(/.*login.aliexpress.com.*/).then(async () => {
|
||||||
|
// manual login
|
||||||
|
console.error('Not logged in! Will wait for 120s for you to login...');
|
||||||
|
// await page.waitForTimeout(120*1000);
|
||||||
|
// or try automated
|
||||||
|
page.locator('span:has-text("Switch account")').click().catch(_ => {}); // sometimes no longer logged in, but previous user/email is pre-selected -> in this case we want to go back to the classic login
|
||||||
|
const login = page.locator('.login-container');
|
||||||
|
const email = cfg.ae_email || await prompt({ message: 'Enter email' });
|
||||||
|
const emailInput = login.locator('input[label="Email or phone number"]');
|
||||||
|
await emailInput.fill(email);
|
||||||
|
await emailInput.blur(); // otherwise Continue button stays disabled
|
||||||
|
const continueButton = login.locator('button:has-text("Continue")');
|
||||||
|
await continueButton.click({ force: true }); // normal click waits for button to no longer be covered by their suggestion menu, so we have to force click somewhere for the menu to close and then click
|
||||||
|
await continueButton.click();
|
||||||
|
const password = email && (cfg.ae_password || await prompt({ type: 'password', message: 'Enter password' }));
|
||||||
|
await login.locator('input[label="Password"]').fill(password);
|
||||||
|
await login.locator('button:has-text("Sign in")').click();
|
||||||
|
const error = login.locator('.error-text');
|
||||||
|
error.waitFor().then(async _ => console.error('Login error:', await error.innerText()));
|
||||||
|
await page.waitForURL(url);
|
||||||
|
// await page.addLocatorHandler(page.getByRole('button', { name: 'Accept cookies' }), btn => btn.click());
|
||||||
|
page.getByRole('button', { name: 'Accept cookies' }).click().then(_ => console.log('Accepted cookies')).catch(_ => { });
|
||||||
|
}), page.locator('#nav-user-account').waitFor()]).catch(_ => {});
|
||||||
|
|
||||||
|
// await page.locator('#nav-user-account').hover();
|
||||||
|
// console.log('Logged in as:', await page.locator('.welcome-name').innerText());
|
||||||
|
};
|
||||||
|
|
||||||
|
// copied URLs from AliExpress app on tablet which has menu for the used webview
|
||||||
|
const urls = {
|
||||||
|
// works with desktop view, but stuck at 100% loading in mobile view:
|
||||||
|
coins: 'https://www.aliexpress.com/p/coin-pc-index/index.html',
|
||||||
|
// only work with mobile view:
|
||||||
|
grow: 'https://m.aliexpress.com/p/ae_fruit/index.html', // firefox: stuck at 60% loading, chrome: loads, but canvas
|
||||||
|
gogo: 'https://m.aliexpress.com/p/gogo-match-cc/index.html', // closes firefox?!
|
||||||
|
// only show notification to install the app
|
||||||
|
euro: 'https://m.aliexpress.com/p/european-cup/index.html', // doesn't load
|
||||||
|
merge: 'https://m.aliexpress.com/p/merge-market/index.html',
|
||||||
|
};
|
||||||
|
|
||||||
|
const coins = async () => {
|
||||||
|
// await auth(urls.coins);
|
||||||
|
await Promise.any([page.locator('.checkin-button').click(), page.locator('.addcoin').waitFor()]);
|
||||||
|
console.log('Coins:', await page.locator('.mycoin-content-right-money').innerText());
|
||||||
|
console.log('Streak:', await page.locator('.title-box').innerText());
|
||||||
|
console.log('Tomorrow:', await page.locator('.addcoin').innerText());
|
||||||
|
};
|
||||||
|
|
||||||
|
const grow = async () => {
|
||||||
|
await page.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
const gogo = async () => {
|
||||||
|
await page.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
const euro = async () => {
|
||||||
|
await page.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
const merge = async () => {
|
||||||
|
await page.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// await coins();
|
||||||
|
await [
|
||||||
|
coins,
|
||||||
|
// grow,
|
||||||
|
// gogo,
|
||||||
|
// euro,
|
||||||
|
// merge,
|
||||||
|
].reduce((a, f) => a.then(async _ => { await auth(urls[f.name]); await f(); console.log() }), Promise.resolve());
|
||||||
|
|
||||||
|
// await page.pause();
|
||||||
|
} catch (error) {
|
||||||
|
process.exitCode ||= 1;
|
||||||
|
console.error('--- Exception:');
|
||||||
|
console.error(error); // .toString()?
|
||||||
|
}
|
||||||
|
if (page.video()) console.log('Recorded video:', await page.video().path());
|
||||||
|
await context.close();
|
||||||
|
|
@ -41,6 +41,9 @@ export const cfg = {
|
||||||
gog_email: process.env.GOG_EMAIL || process.env.EMAIL,
|
gog_email: process.env.GOG_EMAIL || process.env.EMAIL,
|
||||||
gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD,
|
gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD,
|
||||||
gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game
|
gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game
|
||||||
|
// auth AliExpress
|
||||||
|
ae_email: process.env.AE_EMAIL || process.env.EMAIL,
|
||||||
|
ae_password: process.env.AE_PASSWORD || process.env.PASSWORD,
|
||||||
// OTP only via GOG_EMAIL, can't add app...
|
// OTP only via GOG_EMAIL, can't add app...
|
||||||
// experimmental
|
// experimmental
|
||||||
pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores
|
pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue