Support JNC Nina via dynamic domain, add optional Suffix for Series (used for language separation)
This commit is contained in:
parent
d828532239
commit
741eed00ac
2 changed files with 64 additions and 46 deletions
22
jnc/jnc.go
22
jnc/jnc.go
|
@ -15,11 +15,6 @@ import (
|
||||||
"time"
|
"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
|
type ApiFormat int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -56,6 +51,8 @@ type Api struct {
|
||||||
_format ApiFormat
|
_format ApiFormat
|
||||||
_library Library
|
_library Library
|
||||||
_series map[string]SerieAugmented
|
_series map[string]SerieAugmented
|
||||||
|
_apiUrl string
|
||||||
|
_feedUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
|
@ -202,7 +199,8 @@ type Pagination struct {
|
||||||
LastPage bool `json:"lastPage"`
|
LastPage bool `json:"lastPage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewJNC() Api {
|
func NewJNC(domain string) Api {
|
||||||
|
baseUrl := fmt.Sprintf("https://%s/", domain)
|
||||||
jncApi := Api{
|
jncApi := Api{
|
||||||
_auth: Auth{
|
_auth: Auth{
|
||||||
_username: "",
|
_username: "",
|
||||||
|
@ -211,6 +209,8 @@ func NewJNC() Api {
|
||||||
},
|
},
|
||||||
_library: Library{},
|
_library: Library{},
|
||||||
_series: map[string]SerieAugmented{},
|
_series: map[string]SerieAugmented{},
|
||||||
|
_apiUrl: baseUrl + "app/v2/",
|
||||||
|
_feedUrl: baseUrl + "feed/",
|
||||||
}
|
}
|
||||||
return jncApi
|
return jncApi
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ func (jncApi *Api) Login() error {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
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.
|
// Logout invalidates the auth token and resets the jnc instance to a blank slate. No information remains after calling.
|
||||||
func (jncApi *Api) Logout() error {
|
func (jncApi *Api) Logout() error {
|
||||||
fmt.Println("Logging out...")
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
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
|
// FetchLibrary retrieves the list of Book's in the logged-in User's J-Novel Club library
|
||||||
func (jncApi *Api) FetchLibrary() error {
|
func (jncApi *Api) FetchLibrary() error {
|
||||||
fmt.Println("Fetching library contents...")
|
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 {
|
if err != nil {
|
||||||
return err
|
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))
|
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]
|
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 {
|
if err != nil {
|
||||||
return err
|
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
|
// FetchVolumeInfo retrieves additional Volume Info that was not returned when retrieving the entire Library
|
||||||
func (jncApi *Api) FetchVolumeInfo(volume Volume) (Volume, error) {
|
func (jncApi *Api) FetchVolumeInfo(volume Volume) (Volume, error) {
|
||||||
fmt.Println("Fetching Volume details...")
|
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 {
|
if err != nil {
|
||||||
return volume, err
|
return volume, err
|
||||||
}
|
}
|
||||||
|
|
88
main.go
88
main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"container/list"
|
"container/list"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"io"
|
"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() {
|
func main() {
|
||||||
interactive := false
|
interactive := false
|
||||||
if slices.Contains(os.Args, "-I") {
|
if slices.Contains(os.Args, "-I") {
|
||||||
|
@ -77,19 +86,27 @@ func main() {
|
||||||
panic("automatic mode is not implemented yet")
|
panic("automatic mode is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadDir string
|
domain, err := GetArg("-D")
|
||||||
if slices.Contains(os.Args, "-d") {
|
if err != nil {
|
||||||
idx := slices.Index(os.Args, "-d")
|
panic(err)
|
||||||
downloadDir = os.Args[idx+1]
|
}
|
||||||
if strings.LastIndex(downloadDir, "/") != len(downloadDir)-1 {
|
|
||||||
downloadDir = downloadDir + "/"
|
serLan, err := GetArg("-L")
|
||||||
}
|
if serLan != "" {
|
||||||
} else {
|
serLan = " " + serLan
|
||||||
panic("working directory not specified")
|
}
|
||||||
|
|
||||||
|
downloadDir, err := GetArg("-d")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.LastIndex(downloadDir, "/") != len(downloadDir)-1 {
|
||||||
|
downloadDir = downloadDir + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the J-Novel Club Instance
|
// initialize the J-Novel Club Instance
|
||||||
jnovel := jnc.NewJNC()
|
jnovel := jnc.NewJNC(domain)
|
||||||
|
|
||||||
var username string
|
var username string
|
||||||
if slices.Contains(os.Args, "-u") {
|
if slices.Contains(os.Args, "-u") {
|
||||||
|
@ -109,7 +126,7 @@ func main() {
|
||||||
}
|
}
|
||||||
jnovel.SetPassword(password)
|
jnovel.SetPassword(password)
|
||||||
|
|
||||||
err := jnovel.Login()
|
err = jnovel.Login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -153,12 +170,12 @@ func main() {
|
||||||
serie := seriesList[s]
|
serie := seriesList[s]
|
||||||
if mode == "3" || mode == "4" {
|
if mode == "3" || mode == "4" {
|
||||||
if serie.Info.Type == "MANGA" {
|
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 mode == "3" || mode == "5" {
|
||||||
if serie.Info.Type == "NOVEL" {
|
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]
|
serie := seriesList[seriesNumber-1]
|
||||||
|
|
||||||
if mode == "2" {
|
if mode == "2" {
|
||||||
HandleSeries(jnovel, serie, downloadDir, false)
|
HandleSeries(jnovel, serie, downloadDir, false, serLan)
|
||||||
} else {
|
} else {
|
||||||
ClearScreen()
|
ClearScreen()
|
||||||
fmt.Println("\n###[Volume Selection]###")
|
fmt.Println("\n###[Volume Selection]###")
|
||||||
|
@ -206,44 +223,44 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
volume := serie.Volumes[volumeNumber-1]
|
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 {
|
for v := range serie.Volumes {
|
||||||
volume := serie.Volumes[v]
|
volume := serie.Volumes[v]
|
||||||
if updatedOnly {
|
if updatedOnly {
|
||||||
if len(volume.Downloads) != 0 && volume.UpdateAvailable() {
|
if len(volume.Downloads) != 0 && volume.UpdateAvailable() {
|
||||||
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
|
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir, titleSuffix)
|
||||||
HandleVolume(jnovel, serie, volume, downloadDir)
|
HandleVolume(jnovel, serie, volume, downloadDir, titleSuffix)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
|
downloadDir = PrepareSerieDirectory(serie, volume, downloadDir, titleSuffix)
|
||||||
HandleVolume(jnovel, serie, volume, downloadDir)
|
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, "/")
|
splits := strings.Split(downloadDir, "/")
|
||||||
// last element of this split is always empty due to the trailing slash
|
// 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" {
|
if serie.Info.Title == "Ascendance of a Bookworm" {
|
||||||
partSplits := strings.Split(volume.Info.ShortTitle, " ")
|
partSplits := strings.Split(volume.Info.ShortTitle, " ")
|
||||||
part := " " + partSplits[0] + " " + partSplits[1]
|
part := " " + partSplits[0] + " " + partSplits[1]
|
||||||
|
|
||||||
if strings.Split(splits[len(splits)-2], " ")[0] == "Ascendance" {
|
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 {
|
} else {
|
||||||
splits[len(splits)-1] = serie.Info.Title + part
|
splits[len(splits)-1] = serie.Info.Title + part + titleSuffix
|
||||||
splits = append(splits, "")
|
splits = append(splits, "")
|
||||||
}
|
}
|
||||||
downloadDir = strings.Join(splits, "/")
|
downloadDir = strings.Join(splits, "/")
|
||||||
} else {
|
} else {
|
||||||
downloadDir += serie.Info.Title + "/"
|
downloadDir += serie.Info.Title + titleSuffix + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := os.Stat(downloadDir)
|
_, err := os.Stat(downloadDir)
|
||||||
|
@ -258,7 +275,7 @@ func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented,
|
||||||
return downloadDir
|
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
|
var downloadLink string
|
||||||
if len(volume.Downloads) == 0 {
|
if len(volume.Downloads) == 0 {
|
||||||
fmt.Printf("Volume %s currently has no downloads available. Skipping \n", volume.Info.Title)
|
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
|
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{
|
FormatNavigationFile := map[string]string{
|
||||||
"manga": "item/nav.ncx",
|
"manga": "item/nav.ncx",
|
||||||
"novel": "OEBPS/toc.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")
|
fmt.Println("No chapters found, chapter name likely not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
basePath := downloadDirectory + volume.Info.Title + "/"
|
basePath := downloadDirectory + volume.Info.Title + titleSuffix + "/"
|
||||||
PrepareVolumeDirectory(basePath)
|
PrepareVolumeDirectory(basePath)
|
||||||
volume.Info, err = jnovel.FetchVolumeInfo(volume.Info)
|
volume.Info, err = jnovel.FetchVolumeInfo(volume.Info)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -468,10 +485,10 @@ func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc
|
||||||
var number string
|
var number string
|
||||||
if serie.Info.Title == "Ascendance of a Bookworm" {
|
if serie.Info.Title == "Ascendance of a Bookworm" {
|
||||||
splits := strings.Split(volume.Info.ShortTitle, " ")
|
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]
|
number = splits[3]
|
||||||
} else {
|
} else {
|
||||||
title = serie.Info.Title
|
title = serie.Info.Title + titleSuffix
|
||||||
number = strconv.Itoa(volume.Info.Number)
|
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{
|
comicInfo := ComicInfo{
|
||||||
XMLName: "ComicInfo",
|
XMLName: "ComicInfo",
|
||||||
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
|
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
@ -554,7 +571,7 @@ func GenerateChapterMetadata(volume jnc.VolumeAugmented, serie jnc.SerieAugmente
|
||||||
sInfo := serie.Info
|
sInfo := serie.Info
|
||||||
|
|
||||||
comicInfo.Series = sInfo.Title
|
comicInfo.Series = sInfo.Title
|
||||||
comicInfo.Title = vInfo.Title
|
comicInfo.Title = vInfo.Title + titleSuffix
|
||||||
comicInfo.Number = chapterNumber
|
comicInfo.Number = chapterNumber
|
||||||
comicInfo.Volume = vInfo.Number
|
comicInfo.Volume = vInfo.Number
|
||||||
|
|
||||||
|
@ -796,8 +813,9 @@ func GetLastValidChapterNumber(currentChapter *list.Element) Chapter {
|
||||||
if chapterData.numberMain != -1 && chapterData.numberSub == 0 {
|
if chapterData.numberMain != -1 && chapterData.numberSub == 0 {
|
||||||
return chapterData
|
return chapterData
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
return currentChapter.Value.(Chapter)
|
return currentChapter.Value.(Chapter)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue