integration of authentication
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user