const crypto = require('crypto'); const fetch = require('node-fetch'); const Vehicle = require('../models/vehicle'); const PubNub = require('pubnub'); const baseUrl = 'https://avtocod.ru/api/v3'; let deviceToken = crypto.createHash('sha256').update(Date.now().toString()).digest().toString('hex'); function fromBase64(data) { return Buffer.from(data, 'base64').toString('binary'); } async function getPage(url) { let result = await fetch(url); return await result.text(); } async function getJson(url) { let result = await fetch(url); return await result.json(); } function decryptReport(report, hash) { let dataLength = report.length/2; let encryptedObject = JSON.parse(fromBase64(report.slice(-dataLength).concat(report.slice(0, -dataLength)))); let iv = Buffer.from(encryptedObject.iv, 'base64'); let data = Buffer.from(encryptedObject.value, 'base64'); let key = crypto.createHash('sha256').update(hash).digest(); let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); let decrypted = decipher.update(data); decrypted = Buffer.concat([decrypted, decipher.final()]); return JSON.parse(decrypted.toString()); } function waitForReport(pubnubConfig, channel) { return new Promise((resolve, reject) => { let report = null; let retry = 0; let timeout = setTimeout(() => { pubnub.unsubscribeAll(); if(report != null) { resolve(report); } else { reject(new Error('Request to avtocod timed out')); } }, 10000); let pubnub = new PubNub(pubnubConfig); pubnub.addListener({ status: function(event) { if(event.error == true && event.operation == 'PNSubscribeOperation' && event.category == 'PNAccessDeniedCategory' && ++retry < 5) { setTimeout(() => { event.errorData.payload.channels.forEach(c => pubnub.subscribe({ channels: [c] })); }, 1000); } }, message: function(message) { if(message.message.event == 'report-ready') { clearTimeout(timeout); pubnub.unsubscribeAll(); resolve(message.message.data); } else if(message.message.event == 'report-updated') { report = message.message.data; } } }); pubnub.subscribe({ channels: [channel] }); }); } class AvtocodProvider { static async getReport(number) { let url = `${baseUrl}/auto/generate?number=${encodeURIComponent(number)}&device_token=${deviceToken}`; let resp = await getJson(url); let html = await getPage(resp.report_uri); let result = html.match(//); if(result == null) { throw Error('Error getting api version hash'); } let hash = result[1]; result = html.match(/value:(.*report_container.*)/); if(result == null) { throw Error('Error getting report data'); } let mainData = JSON.parse(result[1]); let report = decryptReport(mainData.report_container.report, hash); if(report == null) { report = await waitForReport(mainData.pubnub, mainData.report_container.channel); } let vehicle = Vehicle.fromAvtocod(report); console.log('Avtocod found vehicle: ', vehicle?.brand?.name?.original); return vehicle; } } module.exports = AvtocodProvider;