add possibility to validate url by mobile
This commit is contained in:
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 aujourd’hui."
|
||||
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
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user