integration of authentication

This commit is contained in:
2024-08-06 17:44:09 +02:00
parent d38817f4c5
commit e7a78dcaad
21 changed files with 526 additions and 127 deletions
+98 -48
View File
@@ -1,5 +1,6 @@
// Interencheres.js
'use strict';
const { config } = require('../../../config');
const {Scraper} = require('../Scraper');
const DrouotData = require('./DrouotData');
@@ -131,6 +132,8 @@ class Drouot extends Scraper {
// askStop : sale is followed by the AuctionAgent and the user ask to stop following
// pause : the Sale is stopped by the Auction House and ready to restart
// end : the Sale is ended
// endOnError : the Sale is ended on error
// endOnRequest: end of follow asked by user
let status = 'ready'
@@ -258,6 +261,9 @@ class Drouot extends Scraper {
page = await this.CheckAndConnect(page);
let lastbid = 0;
let CloseCount = 0;
//init Protobuf Decoder
const protobuf = require("protobufjs");
const root = await protobuf.load(this._PATH_PROTOBUF_FILE);
@@ -278,7 +284,6 @@ class Drouot extends Scraper {
if(BideMessage.vente && BideMessage.vente.lot && BideMessage.vente.lot.lotId != undefined){
DataLot = await this.platformData.getLiveDataLot(BideMessage.vente.lot.lotId);
}
//console.log('BideMessage Type: '+BideMessage.type+' Lot: '+BideMessage.vente.lot.lotId);
switch (BideMessage.type) {
case 'PING':
@@ -287,16 +292,31 @@ class Drouot extends Scraper {
break;
case 'INIT':
console.log('INIT');
break;
case 'CLOSE':
case 'CLOSE':
console.log('CLOSE');
//refresh the live to avoid unintoended close
clearInterval(CheckAskStop);
CloseCount++;
if(CloseCount > config.agent.maxCloseWebsockets){
console.log('CloseCount > config.agent.maxCloseWebsockets')
StopLive('endOnError')
return
}else{
console.log('Retry #'+CloseCount)
await GoLive();
}
break;
case 'BID':
console.log('BID');
console.log('Lot: '+BideMessage.vente.lot.lotId+' Amount: '+BideMessage.vente.bid.amount);
lastbid = Date.now();
await this.JucundusBid(
BideMessage.vente.lot.lotId,
Date.now(),
@@ -392,61 +412,91 @@ class Drouot extends Scraper {
//console.log('Unknown type:', BideMessage);
}
};
};
let CheckAskStop = null;
let CheckLastBid = null;
let Socket = null;
// Stop the Live
const StopLive = async (code) => {
clearInterval(CheckAskStop);
clearInterval(CheckLastBid);
await this.JucundusEndSale(code)
page.close()
browser.close()
}
return
// create the live URL
let {saleID} = await this.platformData.getUrlInfo(this.Url);
let UrlLive = this._PAGE_MAIN + "live/bidlive/" + saleID;
let CheckAskStop = null;
let Socket = null;
// Stop the Live
const StopLive = async (params) => {
clearInterval(CheckAskStop);
page.close()
browser.close()
const GoLive = async () => {
try{
console.log('UrlLive : '+UrlLive)
await page.goto(UrlLive, { waitUntil: 'domcontentloaded' });
// intercept Lots Data
await page.route('https://api.drouot.com/drouot/gingolem/api/live/venteInfos/'+saleID+'?lang=fr', async route => {
console.log('GetLiveData')
const response = await route.fetch();
const LotData = await response.json();
this.platformData.setLiveData(LotData.data);
route.continue();
});
page.on('websocket', ws => {
Socket = ws;
Socket.on('framereceived', listener);
console.log('Websocket connected');
});
console.log('UrlLive : reload')
await page.reload();
// check if stop was asked
CheckAskStop = setInterval(async () => {
this.JucundusCheckStop()
.then(async AskStop => {
if(AskStop){
await StopLive('endOnRequest')
return
}
})
}, 10000); // 10000 milliseconds = 10 seconds
// Check every minute if the last event is more than 10 minutes ago
CheckLastBid = setInterval(() => {
if (lastbid && Date.now() - lastbid > 10 * 60 * 1000) {
console.log("More than 10 minutes since the last bid.");
this.JucundusSetSaleStatus(saleInfo, 'end')
(async () => {
await StopLive('end');
})();
return
}
}, 60 * 1000); // 60 seconds = 1 minute
}catch(e){
console.log('Error : '+e)
throw new Error('Error: '+e)
}
}
try{
console.log('UrlLive : '+UrlLive)
await GoLive();
await page.goto(UrlLive, { waitUntil: 'domcontentloaded' });
// intercept Lots Data
await page.route('https://api.drouot.com/drouot/gingolem/api/live/venteInfos/'+saleID+'?lang=fr', async route => {
console.log('GetLiveData')
const response = await route.fetch();
const LotData = await response.json();
this.platformData.setLiveData(LotData.data);
route.continue();
});
}
page.on('websocket', ws => {
Socket = ws;
Socket.on('framereceived', listener);
console.log('Websocket connected');
});
console.log('UrlLive : reload')
await page.reload();
// check if stop was asked
CheckAskStop = setInterval(async () => {
this.JucundusCheckStop()
.then(AskStop => {
if(AskStop){
StopLive()
}
})
}, 10000); // 10000 milliseconds = 10 seconds
}catch(e){
console.log('Error : '+e)
throw new Error('Error: '+e)
}
CaptureVideo = async () => {
}
};
module.exports = Drouot
+28 -2
View File
@@ -344,9 +344,35 @@ class DrouotData extends ScraperTools {
SaleDateString = SaleDateString.trim()
let cleanStr = SaleDateString.replace(/\\s|\\n/g, ' ').replace(/\s+/g, ' ');
SaleDate = moment.tz(cleanStr, 'dddd D MMMM à HH:mm (z)', 'fr', 'Europe/Paris').format();
// Extract the timezone abbreviation
const timezoneAbbrMatch = cleanStr.match(/\(([^)]+)\)$/);
let timezoneAbbr = timezoneAbbrMatch ? timezoneAbbrMatch[1] : null;
// Map of common timezone abbreviations to full timezone names
const timezoneMap = {
'BST': 'Europe/London',
'CET': 'Europe/Paris',
'CEST': 'Europe/Paris',
'EDT': 'America/New_York',
'EST': 'America/New_York',
'PST': 'America/Los_Angeles',
'CDT': 'America/Chicago',
// Add more as needed
};
// Replace the abbreviation with the full timezone name if it exists in the map
if (timezoneAbbr && timezoneMap[timezoneAbbr]) {
cleanStr = cleanStr.replace(`(${timezoneAbbr})`, timezoneMap[timezoneAbbr]);
}
console.log('cleanStr : '+cleanStr)
// Parse the date string with the correct format and timezone
let saleDate = moment.tz(cleanStr, 'dddd D MMMM à HH:mm z', 'fr', timezoneMap[timezoneAbbr] || 'UTC');
// Convert to the desired timezone "Europe/Paris"
SaleDate = saleDate.tz('Europe/Paris').format();
// Live Sale
}else{
SaleDate = moment.tz('Europe/Paris').format();
@@ -120,6 +120,8 @@ class Interencheres extends Scraper {
// askStop : sale is followed by the AuctionAgent and the user ask to stop following
// pause : the Sale is stopped by the Auction House and ready to restart
// end : the Sale is ended
// endOnError : the Sale is ended on error
// endOnRequest: end of follow asked by user
let status = 'ready'
@@ -178,6 +180,7 @@ class Interencheres extends Scraper {
const StopLive = async (params) => {
clearInterval(CheckAskStop);
//Socket.off('Network.webSocketFrameReceived', listener);
await this.JucundusEndSale('end')
page.close()
browser.close()
}
+220 -33
View File
@@ -2,6 +2,8 @@
'use strict';
const fs = require('node:fs');
const fetch = require('node-fetch');
const { config } = require('../../config');
const { Key } = require('../../.Key');
class Scraper {
@@ -13,18 +15,26 @@ class Scraper {
_PWD = ""
_PATH_SESSION_FILE = ""
_PATH_TOKEN_FILE = ""
_BROWSER_TOOL = null
_Proxy = ""
_DebugMode = false
_JucundusUrl = "http://host.docker.internal:3000"
_JucundusUrl = ""
token = ""
constructor(Url) {
this.Url = Url;
this._JucundusUrl = config.jucundus.url;
this._PATH_TOKEN_FILE = ".session/token.json"
this.token = this.getTokenInCache();
}
async _getContext(browser) {
return new Promise(async (resolve, reject) => {
try {
@@ -73,9 +83,146 @@ class Scraper {
getLotList({ page, data}) {}
async Live({ page, data}) {}
getTokenInCache(){
if (fs.existsSync(this._PATH_TOKEN_FILE)) {
let rawdata = fs.readFileSync(this._PATH_TOKEN_FILE);
let token = JSON.parse(rawdata);
return token.token;
}
return "";
}
setTokenInCache(token){
let data = JSON.stringify({token: token});
fs.writeFileSync(this._PATH_TOKEN_FILE, data);
}
isTokenExpired(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
if (!payload || !payload.exp) {
return true;
}
const currentTime = Math.floor(Date.now() / 1000);
return payload.exp < currentTime;
} catch (error) {
console.error('Error decoding token:', error);
return true;
}
}
async isAgentConnected(){
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/user/agentConnected',{
method: 'GET',
headers: {
'Authorization': 'Bearer '+this.token
}
})
.then(response => {
if (!response.ok) {
console.log('isAgentConnected ? Agent not connected: '+response.statusText)
reject(false)
return;
}
console.log('isAgentConnected ? Agent connected')
resolve(true);
return;
})
.catch(error => {
console.log('isAgentConnected ? error Agent not connected: '+error)
reject(false)
return;
});
});
}
async checkJucundusConnexion(){
if(!this.token){
console.log('No token')
return false
}
if(this.isTokenExpired(this.token)){
console.log('Token expired')
return false
}
try {
const isConnected = await this.isAgentConnected();
if (!isConnected) {
console.log('Agent not connected');
return false;
}
} catch (error) {
console.log('Agent not connected');
return false;
}
return true;
}
async getNewToken(email, password) {
try {
const response = await fetch(this._JucundusUrl+'/authenticate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error('Failed to retrieve new token');
}
const data = await response.json();
this.setTokenInCache(data.token);
return data.token;
} catch (error) {
console.error('Error retrieving new token:', error);
throw error;
}
}
async RequestJucundus(url, method, body = null){
console.log('RequestJucundus() '+url)
if (!await this.checkJucundusConnexion()) {
this.token = await this.getNewToken(config.jucundus.useremail, Key.jucundusPassword);
}
return new Promise((resolve, reject) => {
fetch(url,{
method: method,
headers: {
'Authorization': 'Bearer '+this.token,
'Content-Type': 'application/json'
},
body: body
})
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.error || 'Unknown error');
});
}
return response.json();
})
.then(data => {
console.log('RequestJucundus() data:' + JSON.stringify(data, null, 2))
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
async JucundusCheckStop(){
//console.log('Check if Stop is asked')
@@ -83,8 +230,7 @@ class Scraper {
let url = encodeURIComponent(this.Url)
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/sale/getByUrl/'+url)
.then(response => response.json())
this.RequestJucundus(this._JucundusUrl+'/api/sale/getByUrl/'+url, 'GET')
.then(saleInfo => {
let status = saleInfo.status
//console.log('status : '+status)
@@ -93,34 +239,75 @@ class Scraper {
console.log('Stop was asked')
// return to ready status
this.JucundusSetSaleStatus(saleInfo, 'ready')
this.JucundusSetSaleStatus(saleInfo, 'end')
.then(
resolve(true)
);
} else {
resolve(false);
}
}
})
.catch(error => {
console.error(error);
reject(new Error('Error: '+error))
});
})
// return new Promise((resolve, reject) => {
// fetch(this._JucundusUrl+'/api/sale/getByUrl/'+url)
// .then(response => {
// if (!response.ok) {
// return response.json().then(err => {
// throw new Error(err.error || 'Unknown error');
// });
// }
// return response.json();
// })
// .then(saleInfo => {
// let status = saleInfo.status
// //console.log('status : '+status)
// if(status == 'askStop'){
// console.log('Stop was asked')
// // return to ready status
// this.JucundusSetSaleStatus(saleInfo, 'end')
// .then(
// resolve(true)
// );
// } else {
// resolve(false);
// }
// })
// .catch(error => {
// console.error(error);
// reject(new Error('Error: '+error))
// });
// })
}
async JucundusEndSale(){
console.log('JucundusEndSale')
async JucundusEndSale(code){
console.log('JucundusEndSale: '+code)
// check if stop was asked
let url = encodeURIComponent(this.Url)
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/sale/getByUrl/'+url)
.then(response => response.json())
// DEBUG
resolve(true)
if(code != 'end' && code != 'endOnError' && code != 'endOnRequest'){
console.error('Error: code must be end or endOnError or endOnRequest')
reject(new Error('Error: code must be end or endOnError or endOnRequest'))
}
this.RequestJucundus(this._JucundusUrl+'/api/sale/getByUrl/'+url, 'GET')
.then(saleInfo => {
// set end status
this.JucundusSetSaleStatus(saleInfo, 'end')
this.JucundusSetSaleStatus(saleInfo, code)
.then(
resolve(true)
);
@@ -133,13 +320,11 @@ class Scraper {
}
async JucundusSetSaleStatus(saleInfo, status){
// change the status of the sale
saleInfo.status = status
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/sale/sale/'+saleInfo._id, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(saleInfo)})
this.RequestJucundus(this._JucundusUrl+'/api/sale/sale/'+saleInfo._id, 'PUT', JSON.stringify(saleInfo))
.then(resolve(true))
.catch(error => {
console.error(error);
@@ -149,13 +334,14 @@ class Scraper {
}
async JucunduNextItem(sale_id, timestamp, item_id, num_lot, title, description, EstimateLow, EstimateHigh, RawData){
console.log('JucunduNextItem', sale_id, timestamp, item_id, num_lot)
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/lot/NextItem', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(
return new Promise((resolve, reject) => {
this.RequestJucundus(
this._JucundusUrl+'/api/lot/NextItem',
'POST',
JSON.stringify(
{
idPlatform: item_id,
idSalePlatform: sale_id,
@@ -168,7 +354,7 @@ class Scraper {
EstimateHigh: EstimateHigh,
RawData: RawData
}
)})
))
.then(resolve(true))
.catch(error => {
console.error(error);
@@ -178,12 +364,13 @@ class Scraper {
}
async JucundusBid(item_id, timestamp, amount, auctioned_type){
console.log('JucundusBid', timestamp, item_id, amount, auctioned_type)
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/lot/Bid', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(
this.RequestJucundus(
this._JucundusUrl+'/api/lot/Bid',
'POST',
JSON.stringify(
{
idPlatform: item_id,
platform: this._Name,
@@ -191,7 +378,7 @@ class Scraper {
amount: amount,
auctioned_type: auctioned_type
}
)})
))
.then(resolve(true))
.catch(error => {
console.error(error);
@@ -200,14 +387,14 @@ class Scraper {
})
}
async JucunduAuctionedItem(item_id, timestamp, amount, sold, auctioned_type){
async JucunduAuctionedItem(item_id, timestamp, amount, sold, auctioned_type){
console.log('JucunduAuctionedItem', timestamp, item_id, amount, sold)
return new Promise((resolve, reject) => {
fetch(this._JucundusUrl+'/api/lot/AuctionedItem', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(
this.RequestJucundus(
this._JucundusUrl+'/api/lot/AuctionedItem',
'POST',
JSON.stringify(
{
idPlatform: item_id,
platform: this._Name,
@@ -216,7 +403,7 @@ class Scraper {
auctioned_type: auctioned_type,
sold: sold,
}
)})
))
.then(resolve(true))
.catch(error => {
console.error(error);