package main import ( "encoding/json" "fmt" "os" "os/exec" "strings" "time" "github.com/confusedpolarbear/intro_skipper_verifier/structs" ) var spinners []string var spinnerIndex int func generateReport(hostAddress, apiKey, reportDestination string, keepTimestamps bool, pollInterval time.Duration) { start := time.Now() // Setup the spinner spinners = strings.Split("⣷⣯⣟⡿⢿⣻⣽⣾", "") spinnerIndex = -1 // start the spinner on the first graphic // Setup the filename to save intros to if reportDestination == "" { reportDestination = fmt.Sprintf("intros-%s-%d.json", hostAddress, time.Now().Unix()) reportDestination = strings.ReplaceAll(reportDestination, "http://", "") reportDestination = strings.ReplaceAll(reportDestination, "https://", "") } // Ensure the file is writable if err := os.WriteFile(reportDestination, nil, 0600); err != nil { panic(err) } fmt.Printf("Started at: %s\n", start.Format(time.RFC1123)) fmt.Printf("Address: %s\n", hostAddress) fmt.Printf("Destination: %s\n", reportDestination) fmt.Println() // Get Jellyfin server information and plugin configuration info := GetServerInfo(hostAddress, apiKey) config := GetPluginConfiguration(hostAddress, apiKey) fmt.Println() fmt.Printf("Jellyfin OS: %s\n", info.OperatingSystem) fmt.Printf("Jellyfin version: %s\n", info.Version) fmt.Printf("Analysis settings: %s\n", config.AnalysisSettings()) fmt.Printf("Introduction reqs: %s\n", config.IntroductionRequirements()) fmt.Printf("Erase timestamps: %t\n", !keepTimestamps) fmt.Println() // If not keeping timestamps, run the fingerprint task. // Otherwise, log that the task isn't being run if !keepTimestamps { runAnalysisAndWait(hostAddress, apiKey, pollInterval) } else { fmt.Println("[+] Using previously discovered intros") } fmt.Println() // Save all intros from the server fmt.Println("[+] Saving intros") var report structs.Report rawIntros := SendRequest("GET", hostAddress+"/Intros/All", apiKey) if err := json.Unmarshal(rawIntros, &report.Intros); err != nil { panic(err) } // Calculate the durations of all intros for i := range report.Intros { intro := report.Intros[i] intro.Duration = intro.IntroEnd - intro.IntroStart report.Intros[i] = intro } fmt.Println() fmt.Println("[+] Saving report") // Store timing data, server information, and plugin configuration report.StartedAt = start report.FinishedAt = time.Now() report.Runtime = report.FinishedAt.Sub(report.StartedAt) report.ServerInfo = info report.PluginConfig = config // Marshal the report marshalled, err := json.Marshal(report) if err != nil { panic(err) } if err := os.WriteFile(reportDestination, marshalled, 0600); err != nil { panic(err) } // Change report permissions exec.Command("chown", "1000:1000", reportDestination).Run() fmt.Println("[+] Done") } func runAnalysisAndWait(hostAddress, apiKey string, pollInterval time.Duration) { var taskId string = "" type taskInfo struct { State string CurrentProgressPercentage int } fmt.Println("[+] Erasing previously discovered intros") SendRequest("POST", hostAddress+"/Intros/EraseTimestamps", apiKey) fmt.Println() var taskIds = []string{ "f64d8ad58e3d7b98548e1a07697eb100", // v0.1.8 "8863329048cc357f7dfebf080f2fe204", "6adda26c5261c40e8fa4a7e7df568be2"} fmt.Println("[+] Starting analysis task") for _, id := range taskIds { body := SendRequest("POST", hostAddress+"/ScheduledTasks/Running/"+id, apiKey) fmt.Println() // If the scheduled task was found, store the task ID for later if !strings.Contains(string(body), "Not Found") { taskId = id break } } if taskId == "" { panic("unable to find scheduled task") } fmt.Println("[+] Waiting for analysis task to complete") fmt.Print("[+] Episodes analyzed: 0%") var info taskInfo // Last known scheduled task state var lastQuery time.Time // Time the task info was last updated for { time.Sleep(500 * time.Millisecond) // Update the spinner if spinnerIndex++; spinnerIndex >= len(spinners) { spinnerIndex = 0 } fmt.Printf("\r[%s] Episodes analyzed: %d%%", spinners[spinnerIndex], info.CurrentProgressPercentage) if info.CurrentProgressPercentage == 100 { fmt.Printf("\r[+]") // reset the spinner fmt.Println() break } // Get the latest task state & unmarshal (only if enough time has passed since the last update) if time.Since(lastQuery) <= pollInterval { continue } lastQuery = time.Now() raw := SendRequest("GET", hostAddress+"/ScheduledTasks/"+taskId+"?hideUrl=1", apiKey) if err := json.Unmarshal(raw, &info); err != nil { fmt.Printf("[!] Unable to unmarshal response into taskInfo struct: %s\n", err) fmt.Printf("%s\n", raw) continue } // Print the latest task state switch info.State { case "Idle": info.CurrentProgressPercentage = 100 } } }