Split up main.go into multiple files.

Added Db support to save last viewed Manga and chapter, and auto load it next time you start the server.
Added Providers to add more Manga Platforms than just Bato in the future
This commit is contained in:
Pablu23
2024-02-21 19:19:54 +01:00
parent 63b3230c36
commit 8ecbc7e0aa
10 changed files with 690 additions and 344 deletions

390
main.go
View File

@@ -2,14 +2,10 @@ package main
import (
"bytes"
"errors"
"fmt"
"html/template"
"io"
"net/http"
"path/filepath"
"regexp"
"strings"
"os"
"os/signal"
"sync"
)
@@ -18,7 +14,7 @@ type NoMoreError struct {
}
func (e *NoMoreError) Error() string {
return "no more images available"
return e.Err.Error()
}
type Image struct {
@@ -31,365 +27,77 @@ type ImageViewModel struct {
Images []Image
}
type Server struct {
PrevViewModel *ImageViewModel
CurrViewModel *ImageViewModel
NextViewModel *ImageViewModel
ImageBuffers map[string]*bytes.Buffer
NextSubUrl string
CurrSubUrl string
PrevSubUrl string
IsFirst bool
IsLast bool
// I'm not even sure if this helps
// If you press next and then prev too fast you still lock yourself out
NextReady chan bool
PrevReady chan bool
}
func getImageList(html string) ([]string, error) {
reg, err := regexp.Compile(`<astro-island.*props=".*;imageFiles&quot;:\[1,&quot;\[(.*)]&quot;]`)
if err != nil {
return nil, err
}
m := reg.FindStringSubmatch(html)
if len(m) <= 0 {
return nil, &NoMoreError{Err: errors.New("no more content")}
}
match := m[1]
reg, err = regexp.Compile(`\[0,\\&quot;([^&]*)\\&quot;]`)
if err != nil {
return nil, err
}
matches := reg.FindAllStringSubmatch(match, -1)
l := len(matches)
result := make([]string, l)
for i, m := range matches {
result[i] = m[1]
}
return result, nil
}
func getHtmlFor(titleSubUrl string) (string, error) {
url := fmt.Sprintf("https://bato.to%s?load=2", titleSubUrl)
resp, err := http.Get(url)
// TODO: Testing for above 300 is dirty
if err != nil && resp.StatusCode > 300 {
return "", errors.New("could not get html")
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
fmt.Printf("Could not close body because: %v\n", err)
}
}(resp.Body)
all, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
h := string(all)
return h, nil
}
func getNext(html string) (subUrl string, err error) {
reg, err := regexp.Compile(`<a data-hk="0-6-0" .*? href="(.*?)["']`)
match := reg.FindStringSubmatch(html)
return match[1], err
}
func getPrev(html string) (subUrl string, err error) {
reg, err := regexp.Compile(`<a data-hk="0-5-0" .*? href="(.*?)["']`)
match := reg.FindStringSubmatch(html)
return match[1], err
}
func appendImagesToBuf(html string, imageBuffs map[string]*bytes.Buffer) ([]Image, error) {
imgList, err := getImageList(html)
if err != nil {
return nil, err
}
images := make([]Image, len(imgList))
wg := sync.WaitGroup{}
for i, url := range imgList {
wg.Add(1)
go func(i int, url string, wg *sync.WaitGroup) {
buf, err := addFileToRam(url)
if err != nil {
panic(err)
}
name := filepath.Base(url)
imageBuffs[name] = buf
images[i] = Image{Path: name, Index: i}
wg.Done()
}(i, url, &wg)
}
wg.Wait()
return images, nil
}
func main() {
curr := "/title/143267-blooming-love/2636103-ch_20"
db := NewDatabase("db.sqlite", true)
err := db.Open()
if err != nil {
return
}
var latestTimeStamp int64 = 0
var latestUrl string
for _, m := range db.Mangas {
if latestTimeStamp < m.LatestChapter.TimeStampUnix {
latestTimeStamp = m.LatestChapter.TimeStampUnix
latestUrl = m.LatestChapter.Url
}
}
if latestUrl == "" {
latestUrl = "/title/80381-i-stan-the-prince/1539086-ch_16"
}
server := Server{
ImageBuffers: make(map[string]*bytes.Buffer),
CurrSubUrl: curr,
CurrSubUrl: latestUrl,
NextReady: make(chan bool),
PrevReady: make(chan bool),
Provider: &Bato{},
DbMgr: &db,
Mutex: &sync.Mutex{},
}
server.loadCurr()
go server.loadPrev()
go server.loadNext()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
Close(&db)
}
}()
server.LoadCurr()
go server.LoadPrev()
go server.LoadNext()
http.HandleFunc("/", server.HandleCurrent)
http.HandleFunc("/img/{url}/", server.HandleImage)
http.HandleFunc("POST /next", server.handleNext)
http.HandleFunc("POST /prev", server.handlePrev)
http.HandleFunc("POST /next", server.HandleNext)
http.HandleFunc("POST /prev", server.HandlePrev)
http.HandleFunc("POST /exit", func(_ http.ResponseWriter, _ *http.Request) {
Close(&db)
})
http.HandleFunc("/new/{title}/{chapter}", server.HandleNew)
fmt.Println("Server running")
err := http.ListenAndServe(":8000", nil)
fmt.Println("Server starting...")
err = http.ListenAndServe(":8000", nil)
if err != nil {
fmt.Println(err)
return
}
}
func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
u := r.PathValue("url")
buf := s.ImageBuffers[u]
if buf == nil {
fmt.Printf("url: %s is nil\n", u)
w.WriteHeader(400)
return
}
w.Header().Set("Content-Type", "image/webp")
_, err := w.Write(buf.Bytes())
if err != nil {
fmt.Println(err)
}
}
func (s *Server) handleNext(w http.ResponseWriter, r *http.Request) {
fmt.Println("Received Next")
if s.PrevViewModel != nil {
go func(viewModel ImageViewModel, s *Server) {
for _, img := range viewModel.Images {
delete(s.ImageBuffers, img.Path)
}
fmt.Println("Cleaned out of scope Last")
}(*s.PrevViewModel, s)
}
s.PrevViewModel = s.CurrViewModel
s.CurrViewModel = s.NextViewModel
s.PrevSubUrl = s.CurrSubUrl
s.CurrSubUrl = s.NextSubUrl
<-s.NextReady
go s.loadNext()
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
func (s *Server) loadNext() {
c, err := getHtmlFor(s.CurrSubUrl)
func Close(db *DatabaseManager) {
fmt.Println("Attempting to save and close DB")
err := db.Save()
if err != nil {
fmt.Println(err)
return
}
next, err := getNext(c)
if err != nil {
fmt.Println(err)
return
}
html, err := getHtmlFor(next)
if err != nil {
fmt.Println(err)
return
}
imagesNext, err := appendImagesToBuf(html, s.ImageBuffers)
//if err != nil && errors.Is(err, &NoMoreError{}) {
// fmt.Println(err)
// return
//} else
if err != nil {
fmt.Println(err)
return
}
title, chapter, err := getTitleAndChapter(next)
if err != nil {
title = "Unknown"
chapter = "ch_?"
}
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
s.NextViewModel = &ImageViewModel{Images: imagesNext, Title: full}
s.NextSubUrl = next
fmt.Println("Loaded next")
s.NextReady <- true
}
func getTitleAndChapter(url string) (title string, chapter string, err error) {
reg, err := regexp.Compile(`/title/\d*-(.*?)/\d*-(.*)`)
if err != nil {
return "", "", err
}
matches := reg.FindAllStringSubmatch(url, -1)
if len(matches) <= 0 {
return "", "", errors.New("no title or chapter found")
}
return matches[0][1], matches[0][2], nil
}
func (s *Server) loadPrev() {
c, err := getHtmlFor(s.CurrSubUrl)
if err != nil {
fmt.Println(err)
return
}
prev, err := getPrev(c)
if err != nil {
fmt.Println(err)
return
}
html, err := getHtmlFor(prev)
if err != nil {
fmt.Println(err)
return
}
imagesNext, err := appendImagesToBuf(html, s.ImageBuffers)
if err != nil {
fmt.Println(err)
return
}
title, chapter, err := getTitleAndChapter(prev)
if err != nil {
title = "Unknown"
chapter = "ch_?"
}
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
s.PrevViewModel = &ImageViewModel{Images: imagesNext, Title: full}
s.PrevSubUrl = prev
fmt.Println("Loaded prev")
s.PrevReady <- true
}
func (s *Server) handlePrev(w http.ResponseWriter, r *http.Request) {
fmt.Println("Received Prev")
if s.NextViewModel != nil {
go func(viewModel ImageViewModel, s *Server) {
for _, img := range viewModel.Images {
delete(s.ImageBuffers, img.Path)
}
fmt.Println("Cleaned out of scope Last")
}(*s.NextViewModel, s)
}
s.NextViewModel = s.CurrViewModel
s.CurrViewModel = s.PrevViewModel
s.NextSubUrl = s.CurrSubUrl
s.CurrSubUrl = s.PrevSubUrl
<-s.PrevReady
go s.loadPrev()
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
tmpl := template.Must(template.ParseFiles("test.gohtml"))
err := tmpl.Execute(w, s.CurrViewModel)
err = db.Close()
if err != nil {
fmt.Println(err)
}
}
func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
title := r.PathValue("title")
chapter := r.PathValue("chapter")
url := fmt.Sprintf("/title/%s/%s", title, chapter)
s.ImageBuffers = make(map[string]*bytes.Buffer)
s.CurrSubUrl = url
s.PrevSubUrl = ""
s.NextSubUrl = ""
s.loadCurr()
go s.loadNext()
go s.loadPrev()
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
func (s *Server) loadCurr() {
html, err := getHtmlFor(s.CurrSubUrl)
if err != nil {
panic(err)
}
imagesCurr, err := appendImagesToBuf(html, s.ImageBuffers)
title, chapter, err := getTitleAndChapter(s.CurrSubUrl)
if err != nil {
title = "Unknown"
chapter = "ch_?"
}
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
s.CurrViewModel = &ImageViewModel{Images: imagesCurr, Title: full}
fmt.Println("Loaded current")
}
func addFileToRam(url string) (*bytes.Buffer, error) {
// Get the data
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
fmt.Println(err)
}
}(resp.Body)
buf := new(bytes.Buffer)
// Write the body to file
_, err = io.Copy(buf, resp.Body)
return buf, err
os.Exit(0)
}