multi-downloader-nx_mirror/modules/module.cfg-loader.ts
stratumadev 1ff93f2fbd refactor(merger): move from ffprobe to mediainfo
Removes the necessity of ffprobe completely.
Also removed the mention of ffprobe requirement from the readme.
2025-11-30 03:10:28 +01:00

379 lines
10 KiB
TypeScript

import path from 'path';
import yaml from 'yaml';
import fs from 'fs';
import { lookpath } from 'lookpath';
import { console } from './log';
import { GuiState } from '../@types/messageHandler';
// new-cfg
const workingDir = (
process as NodeJS.Process & {
pkg?: unknown;
}
).pkg
? path.dirname(process.execPath)
: process.env.contentDirectory
? process.env.contentDirectory
: path.join(__dirname, '/..');
export { workingDir };
const binCfgFile = path.join(workingDir, 'config', 'bin-path');
const dirCfgFile = path.join(workingDir, 'config', 'dir-path');
const guiCfgFile = path.join(workingDir, 'config', 'gui');
const cliCfgFile = path.join(workingDir, 'config', 'cli-defaults');
const hdPflCfgFile = path.join(workingDir, 'config', 'hd_profile');
const sessCfgFile = {
cr: path.join(workingDir, 'config', 'cr_sess'),
hd: path.join(workingDir, 'config', 'hd_sess'),
adn: path.join(workingDir, 'config', 'adn_sess')
};
const stateFile = path.join(workingDir, 'config', 'guistate');
const tokenFile = {
cr: path.join(workingDir, 'config', 'cr_token'),
hd: path.join(workingDir, 'config', 'hd_token'),
hdNew: path.join(workingDir, 'config', 'hd_new_token'),
adn: path.join(workingDir, 'config', 'adn_token')
};
export const ensureConfig = () => {
if (!fs.existsSync(path.join(workingDir, 'config'))) fs.mkdirSync(path.join(workingDir, 'config'), { recursive: true });
if (process.env.contentDirectory)
[binCfgFile, dirCfgFile, cliCfgFile, guiCfgFile].forEach((a) => {
if (!fs.existsSync(`${a}.yml`)) fs.copyFileSync(path.join(__dirname, '..', 'config', `${path.basename(a)}.yml`), `${a}.yml`);
});
};
const loadYamlCfgFile = <T extends Record<string, any>>(file: string, isSess?: boolean): T => {
if (fs.existsSync(`${file}.user.yml`) && !isSess) {
file += '.user';
}
file += '.yml';
if (fs.existsSync(file)) {
try {
return yaml.parse(fs.readFileSync(file, 'utf8'));
} catch (e) {
console.error('[ERROR]', e);
return {} as T;
}
}
return {} as T;
};
export type WriteObjects = {
gui: GUIConfig;
};
const writeYamlCfgFile = <T extends keyof WriteObjects>(file: T, data: WriteObjects[T]) => {
const fn = path.join(workingDir, 'config', `${file}.yml`);
if (fs.existsSync(fn)) fs.unlinkSync(fn);
fs.writeFileSync(fn, yaml.stringify(data));
};
export type GUIConfig = {
port: number;
password?: string;
};
export type ConfigObject = {
dir: {
content: string;
trash: string;
fonts: string;
config: string;
};
bin: {
ffmpeg?: string;
mkvmerge?: string;
mp4decrypt?: string;
shaka?: string;
};
cli: {
[key: string]: any;
};
gui: GUIConfig;
};
const loadCfg = (): ConfigObject => {
// load cfgs
const defaultCfg: ConfigObject = {
bin: {},
dir: loadYamlCfgFile<{
content: string;
trash: string;
fonts: string;
config: string;
}>(dirCfgFile),
cli: loadYamlCfgFile<{
[key: string]: any;
}>(cliCfgFile),
gui: loadYamlCfgFile<GUIConfig>(guiCfgFile)
};
const defaultDirs = {
fonts: '${wdir}/fonts/',
content: '${wdir}/videos/',
trash: '${wdir}/videos/_trash/',
config: '${wdir}/config'
};
if (typeof defaultCfg.dir !== 'object' || defaultCfg.dir === null || Array.isArray(defaultCfg.dir)) {
defaultCfg.dir = defaultDirs;
}
const keys = Object.keys(defaultDirs) as (keyof typeof defaultDirs)[];
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(defaultCfg.dir, key) || typeof defaultCfg.dir[key] !== 'string') {
defaultCfg.dir[key] = defaultDirs[key];
}
if (!path.isAbsolute(defaultCfg.dir[key])) {
defaultCfg.dir[key] = path.join(workingDir, defaultCfg.dir[key].replace(/^\${wdir}/, ''));
}
}
if (!fs.existsSync(defaultCfg.dir.content)) {
try {
fs.mkdirSync(defaultCfg.dir.content, { recursive: true });
} catch (e) {
console.error('Content directory not accessible!');
return defaultCfg;
}
}
if (!fs.existsSync(defaultCfg.dir.trash)) {
defaultCfg.dir.trash = defaultCfg.dir.content;
}
// output
return defaultCfg;
};
const loadBinCfg = async () => {
const binCfg = loadYamlCfgFile<ConfigObject['bin']>(binCfgFile);
// binaries
const defaultBin = {
ffmpeg: 'ffmpeg',
mkvmerge: 'mkvmerge',
mp4decrypt: 'mp4decrypt',
shaka: 'shaka-packager'
};
const keys = Object.keys(defaultBin) as (keyof typeof defaultBin)[];
for (const dir of keys) {
if (!Object.prototype.hasOwnProperty.call(binCfg, dir) || typeof binCfg[dir] != 'string') {
binCfg[dir] = defaultBin[dir];
}
if ((binCfg[dir] as string).match(/^\${wdir}/)) {
binCfg[dir] = (binCfg[dir] as string).replace(/^\${wdir}/, '');
binCfg[dir] = path.join(workingDir, binCfg[dir] as string);
}
if (!path.isAbsolute(binCfg[dir] as string)) {
binCfg[dir] = path.join(workingDir, binCfg[dir] as string);
}
binCfg[dir] = await lookpath(binCfg[dir] as string);
binCfg[dir] = binCfg[dir] ? binCfg[dir] : undefined;
if (!binCfg[dir]) {
const binFile = await lookpath(path.basename(defaultBin[dir]));
binCfg[dir] = binFile ? binFile : binCfg[dir];
}
}
return binCfg;
};
const loadCRSession = () => {
let session = loadYamlCfgFile(sessCfgFile.cr, true);
if (typeof session !== 'object' || session === null || Array.isArray(session)) {
session = {};
}
for (const cv of Object.keys(session)) {
if (typeof session[cv] !== 'object' || session[cv] === null || Array.isArray(session[cv])) {
session[cv] = {};
}
}
return session;
};
const saveCRSession = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(sessCfgFile.cr);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${sessCfgFile.cr}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save session file to disk!");
}
};
const loadCRToken = () => {
let token = loadYamlCfgFile(tokenFile.cr, true);
if (typeof token !== 'object' || token === null || Array.isArray(token)) {
token = {};
}
return token;
};
const saveCRToken = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(tokenFile.cr);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${tokenFile.cr}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save token file to disk!");
}
};
const loadADNToken = () => {
let token = loadYamlCfgFile(tokenFile.adn, true);
if (typeof token !== 'object' || token === null || Array.isArray(token)) {
token = {};
}
return token;
};
const saveADNToken = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(tokenFile.adn);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${tokenFile.adn}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save token file to disk!");
}
};
const loadHDSession = () => {
let session = loadYamlCfgFile(sessCfgFile.hd, true);
if (typeof session !== 'object' || session === null || Array.isArray(session)) {
session = {};
}
for (const cv of Object.keys(session)) {
if (typeof session[cv] !== 'object' || session[cv] === null || Array.isArray(session[cv])) {
session[cv] = {};
}
}
return session;
};
const saveHDSession = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(sessCfgFile.hd);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${sessCfgFile.hd}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save session file to disk!");
}
};
const loadHDToken = () => {
let token = loadYamlCfgFile(tokenFile.hd, true);
if (typeof token !== 'object' || token === null || Array.isArray(token)) {
token = {};
}
return token;
};
const saveHDToken = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(tokenFile.hd);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${tokenFile.hd}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save token file to disk!");
}
};
const saveHDProfile = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(hdPflCfgFile);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${hdPflCfgFile}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save profile file to disk!");
}
};
const loadHDProfile = () => {
let profile = loadYamlCfgFile(hdPflCfgFile, true);
if (typeof profile !== 'object' || profile === null || Array.isArray(profile) || Object.keys(profile).length === 0) {
profile = {
// base
ipAddress: '',
xNonce: '',
xSignature: '',
// personal
visitId: '',
// profile data
profile: {
userId: 0,
profileId: 0,
deviceId: ''
}
};
}
return profile;
};
const loadNewHDToken = () => {
let token = loadYamlCfgFile(tokenFile.hdNew, true);
if (typeof token !== 'object' || token === null || Array.isArray(token)) {
token = {};
}
return token;
};
const saveNewHDToken = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(tokenFile.hdNew);
try {
fs.mkdirSync(cfgFolder, { recursive: true });
fs.writeFileSync(`${tokenFile.hdNew}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save token file to disk!");
}
};
const cfgDir = path.join(workingDir, 'config');
const getState = (): GuiState => {
const fn = `${stateFile}.json`;
if (!fs.existsSync(fn)) {
return {
setup: false,
services: {}
};
}
try {
return JSON.parse(fs.readFileSync(fn).toString());
} catch (e) {
console.error('Invalid state file, regenerating');
return {
setup: false,
services: {}
};
}
};
const setState = (state: GuiState) => {
const fn = `${stateFile}.json`;
try {
fs.writeFileSync(fn, JSON.stringify(state, null, 2));
} catch (e) {
console.error('Failed to write state file.');
}
};
export {
loadBinCfg,
loadCfg,
saveCRSession,
loadCRSession,
saveCRToken,
loadCRToken,
saveADNToken,
loadADNToken,
saveHDSession,
loadHDSession,
saveHDToken,
loadHDToken,
saveNewHDToken,
loadNewHDToken,
saveHDProfile,
loadHDProfile,
getState,
setState,
writeYamlCfgFile,
sessCfgFile,
hdPflCfgFile,
cfgDir
};