795 lines
18 KiB
Go
795 lines
18 KiB
Go
// backend\pending_autostart.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type PendingAutoStartItem struct {
|
|
ModelKey string `json:"modelKey"`
|
|
URL string `json:"url"`
|
|
Mode string `json:"mode,omitempty"`
|
|
NextProbeAtMs int64 `json:"nextProbeAtMs,omitempty"`
|
|
CurrentShow string `json:"currentShow,omitempty"`
|
|
ImageURL string `json:"imageUrl,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
|
|
OwnerUserKey string `json:"-"`
|
|
}
|
|
|
|
type PendingAutoStartResponse struct {
|
|
Items []PendingAutoStartItem `json:"items"`
|
|
}
|
|
|
|
type pendingAutoStartFile struct {
|
|
Items []PendingAutoStartItem `json:"items"`
|
|
}
|
|
|
|
var pendingAutoStartMu sync.Mutex
|
|
|
|
const pendingAutoStartGlobalUserKey = "__global__"
|
|
|
|
func normalizePendingSourceServer(v string) string {
|
|
switch strings.TrimSpace(strings.ToLower(v)) {
|
|
case "watched":
|
|
return "watched"
|
|
case "resume":
|
|
return "resume"
|
|
default:
|
|
return "manual"
|
|
}
|
|
}
|
|
|
|
func loadResumePendingAutoStartItemsForProvider(provider string) ([]PendingAutoStartItem, error) {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" {
|
|
return nil, errors.New("missing provider")
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(pendingAutoStartGlobalUserKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make([]PendingAutoStartItem, 0, len(items))
|
|
for _, it := range items {
|
|
if normalizePendingSourceServer(it.Source) != "resume" {
|
|
continue
|
|
}
|
|
if pendingProviderFromURL(it.URL) != provider {
|
|
continue
|
|
}
|
|
|
|
out = append(out, it)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func removeResumePendingAutoStartItemForProvider(provider, modelKey string) error {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
modelKey = strings.ToLower(strings.TrimSpace(modelKey))
|
|
|
|
if provider == "" || modelKey == "" {
|
|
return nil
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(pendingAutoStartGlobalUserKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
next := make([]PendingAutoStartItem, 0, len(items))
|
|
for _, it := range items {
|
|
if normalizePendingSourceServer(it.Source) == "resume" &&
|
|
pendingProviderFromURL(it.URL) == provider &&
|
|
strings.ToLower(strings.TrimSpace(it.ModelKey)) == modelKey {
|
|
continue
|
|
}
|
|
|
|
next = append(next, it)
|
|
}
|
|
|
|
return savePendingAutoStartItems(pendingAutoStartGlobalUserKey, next)
|
|
}
|
|
|
|
func saveResumePendingAutoStartItemForProvider(provider string, item PendingAutoStartItem) error {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" {
|
|
return errors.New("missing provider")
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(pendingAutoStartGlobalUserKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
item.ModelKey = strings.ToLower(strings.TrimSpace(item.ModelKey))
|
|
item.URL = strings.TrimSpace(item.URL)
|
|
item.Mode = normalizePendingModeServer(item.Mode)
|
|
item.CurrentShow = normalizePendingShowServer(item.CurrentShow)
|
|
item.ImageURL = strings.TrimSpace(item.ImageURL)
|
|
item.Source = "resume"
|
|
|
|
if item.ModelKey == "" || item.URL == "" {
|
|
return nil
|
|
}
|
|
|
|
replaced := false
|
|
for i := range items {
|
|
if normalizePendingSourceServer(items[i].Source) == "resume" &&
|
|
pendingProviderFromURL(items[i].URL) == provider &&
|
|
strings.ToLower(strings.TrimSpace(items[i].ModelKey)) == item.ModelKey {
|
|
items[i] = item
|
|
replaced = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !replaced {
|
|
items = append(items, item)
|
|
}
|
|
|
|
return savePendingAutoStartItems(pendingAutoStartGlobalUserKey, items)
|
|
}
|
|
|
|
func normalizePendingModeServer(v string) string {
|
|
if strings.TrimSpace(strings.ToLower(v)) == "probe_retry" {
|
|
return "probe_retry"
|
|
}
|
|
return "wait_public"
|
|
}
|
|
|
|
func normalizePendingShowServer(v string) string {
|
|
switch strings.TrimSpace(strings.ToLower(v)) {
|
|
case "public":
|
|
return "public"
|
|
case "private":
|
|
return "private"
|
|
case "hidden":
|
|
return "hidden"
|
|
case "away":
|
|
return "away"
|
|
case "offline":
|
|
return "offline"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func sanitizePendingAutoStartItems(items []PendingAutoStartItem) []PendingAutoStartItem {
|
|
out := make([]PendingAutoStartItem, 0, len(items))
|
|
seen := make(map[string]struct{}, len(items))
|
|
|
|
for _, it := range items {
|
|
it.ModelKey = strings.ToLower(strings.TrimSpace(it.ModelKey))
|
|
it.URL = strings.TrimSpace(it.URL)
|
|
it.Mode = normalizePendingModeServer(it.Mode)
|
|
it.CurrentShow = normalizePendingShowServer(it.CurrentShow)
|
|
it.ImageURL = strings.TrimSpace(it.ImageURL)
|
|
it.Source = normalizePendingSourceServer(it.Source)
|
|
|
|
if it.ModelKey == "" || it.URL == "" {
|
|
continue
|
|
}
|
|
|
|
// Wichtig:
|
|
// Offline-Models gehören nicht in Pending Autostart.
|
|
if it.CurrentShow == "offline" {
|
|
continue
|
|
}
|
|
|
|
dedupeKey := it.Source + "|" + pendingProviderFromURL(it.URL) + "|" + it.ModelKey
|
|
if _, exists := seen[dedupeKey]; exists {
|
|
continue
|
|
}
|
|
seen[dedupeKey] = struct{}{}
|
|
|
|
out = append(out, it)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func safeUserKeyForFile(v string) string {
|
|
re := regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
|
return re.ReplaceAllString(v, "_")
|
|
}
|
|
|
|
func pendingProviderFromURL(raw string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
host := strings.ToLower(strings.TrimSpace(u.Hostname()))
|
|
host = strings.TrimPrefix(host, "www.")
|
|
|
|
switch {
|
|
case host == "chaturbate.com" || strings.HasSuffix(host, ".chaturbate.com"):
|
|
return "chaturbate"
|
|
case host == "myfreecams.com" || strings.HasSuffix(host, ".myfreecams.com"):
|
|
return "mfc"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func pendingAutoStartFilePath(userKey string) string {
|
|
return filepath.Join("data", "pending-autostart", safeUserKeyForFile(userKey)+".json")
|
|
}
|
|
|
|
func clearAllPendingAutoStartOnStartup() error {
|
|
pendingAutoStartMu.Lock()
|
|
defer pendingAutoStartMu.Unlock()
|
|
|
|
dir := filepath.Join("data", "pending-autostart")
|
|
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func listPendingAutoStartUserKeys() ([]string, error) {
|
|
dir := filepath.Join("data", "pending-autostart")
|
|
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return []string{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
out := make([]string, 0, len(entries))
|
|
globalFile := safeUserKeyForFile(pendingAutoStartGlobalUserKey) + ".json"
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
name := strings.TrimSpace(entry.Name())
|
|
if name == "" {
|
|
continue
|
|
}
|
|
if !strings.HasSuffix(strings.ToLower(name), ".json") {
|
|
continue
|
|
}
|
|
if name == globalFile {
|
|
continue
|
|
}
|
|
|
|
userKey := strings.TrimSuffix(name, filepath.Ext(name))
|
|
if userKey == "" {
|
|
continue
|
|
}
|
|
|
|
out = append(out, userKey)
|
|
}
|
|
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|
|
|
|
func loadManualPendingAutoStartItemsForProvider(provider string) ([]PendingAutoStartItem, error) {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" {
|
|
return nil, errors.New("missing provider")
|
|
}
|
|
|
|
userKeys, err := listPendingAutoStartUserKeys()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make([]PendingAutoStartItem, 0, 32)
|
|
|
|
for _, userKey := range userKeys {
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, it := range items {
|
|
if normalizePendingSourceServer(it.Source) != "manual" {
|
|
continue
|
|
}
|
|
if pendingProviderFromURL(it.URL) != provider {
|
|
continue
|
|
}
|
|
|
|
it.OwnerUserKey = userKey
|
|
|
|
out = append(out, it)
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func saveManualPendingAutoStartItemForUser(userKey string, item PendingAutoStartItem) error {
|
|
userKey = strings.ToLower(strings.TrimSpace(userKey))
|
|
if userKey == "" {
|
|
return errors.New("missing userKey")
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
item.ModelKey = strings.ToLower(strings.TrimSpace(item.ModelKey))
|
|
item.URL = strings.TrimSpace(item.URL)
|
|
item.Mode = normalizePendingModeServer(item.Mode)
|
|
item.CurrentShow = normalizePendingShowServer(item.CurrentShow)
|
|
item.ImageURL = strings.TrimSpace(item.ImageURL)
|
|
item.Source = "manual"
|
|
|
|
if item.ModelKey == "" || item.URL == "" {
|
|
return nil
|
|
}
|
|
|
|
replaced := false
|
|
for i := range items {
|
|
if strings.ToLower(strings.TrimSpace(items[i].ModelKey)) == item.ModelKey {
|
|
items[i] = item
|
|
replaced = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !replaced {
|
|
items = append(items, item)
|
|
}
|
|
|
|
return savePendingAutoStartItems(userKey, items)
|
|
}
|
|
|
|
func removeManualPendingAutoStartItemForProvider(provider, modelKey string) error {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
modelKey = strings.ToLower(strings.TrimSpace(modelKey))
|
|
|
|
if provider == "" || modelKey == "" {
|
|
return nil
|
|
}
|
|
|
|
userKeys, err := listPendingAutoStartUserKeys()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, userKey := range userKeys {
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
changed := false
|
|
next := make([]PendingAutoStartItem, 0, len(items))
|
|
|
|
for _, it := range items {
|
|
if normalizePendingSourceServer(it.Source) == "manual" &&
|
|
pendingProviderFromURL(it.URL) == provider &&
|
|
strings.ToLower(strings.TrimSpace(it.ModelKey)) == modelKey {
|
|
changed = true
|
|
continue
|
|
}
|
|
|
|
next = append(next, it)
|
|
}
|
|
|
|
if changed {
|
|
if err := savePendingAutoStartItems(userKey, next); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func removeAllPendingAutoStartItemsForProvider(provider string) (int, error) {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" {
|
|
return 0, nil
|
|
}
|
|
|
|
userKeys, err := listPendingAutoStartUserKeys()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
keys := append([]string{pendingAutoStartGlobalUserKey}, userKeys...)
|
|
removed := 0
|
|
|
|
for _, userKey := range keys {
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
return removed, err
|
|
}
|
|
|
|
changed := false
|
|
next := make([]PendingAutoStartItem, 0, len(items))
|
|
|
|
for _, it := range items {
|
|
if pendingProviderFromURL(it.URL) == provider {
|
|
removed++
|
|
changed = true
|
|
continue
|
|
}
|
|
|
|
next = append(next, it)
|
|
}
|
|
|
|
if changed {
|
|
if err := savePendingAutoStartItems(userKey, next); err != nil {
|
|
return removed, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return removed, nil
|
|
}
|
|
|
|
func loadPendingAutoStartItems(userKey string) ([]PendingAutoStartItem, error) {
|
|
path := pendingAutoStartFilePath(userKey)
|
|
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return []PendingAutoStartItem{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var f pendingAutoStartFile
|
|
if err := json.Unmarshal(raw, &f); err != nil {
|
|
// kaputte Datei => lieber leer zurückgeben statt alles zu crashen
|
|
return []PendingAutoStartItem{}, nil
|
|
}
|
|
|
|
if len(f.Items) == 0 {
|
|
return []PendingAutoStartItem{}, nil
|
|
}
|
|
|
|
out := make([]PendingAutoStartItem, 0, len(f.Items))
|
|
seen := make(map[string]struct{}, len(f.Items))
|
|
|
|
for _, it := range f.Items {
|
|
key := strings.ToLower(strings.TrimSpace(it.ModelKey))
|
|
u := strings.TrimSpace(it.URL)
|
|
if key == "" || u == "" {
|
|
continue
|
|
}
|
|
|
|
show := normalizePendingShowServer(it.CurrentShow)
|
|
|
|
// Offline-Models gehören nicht in Pending Autostart.
|
|
// Alte persistierte Offline-Einträge werden dadurch beim Lesen ignoriert.
|
|
if show == "offline" {
|
|
continue
|
|
}
|
|
|
|
source := normalizePendingSourceServer(it.Source)
|
|
mode := normalizePendingModeServer(it.Mode)
|
|
imageURL := strings.TrimSpace(it.ImageURL)
|
|
provider := pendingProviderFromURL(u)
|
|
|
|
dedupeKey := source + "|" + provider + "|" + key
|
|
if _, exists := seen[dedupeKey]; exists {
|
|
continue
|
|
}
|
|
seen[dedupeKey] = struct{}{}
|
|
|
|
out = append(out, PendingAutoStartItem{
|
|
ModelKey: key,
|
|
URL: u,
|
|
Mode: mode,
|
|
NextProbeAtMs: it.NextProbeAtMs,
|
|
CurrentShow: show,
|
|
ImageURL: imageURL,
|
|
Source: source,
|
|
})
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func loadMergedPendingAutoStartItems(userKey string) ([]PendingAutoStartItem, error) {
|
|
globalItems, err := loadPendingAutoStartItems(pendingAutoStartGlobalUserKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
userItems, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make([]PendingAutoStartItem, 0, len(globalItems)+len(userItems))
|
|
indexByKey := make(map[string]int)
|
|
|
|
for _, it := range globalItems {
|
|
k := strings.ToLower(strings.TrimSpace(it.ModelKey))
|
|
if k == "" {
|
|
continue
|
|
}
|
|
|
|
indexByKey[k] = len(out)
|
|
out = append(out, it)
|
|
}
|
|
|
|
// user-spezifisch überschreibt an gleicher Position
|
|
for _, it := range userItems {
|
|
k := strings.ToLower(strings.TrimSpace(it.ModelKey))
|
|
if k == "" {
|
|
continue
|
|
}
|
|
|
|
if idx, ok := indexByKey[k]; ok {
|
|
out[idx] = it
|
|
continue
|
|
}
|
|
|
|
indexByKey[k] = len(out)
|
|
out = append(out, it)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func saveWatchedPendingAutoStartItemsForProvider(provider string, items []PendingAutoStartItem) error {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" {
|
|
return errors.New("missing provider")
|
|
}
|
|
|
|
existing, err := loadPendingAutoStartItems(pendingAutoStartGlobalUserKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
next := make([]PendingAutoStartItem, 0, len(existing)+len(items))
|
|
|
|
// alte watched-Einträge dieses Providers entfernen, andere behalten
|
|
for _, it := range existing {
|
|
if normalizePendingSourceServer(it.Source) == "watched" && pendingProviderFromURL(it.URL) == provider {
|
|
continue
|
|
}
|
|
next = append(next, it)
|
|
}
|
|
|
|
// neue watched-Einträge dieses Providers anhängen
|
|
for _, it := range items {
|
|
it.ModelKey = strings.ToLower(strings.TrimSpace(it.ModelKey))
|
|
it.URL = strings.TrimSpace(it.URL)
|
|
it.Mode = normalizePendingModeServer(it.Mode)
|
|
it.CurrentShow = normalizePendingShowServer(it.CurrentShow)
|
|
it.ImageURL = strings.TrimSpace(it.ImageURL)
|
|
it.Source = "watched"
|
|
|
|
if it.ModelKey == "" || it.URL == "" {
|
|
continue
|
|
}
|
|
|
|
next = append(next, it)
|
|
}
|
|
|
|
return savePendingAutoStartItems(pendingAutoStartGlobalUserKey, next)
|
|
}
|
|
|
|
func hasHiddenProbeRunningForProvider(provider string) bool {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" {
|
|
return false
|
|
}
|
|
|
|
jobsMu.RLock()
|
|
defer jobsMu.RUnlock()
|
|
|
|
for _, j := range jobs {
|
|
if j == nil {
|
|
continue
|
|
}
|
|
if j.Status != JobRunning {
|
|
continue
|
|
}
|
|
if !j.Hidden {
|
|
continue
|
|
}
|
|
if pendingProviderFromURL(strings.TrimSpace(j.SourceURL)) != provider {
|
|
continue
|
|
}
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func removeWatchedPendingAutoStartItemForProvider(provider, modelKey string) error {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
modelKey = strings.ToLower(strings.TrimSpace(modelKey))
|
|
|
|
if provider == "" || modelKey == "" {
|
|
return nil
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(pendingAutoStartGlobalUserKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
next := make([]PendingAutoStartItem, 0, len(items))
|
|
for _, it := range items {
|
|
if normalizePendingSourceServer(it.Source) == "watched" &&
|
|
strings.ToLower(strings.TrimSpace(it.ModelKey)) == modelKey &&
|
|
pendingProviderFromURL(it.URL) == provider {
|
|
continue
|
|
}
|
|
next = append(next, it)
|
|
}
|
|
|
|
return savePendingAutoStartItems(pendingAutoStartGlobalUserKey, next)
|
|
}
|
|
|
|
func savePendingAutoStartItems(userKey string, items []PendingAutoStartItem) error {
|
|
path := pendingAutoStartFilePath(userKey)
|
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
payload := pendingAutoStartFile{
|
|
Items: sanitizePendingAutoStartItems(items),
|
|
}
|
|
|
|
raw, err := json.MarshalIndent(payload, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, raw, 0o644); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func handlePendingAutoStart(auth *AuthManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
sess, _ := auth.getSession(r)
|
|
if sess == nil || !sess.Authed || strings.TrimSpace(sess.User) == "" {
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
userKey := strings.ToLower(strings.TrimSpace(sess.User))
|
|
|
|
pendingAutoStartMu.Lock()
|
|
defer pendingAutoStartMu.Unlock()
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := loadMergedPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(PendingAutoStartResponse{
|
|
Items: items,
|
|
})
|
|
return
|
|
|
|
case http.MethodPost:
|
|
var in PendingAutoStartItem
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
http.Error(w, "invalid json body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
in.ModelKey = strings.ToLower(strings.TrimSpace(in.ModelKey))
|
|
in.URL = strings.TrimSpace(in.URL)
|
|
in.Mode = normalizePendingModeServer(in.Mode)
|
|
in.CurrentShow = normalizePendingShowServer(in.CurrentShow)
|
|
in.ImageURL = strings.TrimSpace(in.ImageURL)
|
|
in.Source = normalizePendingSourceServer(in.Source)
|
|
|
|
if in.ModelKey == "" {
|
|
http.Error(w, "missing modelKey", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if in.URL == "" {
|
|
http.Error(w, "missing url", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
u, err := url.Parse(in.URL)
|
|
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
|
http.Error(w, "invalid url", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
replaced := false
|
|
for i := range items {
|
|
if strings.ToLower(strings.TrimSpace(items[i].ModelKey)) == in.ModelKey {
|
|
items[i] = in
|
|
replaced = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !replaced {
|
|
items = append(items, in)
|
|
}
|
|
|
|
if err := savePendingAutoStartItems(userKey, items); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
|
|
case http.MethodDelete:
|
|
modelKey := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("modelKey")))
|
|
if modelKey == "" {
|
|
http.Error(w, "missing modelKey", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
next := make([]PendingAutoStartItem, 0, len(items))
|
|
for _, item := range items {
|
|
if strings.ToLower(strings.TrimSpace(item.ModelKey)) == modelKey {
|
|
continue
|
|
}
|
|
next = append(next, item)
|
|
}
|
|
|
|
if err := savePendingAutoStartItems(userKey, next); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
|
|
default:
|
|
w.Header().Set("Allow", "GET, POST, DELETE")
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
}
|
|
}
|