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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
findings.txt
|
||||
test.txt
|
||||
h.html
|
||||
db.sqlite
|
||||
|
||||
83
bato.go
Normal file
83
bato.go
Normal 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":\[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 (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
15
createDb.sql
Normal 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
165
database.go
Normal 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
2
go.mod
@@ -1,3 +1,5 @@
|
||||
module mangaGetter
|
||||
|
||||
go 1.22
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.22
|
||||
|
||||
2
go.sum
2
go.sum
@@ -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
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)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
306
server.go
Normal file
306
server.go
Normal 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
|
||||
}
|
||||
@@ -18,8 +18,6 @@
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.scroll-container img {
|
||||
@@ -84,6 +82,7 @@
|
||||
<div class="center">
|
||||
<form method="post" action="/prev">
|
||||
<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">
|
||||
</form>
|
||||
</div>
|
||||
@@ -95,6 +94,7 @@
|
||||
<div class="center">
|
||||
<form method="post" action="/prev">
|
||||
<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">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
64
util.go
Normal file
64
util.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user