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"
|
2023-09-13 10:32:47 +02:00
|
|
|
"os"
|
2023-09-14 19:13:32 +02:00
|
|
|
"strings"
|
2023-09-10 22:18:30 +02:00
|
|
|
"time"
|
2023-09-16 03:56:32 +02:00
|
|
|
|
|
|
|
"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
|
|
|
|
}
|
|
|
|
|
2023-09-13 12:13:13 +02:00
|
|
|
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)
|
2023-09-11 23:52:54 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-09-11 20:44:41 +02:00
|
|
|
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
|
2023-09-11 22:42:58 +02:00
|
|
|
DownloadURL string
|
2023-09-11 22:27:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewContext(r *http.Request) *Context {
|
2023-09-13 10:32:47 +02:00
|
|
|
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,
|
2023-09-13 10:32:47 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-09-11 19:54:28 +02:00
|
|
|
func init() {
|
|
|
|
|
|
|
|
log.Println("[ init ] Starting...")
|
2023-09-11 20:44:41 +02:00
|
|
|
templates = template.Must(template.ParseFS(TemplatesFS , "templates/*.html"))
|
2023-09-13 10:26:23 +02:00
|
|
|
log.Println("[ init ] Templates Loaded")
|
2023-09-11 19:54:28 +02:00
|
|
|
|
|
|
|
}
|
2023-09-13 10:26:23 +02:00
|
|
|
|
2023-09-10 22:18:30 +02:00
|
|
|
func main() {
|
|
|
|
|
2023-09-16 03:56:32 +02:00
|
|
|
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()
|
2023-09-11 20:44:41 +02:00
|
|
|
handler.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(PublicFS))))
|
2023-09-13 10:26:23 +02:00
|
|
|
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
|
|
|
|
2023-09-14 20:49:17 +02:00
|
|
|
filenameChan := make(chan string)
|
|
|
|
|
|
|
|
go GetContentFilename(userURL, filenameChan)
|
2023-09-11 23:09:24 +02:00
|
|
|
downloadURL, err := getYoutubeDownloadURL(userURL)
|
2023-09-14 20:49:17 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-09-11 22:42:58 +02:00
|
|
|
ctx.DownloadURL = downloadURL
|
2023-09-14 20:49:17 +02:00
|
|
|
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-13 12:13:13 +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
|
|
|
|
}
|
|
|
|
|
2023-09-14 20:49:17 +02:00
|
|
|
|
|
|
|
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 == "" {
|
2023-09-14 20:49:17 +02:00
|
|
|
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 {
|
2023-09-14 20:49:17 +02:00
|
|
|
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)
|
2023-09-15 17:48:45 +02:00
|
|
|
return
|
2023-09-14 19:13:32 +02:00
|
|
|
}
|
2023-09-15 17:48:45 +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
|
|
|
|
}
|
2023-09-15 17:48:45 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
2023-09-13 12:13:13 +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{
|
2023-09-15 17:49:12 +02:00
|
|
|
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())
|
2023-09-16 03:56:32 +02:00
|
|
|
log.Println("HTTP server stopped")
|
|
|
|
updater.Stop()
|
2023-09-10 22:18:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|