Support JNC Nina via dynamic domain, add optional Suffix for Series (used for language separation)

This commit is contained in:
Neshura 2025-02-19 23:20:24 +01:00
parent d828532239
commit 741eed00ac
Signed by: Neshura
GPG key ID: 4E2D47B1374C297D
2 changed files with 64 additions and 46 deletions

View file

@ -15,11 +15,6 @@ import (
"time"
)
const BaseUrl = "https://labs.j-novel.club/"
const ApiV1Url = BaseUrl + "app/v1/"
const ApiV2Url = BaseUrl + "app/v2/"
const FeedUrl = BaseUrl + "feed/"
type ApiFormat int
const (
@ -56,6 +51,8 @@ type Api struct {
_format ApiFormat
_library Library
_series map[string]SerieAugmented
_apiUrl string
_feedUrl string
}
type Auth struct {
@ -202,7 +199,8 @@ type Pagination struct {
LastPage bool `json:"lastPage"`
}
func NewJNC() Api {
func NewJNC(domain string) Api {
baseUrl := fmt.Sprintf("https://%s/", domain)
jncApi := Api{
_auth: Auth{
_username: "",
@ -211,6 +209,8 @@ func NewJNC() Api {
},
_library: Library{},
_series: map[string]SerieAugmented{},
_apiUrl: baseUrl + "app/v2/",
_feedUrl: baseUrl + "feed/",
}
return jncApi
}
@ -240,7 +240,7 @@ func (jncApi *Api) Login() error {
return err
}
res, err := http.Post(ApiV2Url+"auth/login?"+jncApi.ReturnFormat(), "application/json", bytes.NewBuffer(jsonAuthBody))
res, err := http.Post(jncApi._apiUrl+"auth/login?"+jncApi.ReturnFormat(), "application/json", bytes.NewBuffer(jsonAuthBody))
if err != nil {
return err
}
@ -275,7 +275,7 @@ func (jncApi *Api) AuthParam() string {
// Logout invalidates the auth token and resets the jnc instance to a blank slate. No information remains after calling.
func (jncApi *Api) Logout() error {
fmt.Println("Logging out...")
res, err := http.Post(ApiV2Url+"auth/logout?"+jncApi.ReturnFormat()+"&"+jncApi.AuthParam(), "application/json", nil)
res, err := http.Post(jncApi._apiUrl+"auth/logout?"+jncApi.ReturnFormat()+"&"+jncApi.AuthParam(), "application/json", nil)
if err != nil {
panic(err)
}
@ -305,7 +305,7 @@ func (jncApi *Api) SetUsername(username string) {
// FetchLibrary retrieves the list of Book's in the logged-in User's J-Novel Club library
func (jncApi *Api) FetchLibrary() error {
fmt.Println("Fetching library contents...")
res, err := http.Get(ApiV2Url + "me/library?" + jncApi.ReturnFormat() + "&" + jncApi.AuthParam())
res, err := http.Get(jncApi._apiUrl + "me/library?" + jncApi.ReturnFormat() + "&" + jncApi.AuthParam())
if err != nil {
return err
}
@ -364,7 +364,7 @@ func (jncApi *Api) FetchLibrarySeries() error {
fmt.Printf("\rFetching Series Info: %s/%s - %s%%", strconv.Itoa(progress), strconv.Itoa(len(jncApi._series)), strconv.FormatFloat(float64(progress)/float64(len(jncApi._series))*float64(100), 'f', 2, 32))
serie := jncApi._series[i]
res, err := http.Get(ApiV2Url + "series/" + serie.Info.Id + "?" + jncApi.ReturnFormat() + "&" + jncApi.AuthParam())
res, err := http.Get(jncApi._apiUrl + "series/" + serie.Info.Id + "?" + jncApi.ReturnFormat() + "&" + jncApi.AuthParam())
if err != nil {
return err
@ -394,7 +394,7 @@ func (jncApi *Api) FetchLibrarySeries() error {
// FetchVolumeInfo retrieves additional Volume Info that was not returned when retrieving the entire Library
func (jncApi *Api) FetchVolumeInfo(volume Volume) (Volume, error) {
fmt.Println("Fetching Volume details...")
res, err := http.Get(ApiV2Url + "volumes/" + volume.Id + "?" + jncApi.ReturnFormat() + "&" + jncApi.AuthParam())
res, err := http.Get(jncApi._apiUrl + "volumes/" + volume.Id + "?" + jncApi.ReturnFormat() + "&" + jncApi.AuthParam())
if err != nil {
return volume, err
}

88
main.go
View file

@ -6,6 +6,7 @@ import (
"bytes"
"container/list"
"encoding/xml"
"errors"
"fmt"
"github.com/beevik/etree"
"io"
@ -67,6 +68,14 @@ func ClearScreen() {
}
}
func GetArg(argKey string) (string, error) {
if slices.Contains(os.Args, argKey) {
idx := slices.Index(os.Args, argKey)
return os.Args[idx+1], nil
}
return "", errors.New("arg " + argKey + " not found")
}
func main() {
interactive := false
if slices.Contains(os.Args, "-I") {
@ -77,19 +86,27 @@ func main() {
panic("automatic mode is not implemented yet")
}
var downloadDir string
if slices.Contains(os.Args, "-d") {
idx := slices.Index(os.Args, "-d")
downloadDir = os.Args[idx+1]
if strings.LastIndex(downloadDir, "/") != len(downloadDir)-1 {
downloadDir = downloadDir + "/"
}
} else {
panic("working directory not specified")
domain, err := GetArg("-D")
if err != nil {
panic(err)
}
serLan, err := GetArg("-L")
if serLan != "" {
serLan = " " + serLan
}
downloadDir, err := GetArg("-d")
if err != nil {
panic(err)
}
if strings.LastIndex(downloadDir, "/") != len(downloadDir)-1 {
downloadDir = downloadDir + "/"
}
// initialize the J-Novel Club Instance
jnovel := jnc.NewJNC()
jnovel := jnc.NewJNC(domain)
var username string
if slices.Contains(os.Args, "-u") {
@ -109,7 +126,7 @@ func main() {
}
jnovel.SetPassword(password)
err := jnovel.Login()
err = jnovel.Login()
if err != nil {
panic(err)
}
@ -153,12 +170,12 @@ func main() {
serie := seriesList[s]
if mode == "3" || mode == "4" {
if serie.Info.Type == "MANGA" {
HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y")
HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y", serLan)
}
}
if mode == "3" || mode == "5" {
if serie.Info.Type == "NOVEL" {
HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y")
HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y", serLan)
}
}
}
@ -185,7 +202,7 @@ func main() {
serie := seriesList[seriesNumber-1]
if mode == "2" {
HandleSeries(jnovel, serie, downloadDir, false)
HandleSeries(jnovel, serie, downloadDir, false, serLan)
} else {
ClearScreen()
fmt.Println("\n###[Volume Selection]###")
@ -206,44 +223,44 @@ func main() {
}
volume := serie.Volumes[volumeNumber-1]
HandleVolume(jnovel, serie, volume, downloadDir)
HandleVolume(jnovel, serie, volume, downloadDir, serLan)
}
}
}
func HandleSeries(jnovel jnc.Api, serie jnc.SerieAugmented, downloadDir string, updatedOnly bool) {
func HandleSeries(jnovel jnc.Api, serie jnc.SerieAugmented, downloadDir string, updatedOnly bool, titleSuffix string) {
for v := range serie.Volumes {
volume := serie.Volumes[v]
if updatedOnly {
if len(volume.Downloads) != 0 && volume.UpdateAvailable() {
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
HandleVolume(jnovel, serie, volume, downloadDir)
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir, titleSuffix)
HandleVolume(jnovel, serie, volume, downloadDir, titleSuffix)
}
} else {
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
HandleVolume(jnovel, serie, volume, downloadDir)
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir, titleSuffix)
HandleVolume(jnovel, serie, volume, downloadDir, titleSuffix)
}
}
}
func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string) string {
func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string, titleSuffix string) string {
splits := strings.Split(downloadDir, "/")
// last element of this split is always empty due to the trailing slash
if splits[len(splits)-2] != serie.Info.Title {
if splits[len(splits)-2] != serie.Info.Title+titleSuffix {
if serie.Info.Title == "Ascendance of a Bookworm" {
partSplits := strings.Split(volume.Info.ShortTitle, " ")
part := " " + partSplits[0] + " " + partSplits[1]
if strings.Split(splits[len(splits)-2], " ")[0] == "Ascendance" {
splits[len(splits)-2] = serie.Info.Title + part
splits[len(splits)-2] = serie.Info.Title + part + titleSuffix
} else {
splits[len(splits)-1] = serie.Info.Title + part
splits[len(splits)-1] = serie.Info.Title + part + titleSuffix
splits = append(splits, "")
}
downloadDir = strings.Join(splits, "/")
} else {
downloadDir += serie.Info.Title + "/"
downloadDir += serie.Info.Title + titleSuffix + "/"
}
_, err := os.Stat(downloadDir)
@ -258,7 +275,7 @@ func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented,
return downloadDir
}
func HandleVolume(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string) {
func HandleVolume(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string, titleSuffix string) {
var downloadLink string
if len(volume.Downloads) == 0 {
fmt.Printf("Volume %s currently has no downloads available. Skipping \n", volume.Info.Title)
@ -270,10 +287,10 @@ func HandleVolume(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAug
downloadLink = volume.Downloads[0].Link
}
DownloadAndProcessEpub(jnovel, serie, volume, downloadLink, downloadDir)
DownloadAndProcessEpub(jnovel, serie, volume, downloadLink, downloadDir, titleSuffix)
}
func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadLink string, downloadDirectory string) {
func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadLink string, downloadDirectory string, titleSuffix string) {
FormatNavigationFile := map[string]string{
"manga": "item/nav.ncx",
"novel": "OEBPS/toc.ncx",
@ -344,7 +361,7 @@ func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc
fmt.Println("No chapters found, chapter name likely not supported")
}
basePath := downloadDirectory + volume.Info.Title + "/"
basePath := downloadDirectory + volume.Info.Title + titleSuffix + "/"
PrepareVolumeDirectory(basePath)
volume.Info, err = jnovel.FetchVolumeInfo(volume.Info)
if err != nil {
@ -427,7 +444,7 @@ func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc
}
}
comicInfo, err := GenerateChapterMetadata(volume, serie, len(chap.pages), language, chap.chDisplay)
comicInfo, err := GenerateChapterMetadata(volume, serie, len(chap.pages), language, chap.chDisplay, titleSuffix)
if err != nil {
fmt.Println(err)
}
@ -468,10 +485,10 @@ func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc
var number string
if serie.Info.Title == "Ascendance of a Bookworm" {
splits := strings.Split(volume.Info.ShortTitle, " ")
title = serie.Info.Title + " " + splits[0] + " " + splits[1]
title = serie.Info.Title + " " + splits[0] + " " + splits[1] + titleSuffix
number = splits[3]
} else {
title = serie.Info.Title
title = serie.Info.Title + titleSuffix
number = strconv.Itoa(volume.Info.Number)
}
@ -543,7 +560,7 @@ func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc
}
}
func GenerateChapterMetadata(volume jnc.VolumeAugmented, serie jnc.SerieAugmented, pageCount int, language string, chapterNumber string) ([]byte, error) {
func GenerateChapterMetadata(volume jnc.VolumeAugmented, serie jnc.SerieAugmented, pageCount int, language string, chapterNumber string, titleSuffix string) ([]byte, error) {
comicInfo := ComicInfo{
XMLName: "ComicInfo",
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
@ -554,7 +571,7 @@ func GenerateChapterMetadata(volume jnc.VolumeAugmented, serie jnc.SerieAugmente
sInfo := serie.Info
comicInfo.Series = sInfo.Title
comicInfo.Title = vInfo.Title
comicInfo.Title = vInfo.Title + titleSuffix
comicInfo.Number = chapterNumber
comicInfo.Volume = vInfo.Number
@ -796,8 +813,9 @@ func GetLastValidChapterNumber(currentChapter *list.Element) Chapter {
if chapterData.numberMain != -1 && chapterData.numberSub == 0 {
return chapterData
}
} else {
break
}
break
}
return currentChapter.Value.(Chapter)
}