0.16.1 Registry Fix MakeMKV
Deploy Docs to GitHub Pages / Build Documentation (push) Has been cancelled
Deploy Docs to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled

This commit is contained in:
2026-04-29 08:14:06 +00:00
parent d679eface4
commit 6636eb11df
20 changed files with 420 additions and 64 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.16.0", "version": "0.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.16.0", "version": "0.16.1",
"dependencies": { "dependencies": {
"archiver": "^7.0.1", "archiver": "^7.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.16.0", "version": "0.16.1",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
+6
View File
@@ -831,6 +831,12 @@ async function removeDeprecatedSettings(db) {
// Aktualisiert settings_schema-Metadaten (required, description, validation_json) // Aktualisiert settings_schema-Metadaten (required, description, validation_json)
// für bestehende Einträge, ohne user-konfigurierte Werte in settings_values anzutasten. // für bestehende Einträge, ohne user-konfigurierte Werte in settings_values anzutasten.
const SETTINGS_SCHEMA_METADATA_UPDATES = [ const SETTINGS_SCHEMA_METADATA_UPDATES = [
{
key: 'makemkv_registration_key',
required: 0,
description: 'Optionaler Registrierungsschlüssel. Wird beim Speichern nach ~/.MakeMKV/settings.conf synchronisiert. Darunter kann der aktuelle Betakey per API übernommen werden.',
validation_json: '{}'
},
{ {
key: 'handbrake_preset_bluray', key: 'handbrake_preset_bluray',
required: 0, required: 0,
+2 -9
View File
@@ -4,6 +4,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const { SourcePlugin } = require('./PluginBase'); const { SourcePlugin } = require('./PluginBase');
const { spawnTrackedProcess } = require('../services/processRunner'); const { spawnTrackedProcess } = require('../services/processRunner');
const { syncRegistrationKeyToConfig } = require('../services/makemkvKeyService');
const { parseHandBrakeProgress, parseMakeMkvProgress } = require('../utils/progressParsers'); const { parseHandBrakeProgress, parseMakeMkvProgress } = require('../utils/progressParsers');
const { ensureDir, sanitizeFileName, renderTemplate } = require('../utils/files'); const { ensureDir, sanitizeFileName, renderTemplate } = require('../utils/files');
@@ -236,15 +237,7 @@ class ConverterPlugin extends SourcePlugin {
const settings = await ctx.settings.getSettingsMap(); const settings = await ctx.settings.getSettingsMap();
const makemkvCmd = String(settings?.makemkv_command || 'makemkvcon').trim() || 'makemkvcon'; const makemkvCmd = String(settings?.makemkv_command || 'makemkvcon').trim() || 'makemkvcon';
const registrationKey = String(settings?.makemkv_registration_key || '').trim() || null; await syncRegistrationKeyToConfig(settings?.makemkv_registration_key || '');
if (registrationKey) {
try {
await this._runCaptured(makemkvCmd, ['reg', registrationKey], { jobId: job?.id });
} catch (_err) {
// Registrierung ist optional
}
}
const args = ['--noscan', '--decrypt', '-r', 'backup', `iso:${inputPath}`, rawJobDir]; const args = ['--noscan', '--decrypt', '-r', 'backup', `iso:${inputPath}`, rawJobDir];
+50
View File
@@ -11,6 +11,7 @@ const userPresetService = require('../services/userPresetService');
const activationBytesService = require('../services/activationBytesService'); const activationBytesService = require('../services/activationBytesService');
const diskDetectionService = require('../services/diskDetectionService'); const diskDetectionService = require('../services/diskDetectionService');
const coverArtRecoveryService = require('../services/coverArtRecoveryService'); const coverArtRecoveryService = require('../services/coverArtRecoveryService');
const { fetchCurrentBetaKey } = require('../services/makemkvKeyService');
const logger = require('../services/logger').child('SETTINGS_ROUTE'); const logger = require('../services/logger').child('SETTINGS_ROUTE');
const { getDb } = require('../db/database'); const { getDb } = require('../db/database');
@@ -52,6 +53,55 @@ router.get(
}) })
); );
router.get(
'/makemkv/beta-key',
asyncHandler(async (req, res) => {
logger.debug('get:settings:makemkv:beta-key', { reqId: req.reqId });
const result = await fetchCurrentBetaKey();
res.json({
betaKey: result.key,
sourceUrl: result.sourceUrl
});
})
);
router.post(
'/makemkv/beta-key/apply',
asyncHandler(async (req, res) => {
logger.info('post:settings:makemkv:beta-key:apply', { reqId: req.reqId });
const force = req.body?.force === true;
const currentSettings = await settingsService.getSettingsMap({ forceRefresh: true });
const existingKey = String(currentSettings?.makemkv_registration_key || '').trim();
const betaKeyResult = await fetchCurrentBetaKey();
if (!force && existingKey && existingKey !== betaKeyResult.key) {
const syncResult = await settingsService.syncMakeMKVRegistrationKeyFromSettings({
settingsMap: currentSettings
});
res.json({
applied: false,
preservedExistingKey: true,
reason: 'existing_key',
betaKey: betaKeyResult.key,
sourceUrl: betaKeyResult.sourceUrl,
settingsFilePath: syncResult?.path || null
});
return;
}
const updated = await settingsService.setSettingValue('makemkv_registration_key', betaKeyResult.key);
wsService.broadcast('SETTINGS_UPDATED', updated);
res.json({
applied: true,
preservedExistingKey: false,
setting: updated,
betaKey: betaKeyResult.key,
sourceUrl: betaKeyResult.sourceUrl
});
})
);
router.get( router.get(
'/scripts', '/scripts',
asyncHandler(async (req, res) => { asyncHandler(async (req, res) => {
+133
View File
@@ -0,0 +1,133 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const logger = require('./logger').child('MAKEMKV_KEY');
const MAKEMKV_BETA_KEY_API_URL = 'https://cable.ayra.ch/makemkv/api.php?raw';
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
|| 'ripster@localhost';
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 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',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
});
if (!response.ok) {
const error = new Error(`Betakey konnte nicht geladen werden (HTTP ${response.status}).`);
error.statusCode = 502;
throw error;
}
const rawBody = await response.text();
const betaKey = String(rawBody || '')
.split(/\r?\n/)
.map((line) => line.trim())
.find(Boolean) || '';
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
};
}
module.exports = {
MAKEMKV_BETA_KEY_API_URL,
fetchCurrentBetaKey,
getMakeMKVConfigDir,
getMakeMKVSettingsFilePath,
normalizeRegistrationKey,
syncRegistrationKeyToConfig
};
+10 -14
View File
@@ -16207,27 +16207,23 @@ class PipelineService extends EventEmitter {
} }
async ensureMakeMKVRegistration(jobId, stage) { async ensureMakeMKVRegistration(jobId, stage) {
const registrationConfig = await settingsService.buildMakeMKVRegisterConfig(); const syncResult = await settingsService.syncMakeMKVRegistrationKeyFromSettings();
if (!registrationConfig) { if (!syncResult?.applied) {
return { applied: false, reason: 'not_configured' }; return { applied: false, reason: 'not_configured', path: syncResult?.path || null };
} }
await historyService.appendLog( await historyService.appendLog(
jobId, jobId,
'SYSTEM', 'SYSTEM',
'Setze MakeMKV-Registrierungsschlüssel aus den Settings (makemkvcon reg).' 'Synchronisiere MakeMKV-Key aus den Settings nach ~/.MakeMKV/settings.conf.'
); );
await this.runCommand({ return {
jobId, applied: true,
stage, path: syncResult?.path || null,
source: 'MAKEMKV_REG', changed: Boolean(syncResult?.changed),
cmd: registrationConfig.cmd, stage
args: registrationConfig.args, };
argsForLog: registrationConfig.argsForLog
});
return { applied: true };
} }
isReviewRefreshSettingKey(key) { isReviewRefreshSettingKey(key) {
+42 -25
View File
@@ -14,6 +14,10 @@ const {
} = require('../utils/validators'); } = require('../utils/validators');
const { splitArgs } = require('../utils/commandLine'); const { splitArgs } = require('../utils/commandLine');
const { setLogRootDir } = require('./logPathService'); const { setLogRootDir } = require('./logPathService');
const {
syncRegistrationKeyToConfig,
normalizeRegistrationKey
} = require('./makemkvKeyService');
const { const {
defaultRawDir: DEFAULT_RAW_DIR, defaultRawDir: DEFAULT_RAW_DIR,
@@ -75,6 +79,7 @@ const SERVER_CONSOLE_LOG_SETTING_KEYS = new Set([
SERVER_CONSOLE_LOG_WARN_ENABLED_KEY, SERVER_CONSOLE_LOG_WARN_ENABLED_KEY,
SERVER_CONSOLE_LOG_ERROR_ENABLED_KEY SERVER_CONSOLE_LOG_ERROR_ENABLED_KEY
]); ]);
const MAKEMKV_REGISTRATION_KEY_SETTING_KEY = 'makemkv_registration_key';
const MEDIA_PROFILES = ['bluray', 'dvd', 'cd', 'audiobook']; const MEDIA_PROFILES = ['bluray', 'dvd', 'cd', 'audiobook'];
const PROFILED_SETTINGS = { const PROFILED_SETTINGS = {
raw_dir: { raw_dir: {
@@ -1056,25 +1061,36 @@ class SettingsService {
} }
const serializedValue = serializeValueByType(schema.type, result.normalized); const serializedValue = serializeValueByType(schema.type, result.normalized);
const normalizedKey = String(key || '').trim().toLowerCase();
await db.run( try {
` await db.exec('BEGIN');
INSERT INTO settings_values (key, value, updated_at) await db.run(
VALUES (?, ?, CURRENT_TIMESTAMP) `
ON CONFLICT(key) DO UPDATE SET INSERT INTO settings_values (key, value, updated_at)
value = excluded.value, VALUES (?, ?, CURRENT_TIMESTAMP)
updated_at = CURRENT_TIMESTAMP ON CONFLICT(key) DO UPDATE SET
`, value = excluded.value,
[key, serializedValue] updated_at = CURRENT_TIMESTAMP
); `,
[key, serializedValue]
);
if (normalizedKey === MAKEMKV_REGISTRATION_KEY_SETTING_KEY) {
await syncRegistrationKeyToConfig(result.normalized);
}
await db.exec('COMMIT');
} catch (error) {
await db.exec('ROLLBACK');
throw error;
}
logger.info('setting:updated', { logger.info('setting:updated', {
key, key,
value: SENSITIVE_SETTING_KEYS.has(String(key || '').trim().toLowerCase()) ? '[redacted]' : result.normalized value: SENSITIVE_SETTING_KEYS.has(String(key || '').trim().toLowerCase()) ? '[redacted]' : result.normalized
}); });
if (String(key || '').trim().toLowerCase() === LOG_DIR_SETTING_KEY) { if (normalizedKey === LOG_DIR_SETTING_KEY) {
applyRuntimeLogDirSetting(result.normalized); applyRuntimeLogDirSetting(result.normalized);
} }
if (SERVER_CONSOLE_LOG_SETTING_KEYS.has(String(key || '').trim().toLowerCase())) { if (SERVER_CONSOLE_LOG_SETTING_KEYS.has(normalizedKey)) {
const map = await this.getSettingsMap({ forceRefresh: true }); const map = await this.getSettingsMap({ forceRefresh: true });
applyRuntimeServerConsoleLoggingSettingsFromMap(map); applyRuntimeServerConsoleLoggingSettingsFromMap(map);
} }
@@ -1159,6 +1175,12 @@ class SettingsService {
[item.key, item.serializedValue] [item.key, item.serializedValue]
); );
} }
const makemkvKeyChange = normalizedEntries.find(
(item) => String(item?.key || '').trim().toLowerCase() === MAKEMKV_REGISTRATION_KEY_SETTING_KEY
);
if (makemkvKeyChange) {
await syncRegistrationKeyToConfig(makemkvKeyChange.value);
}
await db.exec('COMMIT'); await db.exec('COMMIT');
} catch (error) { } catch (error) {
await db.exec('ROLLBACK'); await db.exec('ROLLBACK');
@@ -1317,20 +1339,15 @@ class SettingsService {
return { cmd, args: [...baseArgs, ...extra] }; return { cmd, args: [...baseArgs, ...extra] };
} }
async buildMakeMKVRegisterConfig() { async syncMakeMKVRegistrationKeyFromSettings(options = {}) {
const map = await this.getSettingsMap(); const map = options?.settingsMap || await this.getSettingsMap();
const registrationKey = String(map.makemkv_registration_key || '').trim(); const registrationKey = normalizeRegistrationKey(map?.makemkv_registration_key);
if (!registrationKey) { const fileInfo = await syncRegistrationKeyToConfig(registrationKey, options);
return null;
}
const cmd = map.makemkv_command || 'makemkvcon';
const args = ['reg', registrationKey];
logger.debug('cli:makemkv:register', { cmd, args: ['reg', '<redacted>'] });
return { return {
cmd, applied: Boolean(registrationKey),
args, key: registrationKey || null,
argsForLog: ['reg', '<redacted>'] path: fileInfo?.path || null,
changed: Boolean(fileInfo?.changed)
}; };
} }
+1 -1
View File
@@ -397,7 +397,7 @@ VALUES ('makemkv_command', 'Tools', 'MakeMKV Kommando', 'string', 1, 'Pfad oder
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_command', 'makemkvcon'); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_command', 'makemkvcon');
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
VALUES ('makemkv_registration_key', 'Tools', 'MakeMKV Key', 'string', 0, 'Optionaler Registrierungsschlüssel. Wird vor Analyze/Rip automatisch per "makemkvcon reg" gesetzt.', NULL, '[]', '{}', 202); VALUES ('makemkv_registration_key', 'Tools', 'MakeMKV Key', 'string', 0, 'Optionaler Registrierungsschlüssel. Wird beim Speichern nach ~/.MakeMKV/settings.conf synchronisiert. Darunter kann der aktuelle Betakey per API übernommen werden.', NULL, '[]', '{}', 202);
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_registration_key', NULL); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_registration_key', NULL);
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
+1 -1
View File
@@ -98,7 +98,7 @@ Features:
- profilspezifische Auflösung (`resolveEffectiveToolSettings`) - profilspezifische Auflösung (`resolveEffectiveToolSettings`)
- CLI-Config-Building für MakeMKV/HandBrake/MediaInfo/FFmpeg - CLI-Config-Building für MakeMKV/HandBrake/MediaInfo/FFmpeg
- HandBrake-Preset-Liste via `HandBrakeCLI -z` - HandBrake-Preset-Liste via `HandBrakeCLI -z`
- MakeMKV-Registration-Command aus `makemkv_registration_key` - MakeMKV-Key-Synchronisation aus `makemkv_registration_key` nach `~/.MakeMKV/settings.conf`
- Pfadauflösung für alle Medienprofile inkl. Audiobook und Converter - Pfadauflösung für alle Medienprofile inkl. Audiobook und Converter
--- ---
+1 -1
View File
@@ -209,7 +209,7 @@ Typische Auswirkungen:
| GUI-Feld | Key | Typ | Default | Wirkung im System | | GUI-Feld | Key | Typ | Default | Wirkung im System |
|---|---|---|---|---| |---|---|---|---|---|
| `MakeMKV Kommando` | `makemkv_command` | `string` | `makemkvcon` | Pfad oder Befehl für makemkvcon. | | `MakeMKV Kommando` | `makemkv_command` | `string` | `makemkvcon` | Pfad oder Befehl für makemkvcon. |
| `MakeMKV Key` | `makemkv_registration_key` | `string` | `leer` | Optionaler Registrierungsschlüssel. Wird vor Analyze/Rip automatisch per "makemkvcon reg" gesetzt. | | `MakeMKV Key` | `makemkv_registration_key` | `string` | `leer` | Optionaler Registrierungsschlüssel. Wird beim Speichern nach `~/.MakeMKV/settings.conf` synchronisiert. In der UI kann darunter der aktuelle Betakey geladen und übernommen werden. |
| `Mediainfo Kommando` | `mediainfo_command` | `string` | `mediainfo` | Pfad oder Befehl für mediainfo. | | `Mediainfo Kommando` | `mediainfo_command` | `string` | `mediainfo` | Pfad oder Befehl für mediainfo. |
| `Minimale Titellänge (Minuten)` | `makemkv_min_length_minutes` | `number` | `60` | Filtert kurze Titel beim Rip. | | `Minimale Titellänge (Minuten)` | `makemkv_min_length_minutes` | `number` | `60` | Filtert kurze Titel beim Rip. |
| `HandBrake Kommando` | `handbrake_command` | `string` | `HandBrakeCLI` | Pfad oder Befehl für HandBrakeCLI. | | `HandBrake Kommando` | `handbrake_command` | `string` | `HandBrakeCLI` | Pfad oder Befehl für HandBrakeCLI. |
+10 -2
View File
@@ -34,12 +34,20 @@ makemkvcon backup <source> <rawDir> --decrypt
## Registrierungsschlüssel (optional) ## Registrierungsschlüssel (optional)
Wenn in `Settings` ein `MakeMKV Key` gesetzt ist, führt Ripster vor Analyse/Rip aus: Wenn in `Settings` ein `MakeMKV Key` gesetzt ist, synchronisiert Ripster ihn nach:
```bash ```bash
makemkvcon reg <key> ~/.MakeMKV/settings.conf
``` ```
Format:
```ini
app_Key = "product-key"
```
Zusätzlich zeigt die UI unterhalb des Feldes den aktuellen Betakey an, der per Button in das Eingabefeld übernommen werden kann.
--- ---
## Relevante Felder in `Settings` ## Relevante Felder in `Settings`
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.16.0", "version": "0.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.16.0", "version": "0.16.1",
"dependencies": { "dependencies": {
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primereact": "^10.9.2", "primereact": "^10.9.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.16.0", "version": "0.16.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+14
View File
@@ -376,6 +376,20 @@ export const api = {
}); });
return normalizeHandBrakePresetPayload(result); return normalizeHandBrakePresetPayload(result);
}, },
getMakeMKVBetaKey(options = {}) {
return requestCachedGet('/settings/makemkv/beta-key', {
ttlMs: 10 * 60 * 1000,
forceRefresh: options.forceRefresh
});
},
async applyMakeMKVBetaKey(payload = {}) {
const result = await request('/settings/makemkv/beta-key/apply', {
method: 'POST',
body: JSON.stringify(payload || {})
});
afterMutationInvalidate(['/settings/makemkv/beta-key', '/settings']);
return result;
},
getScripts(options = {}) { getScripts(options = {}) {
return requestCachedGet('/settings/scripts', { return requestCachedGet('/settings/scripts', {
ttlMs: 2 * 60 * 1000, ttlMs: 2 * 60 * 1000,
@@ -655,6 +655,67 @@ function isReadonlySetting(setting) {
} }
} }
function MakeMKVBetaKeyHint({ onApply, disabled = false }) {
const [loading, setLoading] = useState(true);
const [betaKey, setBetaKey] = useState('');
const [error, setError] = useState('');
useEffect(() => {
let active = true;
setLoading(true);
setError('');
api.getMakeMKVBetaKey({ forceRefresh: true })
.then((response) => {
if (!active) {
return;
}
setBetaKey(String(response?.betaKey || '').trim());
})
.catch((loadError) => {
if (!active) {
return;
}
setBetaKey('');
setError(loadError?.message || 'Betakey konnte nicht geladen werden.');
})
.finally(() => {
if (active) {
setLoading(false);
}
});
return () => {
active = false;
};
}, []);
return (
<div className="makemkv-beta-key-box">
<div className="makemkv-beta-key-head">
<span className="makemkv-beta-key-title">Betakey</span>
<Button
type="button"
size="small"
text
label="übernehmen"
disabled={disabled || loading || !betaKey}
onClick={() => onApply?.(betaKey)}
/>
</div>
{loading ? (
<small className="setting-description">Aktueller Betakey wird geladen</small>
) : null}
{!loading && error ? (
<small className="error-text">{error}</small>
) : null}
{!loading && !error && betaKey ? (
<code className="makemkv-beta-key-value">{betaKey}</code>
) : null}
</div>
);
}
function SettingField({ function SettingField({
setting, setting,
value, value,
@@ -782,6 +843,12 @@ function SettingField({
</a> </a>
</small> </small>
) : null} ) : null}
{normalizeSettingKey(setting?.key) === 'makemkv_registration_key' ? (
<MakeMKVBetaKeyHint
disabled={isDisabled}
onApply={(nextKey) => onChange?.(setting.key, nextKey)}
/>
) : null}
{error ? ( {error ? (
<small className="error-text">{error}</small> <small className="error-text">{error}</small>
) : ( ) : (
+37
View File
@@ -2046,6 +2046,43 @@ body {
line-height: 1.35; line-height: 1.35;
} }
.makemkv-beta-key-box {
display: grid;
gap: 0.35rem;
margin-top: 0.15rem;
padding: 0.55rem 0.65rem;
border: 1px dashed rgba(111, 57, 34, 0.32);
border-radius: 0.45rem;
background: rgba(255, 250, 240, 0.72);
}
.makemkv-beta-key-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.makemkv-beta-key-title {
font-size: 0.8rem;
font-weight: 700;
color: var(--rip-brown-800);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.makemkv-beta-key-value {
display: block;
padding: 0.45rem 0.5rem;
border-radius: 0.35rem;
background: rgba(58, 29, 18, 0.06);
color: var(--rip-brown-900);
font-size: 0.82rem;
line-height: 1.35;
overflow-wrap: anywhere;
word-break: break-word;
}
.notification-toggle-grid { .notification-toggle-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
+37 -2
View File
@@ -440,8 +440,41 @@ install_makemkv() {
cd / cd /
rm -rf "$tmp_dir" rm -rf "$tmp_dir"
ok "MakeMKV $makemkv_version installiert" ok "MakeMKV $makemkv_version installiert"
warn "Hinweis: MakeMKV benötigt eine Lizenz oder den Beta-Key." info "Der MakeMKV-Betakey wird nach dem Backend-Start automatisch per API übernommen, sofern noch kein eigener Key gespeichert ist."
warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053" }
sync_makemkv_beta_key() {
header "MakeMKV Betakey synchronisieren"
local api_url="http://127.0.0.1:${BACKEND_PORT}/settings/makemkv/beta-key/apply"
local response=""
if ! response=$(curl -fsS \
-X POST \
-H 'Content-Type: application/json' \
--data '{}' \
"$api_url" 2>/dev/null); then
warn "MakeMKV-Betakey konnte nicht automatisch übernommen werden."
warn "Backend-Endpunkt fehlgeschlagen: $api_url"
return 0
fi
local applied preserved settings_file
applied=$(printf '%s' "$response" | jq -r '.applied // false' 2>/dev/null || echo "false")
preserved=$(printf '%s' "$response" | jq -r '.preservedExistingKey // false' 2>/dev/null || echo "false")
settings_file=$(printf '%s' "$response" | jq -r '.settingsFilePath // empty' 2>/dev/null || true)
if [[ "$applied" == "true" ]]; then
ok "MakeMKV-Betakey automatisch gespeichert${settings_file:+ ($settings_file)}"
return 0
fi
if [[ "$preserved" == "true" ]]; then
ok "Vorhandener MakeMKV-Key bleibt erhalten${settings_file:+ ($settings_file)}"
return 0
fi
warn "MakeMKV-Betakey wurde nicht übernommen."
} }
select_handbrake_mode() { select_handbrake_mode() {
@@ -987,6 +1020,8 @@ else
exit 1 exit 1
fi fi
sync_makemkv_beta_key
if [[ "$SKIP_NGINX" == false ]]; then if [[ "$SKIP_NGINX" == false ]]; then
systemctl enable nginx systemctl enable nginx
systemctl restart nginx systemctl restart nginx
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster", "name": "ripster",
"version": "0.16.0", "version": "0.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster", "name": "ripster",
"version": "0.16.0", "version": "0.16.1",
"devDependencies": { "devDependencies": {
"concurrently": "^9.1.2" "concurrently": "^9.1.2"
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ripster", "name": "ripster",
"private": true, "private": true,
"version": "0.16.0", "version": "0.16.1",
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
"dev:backend": "npm run dev --prefix backend", "dev:backend": "npm run dev --prefix backend",