Add credential extraction registry for generate
Protocols like Xiaomi need credentials (tokens) in a separate top-level YAML section, not in the stream URL itself. Introduce a registry pattern mirroring streams.HandleFunc / tester.RegisterSource: - pkg/generate/registry.go: ExtractFunc + RegisterExtract - Extractors clean the URL (strip ?token=...) and return section/key/value - writeCredentials emits sorted sections between go2rtc: and cameras: - upsertCredentials in addToConfig merges into existing sections: * replaces value if key exists (token refresh) * inserts in sorted order if new * creates new top-level section before cameras: if missing Xiaomi registers its extractor from internal/xiaomi/xiaomi.go. Adding Tapo/Ring/Roborock later is one line + a small function in their internal/*/ module -- zero changes in pkg/generate/.
This commit is contained in:
@@ -3,6 +3,7 @@ package generate
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -64,6 +65,9 @@ func addToConfig(existing string, info *cameraInfo, req *Request) (*Response, er
|
||||
}
|
||||
result = append(result, rest[split:]...)
|
||||
|
||||
// upsert credential sections (xiaomi, tapo, ...) before cameras:
|
||||
result, added = upsertCredentials(result, info.Credentials, added)
|
||||
|
||||
config := strings.Join(result, "\n")
|
||||
|
||||
addedLines := make([]int, 0, len(added))
|
||||
@@ -156,6 +160,156 @@ func findStreamInsertPoint(lines []string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// upsertCredentials merges creds into existing top-level sections. For each
|
||||
// section: if a matching line ` "<key>":` exists -- replace its value; else
|
||||
// insert in sorted order. If the section itself doesn't exist -- create a new
|
||||
// top-level block just before `cameras:`.
|
||||
func upsertCredentials(lines []string, creds map[string]map[string]string, added map[int]bool) ([]string, map[int]bool) {
|
||||
if len(creds) == 0 {
|
||||
return lines, added
|
||||
}
|
||||
|
||||
sections := make([]string, 0, len(creds))
|
||||
for s := range creds {
|
||||
sections = append(sections, s)
|
||||
}
|
||||
sort.Strings(sections)
|
||||
|
||||
for _, section := range sections {
|
||||
lines, added = upsertSection(lines, section, creds[section], added)
|
||||
}
|
||||
|
||||
return lines, added
|
||||
}
|
||||
|
||||
// upsertSection updates or appends a single top-level section.
|
||||
var reCredKey = regexp.MustCompile(`^\s{2}"([^"]+)":`)
|
||||
|
||||
func upsertSection(lines []string, section string, kv map[string]string, added map[int]bool) ([]string, map[int]bool) {
|
||||
reHeader := regexp.MustCompile(`^` + regexp.QuoteMeta(section) + `:\s*$`)
|
||||
|
||||
headerIdx := -1
|
||||
for i, line := range lines {
|
||||
if reHeader.MatchString(line) {
|
||||
headerIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if headerIdx == -1 {
|
||||
return insertNewSection(lines, section, kv, added)
|
||||
}
|
||||
|
||||
// section exists -- find last content line of the section (skip trailing blanks)
|
||||
end := len(lines)
|
||||
for i := headerIdx + 1; i < len(lines); i++ {
|
||||
if strings.TrimSpace(lines[i]) == "" || reTopLevel.MatchString(lines[i]) {
|
||||
end = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(kv))
|
||||
for k := range kv {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
newLine := fmt.Sprintf(" %q: %s", k, kv[k])
|
||||
|
||||
// try replace -- no length change, just mark modified line as added
|
||||
replaced := false
|
||||
for i := headerIdx + 1; i < end; i++ {
|
||||
if m := reCredKey.FindStringSubmatch(lines[i]); m != nil && m[1] == k {
|
||||
if lines[i] != newLine {
|
||||
lines[i] = newLine
|
||||
added[i] = true
|
||||
}
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if replaced {
|
||||
continue
|
||||
}
|
||||
|
||||
// insert in sorted order within section
|
||||
insertAt := headerIdx + 1
|
||||
for i := headerIdx + 1; i < end; i++ {
|
||||
if m := reCredKey.FindStringSubmatch(lines[i]); m != nil {
|
||||
if m[1] < k {
|
||||
insertAt = i + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
insertAt = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
lines = append(lines[:insertAt], append([]string{newLine}, lines[insertAt:]...)...)
|
||||
added = shiftAdded(added, insertAt)
|
||||
added[insertAt] = true
|
||||
end++
|
||||
}
|
||||
|
||||
return lines, added
|
||||
}
|
||||
|
||||
func insertNewSection(lines []string, section string, kv map[string]string, added map[int]bool) ([]string, map[int]bool) {
|
||||
camIdx := -1
|
||||
for i, line := range lines {
|
||||
if reCamerasHeader.MatchString(line) {
|
||||
camIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if camIdx == -1 {
|
||||
camIdx = len(lines)
|
||||
}
|
||||
|
||||
// insert point: right before the blank line that precedes cameras:
|
||||
// keep one blank line between blocks
|
||||
insertAt := camIdx
|
||||
for insertAt > 0 && strings.TrimSpace(lines[insertAt-1]) == "" {
|
||||
insertAt--
|
||||
}
|
||||
|
||||
block := []string{section + ":"}
|
||||
keys := make([]string, 0, len(kv))
|
||||
for k := range kv {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
block = append(block, fmt.Sprintf(" %q: %s", k, kv[k]))
|
||||
}
|
||||
block = append(block, "")
|
||||
|
||||
lines = append(lines[:insertAt], append(block, lines[insertAt:]...)...)
|
||||
added = shiftAdded(added, insertAt)
|
||||
for i := range block {
|
||||
added[insertAt+i] = true
|
||||
}
|
||||
|
||||
return lines, added
|
||||
}
|
||||
|
||||
// shiftAdded moves all marks at index >= from by +1. Also used with from=len(lines)
|
||||
// as a no-op shift (just return same map).
|
||||
func shiftAdded(added map[int]bool, from int) map[int]bool {
|
||||
out := make(map[int]bool, len(added))
|
||||
for i := range added {
|
||||
if i >= from {
|
||||
out[i+1] = true
|
||||
} else {
|
||||
out[i] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func findCameraInsertPoint(lines []string) int {
|
||||
in := false
|
||||
last := -1
|
||||
|
||||
Reference in New Issue
Block a user