add possibility to validate url by mobile

This commit is contained in:
2022-12-08 13:13:25 +01:00
parent d1478e734b
commit 32347a3865
4 changed files with 286 additions and 3 deletions
+37
View File
@@ -0,0 +1,37 @@
const {MongoManager} = require("./workers/mongo_manager");
const {_android: android} = require("playwright");
const alert = require("alert");
const LinkValidator = require("./workers/LinkValidator");
const mongoManager = new MongoManager();
mongoManager.connect().then(r => {
android.devices().then((devices) => {
if (devices.length === 0) {
alert("未找到连接的设备");
return
}
mongoManager.getAllLinkToValidate().then(list => {
let segmentNumber = list.length / devices.length;
console.log("connected device number:" + devices.length)
console.log("segmentNumber:" + segmentNumber)
for (let i = 0; i < devices.length; i++) {
startWithList(list.slice(i * segmentNumber, segmentNumber * (i + 1)), devices[i]).then(result => {
console.log("stop")
})
}
})
})
});
async function startWithList(listOfUrls, device) {
await listOfUrls.reduce(async (promise, urlToValidate) => {
// This line will wait for the last async function to finish.
// The first iteration uses an already resolved Promise
// so, it will immediately continue.
await promise;
let commandor = new LinkValidator(device, urlToValidate.url, mongoManager);
await commandor.loadPage()
console.log("next");
}, Promise.resolve());
}
-1
View File
@@ -123,7 +123,6 @@ class GeoCaptchaSolver {
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
}
}
+227
View File
@@ -0,0 +1,227 @@
const {SolveCaptcha, TWO_CAPTCHA_CONNECTION_FAILED} = require("./SolveCaptcha");
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"
BLANK_URL = "about:blank"
const ERROR_CAPTCHA_UNSOLVABLE = "ERROR_CAPTCHA_UNSOLVABLE";
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 CONFIRMED_MESSAGE_CN = "您的皮具预约申请已登记"
const SORRY_SENTENCE_FR = "nous sommes sincèrement désolés de n'avoir pu vous satisfaire cette fois-ci"
const DOUBLE_REQUEST_ERROR_MESSAGE_FR = "Une demande avec les données saisies a déjà été validée aujourdhui."
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 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]+"
const PLAY_AUDIO_BTN_ID = ".audio-captcha-play-button"
const homedir = require('os').homedir();
const CAPTCHA_CONTAINER = "#captcha-container";
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 LinkValidator {
constructor(device, urlToValidate, mongoManager) {
this.device = device;
this.mongoManager = mongoManager;
this.isTerminated = false;
this.urlToValidate = urlToValidate;
}
async loadPage() {
// Connect to the device.
log("loadPage() called for url " + this.urlToValidate);
// 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(this.urlToValidate, {timeout: 90 * 1000});
} catch (e) {
log(e)
this.isTerminated = true
}
let cancel
const intervalTask = setInterval(() => {
if (this.isTerminated) {
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(log)
}
async onPageLoad(currentPage) {
try {
let content = await currentPage.content();
let captcha_url = "geo.captcha-delivery.com/captcha";
if (content.toString().includes(captcha_url)) {
log("发现datadome");
await this.checkAudioBtn();
} else {
if (content.includes(CONFIRMED_MESSAGE) || content.includes(CONFIRMED_MESSAGE_FR) || content.includes(CONFIRMED_MESSAGE_CN)) {
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 audioBtn = await this.page.frameLocator("iframe").locator("#captcha__audio__button");
let isBlocked = await this.isBlocked()
if (!isBlocked) {
log("audioBtn found")
if (!this.isTerminated) {
try {
audioBtn.click()
let captchaSolver = new GeoCaptchaSolver(this.page, this.device)
await captchaSolver.solve((isSuccessful) => {
if (!isSuccessful) {
this.isTerminated = true
}
})
} catch (e) {
this.isTerminated = true
log(e)
}
}
} else {
log("audioBtn not found")
console.log("audioBtn not found")
console.log("we are blocked")
//
await this.resetBrowser()
}
}
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 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();
if (url === "https://rendezvousparis.hermes.com/client/welcome") {
return
}
// save to mongoDb
await this.mongoManager.linkValidatedFor(url)
await this.mongoManager.removeFromToValidateList(this.urlToValidate)
// await this.resetBrowser()
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();
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");
} 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 = LinkValidator
+22 -2
View File
@@ -4,9 +4,8 @@ const BLACK_LIST = "BLACK_LIST"
const DB_NAME = "appointment"
const COLLECTION_NAME = "appointment"
const ACCEPTED_APPOINTMENT_LIST = "ACCEPTED_APPOINTMENT_LIST"
const EMAIL_LIST = "EMAIL_LIST"
const BLACK_LIST_COLLECTION_NAME = "BLACK_LIST"
const LINKS_TO_VALIDATE = "LINKS_TO_VALIDATE"
const {MongoClient} = require('mongodb');
const ReserveResultPojo = require("../models/ReserveResultPojo");
require("../models/ContactPojo");
@@ -59,9 +58,30 @@ class MongoManager {
await this.db.collection(BLACK_LIST_COLLECTION_NAME).deleteOne(query);
}
async removeFromToValidateList(url) {
const query = {_id: url};
await this.db.collection(LINKS_TO_VALIDATE).deleteOne(query);
}
async getAllBlackedListItems() {
return await this.db.collection(BLACK_LIST_COLLECTION_NAME).find().toArray()
}
async linkValidatedFor(urlValidated) {
let splitedUrl = urlValidated.split("/");
let id = splitedUrl[splitedUrl.length - 1];
let collectionName = formatDate(new Date());
return await this.db.collection(collectionName).updateOne({'_id': id,}, {
"$set": {
"url_validated": true,
"validated_at": new Date().toLocaleString()
}
}, {upsert: false})
}
async getAllLinkToValidate() {
return await this.db.collection(LINKS_TO_VALIDATE).find().toArray()
}
}