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:
390
main.go
390
main.go
@@ -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":\[1,"\[(.*)]"]`)
|
||||
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,\\"([^&]*)\\"]`)
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user