Files
puppeteerjs/src/workers/CommandorPage.js
T
2023-05-19 13:22:22 +02:00

636 lines
24 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 puppeteer = require('puppeteer');
const {
shell
} = require('electron')
const GeoCaptchaSolver = require("./GeoCaptchaSolver");
const SlidingCaptchaSolver = require("./SlidingCaptchaSolver");
const {de} = require("yarn/lib/cli");
// 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 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+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(page) {
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")
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)
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.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")
console.log("we are blocked")
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())) {
log("rdv url found:" + response.url())
// 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.$('[title="reCAPTCHA"]').getByRole('checkbox', {name: 'I\'m not a robot'}).click();
// } 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