package main import ( "fmt" "log" "net/http" "path/filepath" "strconv" "time" "git.andreafazzi.eu/andrea/youtube-dl-service/downloader" youtube_dl "git.andreafazzi.eu/andrea/youtube-dl-service/downloader/youtube-dl" "git.andreafazzi.eu/andrea/youtube-dl-service/task" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) const ( DefaultJWTExpireTime = 60 DefaultSigningKey = "secret" ) var tasks task.Tasks func init() { log.SetPrefix("[youtube-dl-service] ") tasks = make(task.Tasks, 0) } func download(c *gin.Context, downloader downloader.Downloader) error { video, err := downloader.GetVideo() if err != nil { return err } c.JSON(http.StatusOK, video) go downloader.StartDownload(video, tasks) return nil } func createTask(c *gin.Context) { err := c.Request.ParseForm() if err != nil { panic(err) } if err := download(c, youtube_dl.NewYoutubeDlDownloader(c.PostForm("url"), "yt-dlp")); err != nil { panic(err) } } func getTask(c *gin.Context, id string) error { task, ok := tasks[id] if ok { c.JSON(http.StatusOK, task) } return nil } func data(c *gin.Context, id string) error { c.Writer.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(tasks[id].Filename)) c.Writer.Header().Set("Content-Type", "application/octet-stream") http.ServeFile(c.Writer, c.Request, filepath.Join("data", id+filepath.Ext(tasks[id].Filename))) return nil } func postLogin(c *gin.Context, config *Config) error { err := c.Request.ParseForm() if err != nil { return err } token, err := getToken(config, c.PostForm("password")) if err != nil { return err } c.JSON(http.StatusOK, token) return nil } func getToken(config *Config, password string) ([]byte, error) { if password != config.Password { return nil, fmt.Errorf("Wrong password!") } /* Set token claims */ claims := make(map[string]interface{}) // Token expire time claims["exp"] = time.Now().Add(time.Minute * time.Duration(config.JWTExpireTime)).Unix() /* Create the token */ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims)) /* Sign the token with our secret */ tokenString, err := token.SignedString([]byte(config.JWTSigningKey)) if err != nil { return nil, err } return []byte(tokenString), nil } // API outline // // POST /task content: url=https://foo.org start a new download task // GET /task/:id get the status for the task // GET /task/:id/download download the file resulted from the task execution func main() { r := gin.Default() r.Use(cors.New(cors.Config{ AllowOrigins: []string{ "http://localhost:8080", "http://localhost:5000", "https://yt-dls.andreafazzi.eu", }, AllowCredentials: true, AllowHeaders: []string{"*"}, })) config := new(Config) err := ReadConfig("config.yaml", config) if err != nil { panic(err) } r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) } c.AbortWithStatus(http.StatusInternalServerError) })) r.GET("/data/:id", func(c *gin.Context) { if err := data(c, c.Param("id")); err != nil { panic(err) } }) r.POST("/task", createTask) r.GET("/task/:id", func(c *gin.Context) { if err := getTask(c, c.Param("id")); err != nil { panic(err) } }) r.POST("/login", func(c *gin.Context) { if err := postLogin(c, config); err != nil { panic(err) } }) r.Run() }