fix(v5): Beta-Key mit @ kommt ganz an; Schlüssel-Stand ehrlich (rot statt „Rips laufen", nicht prüfbar ohne Laufwerk); Zeiten in Ortszeit

WAS: KEY_MUSTER nimmt alles bis Leerraum/HTML-Zeichen (der September-Key
„T-…4BI@N5rPp" hat 68 Zeichen mit @; das alte Muster lieferte 62 und
MakeMKV lief danach ohne Schlüssel). extractGueltigBis versteht „end of
September 2026". Die Kette prüft zuerst den vorhandenen Schlüssel und
lässt ihn stehen, wenn MakeMKV ihn annimmt (kein Überschreiben gekaufter
Keys mehr); reg add wird auf Erfolg geprüft; 5042 ohne Lizenzzeile heißt
„nicht prüfbar" statt „angenommen". Fenster: Ablehnung ist ROT mit dem
Satz, dass Blu-rays dann nur noch in MakeMKVs Gnadenfrist laufen; die
MakeMKV-Karte sagt, was Rippy in MakeMKVs Einstellungen schreibt. Ein
neuer Werkzeug-Pfad wird sofort gesucht, ein neuer eigener Schlüssel
sofort geprüft. datumKurz zeigt Ortszeit statt UTC.
WARUM: Durchsicht 12.09.2026, Funde F1, F5, F12, F13 und A2 — am echten
Forum-HTML und an makemkvcon ohne Laufwerk gemessen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-12 21:17:43 +02:00
co-authored by Claude Opus 5
parent 6f1add480e
commit 12b9720386
9 changed files with 224 additions and 47 deletions
+6
View File
@@ -250,6 +250,12 @@ function fensterNachricht(frage: unknown): void {
vorgaenge.senden()
speicher.senden()
}
// 5.7.0 (Durchsicht F13): Ein neuer Werkzeug-Pfad wird sofort neu
// gesucht und gemessen — vorher stand „NICHT GEFUNDEN" so lange, bis
// das Fenster neu geladen wurde (in der Erst-Einrichtung besonders
// irreführend). Ein neuer eigener Schlüssel wird sofort geprüft.
if (frage.schluessel.startsWith('werkzeug.')) void werkzeuge.senden()
if (frage.schluessel === 'makemkvAppKey') void werkzeuge.schluesselLauf().then(() => werkzeuge.senden())
}
return
case 'einstellungen-laden':
+144 -36
View File
@@ -3,20 +3,32 @@
// am Commander-PC gemessen):
//
// 1. Ein eingetragener Dauerlizenz-Schlüssel schlägt alles.
// 2. Sonst der Beta-Key aus dem Forum-Thread des Herstellers (t=1053).
// 3. Abgelegt wird in HKCU\Software\MakeMKV\app_Key — die settings.conf
// 2. Sonst: Ein Schlüssel, der schon im Register steht und angenommen
// wird, bleibt (5.7.0 — vorher überschrieb Rippy jeden vorhandenen
// Schlüssel mit dem Forum-Key, auch einen gekauften aus dem GUI).
// 3. Sonst der Beta-Key aus dem Forum-Thread des Herstellers (t=1053).
// 4. Abgelegt wird in HKCU\Software\MakeMKV\app_Key — die settings.conf
// ist der Linux-Weg; unter Windows liest MakeMKV die Registry.
// 4. GEFRAGT statt gerechnet: makemkvcon selbst sagt per MSG 5020/5021/
// 5. GEFRAGT statt gerechnet: makemkvcon selbst sagt per MSG 5020/5021/
// 5051, ob der Key taugt. Ein ABGELEHNTER Key ist schlimmer als
// keiner (0 Titel, kein Laufwerk) — er wird dann ZURÜCKGENOMMEN.
// 5. app_UpdateEnable=1 nur-wenn-fehlt: Über diesen Kanal holt MakeMKV
// Ohne angeschlossenes Laufwerk sagt makemkvcon NICHTS zur Lizenz
// (12.09.2026 gemessen: nur 5042/5010) — dann heißt das Urteil
// „nicht prüfbar", nicht „angenommen".
// 6. app_UpdateEnable=1 nur-wenn-fehlt: Über diesen Kanal holt MakeMKV
// die 4K-UHD-Disc-Schlüssel nach (MSG 3338; unter Windows gemessen).
import { spawn, spawnSync } from 'node:child_process'
import { textVon } from '../rip/parser'
import { KEY_ABGELEHNT, KRITISCHE_CODES } from '../rip/parser'
export const FORUM_URL = 'https://forum.makemkv.com/forum/viewtopic.php?t=1053'
export const KEY_MUSTER = /T-[A-Za-z0-9_]{50,}/
// 5.7.0 (Durchsicht 12.09.2026, F1): Der September-Key lautet
// „T-vwvn…6Ox4BI@N5rPp" — 68 Zeichen MIT einem @. Das alte Muster
// /T-[A-Za-z0-9_]{50,}/ endete am @ und lieferte 62 Zeichen; MakeMKV lehnte
// den Stumpf ab (5020), Rippy nahm ihn zurück, und MakeMKV lief ohne
// Schlüssel. Jetzt zählt alles bis zum nächsten Leerraum oder HTML-Zeichen —
// Rippy rät nicht, welche Zeichen der Hersteller in seine Schlüssel setzt.
export const KEY_MUSTER = /T-[^\s<>"'&;]{50,}/
const REGISTRY_PFAD = 'HKCU\\Software\\MakeMKV'
const KEY_NAME = 'app_Key'
@@ -28,12 +40,15 @@ export function extractKey(html: string): string {
/** „valid until/through <Datum>" aus dem Thread — best effort, Rohtext.
* § 9.1 will das Ablaufdatum sichtbar; die verlässliche Instanz bleibt
* aber makemkvcon selbst (MSG 5051 = abgelaufen). */
* aber makemkvcon selbst (MSG 5051 = abgelaufen). Der Thread schreibt
* mal „September 30th, 2026", mal „end of September 2026" (so am
* 12.09.2026 gemessen) — beide Formen kommen an. */
export function extractGueltigBis(html: string): string {
const treffer = /valid\s+(?:until|through)\s+([A-Za-zäöüÄÖÜ]+\s+\d{1,2}(?:st|nd|rd|th)?,?\s+\d{4})/i.exec(
html ?? '',
)
return treffer === null ? '' : treffer[1].replace(/(\d)(st|nd|rd|th)/, '$1')
const treffer =
/valid\s+(?:until|through)\s+((?:(?:the\s+)?end\s+of\s+|(?:the\s+)?beginning\s+of\s+|mid-?\s*)?[A-Za-zäöüÄÖÜ]+(?:\s+\d{1,2}(?:st|nd|rd|th)?,?)?\s+\d{4})/i.exec(
html ?? '',
)
return treffer === null ? '' : treffer[1].replace(/(\d)(st|nd|rd|th)/, '$1').replace(/\s+/g, ' ')
}
export interface ForumKey {
@@ -79,6 +94,19 @@ export function keyZuruecknehmen(): boolean {
return reg(['delete', REGISTRY_PFAD, '/v', KEY_NAME, '/f']).ok
}
/** Den Wert aus einer `reg query`-Ausgabe lesen (pur, testbar):
* „ app_Key REG_SZ T-…" — '' wenn keiner dasteht. */
export function keyAusQuery(ausgabe: string): string {
const treffer = new RegExp(`${KEY_NAME}\\s+REG_SZ\\s+(\\S+)`).exec(ausgabe ?? '')
return treffer === null ? '' : treffer[1].trim()
}
/** Der Schlüssel, der gerade im Register steht — '' wenn keiner. */
export function keyLesen(): string {
const vorhanden = reg(['query', REGISTRY_PFAD, '/v', KEY_NAME])
return vorhanden.ok ? keyAusQuery(vorhanden.ausgabe) : ''
}
/** app_UpdateEnable=1 setzen, NUR wenn es fehlt — wer den Web-Kontakt
* bewusst abgeschaltet hat, behält seine Entscheidung. */
export function updateKanalSicherstellen(): boolean {
@@ -89,6 +117,9 @@ export function updateKanalSicherstellen(): boolean {
export interface KeyUrteil {
ok: boolean
/** false: makemkvcon hat die Lizenz gar nicht angesehen — es sah kein
* Laufwerk (5042). Dann ist `ok` keine Aussage über den Schlüssel. */
pruefbar: boolean
code: number | null
grund: string
/** Die MakeMKV-Version aus MSG 1005 („MakeMKV v1.18.4 …") — '' wenn
@@ -98,7 +129,9 @@ export interface KeyUrteil {
/** Nimmt MakeMKV den hinterlegten Schlüssel an? — es selbst gefragt:
* `info disc:9999` braucht keinen Datenträger, die Freischaltungs-Meldung
* kommt trotzdem (makemkv_aufruf.py, 29.08.2026).
* kommt trotzdem (makemkv_aufruf.py, 29.08.2026) — ABER nur mit einem
* angeschlossenen Laufwerk (12.09.2026 ohne Laufwerk gemessen: 5042 und
* 5010, keine Lizenzzeile).
*
* ⚠ ALLE Codes einsammeln, dann urteilen: Beim Commander kamen 5020 UND
* 5021 zusammen (30.08.2026 live gemessen) — 5020 allein klingt nach
@@ -108,7 +141,7 @@ export interface KeyUrteil {
export function keyPruefen(programm: string, timeoutMs = 120_000): Promise<KeyUrteil> {
return new Promise((fertig) => {
if (programm.length === 0) {
fertig({ ok: false, code: null, grund: 'MakeMKV nicht gefunden', version: '' })
fertig({ ok: false, pruefbar: false, code: null, grund: 'MakeMKV nicht gefunden', version: '' })
return
}
const kind = spawn(programm, ['-r', '--noscan', 'info', 'disc:9999'], {
@@ -121,7 +154,7 @@ export function keyPruefen(programm: string, timeoutMs = 120_000): Promise<KeyUr
const zeiger = setTimeout(() => kind.kill(), timeoutMs)
kind.on('error', (fehler) => {
clearTimeout(zeiger)
fertig({ ok: false, code: null, grund: `MakeMKV nicht befragbar (${String(fehler)})`, version: '' })
fertig({ ok: false, pruefbar: false, code: null, grund: `MakeMKV nicht befragbar (${String(fehler)})`, version: '' })
})
kind.on('close', () => {
clearTimeout(zeiger)
@@ -130,10 +163,14 @@ export function keyPruefen(programm: string, timeoutMs = 120_000): Promise<KeyUr
})
}
/** MakeMKV sah kein Laufwerk — ohne eines prüft es die Lizenz nicht. */
export const KEIN_LAUFWERK = 5042
/** Die Ausgabe eines info-Laufs → Urteil (pur, testbar). */
export function urteilAusAusgabe(ausgabe: string): KeyUrteil {
const codes: number[] = []
let version = ''
let keinLaufwerk = false
for (const zeile of ausgabe.split(/\r?\n/)) {
if (!zeile.startsWith('MSG:')) continue
const code = Number(zeile.slice(4).split(',', 1)[0])
@@ -142,21 +179,29 @@ export function urteilAusAusgabe(ausgabe: string): KeyUrteil {
const treffer = /MakeMKV v([0-9][0-9.]*)/.exec(zeile)
if (treffer !== null) version = treffer[1]
}
if (code === KEIN_LAUFWERK) keinLaufwerk = true
if (KEY_ABGELEHNT.includes(code) || code === 5051) codes.push(code)
}
if (codes.length === 0) return { ok: true, code: null, grund: '', version }
if (codes.length === 0) {
// Keine Lizenzzeile: entweder angenommen — oder gar nicht angesehen.
return keinLaufwerk
? { ok: true, pruefbar: false, code: KEIN_LAUFWERK, grund: 'MakeMKV sah kein Laufwerk und hat die Lizenz nicht geprüft', version }
: { ok: true, pruefbar: true, code: null, grund: '', version }
}
// 5021 schlägt alles: Es benennt die URSACHE (Version zu alt) — 5020
// ist dann nur die Folge und würde den Schlüssel zu Unrecht beschuldigen.
const code = codes.includes(5021) ? 5021 : codes[0]
return { ok: false, code, grund: KRITISCHE_CODES[code] ?? `MSG ${code}`, version }
return { ok: false, pruefbar: true, code, grund: KRITISCHE_CODES[code] ?? `MSG ${code}`, version }
}
export interface SchluesselStand {
/** 'dauerlizenz' | 'forum' | 'keiner' */
/** 'dauerlizenz' | 'vorhanden' | 'forum' | 'keiner' */
quelle: string
/** true angenommen · false abgelehnt/fehlt · null nicht prüfbar (kein Laufwerk). */
angenommen: boolean | null
/** Laufen Rips trotzdem? (Beta-Modus ohne Key = ja — das ist eine
* WARNUNG mit Handlungsempfehlung, kein Ausfall.) */
/** Laufen Rips trotzdem? Ohne angenommenen Schlüssel: DVDs ja (laut
* Hersteller dauerhaft frei), Blu-rays nur in MakeMKVs Gnadenfrist —
* das ist ein AUSFALL für Blu-ray, keine Warnung (5.7.0). */
weiterBetrieb: boolean
gueltigBis: string
geholtAm: string
@@ -165,19 +210,51 @@ export interface SchluesselStand {
version: string
}
/** Die ganze Kette: Dauerlizenz → Forum-Key → ablegen → PRÜFEN →
* bei Ablehnung zurücknehmen. Gibt den Stand fürs Dashboard zurück. */
/** Was ein abgelehnter oder fehlender Schlüssel bedeutet — EIN Satz für
* alle Wege, damit das Fenster nie „Rips laufen" sagt, wo Blu-rays nicht
* laufen. */
export const OHNE_SCHLUESSEL_FOLGE =
'Ohne angenommenen Schlüssel liest MakeMKV DVDs weiter (dauerhaft frei), Blu-rays und 4K aber nur in seiner Gnadenfrist nach dem Testzeitraum — danach verweigert es sie.'
/** Die ganze Kette: Dauerlizenz → vorhandener Schlüssel → Forum-Key →
* ablegen → PRÜFEN → bei Ablehnung zurücknehmen. Gibt den Stand fürs
* Dashboard zurück. */
export async function schluesselKette(
makemkvcon: string,
dauerlizenz: string,
jetztIso: string,
holen: typeof fetch = fetch,
registerLesen: () => string = keyLesen,
): Promise<SchluesselStand> {
updateKanalSicherstellen()
const versionsText = (urteil: KeyUrteil): string => (urteil.version.length > 0 ? ` ${urteil.version}` : '')
const nichtPruefbar = (quelle: string, gueltigBis: string, urteil: KeyUrteil, wer: string): SchluesselStand => ({
quelle,
angenommen: null,
weiterBetrieb: true,
gueltigBis,
geholtAm: jetztIso,
text: `${wer} hinterlegt — ob MakeMKV${versionsText(urteil)} ihn annimmt, lässt sich erst mit angeschlossenem Laufwerk prüfen (${urteil.grund}).`,
version: urteil.version,
})
if (dauerlizenz.trim().length > 0) {
keyAblegen(dauerlizenz)
// 5.7.0: Das Ergebnis von reg add zählt — „hinterlegt" stand vorher
// auch da, wenn nichts geschrieben wurde.
if (!keyAblegen(dauerlizenz)) {
return {
quelle: 'dauerlizenz',
angenommen: false,
weiterBetrieb: false,
gueltigBis: '',
geholtAm: jetztIso,
text: 'Eigener Schlüssel konnte nicht ins Register geschrieben werden (reg add scheiterte). ' + OHNE_SCHLUESSEL_FOLGE,
version: '',
}
}
const urteil = await keyPruefen(makemkvcon)
if (!urteil.pruefbar) return nichtPruefbar('dauerlizenz', '', urteil, 'Eigener Schlüssel')
return {
quelle: 'dauerlizenz',
angenommen: urteil.ok,
@@ -185,54 +262,85 @@ export async function schluesselKette(
gueltigBis: '',
geholtAm: jetztIso,
text: urteil.ok
? `Eigener Schlüssel hinterlegt und von MakeMKV${urteil.version.length > 0 ? ` ${urteil.version}` : ''} angenommen.`
: `Eigener Schlüssel abgelehnt: ${urteil.grund}`,
? `Eigener Schlüssel hinterlegt und von MakeMKV${versionsText(urteil)} angenommen.`
: `Eigener Schlüssel abgelehnt: ${urteil.grund}. ${OHNE_SCHLUESSEL_FOLGE}`,
version: urteil.version,
}
}
// 5.7.0: Ein Schlüssel, der schon steht und angenommen wird, bleibt —
// egal ob gekauft (im GUI eingetragen) oder ein älterer Forum-Key.
const vorhanden = registerLesen()
if (vorhanden.length > 0) {
const urteil = await keyPruefen(makemkvcon)
if (!urteil.pruefbar) return nichtPruefbar('vorhanden', '', urteil, 'Ein Schlüssel ist')
if (urteil.ok) {
return {
quelle: 'vorhanden',
angenommen: true,
weiterBetrieb: true,
gueltigBis: '',
geholtAm: jetztIso,
text: `Der hinterlegte Schlüssel wird von MakeMKV${versionsText(urteil)} angenommen — Rippy lässt ihn stehen.`,
version: urteil.version,
}
}
// Abgelehnt: dann darf der Forum-Key ihn ersetzen (unten).
}
const forum = await betaKeyHolen(holen)
if (forum === null) {
// Kein neuer Key erreichbar — der VORHANDENE bleibt stehen (R2) und
// wird geprüft, damit der Stand ehrlich ist.
const urteil = await keyPruefen(makemkvcon)
if (!urteil.pruefbar) return nichtPruefbar('keiner', '', urteil, 'Forum nicht erreichbar; der vorhandene Stand ist')
return {
quelle: 'keiner',
angenommen: urteil.ok,
weiterBetrieb: true,
weiterBetrieb: urteil.ok,
gueltigBis: '',
geholtAm: jetztIso,
text: urteil.ok
? 'Forum nicht erreichbar — der hinterlegte Schlüssel funktioniert weiter.'
: `Forum nicht erreichbar und der hinterlegte Schlüssel taugt nicht: ${urteil.grund}`,
: `Forum nicht erreichbar und der hinterlegte Schlüssel taugt nicht: ${urteil.grund}. ${OHNE_SCHLUESSEL_FOLGE}`,
version: urteil.version,
}
}
keyAblegen(forum.key)
if (!keyAblegen(forum.key)) {
return {
quelle: 'forum',
angenommen: false,
weiterBetrieb: false,
gueltigBis: forum.gueltigBis,
geholtAm: jetztIso,
text: 'Beta-Key geholt, aber nicht ins Register schreibbar (reg add scheiterte). ' + OHNE_SCHLUESSEL_FOLGE,
version: '',
}
}
const urteil = await keyPruefen(makemkvcon)
if (!urteil.pruefbar) return nichtPruefbar('forum', forum.gueltigBis, urteil, 'Beta-Key aus dem Forum')
if (!urteil.ok) {
// Ein abgelehnter Schlüssel ist schlimmer als keiner — zurücknehmen.
// (Live gemessen 30.08.2026: MIT abgelehntem Key verweigert MakeMKV
// alles; OHNE läuft es im Beta-Modus weiter und rippt.)
// alles; OHNE läuft es im Beta-Modus weiter und rippt — DVDs immer,
// Blu-rays nur bis zum Ende des Testzeitraums.)
keyZuruecknehmen()
}
const versionsText = urteil.version.length > 0 ? ` ${urteil.version}` : ''
return {
quelle: 'forum',
angenommen: urteil.ok,
// Der Zurücknahme-Weg lässt MakeMKV im Beta-Modus weiterlaufen —
// Rips gehen, nur eben ohne Freischaltungs-Komfort.
weiterBetrieb: true,
weiterBetrieb: urteil.ok,
gueltigBis: forum.gueltigBis,
geholtAm: jetztIso,
text: urteil.ok
? `Beta-Key aus dem Forum hinterlegt und von MakeMKV${versionsText} angenommen${forum.gueltigBis.length > 0 ? ` — gültig bis ${forum.gueltigBis}` : ''}.`
? `Beta-Key aus dem Forum hinterlegt und von MakeMKV${versionsText(urteil)} angenommen${forum.gueltigBis.length > 0 ? ` — gültig bis ${forum.gueltigBis}` : ''}.`
: urteil.code === 5021
? `Der aktuelle Beta-Key ist in Ordnung — aber MakeMKV${versionsText} ist ZU ALT für ihn. ` +
'Rippy hat ihn wieder entfernt, damit MakeMKV im Beta-Modus weiterläuft (so klappen Rips trotzdem). ' +
'Abhilfe: MakeMKV von makemkv.com aktualisieren — danach hinterlegt Rippy den Key automatisch neu.'
: `Beta-Key abgelehnt (${urteil.grund}) — wieder entfernt, damit MakeMKV im Beta-Modus weiterläuft.`,
? `Der aktuelle Beta-Key ist in Ordnung — aber MakeMKV${versionsText(urteil)} ist ZU ALT für ihn. ` +
'Rippy hat ihn wieder entfernt. ' +
OHNE_SCHLUESSEL_FOLGE +
' Abhilfe: MakeMKV von makemkv.com aktualisieren — danach hinterlegt Rippy den Key automatisch neu.'
: `Beta-Key abgelehnt (${urteil.grund}) — wieder entfernt. ${OHNE_SCHLUESSEL_FOLGE}`,
version: urteil.version,
}
}