Files
CrawlFlix-API/services/softwares.js
Joris Bertomeu 4802f808cf
All checks were successful
ci / Image build (push) Successful in 50s
ci / Deployment (push) Successful in 11s
Add new binary update mechanism
2024-10-01 17:44:39 +02:00

276 lines
7.9 KiB
JavaScript

const axios = require('axios');
const os = require('os');
const fs = require("fs");
const cheerio = require('cheerio');
const unzipper = require('unzipper');
const tar = require('tar');
const BASE_PATH = process.env.DATA_PATH || `./data`;
const BIN_PATH = `${BASE_PATH}/bin`;
const fetchAndParseHTML = async (url) => {
try {
const { data: html } = await axios.get(url);
const $ = cheerio.load(html);
const files = [];
$('a').each((index, element) => {
const fileName = $(element).text().trim();
const href = $(element).attr('href');
const nextElement = $(element).nextAll().filter((i, el) => $(el).text().trim() !== '-').first();
const details = nextElement.text().trim().split(/\s+/);
if (href && fileName !== "Parent Directory") {
files.push({
fileName: href,
href: `https://www.bok.net/Bento4/binaries/${href}`
});
}
});
return files;
} catch (error) {
console.error('Erreur lors de la récupération/parsing:', error);
}
}
const getLatestBentoBinary = async (platform) => {
try {
const last = (await fetchAndParseHTML('https://www.bok.net/Bento4/binaries/')).slice(-3);
const releases = last.map((elem) => {
let version = elem.fileName.split('.');
version = version?.[0]?.split('Bento4-SDK-')?.[1] || null;
return {
downloadUrl: elem.href,
filename: elem.fileName,
platform: elem.fileName.includes('macosx') ? 'darwin' : elem.fileName.includes('linux') ? 'linux' : 'win32',
version
}
});
return releases.find((elem) => elem.platform === platform);
} catch(e) {
throw e;
}
}
const translateDownloaderArch = () => {
const platform = os.platform();
const arch = os.arch();
switch (platform) {
case 'linux':
if (arch === 'x64') {
return 'linux-x64';
} else if (arch === 'arm64') {
return 'linux-arm64';
}
break;
case 'darwin': // macOS
if (arch === 'x64') {
return 'osx-x64';
} else if (arch === 'arm64') {
return 'osx-arm64';
}
break;
case 'win32': // Windows
if (arch === 'x64') {
return 'win-x64';
} else if (arch === 'arm64') {
return 'win-arm64';
}
break;
default:
throw new Error(`OS or architecture unsuported : ${platform}, ${arch}`);
}
throw new Error(`Unsuported architecture for this platform ${platform} : ${arch}`);
};
const writeBinVersion = (versionFilePath, version) => {
const data = { version };
try {
fs.writeFileSync(versionFilePath, JSON.stringify(data, null, 2), 'utf8');
} catch (error) {
throw e;
}
}
getBinVersion = (versionFilePath) => {
try {
const data = fs.readFileSync(versionFilePath, 'utf8');
const json = JSON.parse(data);
return json.version || null;
} catch (error) {
if (error.code === 'ENOENT') {
return null;
} else {
throw error;
}
}
}
const getLocalBinFileInfo = (binType) => {
if (['downloader', 'mp4decrypt'].includes(binType)) {
const path = `${BIN_PATH}/${binType}`;
return {
path,
stat: fs.existsSync(path) ? fs.statSync(path) : null,
version: getBinVersion(`${BIN_PATH}/.${binType}.version`)
}
} else {
throw new Error(`Bad binType "${binType}" provided`);
}
}
const downloadFile = async (url, dest) => {
const writer = fs.createWriteStream(dest);
const response = await axios({
url,
method: 'GET',
responseType: 'stream',
});
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
}
const checkDownloaderUpdate = async () => {
try {
const localInfos = getLocalBinFileInfo('downloader');
const remoteInfos = await getLatestGithubReleaseAssetUrl('nilaoda', 'N_m3u8DL-RE', translateDownloaderArch());
return {
details: {
localInfos,
remoteInfos
},
newReleaseAvailable: localInfos?.version ? parseInt(localInfos.version) !== remoteInfos?.id : remoteInfos?.name || false,
filsIsPresent: localInfos?.stat ? true : false
}
} catch(e) {
throw e;
}
}
const extractFile = (source, destination) => {
return new Promise((resolve, reject) => {
if (source.endsWith('.zip')) {
fs.createReadStream(source)
.pipe(unzipper.Extract({ path: destination }))
.on('close', resolve)
.on('error', reject);
} else if (source.endsWith('.tar.gz')) {
fs.createReadStream(source)
.pipe(tar.x({ C: destination }))
.on('close', resolve)
.on('error', reject);
} else {
reject(new Error('Unsupported file type'));
}
});
}
const processUpdate = async (data) => {
const tmpUpdatePath = `${BIN_PATH}/update/`;
try {
let downloadURL = data.binType === 'downloader' ? data.details.remoteInfos.browser_download_url : data.details.remoteInfos.downloadUrl;
let filename = data.binType === 'downloader' ? data.details.remoteInfos.name : data.details.remoteInfos.filename;
fs.mkdirSync(tmpUpdatePath);
const zipDest = `${tmpUpdatePath}/${filename}`;
console.log('Will download file from ' + downloadURL);
await downloadFile(downloadURL, zipDest);
console.log('File downloaded to ' + zipDest)
console.log('Will decompress downloaded file');
const unzippedFolderDest = `${tmpUpdatePath}/${data.binType}_tmp`
fs.mkdirSync(unzippedFolderDest);
await extractFile(zipDest, unzippedFolderDest);
console.log('Unzipped !');
const uncompressedFoldersS = fs.readdirSync(unzippedFolderDest);
console.log(uncompressedFoldersS)
if (uncompressedFoldersS.length !== 1)
throw new Error('Unable to retrieve decompressed folder');
const uncompressedFolders = fs.readdirSync(`${unzippedFolderDest}/${uncompressedFoldersS[0]}`);
if (uncompressedFolders.length === 0)
throw new Error('Unable to retrieve archive content');
if (data.binType === 'downloader')
fs.renameSync(`${unzippedFolderDest}/${uncompressedFoldersS[0]}/${uncompressedFolders[0]}`, data.details.localInfos.path);
else if (data.binType === 'mp4decrypt')
fs.renameSync(`${unzippedFolderDest}/${uncompressedFoldersS[0]}/bin/mp4decrypt`, data.details.localInfos.path);
writeBinVersion(`${BIN_PATH}/.${data.binType}.version`, data.details.remoteInfos[data.binType === 'downloader' ? 'id' : 'version'])
} catch(e) {
throw e;
} finally {
fs.rmSync(tmpUpdatePath, { recursive: true, force: true });
}
}
const checkMp4decryptUpdate = async () => {
try {
const localInfos = getLocalBinFileInfo('mp4decrypt');
const remoteInfos = await getLatestBentoBinary(os.platform());
console.log(remoteInfos);
return {
details: {
localInfos,
remoteInfos
},
newReleaseAvailable: localInfos?.version ? localInfos.version !== remoteInfos?.version : remoteInfos?.filename || false,
filsIsPresent: localInfos?.stat ? true : false
}
} catch(e) {
throw e;
}
}
const getLatestGithubReleaseAssetUrl = async (owner, repo, assetName) => {
try {
const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/releases/latest`, {
headers: { 'User-Agent': 'node.js' }
});
const release = response.data;
const asset = release.assets.find(a => a.name.includes(assetName));
if (asset) {
return asset;
} else {
throw new Error('Asset not found');
}
} catch (error) {
console.error('Erreur:', error.message);
throw error;
}
}
const init = async () => {
try {
if (!fs.existsSync(BIN_PATH)) {
fs.mkdirSync(BIN_PATH);
console.log('Creating bin path...')
}
} catch(e) {
throw e;
}
}
module.exports = {
init,
getLatestGithubReleaseAssetUrl,
translateDownloaderArch,
getLocalBinFileInfo,
checkDownloaderUpdate,
checkMp4decryptUpdate,
processUpdate
};