const {_android: android, devices} = require("playwright"); const {SolveCaptcha, TWO_CAPTCHA_CONNECTION_FAILED} = require("./SolveCaptcha"); const ReserveResultPojo = require("../models/ReserveResultPojo"); const PublishType = require("../models/PublishType"); const beep = require('beepbeep') // const RDV_URL = "http://192.168.1.16:8000/test_appointment.html" const RDV_URL = "https://rendezvousparis.hermes.com/client/register"; BLANK_URL = "about:blank" const ERROR_CAPTCHA_UNSOLVABLE = "ERROR_CAPTCHA_UNSOLVABLE"; const COUNTRY_ID = "#phone_country" const PHONE_NUMBER = "#phone_number" const EMAIL_ID = "#email" const PREFER_STORE = "#prefer" const LAST_NAME = "#surname" const FIRST_NAME = "#name" const CGU_ID = "#cgu" const PROCESSING_ID = "#processing" const PASSPORT_ID = "#passport_id" const TIME_OUT = 60 * 1000 * 4//4 mins const CONFIRMED_MESSAGE = "Your request for a Leather Goods appointment has been registered" const CONFIRMED_MESSAGE_FR = "Votre demande de rendez-vous Maroquinerie a bien été enregistrée et nous vous en remercions." const MESSAGE_URL_VALIDATION_FR = "Nous avons envoyé un lien par e-mail." const MESSAGE_URL_VALIDATION_EN = "Please click on the link we sent by email" const DOUBLE_REQUEST_ERROR_MESSAGE = "A request with the same data has already been validated today." const DOUBLE_REQUEST_ERROR_MESSAGE_FR = "Une demande avec les données saisies a déjà été validée aujourd’hui." const TOO_MANY_REQUEST_ERROR_MESSAGE = "Due to a large number of requests" const TOO_MANY_REQUEST_ERROR_MESSAGE_FR = "Suite à un trop grand nombre de demandes" const CAPTCHA_ERROR_MESSAGE = "Error verifying captcha, please try again" const CAPTCHA_ERROR_MESSAGE_FR = "La vérification du captcha a échoué" REGEX_RDV_URL = "https:\/\/rendezvousparis\.hermes\.com\/client\/register\/[A-Z0-9]+" function delay(delayInms) { return new Promise(resolve => { setTimeout(() => { resolve(2); }, delayInms); }); } function getRandom() { return Math.floor(Math.random() * 5); } function getRandomWaitTime() { return getRandom() * 1000 } class CommandorPage { constructor(contact, device, mongoManager) { this.contact = contact; this.device = device; this.mongoManager = mongoManager; this.isFillingFields = false; this.isTerminated = false; } async loadPage() { // Connect to the device. console.log("loadPage() called"); await this.device.shell('am force-stop com.android.chrome'); const context = await this.device.launchBrowser(); // await context.clearCookies() // Use BrowserContext as usual. this.page = await context.newPage(); this.page.on("load", (loadedPage) => { this.onPageLoad(loadedPage) }) this.page.on("response", (response) => { this.onResponse(response) }) try { await this.page.goto(RDV_URL, {timeout: 90 * 1000}); } catch (e) { console.log(e) this.isTerminated = true } let cancel const intervalTask = setInterval(() => { if (this.isTerminated) { console.log("request terminated, will close device") context.close() // this.page.close() this.device.close() clearInterval(intervalTask) cancel() return context } }, 10 * 1000)//interval of 10 seconds await new Promise(function (fulfill, reject) { cancel = function () { fulfill(Promise.resolve()) } setTimeout(fulfill, TIME_OUT, 5) }).then(console.log) } async chooseCountry(page) { if (!page.isClosed()) { try { await page.locator(COUNTRY_ID).focus(); await delay(getRandomWaitTime()) await page.click(COUNTRY_ID); await delay(getRandomWaitTime()) await page.selectOption(COUNTRY_ID, 'FR'); await delay(getRandomWaitTime()) } catch (e) { console.log(e); this.isTerminated = true; } // await page.click(COUNTRY_ID); } } async fillEmail(page) { try { if (!page.isClosed()) { await page.locator(EMAIL_ID).focus(); await delay(getRandomWaitTime()) await page.locator(EMAIL_ID).fill(this.contact.mail); } } catch (e) { console.log(e) } } async inputPhoneNumber(page) { try { if (!page.isClosed()) { await page.locator(PHONE_NUMBER).focus() await page.locator(PHONE_NUMBER).fill("0" + this.contact.phoneNumber) } } catch (e) { console.log(e); this.isTerminated = true; } } async inputName(page) { try { if (!page.isClosed()) { await page.locator(LAST_NAME).focus() await delay(getRandomWaitTime()) await page.locator(LAST_NAME).fill(this.contact.lastName) await page.locator(FIRST_NAME).focus() await delay(getRandomWaitTime()) await page.locator(FIRST_NAME).fill(this.contact.firstName) } } catch (e) { console.log(e); this.isTerminated = true } } async inputPassportId(page) { try { if (!page.isClosed()) { await page.locator(PASSPORT_ID).focus() await delay(getRandomWaitTime()) await page.locator(PASSPORT_ID).fill(this.contact.passportNumber.toString()) } } catch (e) { console.log(e); this.isTerminated = true; } } async checkCGU(page) { try { if (!page.isClosed()) { await page.locator(CGU_ID).focus() await page.locator(CGU_ID).click() await delay(getRandomWaitTime()) await page.locator(PROCESSING_ID).focus() await page.locator(PROCESSING_ID).click() } } catch (e) { console.log(e); this.isTerminated = true; } } async chooseStore(page) { try { if (!page.isClosed()) { await page.locator(PREFER_STORE).focus() await delay(1000) await page.click(PREFER_STORE); await page.selectOption(PREFER_STORE, "faubourg"); } } catch (e) { console.log(e); this.isTerminated = true; } // await page.click(PREFER_STORE); } async fillFields(page) { console.log("fillFields called") if (!this.isFillingFields) { this.isFillingFields = true; await this.chooseStore(page); await this.inputName(page); await this.chooseCountry(page); await this.inputPhoneNumber(page) await this.fillEmail(page) await this.inputPhoneNumber(page) await this.inputPassportId(page) await this.checkCGU(page) await this.resolveCaptcha(page) await delay(10 * 1000) } } async clickValid(page) { await delay(getRandomWaitTime()) try { if (!this.page.isClosed()) { this.page.evaluate(() => { document.getElementsByClassName("btn")[0].focus(); }) await delay(getRandomWaitTime()) if (!this.page.isClosed()) { this.page.evaluate(() => { document.getElementsByClassName("btn")[0].click(); }) } } } catch (e) { console.log(e) } } async resolveCaptcha(page) { if (RDV_URL.includes("192")) { await this.push_message_to_queue(PublishType.SUCCESS) return } this.captchaSolver = new SolveCaptcha(page); await this.captchaSolver.start((solution) => { console.log("solution is: " + solution); if (solution !== ERROR_CAPTCHA_UNSOLVABLE && solution !== TWO_CAPTCHA_CONNECTION_FAILED) { try { if (!page.isClosed()) { page.evaluate((solution) => { let element = document.getElementById("g-recaptcha-response"); if (element != null) document.getElementById("g-recaptcha-response").innerHTML = solution; }, solution) this.clickValid(); } } catch (e) { console.log(e) this.isTerminated = true; } } else { this.isTerminated = true; } }) } async onPageLoad(currentPage) { let content = await currentPage.content(); let captcha_url = "geo.captcha-delivery.com/captcha"; if (content.toString().includes(captcha_url)) { await this.checkAudioBtn(); beep(20) console.log("发现datadome"); } else { if (currentPage.url() === RDV_URL) { await this.fillFields(this.page); if (this.isFillingFields) await this.getErrors() } else { if (content.includes(MESSAGE_URL_VALIDATION_FR) || content.includes(MESSAGE_URL_VALIDATION_EN)) { console.log("successful"); await this.push_message_to_queue(PublishType.SUCCESS); } else { // try to get errors await this.getErrors() } } } } async checkAudioBtn() { // let iframe = await this.page.locator("iframe").contentFrame; // let audioBtn = iframe.locator("#captcha__audio__button"); // if (audioBtn) { // console.log("audioBtn found") // } else { // console.log("audioBtn not found") // } } async onResponse(response) { // let rex = new RegExp(REGEX_RDV_URL) // console.log("onResponse with url:" + response.url()) // if (rex.test(response.url())) { // console.log("rdv url found:" + response.url()) // await this.push_message_to_db(PublishType.SUCCESS, response.url()) // } } async push_message_to_queue(publishType) { let url = this.page.url(); let splitedUrl = url.split("/"); let id = splitedUrl[splitedUrl.length - 1]; if (url === "https://rendezvousparis.hermes.com/client/welcome") { return } // save to mongoDb let reserve = ReserveResultPojo.create_from_contact(this.contact, id, this.page.url(), 1, publishType); reserve.source_from = this.device.model(); await this.mongoManager.saveReserveToDb(reserve.to_mongo_dict()) this.isTerminated = true } async push_message_to_db(publishType, url) { let splitedUrl = url.split("/"); let id = splitedUrl[splitedUrl.length - 1]; if (url === "https://rendezvousparis.hermes.com/client/welcome") { return } // save to mongoDb let reserve = ReserveResultPojo.create_from_contact(this.contact, id, this.page.url(), 1, publishType); reserve.source_from = this.device.model(); await this.mongoManager.saveReserveToDb(reserve.to_mongo_dict()) await delay(2 * 1000) this.isTerminated = true } async getErrors() { if (this.page.url() === BLANK_URL) { this.isTerminated = true; } else { try { let errorItem = this.page.locator("div.alert"); if (errorItem) { let errorContent = await errorItem.innerHTML(); console.log("error:" + errorContent); this.handleError(errorContent); } } catch (e) { console.log(e); } } } handleError(errorContent) { if (errorContent.includes(DOUBLE_REQUEST_ERROR_MESSAGE) || errorContent.includes(DOUBLE_REQUEST_ERROR_MESSAGE_FR)) { this.isTerminated = true; } else if (errorContent.includes(TOO_MANY_REQUEST_ERROR_MESSAGE) || errorContent.includes(TOO_MANY_REQUEST_ERROR_MESSAGE_FR)) { // todo, add contact to black list this.isTerminated = true; } else if (errorContent.includes(CAPTCHA_ERROR_MESSAGE) || errorContent.includes(CAPTCHA_ERROR_MESSAGE_FR)) { this.isTerminated = true; } } } module.exports = CommandorPage