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

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/.idea /.idea
findings.txt findings.txt
test.txt test.txt
h.html h.html
db.sqlite

83
bato.go Normal file
View File

@@ -0,0 +1,83 @@
package main
import (
"errors"
"fmt"
"io"
"net/http"
"regexp"
)
type Provider interface {
GetImageList(html string) (imageUrls []string, err error)
GetHtml(url string) (html string, err error)
GetNext(html string) (url string, err error)
GetPrev(html string) (url string, err error)
}
type Bato struct{}
func (b *Bato) 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 (b *Bato) GetHtml(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 (b *Bato) 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 (b *Bato) 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
}

15
createDb.sql Normal file
View File

@@ -0,0 +1,15 @@
create table if not exists Manga (
ID integer not null primary key,
Title text,
TimeStampUnixEpoch int
);
create table if not exists Chapter (
ID integer not null primary key,
MangaID integer not null,
Url text not null,
Name text null,
Number int not null,
TimeStampUnixEpoch int,
foreign key(MangaID) references Manga(ID)
);

165
database.go Normal file
View File

@@ -0,0 +1,165 @@
package main
import (
"database/sql"
_ "embed"
_ "github.com/mattn/go-sqlite3"
)
type Manga struct {
Id int
Title string
TimeStampUnix int64
// Not in DB
LatestChapter *Chapter
}
type Chapter struct {
Id int
Manga *Manga
Url string
Name string
Number int
TimeStampUnix int64
}
type DatabaseManager struct {
ConnectionString string
db *sql.DB
Mangas map[int]*Manga
Chapters map[int]*Chapter
CreateIfNotExists bool
}
func NewDatabase(connectionString string, createIfNotExists bool) DatabaseManager {
return DatabaseManager{
ConnectionString: connectionString,
Mangas: make(map[int]*Manga),
Chapters: make(map[int]*Chapter),
CreateIfNotExists: createIfNotExists,
}
}
func (dbMgr *DatabaseManager) Open() error {
db, err := sql.Open("sqlite3", dbMgr.ConnectionString)
if err != nil {
return err
}
dbMgr.db = db
if dbMgr.CreateIfNotExists {
err = dbMgr.createDatabaseIfNotExists()
if err != nil {
return err
}
}
err = dbMgr.load()
return err
}
func (dbMgr *DatabaseManager) Close() error {
err := dbMgr.db.Close()
if err != nil {
return err
}
dbMgr.Mangas = nil
dbMgr.Chapters = nil
dbMgr.db = nil
return nil
}
func (dbMgr *DatabaseManager) Save() error {
db := dbMgr.db
for _, m := range dbMgr.Mangas {
count := 0
err := db.QueryRow("SELECT COUNT(*) FROM Manga where ID = ?", m.Id).Scan(&count)
if err != nil {
return err
}
if count == 0 {
_, err := db.Exec("INSERT INTO Manga(ID, Title, TimeStampUnixEpoch) values(?, ?, ?)", m.Id, m.Title, m.TimeStampUnix)
if err != nil {
return err
}
} else {
_, err := db.Exec("UPDATE Manga set Title = ?, TimeStampUnixEpoch = ? WHERE ID = ?", m.Title, m.TimeStampUnix, m.Id)
if err != nil {
return err
}
}
}
for _, c := range dbMgr.Chapters {
count := 0
err := db.QueryRow("SELECT COUNT(*) FROM Chapter where ID = ?", c.Id).Scan(&count)
if err != nil {
return err
}
if count == 0 {
_, err := db.Exec("INSERT INTO Chapter(ID, MangaID, Url, Name, Number, TimeStampUnixEpoch) VALUES (?, ?, ?, ?, ?, ?)", c.Id, c.Manga.Id, c.Url, c.Name, c.Number, c.TimeStampUnix)
if err != nil {
return err
}
} else {
_, err = db.Exec("UPDATE Chapter set Name = ?, Url = ?, Number = ?, TimeStampUnixEpoch = ? where ID = ?", c.Name, c.Url, c.Number, c.TimeStampUnix, c.Id)
if err != nil {
return err
}
}
}
return nil
}
//go:embed createDb.sql
var createSql string
func (dbMgr *DatabaseManager) createDatabaseIfNotExists() error {
_, err := dbMgr.db.Exec(createSql)
return err
}
func (dbMgr *DatabaseManager) load() error {
db := dbMgr.db
rows, err := db.Query("SELECT * FROM Manga")
if err != nil {
return err
}
for rows.Next() {
manga := Manga{}
if err = rows.Scan(&manga.Id, &manga.Title, &manga.TimeStampUnix); err != nil {
return err
}
dbMgr.Mangas[manga.Id] = &manga
}
rows, err = db.Query("SELECT * FROM Chapter")
if err != nil {
return err
}
for rows.Next() {
chapter := Chapter{}
var mangaID int
if err = rows.Scan(&chapter.Id, &mangaID, &chapter.Url, &chapter.Name, &chapter.Number, &chapter.TimeStampUnix); err != nil {
return err
}
chapter.Manga = dbMgr.Mangas[mangaID]
if dbMgr.Mangas[mangaID].LatestChapter == nil || dbMgr.Mangas[mangaID].LatestChapter.TimeStampUnix < chapter.TimeStampUnix {
dbMgr.Mangas[mangaID].LatestChapter = &chapter
}
dbMgr.Chapters[chapter.Id] = &chapter
}
return nil
}

