package main import ( "bytes" "encoding/json" "fmt" "github.com/go-co-op/gocron/v2" "github.com/joho/godotenv" "log" "math/rand/v2" "net/http" "os" "strconv" "time" ) var loginPath string = "/login" var subscriptionsPath string = "/subscriptions" type AuthResult struct { Token string } type Subscription struct { Url string Name string Avatar string Verified bool } func login(basePath string, username string, password string) AuthResult { loginUrl := basePath + loginPath login := map[string]string{"username": username, "password": password} jsonLogin, _ := json.Marshal(login) res, err := http.Post(loginUrl, "application/json", bytes.NewBuffer(jsonLogin)) if err != nil { panic(err) } defer res.Body.Close() var authResult AuthResult authResultDecoder := json.NewDecoder(res.Body) err = authResultDecoder.Decode(&authResult) if err != nil { panic(err) } return authResult } func getSubscriptions(basePath string, authToken string) []Subscription { subscriptionsUrl := basePath + subscriptionsPath // Create a new request using http req, err := http.NewRequest("GET", subscriptionsUrl, nil) if err != nil { panic(err) } req.Header.Add("Authorization", authToken) client := &http.Client{} res, err := client.Do(req) if err != nil { log.Println("Error on response.\n[ERROR] -", err) } defer res.Body.Close() var subsResult []Subscription subsResultDecoder := json.NewDecoder(res.Body) err = subsResultDecoder.Decode(&subsResult) if err != nil { panic(err) } return subsResult } func visit(url string, authToken string) { req, err := http.NewRequest("GET", url, nil) if err != nil { panic(err) } req.Header.Add("Authorization", authToken) client := &http.Client{} res, err := client.Do(req) if err != nil { log.Println("Error on response.\n[ERROR] -", err) } defer res.Body.Close() fmt.Println(res.Status) } // Randomize subscription order func randomize[S any](slice []S) { for i := range slice { j := rand.IntN(i + 1) slice[i], slice[j] = slice[j], slice[i] } } // Update Piped videos for all subscriptions given the Piped instance and the matching login data func update(basePath string, maxWaitTime int, username string, password string) { authResult := login(basePath, username, password) subscriptions := getSubscriptions(basePath, authResult.Token) randomize(subscriptions) for i := range subscriptions { fmt.Println(subscriptions[i].Name) visit(basePath+subscriptions[i].Url, authResult.Token) randomWaitTime := time.Duration(rand.IntN(maxWaitTime)) * time.Millisecond fmt.Println("Waiting for", randomWaitTime) fmt.Println("") time.Sleep(randomWaitTime) } } func main() { err := godotenv.Load() if err != nil { fmt.Println("Couldn't find a .env file, assuming already present environment variables") } var username string = os.Getenv("PIPED_USERNAME") // piped username var password string = os.Getenv("PIPED_PASSWORD") // piped password var basePath string = os.Getenv("PIPED_API_URL") // piped API URL var cronSchedule string = os.Getenv("QUERY_CRON") // cron schedule how often update should be triggered maxWaitTime, err := strconv.Atoi(os.Getenv("QUERY_WAIT_TIME")) // in milliseconds, change for potential ban evasion if err != nil { panic(err) } fmt.Printf("Logging in as %s on Piped instance %s\n", username, basePath) var infinityDuration time.Duration = 42069 * time.Hour // basically infinity // create a scheduler s, err := gocron.NewScheduler(gocron.WithStopTimeout(infinityDuration)) // we don't want the lengthy update task to time out if err != nil { panic(err) } // add a job to the scheduler _, err = s.NewJob( gocron.CronJob( cronSchedule, false, ), gocron.NewTask( func() { update(basePath, maxWaitTime, username, password) }, ), gocron.WithSingletonMode(gocron.LimitModeReschedule), ) if err != nil { panic(err) } // start the scheduler s.Start() fmt.Print("Starting scheduler...\n\n") // block until you are ready to shut down select { case <-time.After(time.Minute): } // when you're done, shut it down err = s.Shutdown() if err != nil { panic(err) } }