refactor: switch the whole project to English

talk is going to be published. Code comments, CLI output, error messages,
daemon log lines, MCP descriptions and results, test names and assertions,
README, spec, skill, build scripts and flake are now English. Only the
spoken announcement texts stay bilingual: the wrap result and the test
sentence are German or English depending on the detected language.

Language fallback is now configurable: `lang = "en" | "de"` (env
TALK_LANG, default en) is used when detection cannot tell (numbers, single
words, ties) and for talk's own announcements. langdetect.Detect takes the
fallback as an argument instead of hard-coding German. `talk setup` asks for
it; `talk doctor` and `talk status` show it.

Vocabulary pinned across packages so the process-boundary tests observe
stable strings: chain log lines ("chain: tier X skipped (not available)",
"falling back to the next tier", "spoken via X"), queue lines ("discarded:
quiet hours ... active", "replaced by toast", "max_age"), CLI notes
("speaking directly", "daemon started at", "no daemon", "nothing to cancel"),
status block with aligned English labels, MCP messages, origin guard 403.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
m3ta-chironandClaude Fable 5.1 committed 2026-10-24 11:15:35 +02:00
1 parent 4d30bd95ad
commit 85fa64cfe6
95 files changed
+4707 -4854

No files matched your search

+46 -46
View File
@@ -4,35 +4,35 @@ import (
"encoding/binary"
)
// Edge-DRM: Sec-MS-GEC v2. Microsoft verlangt seit der Protokoll-Änderung
// (edge-tts-Referenz, Stand 2026) einen Token der Form
// „<16 Hex CS64>-<64 Hex SHA256-Variante>" über den 5-Minuten-gerundeten
// Windows-Filetime-Ticks + Trusted-Client-Token. Beide Digests sind
// absichtlich verschleierte Varianten bekannter Algorithmen — die
// Konstanten und der Kontrollfluss unten sind eine wörtliche Portierung
// der Referenz (edge-tts src/edge_tts/drm.py, MIT) und gegen deren
// Ausgaben tabellengeprüft (TestEdgeDRMReferenzvektoren). Ändert Microsoft
// das Schema erneut, liefert der Edge-Provider klare Fehler („Protokoll
// geändert?"), statt still zu versagen.
// Edge DRM: Sec-MS-GEC v2. Since the protocol change (edge-tts reference,
// as of 2026) Microsoft requires a token of the form
// "<16 hex CS64>-<64 hex SHA256 variant>" over the 5-minute-rounded
// Windows filetime ticks + trusted client token. Both digests are
// deliberately obfuscated variants of well-known algorithms; the constants
// and the control flow below are a literal port of the reference (edge-tts
// src/edge_tts/drm.py, MIT) and are table-checked against its outputs
// (TestEdgeSecMSGEC). If Microsoft changes the scheme again, the Edge
// provider returns clear errors ("protocol changed?") instead of failing
// silently.
const (
drmWinEpoch = 11644473600 // Sekunden zwischen 1601-01-01 und 1970-01-01
drmWinEpoch = 11644473600 // seconds between 1601-01-01 and 1970-01-01
)
// drmIV ist der Standard-SHA-256-Initialvektor.
// drmIV is the standard SHA-256 initial vector.
var drmIV = [8]uint32{
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
}
// drmMidState ist der Arbeitsregister-Stand der Referenz für den
// Mid-Block-Einstieg (Runde 14 des ersten Blocks).
// drmMidState is the reference's working-register state for the mid-block
// entry (round 14 of the first block).
var drmMidState = [8]uint32{
0xFD9FBEA2, 0x91243CE7, 0xBE499094, 0xCD2E1E0A,
0x6C006F49, 0x04BD35AB, 0xFEB0A7E4, 0x03EFFBD2,
}
// drmSchedule sind vorberechnete Schedule-Anteile des ersten Blocks.
// drmSchedule holds precomputed schedule parts of the first block.
var drmSchedule = [14]uint32{
0xEB24BF9E, 0x1558691F, 0xA0C09EE2, 0x0CFA32BA,
0xA622EE8C, 0xD1DD9491, 0xC0DD25A0, 0xF9E70AB6,
@@ -40,7 +40,7 @@ var drmSchedule = [14]uint32{
0xD258B060, 0x72766564,
}
// drmAdjustment ersetzt die Rundungskonstante K[14] im Mid-Block-Pfad.
// drmAdjustment replaces the round constant K[14] on the mid-block path.
const (
drmAdjustment uint32 = 0xC9DC06A7
drmSigma0Adj uint32 = 0x059C0B80
@@ -50,7 +50,7 @@ const (
drmCS64Adj1 uint32 = 0x0B1A0000
)
// drmK sind die Standard-SHA-256-Rundungskonstanten.
// drmK are the standard SHA-256 round constants.
var drmK = [64]uint32{
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
@@ -62,7 +62,7 @@ var drmK = [64]uint32{
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
// drmCS64Coefficients: (Term-, Offset-Koeffizient) je Zeile.
// drmCS64Coefficients: (term coefficient, offset coefficient) per row.
var drmCS64Coefficients = [14][2]uint32{
{0xE82E91B1, 0xB32700D0}, {0x44DE24C6, 0x88DFF51D},
{0x3BEB65BC, 0x29514038}, {0xC89C5933, 0xB328CBDF},
@@ -73,23 +73,23 @@ var drmCS64Coefficients = [14][2]uint32{
{0x35FA38C6, 0xBB04A2C1}, {0xCB49E3C6, 0xC5626167},
}
// drmSigma0 ist die (verschleiert notierte) SHA-256-σ0-Mischfunktion.
// drmSigma0 is the (obfuscated-notation) SHA-256 σ0 mixing function.
func drmSigma0(v uint32) uint32 {
return v>>7 ^ v<<25 ^ v>>18 ^ v<<14 ^ v>>3
}
// drmSigma1 ist die SHA-256-σ1-Mischfunktion.
// drmSigma1 is the SHA-256 σ1 mixing function.
func drmSigma1(v uint32) uint32 {
return v>>17 ^ v<<15 ^ v>>19 ^ v<<13 ^ v>>10
}
// drmRol16 rotiert 32 Bit um 16.
// drmRol16 rotates 32 bits by 16.
func drmRol16(v uint32) uint32 { return v<<16 | v>>16 }
// drmRotR rotiert 32 Bit rechts um n.
// drmRotR rotates 32 bits right by n.
func drmRotR(v uint32, n uint) uint32 { return v>>n | v<<(32-n) }
// drmExpand erweitert die Message-Schedule ab from auf 64 Wörter.
// drmExpand extends the message schedule from index from to 64 words.
func drmExpand(words []uint32, from int) []uint32 {
for i := from; i < 64; i++ {
x, y := words[i-2], words[i-15]
@@ -98,9 +98,9 @@ func drmExpand(words []uint32, from int) []uint32 {
return words
}
// drmRounds führt die SHA-256-Runden aus — wörtlich nach Referenz,
// inklusive des Mid-Block-Einstiegs (firstRound 14 mit ersetzter
// Rundungskonstante) und der 8er-Rollen-Rotation der Register.
// drmRounds executes the SHA-256 rounds, literally following the
// reference, including the mid-block entry (firstRound 14 with the replaced
// round constant) and the 8-way role rotation of the registers.
func drmRounds(state [8]uint32, words []uint32, firstRound int) [8]uint32 {
a, b, c, d, e, f, g, h := state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]
midBlock := firstRound == 14
@@ -120,35 +120,35 @@ func drmRounds(state [8]uint32, words []uint32, firstRound int) [8]uint32 {
a, b, c, d, e, f, g, h = t1+t2, a, b, c, d+t1, e, f, g
}
for i := aligned; i < 64; i += 8 {
// Runde i
// round i
t1 := h + (drmRotR(e, 6) ^ drmRotR(e, 11) ^ drmRotR(e, 25)) + (g ^ (e & (f ^ g))) + k(i) + words[i]
d += t1
h = t1 + (drmRotR(a, 2) ^ drmRotR(a, 13) ^ drmRotR(a, 22)) + ((a & b) | (c & (a | b)))
// Runde i+1 (Rollen rotiert)
// round i+1 (roles rotated)
t1 = g + (drmRotR(d, 6) ^ drmRotR(d, 11) ^ drmRotR(d, 25)) + (f ^ (d & (e ^ f))) + k(i+1) + words[i+1]
c += t1
g = t1 + (drmRotR(h, 2) ^ drmRotR(h, 13) ^ drmRotR(h, 22)) + ((h & a) | (b & (h | a)))
// Runde i+2
// round i+2
t1 = f + (drmRotR(c, 6) ^ drmRotR(c, 11) ^ drmRotR(c, 25)) + (e ^ (c & (d ^ e))) + k(i+2) + words[i+2]
b += t1
f = t1 + (drmRotR(g, 2) ^ drmRotR(g, 13) ^ drmRotR(g, 22)) + ((g & h) | (a & (g | h)))
// Runde i+3
// round i+3
t1 = e + (drmRotR(b, 6) ^ drmRotR(b, 11) ^ drmRotR(b, 25)) + (d ^ (b & (c ^ d))) + k(i+3) + words[i+3]
a += t1
e = t1 + (drmRotR(f, 2) ^ drmRotR(f, 13) ^ drmRotR(f, 22)) + ((f & g) | (h & (f | g)))
// Runde i+4
// round i+4
t1 = d + (drmRotR(a, 6) ^ drmRotR(a, 11) ^ drmRotR(a, 25)) + (c ^ (a & (b ^ c))) + k(i+4) + words[i+4]
h += t1
d = t1 + (drmRotR(e, 2) ^ drmRotR(e, 13) ^ drmRotR(e, 22)) + ((e & f) | (g & (e | f)))
// Runde i+5
// round i+5
t1 = c + (drmRotR(h, 6) ^ drmRotR(h, 11) ^ drmRotR(h, 25)) + (b ^ (h & (a ^ b))) + k(i+5) + words[i+5]
g += t1
c = t1 + (drmRotR(d, 2) ^ drmRotR(d, 13) ^ drmRotR(d, 22)) + ((d & e) | (f & (d | e)))
// Runde i+6
// round i+6
t1 = b + (drmRotR(g, 6) ^ drmRotR(g, 11) ^ drmRotR(g, 25)) + (a ^ (g & (h ^ a))) + k(i+6) + words[i+6]
f += t1
b = t1 + (drmRotR(c, 2) ^ drmRotR(c, 13) ^ drmRotR(c, 22)) + ((c & d) | (e & (c | d)))
// Runde i+7
// round i+7
t1 = a + (drmRotR(f, 6) ^ drmRotR(f, 11) ^ drmRotR(f, 25)) + (h ^ (f & (g ^ h))) + k(i+7) + words[i+7]
e += t1
a = t1 + (drmRotR(b, 2) ^ drmRotR(b, 13) ^ drmRotR(b, 22)) + ((b & c) | (d & (b | c)))
@@ -156,16 +156,16 @@ func drmRounds(state [8]uint32, words []uint32, firstRound int) [8]uint32 {
return [8]uint32{a, b, c, d, e, f, g, h}
}
// drmSHA256Init berechnet den Zustand nach dem (verschleiert notierten)
// ersten Block: 14 Nullwörter + die ersten 7 Bytes von data als
// 24-Bit-/32-Bit-Big-Endian-Wörter, Schedule mit Korrekturen, Runden ab 14.
// drmSHA256Init computes the state after the (obfuscated-notation) first
// block: 14 zero words + the first 7 bytes of data as 24-bit/32-bit
// big-endian words, schedule with corrections, rounds from 14 onwards.
func drmSHA256Init(data []byte) [8]uint32 {
words := make([]uint32, 30, 64)
words[14] = uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2])
words[15] = binary.BigEndian.Uint32(data[3:7])
for i := 16; i < 30; i++ {
v := drmSchedule[i-16]
// σ1-Anteil von W[i-2] (Index 16 liest W[14], dazu die Korrektur).
// σ1 part of W[i-2] (index 16 reads W[14], plus the correction).
s1 := drmSigma1(words[i-2])
if i == 16 {
s1 ^= drmSigma1Adj
@@ -174,7 +174,7 @@ func drmSHA256Init(data []byte) [8]uint32 {
if i-7 >= 14 {
v += words[i-7]
}
if i-15 >= 14 { // nur i == 29, liest W[14], dazu die Korrektur
if i-15 >= 14 { // only i == 29, reads W[14], plus the correction
s0 := drmSigma0(words[i-15])
if i == 29 {
s0 ^= drmSigma0Adj
@@ -193,9 +193,9 @@ func drmSHA256Init(data []byte) [8]uint32 {
return out
}
// drmSHA256Digest ist die verschleierte SHA-256-Variante der Referenz:
// Block 1 steckt in drmSHA256Init (erste 7 Bytes), danach data[7:] mit
// Referenz-Längenfeld (57+len(data)) und Standard-Runden.
// drmSHA256Digest is the reference's obfuscated SHA-256 variant: block 1
// lives in drmSHA256Init (first 7 bytes), followed by data[7:] with the
// reference length field (57+len(data)) and standard rounds.
func drmSHA256Digest(data []byte) [8]uint32 {
length := 57 + len(data)
padded := append([]byte(nil), data[7:]...)
@@ -224,8 +224,8 @@ func drmSHA256Digest(data []byte) [8]uint32 {
return state
}
// drmWordsLE4Even zerlegt data in little-endian u32-Wörter (auf 4 Bytes
// null-aufgefüllt, Wortzahl gerade).
// drmWordsLE4Even splits data into little-endian u32 words (zero-padded to
// 4 bytes, even word count).
func drmWordsLE4Even(data []byte) []uint32 {
padded := make([]byte, len(data))
copy(padded, data)
@@ -242,8 +242,8 @@ func drmWordsLE4Even(data []byte) []uint32 {
return words
}
// drmCS64 berechnet die beiden CS64-Prüfsummen-Wörter (wörtliche
// Portierung; Schlüssel aus den ersten 8 Digest-Bytes little-endian).
// drmCS64 computes the two CS64 checksum words (literal port; keys from
// the first 8 digest bytes, little-endian).
func drmCS64(data []byte, digest [8]uint32) (a, b uint32) {
var digestBytes [32]byte
for i, w := range digest {