2
go.mod
View File

@@ -1,3 +1,5 @@
module mangaGetter module mangaGetter
go 1.22 go 1.22
require github.com/mattn/go-sqlite3 v1.14.22

2
go.sum
View File

@@ -0,0 +1,2 @@
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=

390
main.go
View File

@@ -2,14 +2,10 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"html/template"
"io"
"net/http" "net/http"
"path/filepath" "os"
"regexp" "os/signal"
"strings"
"sync" "sync"
) )
@@ -18,7 +14,7 @@ type NoMoreError struct {
} }
func (e *NoMoreError) Error() string { func (e *NoMoreError) Error() string {
return "no more images available" return e.Err.Error()
} }
type Image struct { type Image struct {
@@ -31,365 +27,77 @@ type ImageViewModel struct {
Images []Image 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() { 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{ server := Server{
ImageBuffers: make(map[string]*bytes.Buffer), ImageBuffers: make(map[string]*bytes.Buffer),
CurrSubUrl: curr, CurrSubUrl: latestUrl,
NextReady: make(chan bool), NextReady: make(chan bool),
PrevReady: make(chan bool), PrevReady: make(chan bool),
Provider: &Bato{},
DbMgr: &db,
Mutex: &sync.Mutex{},
} }
server.loadCurr() c := make(chan os.Signal, 1)
go server.loadPrev() signal.Notify(c, os.Interrupt)
go server.loadNext()
go func() {
for range c {
Close(&db)
}
}()
server.LoadCurr()
go server.LoadPrev()
go server.LoadNext()
http.HandleFunc("/", server.HandleCurrent) http.HandleFunc("/", server.HandleCurrent)
http.HandleFunc("/img/{url}/", server.HandleImage) http.HandleFunc("/img/{url}/", server.HandleImage)
http.HandleFunc("POST /next", server.handleNext) http.HandleFunc("POST /next", server.HandleNext)
http.HandleFunc("POST /prev", server.handlePrev) http.HandleFunc("POST /prev", server.HandlePrev)
http.HandleFunc("POST /exit", func(_ http.ResponseWriter, _ *http.Request) {
Close(&db)
})
http.HandleFunc("/new/{title}/{chapter}", server.HandleNew) http.HandleFunc("/new/{title}/{chapter}", server.HandleNew)
fmt.Println("Server running") fmt.Println("Server starting...")
err := http.ListenAndServe(":8000", nil) err = http.ListenAndServe(":8000", nil)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
} }
func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) { func Close(db *DatabaseManager) {
u := r.PathValue("url") fmt.Println("Attempting to save and close DB")
buf := s.ImageBuffers[u] err := db.Save()
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)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
next, err := getNext(c) err = db.Close()
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)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
} os.Exit(0)
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
} }

306
server.go Normal file
View File

