const {SolveCaptcha, TWO_CAPTCHA_CONNECTION_FAILED} = require("./SolveCaptcha"); const ReserveResultPojo = require("../models/ReserveResultPojo"); const BlackListContactPojo = require("../models/BlackListContactPojo"); const appointmentLogger = require("../utiles/LoggerUtils") const PublishType = require("../models/PublishType"); const puppeteer = require('puppeteer'); const { shell } = require('electron') const GeoCaptchaSolver = require("./GeoCaptchaSolver"); const SlidingCaptchaSolver = require("./SlidingCaptchaSolver"); const {de} = require("yarn/lib/cli"); const OCRChecker = require("./OCRChecker"); // const RDV_URL = "http://192.168.0.44:8000/test_appointment.html" const RDV_URL = "https://rendezvousparis.hermes.com/client/register"; const 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 CAPTCHA_CONTAINER = "#captcha-container"; const TIME_OUT = 60 * 1000 * 4//4 mins const EMPTY_RESPONSE_ERROR = "ERR_EMPTY_RESPONSE" 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é" const REGEX_RDV_URL = "https:\/\/rendezvousparis\.hermes\.com\/client\/register\/[A-Z0-9]+" const DEFAULT_STORE = 'faubourg'; const searchTexts = ['hermes+rdv+online+paris', 'hermes+rdv+enligne+paris', 'hermes+rdv+en+ligne+paris', 'hermes+rendezvous+en+ligne+paris', 'hermes+appointment+online+paris', 'hermes+appointment+online+paris', 'appointment+hermes+paris+on+line', 'hermes+rendez+vous+online+paris', 'hermes+rendez+vous+paris+en+ligne', 'hermes+rendez+vous+paris+enligne', 'hermes+rendez+vous+paris+online', 'online+appointment+hermes+paris', 'hermes+online+appointment+paris', 'paris+hermes+online+appointment'] function delay(delayInMs) { return new Promise(resolve => { setTimeout(() => { resolve(2); }, delayInMs); }); } function getRandom() { return Math.floor(Math.random() * 3); } function getRandomWaitTime() { return getRandom() * 1000 } function log(message) { appointmentLogger.log({level: "info", message: message}) } function logWithDevice(message, device) { appointmentLogger.log({level: "info", message: device.model() + ":" + message}) } class CommandorPage { constructor(contact, device, mongoManager, selectedStore = DEFAULT_STORE, audioAnalyse = false, alertBeep = false, port = 9000) { this.contact = contact; this.device = device; this.mongoManager = mongoManager; this.selectedStore = selectedStore; this.choosedStore = selectedStore; this.port = port; this.isFillingFields = false; this.isTerminated = false; this.audioAnalyse = audioAnalyse; this.alertBeep = alertBeep; this.cguChecked = false; this.isNameInput = false; this.isEmailFilled = false; this.isCountryChoosen = false; this.isPhoneInput = false; } async loadPage() { // Connect to the device. log("loadPage() called, with port:" + this.port); try { this.browser = await puppeteer.connect({ browserWSEndpoint: "ws://127.0.0.1:" + this.port + "/devtools/browser", headless: false, defaultViewport: null }) this.page = await this.browser.newPage(); this.page.on("load", (loadedPage) => { this.onPageLoad(loadedPage) }) this.page.on("response", (response) => { this.onResponse(response) }) } catch (e) { console.log(e) this.isTerminated = true } try { console.log("will open google") const item = searchTexts[Math.floor(Math.random() * searchTexts.length)]; await this.page.goto("https://www.google.com/search?q=" + item + "&lr=lang_en", {timeout: 90 * 1000}); // await this.page.goto(RDV_URL, {timeout: 90 * 1000}); } catch (e) { log(e) this.isTerminated = true } try { if (this.page.url().includes("google")) { this.page.focus('button >> nth=3') this.page.click() } } catch (e) { log(e) } try { const [button] = await this.page.$x("//a[contains(., 'rendezvousparis')]"); console.log("button is " + button) if (button) { console.log("will click on the button") await button.click(); } // await this.page.$eval(':nth-match(:text("rendezvousparis"), 1)', el => { // el.click() // }); } catch (e) { log(e) if (!this.page.url().includes(RDV_URL)) { this.isTerminated = true; } } let cancel const intervalTask = setInterval(() => { if (this.isTerminated) { log(this.device.model() + ":request terminated, will close device"); if (this.browser !== undefined) this.browser.close(); // this.page.close() // this.device.close() clearInterval(intervalTask) cancel() return this.browser } else { if (this.page.url() === RDV_URL) { if (!this.isFillingFields) this.fillFields(this.page); } } }, 10 * 1000)//interval of 10 seconds await new Promise(function (fulfill, reject) { cancel = function () { fulfill(Promise.resolve()) } setTimeout(fulfill, TIME_OUT, 5) }).then(log) } async chooseCountry(page) { if (!page.isClosed()) { try { if (!this.isCountryChoosen) { await page.focus(COUNTRY_ID); await delay(getRandomWaitTime()) await page.click(COUNTRY_ID); await delay(getRandomWaitTime()) await page.select(COUNTRY_ID, 'FR'); await delay(getRandomWaitTime()) this.isCountryChoosen = true; } } catch (e) { log(e); this.isTerminated = true; } } } async fillEmail(page) { try { if (!page.isClosed()) { if (!this.isEmailFilled) { await page.focus(EMAIL_ID); await delay(getRandomWaitTime()) await page.keyboard.type(this.contact.mail); this.isEmailFilled = true; } } } catch (e) { log(e) } } async inputPhoneNumber(page) { try { if (!page.isClosed()) { if (!this.isPhoneInput) { await page.focus(PHONE_NUMBER); await page.keyboard.type("" + this.contact.phoneNumber); this.isPhoneInput = true; } } } catch (e) { log(e); this.isTerminated = true; } } async inputName(page) { try { if (!page.isClosed()) { if (!this.isNameInput) { await page.focus(LAST_NAME); await delay(getRandomWaitTime()); await page.keyboard.type(this.contact.lastName); await page.focus(FIRST_NAME); await delay(getRandomWaitTime()); await page.keyboard.type(this.contact.firstName); this.isNameInput = true; } } } catch (e) { log(e); this.isTerminated = true } } async inputPassportId(page) { try { if (!page.isClosed()) { if (!this.isPasspordInput) { await page.focus(PASSPORT_ID); await delay(getRandomWaitTime()); await page.keyboard.type(this.contact.passportNumber.toString()) this.isPasspordInput = true; } } } catch (e) { log(e); this.isTerminated = true; } } async checkCGU(page) { try { if (!page.isClosed()) { if (!this.cguChecked) { await page.focus(CGU_ID); await page.evaluate(() => { if (!document.querySelector("#cgu").checked) { document.querySelector("#cgu").checked = true } }); // await page.click(CGU_ID); await delay(getRandomWaitTime()); await page.focus(PROCESSING_ID); // await page.click(PROCESSING_ID) await page.evaluate(() => { if (!document.querySelector("#processing").checked) { document.querySelector("#processing").checked = true } }); this.cguChecked = true; } } } catch (e) { log(e); this.isTerminated = true; } } async chooseStore(page) { try { if (!page.isClosed()) { if (this.selectedStore !== "random") { page.focus(PREFER_STORE); await delay(1000) page.click(PREFER_STORE); let stores = this.selectedStore.split(":") this.choosedStore = stores[Math.floor(Math.random() * stores.length)] await page.select(PREFER_STORE, this.choosedStore); } } } catch (e) { log(e); this.isTerminated = true; } } async fillFields(page) { 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) this.isFillingFields = false } } async clickValid() { // await this.saveCookies() await delay(getRandomWaitTime()) try { if (!this.page.isClosed()) { this.page.evaluate(() => { let element = document.getElementsByClassName("btn")[0]; if (typeof element !== 'undefined') document.getElementsByClassName("btn")[0].focus(); }) await delay(getRandomWaitTime()) if (!this.page.isClosed()) { try { // remove debug flag // const validElement = await page.$('.btn'); console.log("will click on valid button") console.log("will click on valid button") console.log("will click on valid button") this.page.evaluate(() => { document.getElementsByClassName("btn")[0].click(); }) // this.browser.disconnect(); await delay(2000); let ocrChecker = new OCRChecker(this.device, this.contact) await ocrChecker.get_result() } catch (e) { log(e) } } } } catch (e) { log(e) } } async resolveCaptcha(page) { if (RDV_URL.includes("192")) { // await this.push_message_to_queue(PublishType.SUCCESS) await delay(100000) return } //check whether there is captcha let pageContent = await page.content() let hasCaptcha = pageContent.includes("g-recaptcha-response") if (hasCaptcha) { // await this.browser.disconnect() await this.clickCheckbox() await delay(1000) this.captchaSolver = new SolveCaptcha(page); await this.captchaSolver.start((solution) => { logWithDevice("solution is: " + solution, this.device); 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) { log(e) this.isTerminated = true; } } else { this.isTerminated = true; } }) } else { await this.clickValid(); } } async isBlocked() { let iframeHandler = await this.page.$("body > iframe"); let captcha_container = await iframeHandler.$(CAPTCHA_CONTAINER) let html = await captcha_container.innerHTML() console.log("audio_tag: " + html); return html.includes("You have been blocked") || html.includes("Vous avez été bloqué") || html.includes("封锁") } async onPageLoad(currentPage) { try { let content = await currentPage.content(); let captcha_url = "geo.captcha-delivery.com/captcha"; if (content.toString().includes(captcha_url)) { if (this.audioAnalyse) { await this.checkAudioBtn(); } if (this.alertBeep) { for (let i = 0; i < 15; i++) { await delay(1000) shell.beep() } } logWithDevice("发现datadome", this.device); } else if (content.includes("502 Bad Gateway")) { logWithDevice("502 Bad Gateway found", this.device) await this.page.reload() } else if (currentPage.url().includes("sorry")) { await this.resetBrowser() } else if (content.includes("PROXY_CONNECTION_FAILED")) { logWithDevice("PROXY_CONNECTION_FAILED, will reload page", this.device); await delay(2000) await this.page.reload() } else if (content.includes("ERR_NETWORK_CHANGED")) { logWithDevice("ERR_NETWORK_CHANGED, will reload page", this.device); await delay(2000) await this.page.reload() } else if (content.includes("CACHE_MISS")) { logWithDevice("CACHE_MISS, will reload page", this.device); await delay(2000) // await this.setUpCookies() await this.page.reload() } else if (content.includes("ERR_TIMED_OUT")) { logWithDevice("ERR_TIMED_OUT, will reload page", this.device); await delay(2000) await this.page.reload() } else if (content.includes("408 Request Time-out")) { logWithDevice("Request Time-out, will reload page", this.device); await delay(2000) await this.page.reload() } else { if (currentPage.url() === RDV_URL) { await this.fillFields(this.page); await this.saveCookies() // if (this.isFillingFields) // await this.getErrors() } else { if (content.includes(MESSAGE_URL_VALIDATION_FR) || content.includes(MESSAGE_URL_VALIDATION_EN)) { log("successful"); await this.push_message_to_queue(PublishType.SUCCESS); } else if (content.includes(EMPTY_RESPONSE_ERROR)) { log("EMPTY_RESPONSE_ERROR error received, will quit") this.isTerminated = true } else { // try to get errors await this.getErrors() } } } } catch (e) { log(e) } } async saveCookies() { log("saveCookies() called.") // try { // let cookies = await this.page.context().cookies(); // let cookiesInJson = []; // cookies.forEach((cookie) => { // cookiesInJson.push(JSON.stringify(cookie)) // }) // await require("fs").writeFileSync(this.contact.mail + '.txt', cookiesInJson.join('\n')); // } catch (e) { // console.log(e) // } } async setUpCookies() { // load cookies from file // try { // logWithDevice("will add cookies", this.device) // const fs = require('fs'); // // let cookiesFile = fs.readFileSync('vampuka_fisherleyba@aol.com.txt', 'utf8'); // let cookiesFile = fs.readFileSync(this.contact.mail + '.txt', 'utf8'); // let cookiesList = cookiesFile.split("\n") // let cookiesListToAdd = [] // cookiesList.forEach((cookie) => { // let cookieToAdd = JSON.parse(cookie) // // if (cookieToAdd.name === "datadome") { // cookiesListToAdd.push(cookieToAdd) // // } // }) // await this.context.addCookies(cookiesListToAdd) // } catch (e) { // console.log(e) // } } async checkAudioBtn() { let isBlocked = await this.isBlocked() if (!isBlocked) { //try to sliding capthca at first let slidingCaptchaSolver = new SlidingCaptchaSolver(this.device); await slidingCaptchaSolver.solve(this.page, async (isSuccessful) => { console.log("check isAlwaysBlocked") let isAlwaysBlocked = await this.isBlocked(); if (isAlwaysBlocked) { await this.resolveByAudio(); } }) } else { log("audioBtn not found") console.log("audioBtn not found") logWithDevice("we are blocked", this.device) await this.resetBrowser() } } async resolveByAudio() { // let audioBtn = await this.page.frameLocator("iframe").locator("#captcha__audio__button"); // log("audioBtn found") // audioBtn.click() // let captchaSolver = new GeoCaptchaSolver(this.page, this.device, this.isTerminated) // await captchaSolver.solve((isSuccessful) => { // if (!isSuccessful) { // this.isTerminated = true // } // }) } async onResponse(response) { let rex = new RegExp(REGEX_RDV_URL) // log("onResponse with url:" + response.url()) // log("onResponse with url:" + response.body()) if (rex.test(response.url())) { logWithDevice("rdv url found:" + response.url(), this.device) // save cookies await this.saveCookies(); await this.push_message_to_db(PublishType.SUCCESS, response.url()) } // if (response.url().includes("geo.captcha-delivery.com/interstitial")) { // logWithDevice("interstitial: response.status is " + response.status()) // await this.setUpCookies() // } } async push_message_to_queue(publishType) { let url = this.page.url(); await this.push_message_to_db(publishType, url) } 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, url, this.choosedStore, publishType); reserve.source_from = this.device.model(); await this.mongoManager.saveReserveToDb(reserve.to_mongo_dict()) await this.deleteFromBlackList() // await this.resetBrowser() this.isTerminated = true } async saveToBlackList() { await this.mongoManager.saveBlackListToDb(new BlackListContactPojo(this.contact)) // await this.resetBrowser() this.isTerminated = true } async deleteFromBlackList() { await this.mongoManager.removeFromBlackList(this.contact) } 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(); // await this.handleError(errorContent); // } } catch (e) { log(e); } } } async clickCheckbox() { try { // let errorItem = await this.page.click("#recaptcha-anchor > div.recaptcha-checkbox-border") await this.page.waitForSelector("iframe") let elementHandle = await this.page.$('[title="reCAPTCHA"]') const iframe = await elementHandle.contentFrame() await iframe.click("#recaptcha-anchor > div.recaptcha-checkbox-border") // // .getByRole('checkbox', {name: 'I\'m not a robot'}) // if (enCheckbox) { // enCheckbox.click() // } else { // let frCheckbox = await this.page.$('[title="reCAPTCHA"]').getByRole('checkbox', {name: 'Je ne suis pas un robot'}) // if (frCheckbox) { // frCheckbox.click() // } else { // console.log("recaptcha checkbox not found") // } // } } catch (e) { log(e); } } async handleError(errorContent) { log("handle error:" + 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)) { //add contact to black list and set terminated the task log("handle error: will save to black list db"); await this.saveToBlackList() } else if (errorContent.includes(CAPTCHA_ERROR_MESSAGE) || errorContent.includes(CAPTCHA_ERROR_MESSAGE_FR)) { this.isTerminated = true; } } async removeDebugFlag() { await delay(1000) await this.device.shell("am clear-debug-app --persistent com.android.chrome") await delay(1000) } async resetBrowser() { console.log("will reset browser") await this.device.shell("pm clear com.android.chrome") await delay(1000) await this.device.shell("am set-debug-app --persistent com.android.chrome") await delay(1000) await this.device.shell("pm am start -n com.android.chrome/com.google.android.apps.chrome.Main") await delay(1000) this.isTerminated = true } } module.exports = CommandorPage