216 lines
6.2 KiB
JavaScript
216 lines
6.2 KiB
JavaScript
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { execFile } = require('child_process');
|
|
const logger = require('./logger').child('MAKEMKV_KEY');
|
|
|
|
const MAKEMKV_BETA_KEY_API_URL = 'https://cable.ayra.ch/makemkv/api.php?json';
|
|
const MAKEMKV_BETA_KEY_API_USER_AGENT = process.env.MAKEMKV_BETA_KEY_API_USER_AGENT
|
|
|| 'Ripster/1.0 (MakeMKV beta key sync)';
|
|
const MAKEMKV_BETA_KEY_API_FROM = process.env.MAKEMKV_BETA_KEY_API_FROM
|
|
|| 'admin@example.invalid';
|
|
const MAKEMKV_BETA_KEY_API_TIMEOUT_MS = Math.max(
|
|
1000,
|
|
Number(process.env.MAKEMKV_BETA_KEY_API_TIMEOUT_MS || 15000)
|
|
);
|
|
|
|
function normalizeRegistrationKey(rawValue) {
|
|
return String(rawValue || '').trim();
|
|
}
|
|
|
|
function getMakeMKVConfigDir(homeDir = os.homedir()) {
|
|
return path.join(String(homeDir || '').trim() || os.homedir(), '.MakeMKV');
|
|
}
|
|
|
|
function getMakeMKVSettingsFilePath(homeDir = os.homedir()) {
|
|
return path.join(getMakeMKVConfigDir(homeDir), 'settings.conf');
|
|
}
|
|
|
|
function escapeKeyForSettingsFile(value) {
|
|
return String(value || '')
|
|
.replace(/\\/g, '\\\\')
|
|
.replace(/"/g, '\\"');
|
|
}
|
|
|
|
function buildUpdatedSettingsContent(currentContent, registrationKey) {
|
|
const existingLines = String(currentContent || '')
|
|
.split(/\r?\n/)
|
|
.filter((line) => !/^\s*app_Key\s*=/.test(line));
|
|
const normalizedKey = normalizeRegistrationKey(registrationKey);
|
|
|
|
while (existingLines.length > 0 && existingLines[existingLines.length - 1] === '') {
|
|
existingLines.pop();
|
|
}
|
|
|
|
if (normalizedKey) {
|
|
existingLines.push(`app_Key = "${escapeKeyForSettingsFile(normalizedKey)}"`);
|
|
}
|
|
|
|
return existingLines.length > 0 ? `${existingLines.join('\n')}\n` : '';
|
|
}
|
|
|
|
async function syncRegistrationKeyToConfig(rawValue, options = {}) {
|
|
const normalizedKey = normalizeRegistrationKey(rawValue);
|
|
const homeDir = String(options?.homeDir || '').trim() || os.homedir();
|
|
const configDir = getMakeMKVConfigDir(homeDir);
|
|
const settingsFilePath = getMakeMKVSettingsFilePath(homeDir);
|
|
const fileExists = fs.existsSync(settingsFilePath);
|
|
const currentContent = fileExists
|
|
? await fs.promises.readFile(settingsFilePath, 'utf8')
|
|
: '';
|
|
const nextContent = buildUpdatedSettingsContent(currentContent, normalizedKey);
|
|
|
|
if (!normalizedKey && !fileExists) {
|
|
return {
|
|
changed: false,
|
|
path: settingsFilePath,
|
|
hasKey: false
|
|
};
|
|
}
|
|
|
|
await fs.promises.mkdir(configDir, { recursive: true });
|
|
|
|
if (nextContent) {
|
|
await fs.promises.writeFile(settingsFilePath, nextContent, 'utf8');
|
|
await fs.promises.chmod(settingsFilePath, 0o600).catch(() => {});
|
|
logger.info('settings-conf:key-synced', {
|
|
path: settingsFilePath,
|
|
hasKey: true
|
|
});
|
|
} else {
|
|
await fs.promises.writeFile(settingsFilePath, '', 'utf8');
|
|
await fs.promises.chmod(settingsFilePath, 0o600).catch(() => {});
|
|
logger.info('settings-conf:key-cleared', {
|
|
path: settingsFilePath,
|
|
hasKey: false
|
|
});
|
|
}
|
|
|
|
return {
|
|
changed: currentContent !== nextContent,
|
|
path: settingsFilePath,
|
|
hasKey: Boolean(normalizedKey)
|
|
};
|
|
}
|
|
|
|
async function fetchCurrentBetaKey() {
|
|
const errors = [];
|
|
|
|
try {
|
|
return await fetchCurrentBetaKeyViaCurl();
|
|
} catch (error) {
|
|
errors.push(error?.message || String(error));
|
|
logger.warn('beta-key:curl-failed', {
|
|
error: error?.message || String(error)
|
|
});
|
|
}
|
|
|
|
try {
|
|
return await fetchCurrentBetaKeyViaFetch();
|
|
} catch (error) {
|
|
errors.push(error?.message || String(error));
|
|
logger.warn('beta-key:fetch-failed', {
|
|
error: error?.message || String(error)
|
|
});
|
|
}
|
|
|
|
const error = new Error(`Betakey konnte nicht geladen werden. Details: ${errors.join(' | ')}`);
|
|
error.statusCode = 502;
|
|
throw error;
|
|
}
|
|
|
|
function parseBetaKeyResponse(rawBody) {
|
|
let payload = null;
|
|
try {
|
|
payload = JSON.parse(String(rawBody || '{}'));
|
|
} catch (_error) {
|
|
const error = new Error('Betakey-Antwort ist kein gültiges JSON.');
|
|
error.statusCode = 502;
|
|
throw error;
|
|
}
|
|
|
|
const betaKey = String(payload?.key || '').trim();
|
|
|
|
if (!/^[A-Za-z0-9][A-Za-z0-9-]{15,}$/.test(betaKey)) {
|
|
const error = new Error('Betakey-Antwort ist ungültig.');
|
|
error.statusCode = 502;
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
key: betaKey,
|
|
sourceUrl: MAKEMKV_BETA_KEY_API_URL
|
|
};
|
|
}
|
|
|
|
function fetchCurrentBetaKeyViaCurl() {
|
|
return new Promise((resolve, reject) => {
|
|
execFile(
|
|
'curl',
|
|
[
|
|
'-fsSL',
|
|
'--max-time', String(Math.ceil(MAKEMKV_BETA_KEY_API_TIMEOUT_MS / 1000)),
|
|
'-A', MAKEMKV_BETA_KEY_API_USER_AGENT,
|
|
'-H', `From: ${MAKEMKV_BETA_KEY_API_FROM}`,
|
|
'-H', 'Accept: text/plain, text/html;q=0.9, */*;q=0.8',
|
|
'-H', 'Accept-Language: de,en-US;q=0.7,en;q=0.3',
|
|
'-H', 'Cache-Control: no-cache',
|
|
'-H', 'Pragma: no-cache',
|
|
MAKEMKV_BETA_KEY_API_URL
|
|
],
|
|
{
|
|
timeout: MAKEMKV_BETA_KEY_API_TIMEOUT_MS,
|
|
maxBuffer: 1024 * 1024
|
|
},
|
|
(error, stdout, stderr) => {
|
|
if (error) {
|
|
const resolvedError = new Error(
|
|
`curl fehlgeschlagen (${error.code || 'unknown'}): ${String(stderr || error.message || '').trim() || 'keine Details'}`
|
|
);
|
|
resolvedError.statusCode = 502;
|
|
reject(resolvedError);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
resolve(parseBetaKeyResponse(stdout));
|
|
} catch (parseError) {
|
|
reject(parseError);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async function fetchCurrentBetaKeyViaFetch() {
|
|
const response = await fetch(MAKEMKV_BETA_KEY_API_URL, {
|
|
headers: {
|
|
'User-Agent': MAKEMKV_BETA_KEY_API_USER_AGENT,
|
|
'From': MAKEMKV_BETA_KEY_API_FROM,
|
|
'Accept': 'text/plain, text/html;q=0.9, */*;q=0.8',
|
|
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
|
|
'Cache-Control': 'no-cache',
|
|
'Pragma': 'no-cache'
|
|
},
|
|
signal: AbortSignal.timeout(MAKEMKV_BETA_KEY_API_TIMEOUT_MS)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = new Error(`HTTP ${response.status}`);
|
|
error.statusCode = 502;
|
|
throw error;
|
|
}
|
|
|
|
const rawBody = await response.text();
|
|
return parseBetaKeyResponse(rawBody);
|
|
}
|
|
|
|
module.exports = {
|
|
MAKEMKV_BETA_KEY_API_URL,
|
|
fetchCurrentBetaKey,
|
|
getMakeMKVConfigDir,
|
|
getMakeMKVSettingsFilePath,
|
|
normalizeRegistrationKey,
|
|
syncRegistrationKeyToConfig
|
|
};
|