Files
puppeteerjs/src/workers/CommandorPage.js
T
2023-02-18 13:59:00 +01:00

484 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 {
shell
} = require('electron')
const GeoCaptchaSolver = require("./GeoCaptchaSolver");
// const RDV_URL = "http://192.168.0.41: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 aujourdhui."
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', 'hermes+rendezvous', 'hermes+appointment', 'hermes+appointment+online', 'appointment+hermes', 'hermes+rendez+vous', 'online+appointment+hermes', '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})
}
class CommandorPage {
constructor(contact, device, mongoManager, selectedStore = DEFAULT_STORE, audioAnalyse = false, alertBeep = false) {
this.contact = contact;
this.device = device;
this.mongoManager = mongoManager;
this.selectedStore = selectedStore;
this.choosedStore = selectedStore
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");
// 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 {
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.locator('button >> nth=3').click()
// this.page.locator('button:has-text("Tout accepter")').click()
} catch (e) {
log(e)
}
// try {
// this.page.locator('button:has-text("Accept all")').click()
// } catch (e) {
// log(e)
// }
try {
this.page.locator(':nth-match(:text("Paris Online Appointment"), 1)').click()
} catch (e) {
log(e)
this.isTerminated = true
}
let cancel
const intervalTask = setInterval(() => {
if (this.isTerminated) {
log(this.device.model() + ":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(log)
}
async chooseCountry(page) {
if (!page.isClosed()) {
try {
if (!this.isCountryChoosen) {
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())
this.isCountryChoosen = true;
}
} catch (e) {
log(e);
this.isTerminated = true;
}
}
}
async fillEmail(page) {
try {
if (!page.isClosed()) {
if (!this.isEmailFilled) {
await page.locator(EMAIL_ID).focus();
await delay(getRandomWaitTime())
await page.locator(EMAIL_ID).fill(this.contact.mail);
this.isEmailFilled = true;
}
}
} catch (e) {
log(e)
}
}
async inputPhoneNumber(page) {
try {
if (!page.isClosed()) {
if (!this.isPhoneInput) {
await page.locator(PHONE_NUMBER).focus();
await page.locator(PHONE_NUMBER).fill("0" + 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.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)
this.isNameInput = true
}
}
} catch (e) {
log(e);
this.isTerminated = true
}
}
async inputPassportId(page) {
try {
if (!page.isClosed()) {
if (!this.isPasspordInput) {
await page.locator(PASSPORT_ID).focus();
await delay(getRandomWaitTime());
await page.locator(PASSPORT_ID).fill(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.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()
this.cguChecked = true;
}
}
} catch (e) {
log(e);
this.isTerminated = true;
}
}
async chooseStore(page) {
try {
if (!page.isClosed()) {
if (this.selectedStore !== "random") {
await page.locator(PREFER_STORE).focus()
await delay(1000)
await page.click(PREFER_STORE);
let stores = this.selectedStore.split(":")
this.choosedStore = stores[Math.floor(Math.random() * stores.length)]
await page.selectOption(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(page) {
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 {
this.page.evaluate(() => {
document.getElementsByClassName("btn")[0].click();
})
} catch (e) {
log(e)
}
}
}
} catch (e) {
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) => {
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) {
log(e)
this.isTerminated = true;
}
} else {
this.isTerminated = true;
}
})
}
async isBlocked() {
let iframeHandler = await this.page.frameLocator("body > iframe");
let captcha_container = await iframeHandler.locator(CAPTCHA_CONTAINER)
let html = await captcha_container.innerHTML()
console.log("audio_tag: " + html);
return html.includes("You have been blocked")
}
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()
}
}
log("发现datadome");
} else if (currentPage.url().includes("sorry")) {
await this.resetBrowser()
} 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)) {
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 checkAudioBtn() {
let isBlocked = await this.isBlocked()
if (!isBlocked) {
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
}
})
} else {
log("audioBtn not found")
console.log("audioBtn not found")
console.log("we are blocked")
//
await this.resetBrowser()
}
}
async onResponse(response) {
// let rex = new RegExp(REGEX_RDV_URL)
// log("onResponse with url:" + response.url())
// if (rex.test(response.url())) {
// 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(), 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 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 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