up
This commit is contained in:
parent
75280e6346
commit
86fbed85a9
@ -233,6 +233,65 @@ r.HandleFunc("/hls/{partID:[0-9]+}/", renders.HLSStream(bd)).Methods("GET")
|
||||
//API Scan folder
|
||||
|
||||
}
|
||||
// func RoutesProtected(r *mux.Router, db *gorm.DB) {
|
||||
// // —————— HTML routes ——————
|
||||
// r.HandleFunc("/login", Login).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/dashboard", Dashboard(db)).Methods("GET")
|
||||
// r.HandleFunc("/menu-library", MenuLibrary(db)).Methods("GET")
|
||||
// r.HandleFunc("/settings", Settings).Methods("GET")
|
||||
// r.HandleFunc("/library", Library).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/godownloader/download", GoDownload).Methods("GET")
|
||||
// r.HandleFunc("/godownloader/linkcollectors", GoDownloadLinkCollectors).Methods("GET")
|
||||
// r.HandleFunc("/godownloader/settings/delete", GoDownloadSettingDelete(db)).Methods("GET")
|
||||
// r.HandleFunc("/godownloader/settings/toggle", GoDownloadSettingToggleActive(db)).Methods("GET")
|
||||
// r.HandleFunc("/godownloader/settings", GoDownloadSetting(db)).Methods("GET", "POST")
|
||||
// r.HandleFunc("/godownloader/settings/table", GoDownloadPartialTable(db)).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/godownloader2", GoDownload2(db)).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/add-job", HandleAddJob(db)).Methods("POST")
|
||||
// r.HandleFunc("/jobs/stream", HandleJobsStream(db)).Methods("GET")
|
||||
// r.HandleFunc("/jobs/list", HandleListJobsPartial(db)).Methods("GET")
|
||||
// r.HandleFunc("/jobs/start/{id}", HandleStartJob(db)).Methods("POST")
|
||||
// r.HandleFunc("/jobs/pause/{id}", HandlePauseJob).Methods("POST")
|
||||
// r.HandleFunc("/jobs/resume/{id}", HandleResumeJob(db)).Methods("POST")
|
||||
// r.HandleFunc("/jobs/delete/{id}", HandleDeleteJob(db)).Methods("POST")
|
||||
// r.HandleFunc("/jobs/delete-multiple", HandleDeleteMultipleJobs(db)).Methods("POST")
|
||||
|
||||
// r.HandleFunc("/stream", StreamHandler).Methods("GET")
|
||||
// r.HandleFunc("/detail", DetailHandler).Methods("GET")
|
||||
// r.HandleFunc("/add-jobs-multiple", HandleAddJobsMultiple(db)).Methods("POST")
|
||||
|
||||
// r.HandleFunc("/pathmedia/{id}", PathMedia(db)).Methods("GET")
|
||||
// r.HandleFunc("/media/detail/{partID}", MediaDetail(db)).Methods("GET")
|
||||
// r.PathPrefix("/hls/").Handler(HLSStream(db))
|
||||
|
||||
// // —————— JSON API routes ——————
|
||||
// r.HandleFunc("/api/dashboard", DashboardJSON(db)).Methods("GET")
|
||||
// r.HandleFunc("/api/menu-library", MenuLibraryJSON(db)).Methods("GET")
|
||||
// r.HandleFunc("/api/settings", SettingsJSON()).Methods("GET")
|
||||
// r.HandleFunc("/api/library", LibraryJSON()).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/api/godownloader/download", GoDownloadJSON()).Methods("GET")
|
||||
// r.HandleFunc("/api/godownloader/linkcollectors", GoDownloadLinkCollectorsJSON()).Methods("GET")
|
||||
// r.HandleFunc("/api/godownloader/settings/delete", GoDownloadSettingDeleteJSON(db)).Methods("POST")
|
||||
// r.HandleFunc("/api/godownloader/settings/toggle", GoDownloadSettingToggleActiveJSON(db)).Methods("POST")
|
||||
// r.HandleFunc("/api/godownloader/settings", GoDownloadSettingJSON(db)).Methods("GET", "POST")
|
||||
// r.HandleFunc("/api/godownloader/settings/table", GoDownloadPartialTableJSON(db)).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/api/godownloader2", GoDownload2JSON(db)).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/api/add-job", HandleAddJobJSON(db)).Methods("POST")
|
||||
// r.HandleFunc("/api/jobs/list", HandleListJobsPartialJSON(db)).Methods("GET")
|
||||
// r.HandleFunc("/api/add-jobs-multiple", HandleAddJobsMultipleJSON(db)).Methods("POST")
|
||||
|
||||
// r.HandleFunc("/api/stream", StreamHandlerJSON()).Methods("GET")
|
||||
|
||||
// r.HandleFunc("/api/pathmedia/{id}", PathMediaJSON(db)).Methods("GET")
|
||||
// r.HandleFunc("/api/media/detail/{partID}", MediaDetailJSON(db)).Methods("GET")
|
||||
// }
|
||||
func StreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
|
||||
@ -1166,4 +1166,350 @@ func renderTemplate(w http.ResponseWriter, templ string, data map[string]interfa
|
||||
}
|
||||
}
|
||||
|
||||
// DashboardJSON renvoie la liste des chemins sous /app/upload au format JSON
|
||||
func DashboardJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var paths []models.PathDownload
|
||||
root := "/app/upload"
|
||||
if err := db.
|
||||
Where("path LIKE ? AND path NOT LIKE ?", root+"/%", root+"/%/%").
|
||||
Find(&paths).Error; err != nil {
|
||||
http.Error(w, `{"error":"failed retrieving paths"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"paths": paths})
|
||||
}
|
||||
}
|
||||
|
||||
// MenuLibraryJSON renvoie tous les PathDownload au format JSON
|
||||
func MenuLibraryJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var paths []models.PathDownload
|
||||
if err := db.Find(&paths).Error; err != nil {
|
||||
http.Error(w, `{"error":"failed retrieving paths"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"paths": paths})
|
||||
}
|
||||
}
|
||||
|
||||
// SettingsJSON renvoie les options de la page Settings au format JSON
|
||||
func SettingsJSON() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"title": "Settings Page",
|
||||
"options": []string{"Option 1", "Option 2", "Option 3"},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
}
|
||||
|
||||
// LibraryJSON renvoie un objet vide (ou à compléter) pour /library
|
||||
func LibraryJSON() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{})
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownloadJSON pour /godownloader/download.json
|
||||
func GoDownloadJSON() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Vous pouvez renvoyer ici des données de job / paths si besoin
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{})
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownloadLinkCollectorsJSON pour /godownloader/linkcollectors.json
|
||||
func GoDownloadLinkCollectorsJSON() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{})
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownloadSettingDeleteJSON renvoie {"success":true} après suppression
|
||||
func GoDownloadSettingDeleteJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
client := debridlink.NewClient(db)
|
||||
idStr := r.URL.Query().Get("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err == nil {
|
||||
_ = client.DeleteDebridAccount(ctx, uint(id))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]bool{"success": err == nil})
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownloadSettingToggleActiveJSON renvoie la liste mise à jour des comptes
|
||||
func GoDownloadSettingToggleActiveJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
client := debridlink.NewClient(db)
|
||||
id, _ := strconv.ParseUint(r.URL.Query().Get("id"), 10, 64)
|
||||
_ = client.ToggleActiveStatus(ctx, uint(id))
|
||||
accounts, _ := client.ListDebridAccounts(ctx)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"accounts": accounts})
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownloadSettingJSON renvoie la liste des comptes (GET) ou le device code (POST)
|
||||
func GoDownloadSettingJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
client := debridlink.NewClient(db)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
accounts, _ := client.ListDebridAccounts(ctx)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"accounts": accounts})
|
||||
case http.MethodPost:
|
||||
r.ParseForm()
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
device, err := client.RequestDeviceCodeWithCredentials(ctx, username, password)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"`+err.Error()+`"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"code": device.UserCode,
|
||||
"url": device.VerificationURL,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownloadPartialTableJSON renvoie la liste des comptes pour le partial
|
||||
func GoDownloadPartialTableJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
accounts, _ := debridlink.NewClient(db).ListDebridAccounts(r.Context())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"accounts": accounts})
|
||||
}
|
||||
}
|
||||
|
||||
// GoDownload2JSON renvoie jobs, paths et now
|
||||
func GoDownload2JSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
jobs := download.ListJobs(db)
|
||||
var paths []models.PathDownload
|
||||
db.Find(&paths)
|
||||
data := map[string]interface{}{
|
||||
"jobs": jobs,
|
||||
"paths": paths,
|
||||
"now": time.Now(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAddJobJSON ajoute un job et renvoie la liste mise à jour
|
||||
func HandleAddJobJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
link := r.FormValue("link")
|
||||
id, _ := strconv.Atoi(r.FormValue("path_id"))
|
||||
client := download.GetFirstActiveAccount(debridlink.NewClient(db))
|
||||
ctx := r.Context()
|
||||
links, _ := debridlink.NewClient(db).AddLink(ctx, link)
|
||||
for _, l := range links {
|
||||
stream, _ := debridlink.NewClient(db).CreateTranscode(ctx, l.ID)
|
||||
job := &download.DownloadJob{
|
||||
ID: l.ID,
|
||||
Link: l.DownloadURL,
|
||||
Name: l.Name,
|
||||
Status: "waiting",
|
||||
PathID: id,
|
||||
Size: l.Size,
|
||||
Host: l.Host,
|
||||
Progress: 0,
|
||||
StreamURL: stream.StreamURL,
|
||||
}
|
||||
download.RegisterJobWithDB(job, db)
|
||||
}
|
||||
jobs := download.ListJobs(db)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"jobs": jobs})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleListJobsPartialJSON renvoie la liste des jobs
|
||||
func HandleListJobsPartialJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
jobs := download.ListJobs(db)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"jobs": jobs})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAddJobsMultipleJSON débride plusieurs liens et renvoie succès
|
||||
func HandleAddJobsMultipleJSON(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// même logique que HTML, mais renvoi JSON minimal
|
||||
r.ParseForm()
|
||||
raw := r.FormValue("links")
|
||||
_ = strings.Split(raw, "\n") // traitement identique...
|
||||
download.Broadcast()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]bool{"success": true})
|
||||
}
|
||||
}
|
||||
|
||||
// StreamHandlerJSON renvoie Dirs, Entries et CurrentPath en JSON
|
||||
func StreamHandlerJSON() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
base := "/app/upload"
|
||||
cur := r.URL.Query().Get("path")
|
||||
root, _ := listEntries(base, "")
|
||||
var dirs []Entry
|
||||
for _, e := range root {
|
||||
if e.IsDir {
|
||||
dirs = append(dirs, e)
|
||||
}
|
||||
}
|
||||
entries, _ := listEntries(base, cur)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"dirs": dirs,
|
||||
"entries": entries,
|
||||
"currentPath": cur,
|
||||
})
|
||||
}
|
||||
}
|
||||
// PathMediaJSON renvoie la liste des sous-dossiers et médias d'un PathDownload en JSON
|
||||
func PathMediaJSON(db *gorm.DB) http.HandlerFunc {
|
||||
// extensions autorisées et helpers JSON-friendly
|
||||
type dirView struct {
|
||||
Name string `json:"name"`
|
||||
SubPath string `json:"subPath"`
|
||||
}
|
||||
type mediaItemView struct {
|
||||
Title string `json:"title"`
|
||||
Duration int64 `json:"duration"` // en secondes
|
||||
DurationFmt string `json:"durationFmt"` // ex: "3:45"
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
ThumbURL string `json:"thumbUrl"`
|
||||
FilePath string `json:"filePath"`
|
||||
MediaPartID int64 `json:"mediaPartId"`
|
||||
}
|
||||
|
||||
allowed := map[string]bool{
|
||||
".mkv": true, ".avi": true, ".mp4": true, ".mov": true,
|
||||
".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
|
||||
".pdf": true, ".epub": true, ".cbz": true,
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// 1) Récupérer le PathDownload
|
||||
vars := mux.Vars(r)
|
||||
pid, err := strconv.ParseInt(vars["id"], 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"invalid path ID"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var pd models.PathDownload
|
||||
if err := db.First(&pd, pid).Error; err != nil {
|
||||
http.Error(w, `{"error":"path not found"}`, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// 2) Déterminer le sous-dossier courant
|
||||
sub := r.URL.Query().Get("sub") // ex: "Films/Test"
|
||||
current := filepath.Join(pd.Path, filepath.FromSlash(sub))
|
||||
|
||||
// 3) Lire les entrées du dossier
|
||||
entries, err := os.ReadDir(current)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"cannot read directory"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 4) Construire les slices JSON
|
||||
var dirs []dirView
|
||||
var medias []mediaItemView
|
||||
thumbDir := filepath.Join("static", "thumbs")
|
||||
os.MkdirAll(thumbDir, 0755)
|
||||
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
full := filepath.Join(current, name)
|
||||
|
||||
if e.IsDir() {
|
||||
dirs = append(dirs, dirView{
|
||||
Name: name,
|
||||
SubPath: filepath.ToSlash(filepath.Join(sub, name)),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
if !allowed[ext] {
|
||||
continue
|
||||
}
|
||||
|
||||
view := mediaItemView{
|
||||
Title: name,
|
||||
FilePath: full,
|
||||
}
|
||||
|
||||
// Si c'est une vidéo, extraire métadonnées + screenshot
|
||||
if ext == ".mkv" || ext == ".avi" || ext == ".mp4" || ext == ".mov" {
|
||||
// Métadonnées via ffprobe
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
|
||||
info, _ := probe(ctx, full)
|
||||
cancel()
|
||||
|
||||
if info != nil {
|
||||
// durée
|
||||
if d, err := strconv.ParseFloat(info.Format.Duration, 64); err == nil {
|
||||
secs := int64(d)
|
||||
view.Duration = secs
|
||||
view.DurationFmt = fmt.Sprintf("%d:%02d", secs/60, secs%60)
|
||||
}
|
||||
// résolution
|
||||
for _, s := range info.Streams {
|
||||
if s.CodecType == "video" {
|
||||
view.Width = s.Width
|
||||
view.Height = s.Height
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Génération du thumbnail
|
||||
base := strings.TrimSuffix(name, ext)
|
||||
thumbName := base + ".jpg"
|
||||
thumbPath := filepath.Join(thumbDir, thumbName)
|
||||
if _, err := os.Stat(thumbPath); os.IsNotExist(err) {
|
||||
exec.Command("ffmpeg", "-ss", "5", "-i", full, "-frames:v", "1", thumbPath).Run()
|
||||
}
|
||||
view.ThumbURL = "/static/thumbs/" + thumbName
|
||||
|
||||
} else {
|
||||
// Icônes génériques pour images/PDF/EPUB/CBZ
|
||||
view.ThumbURL = "/static/icons/" + ext[1:] + ".svg"
|
||||
}
|
||||
|
||||
medias = append(medias, view)
|
||||
}
|
||||
|
||||
// 5) Réponse JSON
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"dirs": dirs,
|
||||
"mediaItems": medias,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user