@@ -0,0 +1,306 @@
package main
import (
"bytes"
"fmt"
"html/template"
"net/http"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
type Server struct {
PrevViewModel *ImageViewModel
CurrViewModel *ImageViewModel
NextViewModel *ImageViewModel
ImageBuffers map[string]*bytes.Buffer
Mutex *sync.Mutex
NextSubUrl string
CurrSubUrl string
PrevSubUrl string
Provider Provider
IsFirst bool
IsLast bool
DbMgr *DatabaseManager
// 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 (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
u := r.PathValue("url")
s.Mutex.Lock()
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)
}
s.Mutex.Unlock()
}
func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
fmt.Println("Received Next")
if s.PrevViewModel != nil {
go func(viewModel ImageViewModel, s *Server) {
s.Mutex.Lock()
for _, img := range viewModel.Images {
delete(s.ImageBuffers, img.Path)
}
s.Mutex.Unlock()
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 := s.Provider.GetHtml(s.CurrSubUrl)
if err != nil {
fmt.Println(err)
return
}
next, err := s.Provider.GetNext(c)
if err != nil {
fmt.Println(err)
return
}
html, err := s.Provider.GetHtml(next)
if err != nil {
fmt.Println(err)
return
}
imagesNext, err := s.AppendImagesToBuf(html)
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 (s *Server) LoadPrev() {
c, err := s.Provider.GetHtml(s.CurrSubUrl)
if err != nil {
fmt.Println(err)
return
}
prev, err := s.Provider.GetPrev(c)
if err != nil {
fmt.Println(err)
return
}
html, err := s.Provider.GetHtml(prev)
if err != nil {
fmt.Println(err)
return
}
imagesNext, err := s.AppendImagesToBuf(html)
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) {
s.Mutex.Lock()
for _, img := range viewModel.Images {
delete(s.ImageBuffers, img.Path)
}
s.Mutex.Unlock()
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"))
mangaId, chapterId, err := getMangaIdAndChapterId(s.CurrSubUrl)
if err != nil {
fmt.Println(err)
} else {
title, chapter, err := getTitleAndChapter(s.CurrSubUrl)
if err != nil {
fmt.Println(err)
} else {
var manga *Manga
if s.DbMgr.Mangas[mangaId] == nil {
manga = &Manga{
Id: mangaId,
Title: title,
TimeStampUnix: time.Now().Unix(),
}
s.DbMgr.Mangas[mangaId] = manga
} else {
manga = s.DbMgr.Mangas[mangaId]
s.DbMgr.Mangas[mangaId].TimeStampUnix = time.Now().Unix()
}
if s.DbMgr.Chapters[chapterId] == nil {
chapterNumberStr := strings.Replace(chapter, "ch_", "", 1)
number, err := strconv.Atoi(chapterNumberStr)
if err != nil {
fmt.Println(err)
number = 0
}
s.DbMgr.Chapters[chapterId] = &Chapter{
Id: chapterId,
Manga: manga,
Url: s.CurrSubUrl,
Name: chapter,
Number: number,
TimeStampUnix: time.Now().Unix(),
}
} else {
s.DbMgr.Chapters[chapterId].TimeStampUnix = time.Now().Unix()
}
}
}
err = tmpl.Execute(w, s.CurrViewModel)
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.Mutex.Lock()
s.ImageBuffers = make(map[string]*bytes.Buffer)
s.Mutex.Unlock()
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 := s.Provider.GetHtml(s.CurrSubUrl)
if err != nil {
panic(err)
}
imagesCurr, err := s.AppendImagesToBuf(html)
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 (s *Server) AppendImagesToBuf(html string) ([]Image, error) {
imgList, err := s.Provider.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)
s.Mutex.Lock()
s.ImageBuffers[name] = buf
s.Mutex.Unlock()
images[i] = Image{Path: name, Index: i}
wg.Done()
}(i, url, &wg)
}
wg.Wait()
return images, nil
}

View File

@@ -18,8 +18,6 @@
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
border-width: 0; border-width: 0;
border-style: solid;
border-color: white;
} }
.scroll-container img { .scroll-container img {
@@ -84,6 +82,7 @@
<div class="center"> <div class="center">
<form method="post" action="/prev"> <form method="post" action="/prev">
<input type="submit" name="Prev" value="Prev" class="button-36"> <input type="submit" name="Prev" value="Prev" class="button-36">
<input type="submit" name="Exit" value="Exit" class="button-36" formaction="/exit">
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next"> <input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
</form> </form>
</div> </div>
@@ -95,6 +94,7 @@
<div class="center"> <div class="center">
<form method="post" action="/prev"> <form method="post" action="/prev">
<input type="submit" name="Prev" value="Prev" class="button-36"> <input type="submit" name="Prev" value="Prev" class="button-36">
<input type="submit" name="Exit" value="Exit" class="button-36" formaction="/exit">
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next"> <input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
</form> </form>
</div> </div>

64
util.go Normal file
View File

@@ -0,0 +1,64 @@
package main
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
)
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 getMangaIdAndChapterId(url string) (titleId int, chapterId int, err error) {
reg, err := regexp.Compile(`/title/(\d*)-.*?/(\d*)-.*`)
if err != nil {
return 0, 0, err
}
matches := reg.FindAllStringSubmatch(url, -1)
if len(matches) <= 0 {
return 0, 0, errors.New("no title or chapter found")
}
t, err := strconv.Atoi(matches[0][1])
if err != nil {
return 0, 0, err
}
c, err := strconv.Atoi(matches[0][2])
return t, c, err
}
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
}