viddl/main.go

388 lines
10 KiB
Go
Raw Normal View History

2023-09-10 22:18:30 +02:00
package main
import (
"encoding/json"
2023-09-14 19:13:32 +02:00
"fmt"
2023-09-11 01:46:14 +02:00
"html/template"
2023-09-14 19:13:32 +02:00
"io"
2023-09-10 22:18:30 +02:00
"log"
"net/http"
2023-09-14 19:13:32 +02:00
"net/url"
"os"
2023-09-14 19:13:32 +02:00
"strings"
2023-09-10 22:18:30 +02:00
"time"
"gitea.hbanafa.com/hesham/viddl/apkupdater"
2023-09-10 22:18:30 +02:00
)
const (
DEFAULT_HTTP_PORT = "8080"
DEFAULT_HTTPS_PORT = "4433"
)
2023-09-11 01:46:14 +02:00
type DownloadFormats struct {
VideoRes string
videoOnly bool
audioOnly bool
}
type URLValidationCtx struct {
URL string
Valid bool
}
2023-09-10 22:18:30 +02:00
type apiMessageResponse struct {
Message string
}
type Logger struct {
Handler http.Handler
}
func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tNow := time.Now().UTC()
l.Handler.ServeHTTP(w, r)
methodString := l.getMethodLogString(r.Method)
remote := r.RemoteAddr
realIP := r.Header.Get("X-Real-IP")
if realIP != "" {
remote = realIP
}
log.Printf(" %s %s %s %v", remote, methodString, r.URL, time.Since(tNow))
2023-09-10 22:18:30 +02:00
}
func (l *Logger) getMethodLogString(method string) string {
colorBlue := "\033[34m"
colorReset := "\033[0m"
2023-09-10 22:33:06 +02:00
colorRed := "\033[31m"
colorGreen := "\033[32m"
colorYellow := "\033[33m"
2023-09-10 22:18:30 +02:00
// colorPurple := "\033[35m"
// colorCyan := "\033[36m"
// colorWhite := "\033[37m"
2023-09-10 22:33:06 +02:00
switch method {
case "GET": return colorBlue + "GET" + colorReset
case "POST": return colorGreen + "POST" + colorReset
case "DELETE": return colorRed + "DELETE" + colorReset
case "PUT": return colorYellow + "PUT" + colorReset
default: return method
}
2023-09-10 22:18:30 +02:00
}
func NewLogger(handler http.Handler) *Logger {
return &Logger{Handler: handler}
}
func writeJSONResponse(w http.ResponseWriter, s string) http.ResponseWriter {
w.WriteHeader(http.StatusBadRequest)
2023-09-11 01:46:14 +02:00
w.Header().Set("Content-Type", "application/json")
2023-09-10 22:18:30 +02:00
jsonResp, err := json.Marshal(apiMessageResponse{Message: s})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return w
}
_, err = w.Write(jsonResp)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return w
}
return w
}
var templates *template.Template
// TODO: Change all this to have a unified context
2023-09-11 22:27:18 +02:00
type Context struct {
request *http.Request
Formats []DownloadFormats
AppURL string
StatusCode int
Err *error
IsTLS bool
DownloadURL string
2023-09-11 22:27:18 +02:00
}
func NewContext(r *http.Request) *Context {
isTLS := false
tls := os.Getenv("TLS")
if tls != "" {
isTLS = true
}
2023-09-11 22:27:18 +02:00
return &Context{
request: r,
StatusCode: 200,
Formats: []DownloadFormats{
{
VideoRes: "720p",
videoOnly: false,
audioOnly: false,
},
},
AppURL: r.Host,
IsTLS: isTLS,
2023-09-11 22:27:18 +02:00
}
}
2023-09-14 19:13:32 +02:00
func handleToAudio(ctx *Context, w http.ResponseWriter) http.ResponseWriter {
log.Println("User requested audio")
w.WriteHeader(400)
jsonMsg, err := json.Marshal(
apiMessageResponse{
Message: "Audio only is not implemented",
},
)
if err != nil {
log.Println(err.Error())
w.WriteHeader(500)
}
w.Write(jsonMsg)
return w
}
func init() {
log.Println("[ init ] Starting...")
templates = template.Must(template.ParseFS(TemplatesFS , "templates/*.html"))
log.Println("[ init ] Templates Loaded")
}
2023-09-10 22:18:30 +02:00
func main() {
updater, err := apkupdater.InitPkgUpdater()
if err != nil {
log.Println("Could not init Package Updater!\n", err.Error())
}
updater.Run()
2023-09-10 22:18:30 +02:00
handler := http.NewServeMux()
handler.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(PublicFS))))
handler.Handle("/assets/", http.FileServer(http.FS(AssetsFS)))
2023-09-10 22:18:30 +02:00
2023-09-13 13:52:57 +02:00
handler.HandleFunc(
"/robots.txt",
func(w http.ResponseWriter, r *http.Request) {
robotsFile, err := RobotsFS.ReadFile("static/robots.txt")
if err != nil {
log.Println(err.Error())
w.WriteHeader(500)
return
}
w.Write(robotsFile)
},
)
2023-09-13 17:23:15 +02:00
handler.HandleFunc(
"/sitemap.txt",
func(w http.ResponseWriter, r *http.Request) {
sitemapFile, err := AssetsFS.ReadFile("assets/sitemap.txt")
if err != nil {
log.Println(err.Error())
w.WriteHeader(500)
return
}
w.Write(sitemapFile)
},
)
2023-09-10 22:18:30 +02:00
handler.HandleFunc(
"/download",
func(w http.ResponseWriter, r *http.Request) {
2023-09-11 22:27:18 +02:00
ctx := NewContext(r)
2023-09-11 01:46:14 +02:00
if r.Method != "POST" {
w.WriteHeader(400)
return
}
err := r.ParseForm()
if err != nil {
w.WriteHeader(400)
return
}
userURL := r.FormValue("URL")
2023-09-10 22:18:30 +02:00
if userURL == "" {
w = writeJSONResponse(w, "Provide URL as query")
return
}
2023-09-13 15:16:29 +02:00
toAudio := r.FormValue("toaudio")
if toAudio == "on" {
log.Println("User requested audio")
}
2023-09-11 01:46:14 +02:00
filenameChan := make(chan string)
go GetContentFilename(userURL, filenameChan)
downloadURL, err := getYoutubeDownloadURL(userURL)
filename := <- filenameChan
2023-09-11 01:46:14 +02:00
if err != nil {
log.Println(err.Error())
2023-09-11 22:27:18 +02:00
ctx.StatusCode = 500
ctx.Err = &err
err = templates.ExecuteTemplate(w,"download-result.html", ctx)
2023-09-11 01:46:14 +02:00
return
}
ctx.DownloadURL = downloadURL
w.Header().Add(
"Hx-Redirect",
fmt.Sprintf("/download-direct?URL=%s&filename=%s",
url.QueryEscape(ctx.DownloadURL),
url.QueryEscape(filename)),
)
2023-09-11 22:27:18 +02:00
err = templates.ExecuteTemplate(w,"download-result.html", ctx)
2023-09-11 01:46:14 +02:00
if err != nil {
log.Println(err.Error())
2023-09-11 22:27:18 +02:00
ctx.StatusCode = 500
ctx.Err = &err
err = templates.ExecuteTemplate(w,"download-result.html", ctx)
2023-09-11 01:46:14 +02:00
return
}
},
2023-09-10 22:18:30 +02:00
)
2023-09-14 19:13:32 +02:00
handler.HandleFunc(
"/download-direct",
func(w http.ResponseWriter, r *http.Request) {
ctx := NewContext(r)
if r.Method != "GET" {
w.WriteHeader(400)
return
}
userURL := strings.Trim(r.URL.Query().Get("URL"), "\n")
filename := strings.Trim(r.URL.Query().Get("filename"), "\n")
2023-09-14 19:13:32 +02:00
if userURL == "" {
log.Println("Empty URL")
2023-09-14 19:13:32 +02:00
w.WriteHeader(400)
ctx.StatusCode = 400
if err := templates.ExecuteTemplate(w,"download-result.html", ctx); err != nil {
log.Println(err.Error())
}
return
}
ctx.DownloadURL = userURL
req, err := http.NewRequest("GET", ctx.DownloadURL, nil)
if err != nil {
log.Println(err.Error())
2023-09-14 19:13:32 +02:00
ctx.StatusCode = 500
ctx.Err = &err
if err := templates.ExecuteTemplate(w,"download-result.html", ctx); err != nil {
log.Println(err.Error())
}
return
}
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0")
client := http.Client{}
dataRequest, err := client.Do(req)
if err != nil {
log.Println(err.Error())
ctx.StatusCode = 500
ctx.Err = &err
if err := templates.ExecuteTemplate(w,"download-result.html", ctx); err != nil {
log.Println(err.Error())
}
return
}
defer dataRequest.Body.Close()
if dataRequest.StatusCode != 200 {
log.Println("Failed to get content for URL", userURL)
return
2023-09-14 19:13:32 +02:00
}
contentLength := dataRequest.Header.Get("Content-Length")
2023-09-14 19:13:32 +02:00
if dataRequest.ContentLength == 0 {
log.Println("Empty body from content url")
w.WriteHeader(500)
return
}
w.Header().Set(
"Content-Disposition",
fmt.Sprintf("attachment;filename=%s;", filename),
)
w.Header().Set("Content-Length", contentLength)
w.WriteHeader(206)
2023-09-14 19:13:32 +02:00
n, err := io.Copy(w, dataRequest.Body)
if err != nil {
log.Println(err.Error())
}
log.Printf("Copied %d bytes", n)
},
)
2023-09-11 01:46:14 +02:00
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
2023-09-11 22:27:18 +02:00
ctx := NewContext(r)
2023-09-11 01:46:14 +02:00
formats := []DownloadFormats{}
formats = append(formats, DownloadFormats{
VideoRes: "720p",
audioOnly: false,
videoOnly: false,
})
2023-09-11 22:27:18 +02:00
err := templates.ExecuteTemplate(w, "download.html", ctx)
2023-09-11 01:46:14 +02:00
if err != nil {
log.Println(err.Error())
w.WriteHeader(500)
return
}
})
2023-09-10 22:18:30 +02:00
handler.HandleFunc("/valid-link", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(400)
return
}
err := r.ParseForm()
if err != nil {
log.Println(err.Error())
w.WriteHeader(400)
return
}
url := r.FormValue("URL")
ctx := URLValidationCtx{
URL: url,
Valid: isValidURL(url),
}
templates.ExecuteTemplate(w, "url-validation.html", ctx)
})
2023-09-10 22:18:30 +02:00
wrappedHandler := NewLogger(handler)
srv := http.Server{
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
2023-09-10 22:18:30 +02:00
Addr: ":" + DEFAULT_HTTP_PORT,
Handler: wrappedHandler,
}
log.Printf("Starting HTTP on %s", DEFAULT_HTTP_PORT)
log.Fatalln(srv.ListenAndServe())
log.Println("HTTP server stopped")
updater.Stop()
2023-09-10 22:18:30 +02:00
}