Optionally receiving avtocod data from clients

This commit is contained in:
Selim Mustafaev 2025-08-16 12:30:51 +03:00
parent c89ce6071c
commit 33e64ac412
3 changed files with 16 additions and 95 deletions

View File

@ -4,27 +4,12 @@ import DebugInfo from '../models/DebugInfo.js';
import { Centrifuge } from 'centrifuge'; import { Centrifuge } from 'centrifuge';
import WebSocket from 'ws'; import WebSocket from 'ws';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';
const baseUrl = 'https://avtocod.ru/api/v4'; const baseUrl = 'https://avtocod.ru/api/v4';
const tokenRefreshUrl = 'https://avtocod.ru/api/centrifuge/refresh'; const tokenRefreshUrl = 'https://avtocod.ru/api/centrifuge/refresh';
const minimalVersionUrl = 'https://avtocod.ru/api/v4/info/minimal_version?platform=ios'; // Just for getting cookies
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0'; const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0';
const sign = {
version: '4.6.2',
sign: '3343fc07110c5df19b840d688bc45b78',
platform: 'Win32',
browsers: ['chrome'],
score: 0.6,
};
let deviceToken = uuidv4().replaceAll('-', '').toUpperCase(); let deviceToken = uuidv4().replaceAll('-', '').toUpperCase();
var cookie = null;
setInterval(() => {
cookie = null;
}, 30*60*1000);
const myWs = function (options) { const myWs = function (options) {
return class wsClass extends WebSocket { return class wsClass extends WebSocket {
@ -34,91 +19,24 @@ const myWs = function (options) {
}; };
}; };
function findValue(key, str) {
let result = str.match(new RegExp(`"(${key}=.*?)"`));
if(result == null) {
throw Error(`Cannot find ${key} data`);
}
return result[1];
}
function getSpsnCookie(html, sign) {
let signStr = JSON.stringify(sign);
let signHex = Buffer.from(signStr).toString('hex');
let spsnStart = findValue('spsn', html);
return spsnStart + signHex + ';';
}
function getSpidCookie(html) {
return findValue('spid', html) + ';';
}
function getSpscCookie(html) {
let keyResult = html.match(/-----BEGIN.*?KEY-----.*?-----END.*?KEY-----/s);
if(keyResult == null) {
throw Error('Cannot find key for decrypting spsc data');
}
let key = keyResult[0];
let srcResult = html.match(/decrypt\("(.*?)"/);
if(srcResult == null) {
throw Error('Cannot find spsc data');
}
let dataToDecrypt = Buffer.from(srcResult[1], 'hex');
let decrypted = crypto.privateDecrypt({
key,
padding: crypto.constants.RSA_PKCS1_PADDING
}, dataToDecrypt);
return 'spsc=' + decrypted.toString() + ';';
}
function fromBase64(data) { function fromBase64(data) {
return Buffer.from(data, 'base64').toString('binary'); return Buffer.from(data, 'base64').toString('binary');
} }
async function getPage(url) { async function getPage(url) {
let result = await fetch(url, { let result = await fetch(url, {
headers: {
'User-Agent': userAgent,
'cookie': await getCookies()
}
});
return await result.text();
}
async function getCookies() {
if(cookie != null) {
return cookie;
}
let result = await fetch(minimalVersionUrl, {
headers: { headers: {
'User-Agent': userAgent 'User-Agent': userAgent
} }
}); });
return await result.text();
let html = await result.text();
let spsnCookie = getSpsnCookie(html, sign);
let spidCookie = getSpidCookie(html);
let spscCookie = getSpscCookie(html);
cookie = [spsnCookie, spidCookie, spscCookie].join(' ');
return cookie;
} }
async function getJson(url) { async function getJson(url) {
let jsonResult = await fetch(url, { let jsonResult = await fetch(url, {
headers: { headers: {
'User-Agent': userAgent, 'User-Agent': userAgent
'cookie': await getCookies()
} }
}); });
return await jsonResult.json(); return await jsonResult.json();
@ -145,8 +63,7 @@ function getToken(ctx) {
fetch(tokenRefreshUrl, { fetch(tokenRefreshUrl, {
method: 'POST', method: 'POST',
headers: new Headers({ headers: new Headers({
'Content-Type': 'application/json', 'Content-Type': 'application/json'
'cookie': cookie
}), }),
body: JSON.stringify(ctx) body: JSON.stringify(ctx)
}) })
@ -211,13 +128,17 @@ function waitForReport(centrifugoConfig, channel) {
} }
class AvtocodProvider { class AvtocodProvider {
static async getReport(number, type) { static async getReport(number, type, htmlReport) {
try { try {
let numberType = type ?? 'GRZ'; let html = '';
let url = `${baseUrl}/auto/generate?number=${encodeURIComponent(number)}&device_token=${deviceToken}&type=${numberType}`; if(htmlReport) {
let resp = await getJson(url); html = Buffer.from(htmlReport, 'base64').toString('utf-8');
} else {
let html = await getPage(resp.report_uri); let numberType = type ?? 'GRZ';
let url = `${baseUrl}/auto/generate?number=${encodeURIComponent(number)}&device_token=${deviceToken}&type=${numberType}`;
let resp = await getJson(url);
html = await getPage(resp.report_uri);
}
let result = html.match(/<meta name="app-version-hash" content="(.*?)" \/>/); let result = html.match(/<meta name="app-version-hash" content="(.*?)" \/>/);
if(result == null) { if(result == null) {

View File

@ -23,7 +23,7 @@ app.use(responseTime(function (req, res, time) {
})); }));
app.use(compression()); app.use(compression());
app.use(express.json()); app.use(express.json({ limit: '5Mb' }));
app.use(expressMongoDb(process.env.MONGO_CONNECTION_STRING)); app.use(expressMongoDb(process.env.MONGO_CONNECTION_STRING));
app.use(bearerToken()); app.use(bearerToken());
app.use(jwt({ secret: process.env.JWT_SECRET_AUTH, exclude: ['/user/signup', '/user/login', '/vehicles/shared_report'] })); app.use(jwt({ secret: process.env.JWT_SECRET_AUTH, exclude: ['/user/signup', '/user/login', '/vehicles/shared_report'] }));

View File

@ -19,7 +19,7 @@ const makeError = (error) => ({ success: false, error });
router.post('/check', async (req, res) => { router.post('/check', async (req, res) => {
const number = req.body.number.replace(/ /g, '').toUpperCase(); const number = req.body.number.replace(/ /g, '').toUpperCase();
const type = req.body.type; const type = req.body.type;
const { googleIdToken, forceUpdate, notes, events } = req.body; const { googleIdToken, forceUpdate, notes, events, avtocodReport } = req.body;
const { email } = req.user; const { email } = req.user;
console.log( console.log(
@ -54,7 +54,7 @@ router.post('/check', async (req, res) => {
res.send({ success: true, data: vehicles[0] }); res.send({ success: true, data: vehicles[0] });
} else { } else {
try { try {
let autocodPromise = AvtocodProvider.getReport(number, type); let autocodPromise = AvtocodProvider.getReport(number, type, avtocodReport);
let all = [autocodPromise]; let all = [autocodPromise];
if (googleIdToken) { if (googleIdToken) {