diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/kavita-helper-go.iml b/.idea/kavita-helper-go.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/kavita-helper-go.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..9deb3c9
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MaterialThemeProjectNewConfig">
+    <option name="metadata">
+      <MTProjectMetadataState>
+        <option name="migrated" value="true" />
+        <option name="pristineConfig" value="false" />
+        <option name="userId" value="-349a7812:1900e527711:-7ffe" />
+      </MTProjectMetadataState>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..6cc55c5
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/kavita-helper-go.iml" filepath="$PROJECT_DIR$/.idea/kavita-helper-go.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..755b411
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module kavita-helper-go
+
+go 1.23
+
+require github.com/beevik/etree v1.5.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..92e3ab9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs=
+github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
diff --git a/jnc/jnc.go b/jnc/jnc.go
new file mode 100644
index 0000000..5f0a094
--- /dev/null
+++ b/jnc/jnc.go
@@ -0,0 +1,417 @@
+package jnc
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"sort"
+	"strconv"
+	"strings"
+	"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 (
+	JSON = iota
+	TEXT
+	PROTOBUF
+)
+
+var ApiFormatParam = map[ApiFormat]string{
+	JSON:     "format=json",
+	TEXT:     "format=text",
+	PROTOBUF: "",
+}
+
+type BookStatus string
+type PublishingStatus string
+type SeriesFormat string
+type DownloadType string
+
+type AuthBody struct {
+	Login    string `json:"login"`
+	Password string `json:"password"`
+	Slim     bool   `json:"slim"`
+}
+
+type JWT struct {
+	Token   string `json:"id"`
+	TTL     string `json:"ttl"`
+	Created string `json:"created"`
+}
+
+type Api struct {
+	_auth    Auth
+	_format  ApiFormat
+	_library Library
+	_series  map[string]SerieAugmented
+}
+
+type Auth struct {
+	_username string
+	_password string
+	_jwt      JWT
+}
+
+type Library struct {
+	Books      []Book     `json:"books"`
+	Pagination Pagination `json:"pagination"`
+}
+
+type Book struct {
+	Id           string     `json:"id"`
+	LegacyId     string     `json:"legacyId"`
+	Volume       Volume     `json:"volume"`
+	Serie        Serie      `json:"serie"`
+	Purchased    string     `json:"purchased"`
+	LastDownload string     `json:"lastDownload"`
+	LastUpdated  string     `json:"lastUpdated"`
+	Downloads    []Download `json:"downloads"`
+	Status       BookStatus `json:"status"`
+}
+
+type Volume struct {
+	Id                string   `json:"id"`
+	LegacyId          string   `json:"legacyId"`
+	Title             string   `json:"title"`
+	ShortTitle        string   `json:"shortTitle"`
+	OriginalTitle     string   `json:"originalTitle"`
+	Slug              string   `json:"slug"`
+	Number            int      `json:"number"`
+	OriginalPublisher string   `json:"originalPublisher"`
+	Label             string   `json:"label"`
+	Creators          []string `json:"creators"` // TODO: check, might not be correct
+	hidden            bool
+	ForumTopicId      int    `json:"forumTopicId"`
+	Created           string `json:"created"`
+	Publishing        string `json:"publishing"`
+	Description       string `json:"description"`
+	ShortDescription  string `json:"shortDescription"`
+	Cover             Cover  `json:"cover"`
+	Owned             bool   `json:"owned"`
+	PremiumExtras     string `json:"premiumExtras"`
+	NoEbook           bool   `json:"noEbook"`
+	TotalParts        int    `json:"totalParts"`
+	OnSale            bool   `json:"onSale"`
+}
+
+type VolumeAugmented struct {
+	Info       Volume
+	Downloads  []Download
+	updated    string
+	downloaded string
+}
+
+func (volume *VolumeAugmented) UpdateAvailable() bool {
+	if volume.downloaded == "" && len(volume.Downloads) != 0 {
+		return true // The file has never been downloaded but at least one is available
+	}
+	
+	downloadedTime, err := time.Parse(time.RFC3339, volume.downloaded)
+	if err != nil {
+		panic(err)
+	}
+
+	updatedTime, err := time.Parse(time.RFC3339, volume.updated)
+	if err != nil {
+		panic(err)
+	}
+
+	if downloadedTime.Before(updatedTime) {
+		return true
+	} else {
+		return false
+	}
+}
+
+type Cover struct {
+	OriginalUrl  string `json:"originalUrl"`
+	CoverUrl     string `json:"coverUrl"`
+	ThumbnailUrl string `json:"thumbnail"`
+}
+
+type Serie struct {
+	Id               string           `json:"id"`
+	LegacyId         string           `json:"legacyId"`
+	Type             SeriesFormat     `json:"type"`
+	Status           PublishingStatus `json:"status"`
+	Title            string           `json:"title"`
+	ShortTitle       string           `json:"shortTitle"`
+	OriginalTitle    string           `json:"originalTitle"`
+	Slug             string           `json:"slug"`
+	Hidden           bool             `json:"hidden"`
+	Created          string           `json:"created"`
+	Description      string           `json:"description"`
+	ShortDescription string           `json:"shortDescription"`
+	Tags             []string         `json:"tags"`
+	Cover            Cover            `json:"cover"`
+	Following        bool             `json:"following"`
+	Catchup          bool             `json:"catchup"`
+	Rentals          bool             `json:"rentals"`
+	TopicId          int              `json:"topicId"`
+	AgeRating        int              `json:"ageRating"`
+}
+
+type SerieAugmented struct {
+	Info    Serie             `json:"series"`
+	Volumes []VolumeAugmented `json:"volumes"`
+}
+
+type Download struct {
+	Link  string       `json:"link"`
+	Type  DownloadType `json:"type"`
+	Label string       `json:"label"`
+}
+
+type Pagination struct {
+	Limit    int  `json:"limit"`
+	Skip     int  `json:"skip"`
+	LastPage bool `json:"lastPage"`
+}
+
+func NewJNC() Api {
+	jncApi := Api{
+		_auth: Auth{
+			_username: "",
+			_password: "",
+			_jwt:      JWT{},
+		},
+		_library: Library{},
+		_series:  map[string]SerieAugmented{},
+	}
+	return jncApi
+}
+
+func (jncApi *Api) ReturnFormat() string {
+	return ApiFormatParam[jncApi._format]
+}
+
+// Login attempts a login using the username and password set with SetUsername and SetPassword
+func (jncApi *Api) Login() error {
+	fmt.Println("Logging in...")
+	if jncApi._auth._username == "" {
+		return errors.New("username not specified")
+	}
+	if jncApi._auth._password == "" {
+		return errors.New("password not specified")
+	}
+
+	authBody := AuthBody{
+		Login:    jncApi._auth._username,
+		Password: jncApi._auth._password,
+		Slim:     true,
+	}
+
+	jsonAuthBody, err := json.Marshal(authBody)
+	if err != nil {
+		return err
+	}
+
+	res, err := http.Post(ApiV2Url+"auth/login?"+jncApi.ReturnFormat(), "application/json", bytes.NewBuffer(jsonAuthBody))
+	if err != nil {
+		return err
+	}
+	if res.StatusCode != 200 && res.StatusCode != 201 {
+		return errors.New(res.Status)
+	}
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	err = json.Unmarshal(body, &jncApi._auth._jwt)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// LoginOTP starts an OTP based login using the username provided with SetUsername. NOTE: Currently not implemented
+func (jncApi *Api) LoginOTP() error {
+	if jncApi._auth._username == "" {
+		return errors.New("username not specified")
+	}
+
+	return errors.New("not implemented")
+}
+
+func (jncApi *Api) AuthParam() string {
+	return "access_token=" + jncApi._auth._jwt.Token
+}
+
+// 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)
+	if err != nil {
+		panic(err)
+	}
+
+	if res.StatusCode != 204 {
+		return errors.New(res.Status)
+	}
+
+	*jncApi = Api{
+		_auth:    Auth{},
+		_format:  JSON,
+		_library: Library{},
+		_series:  map[string]SerieAugmented{},
+	}
+
+	return nil
+}
+
+func (jncApi *Api) SetPassword(password string) {
+	jncApi._auth._password = password
+}
+
+func (jncApi *Api) SetUsername(username string) {
+	jncApi._auth._username = username
+}
+
+// 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())
+	if err != nil {
+		return err
+	}
+
+	if res.StatusCode != 200 {
+		return errors.New(res.Status)
+	}
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	err = json.Unmarshal(body, &jncApi._library)
+	if err != nil {
+		return err
+	}
+
+	for i := range jncApi._library.Books {
+		book := &jncApi._library.Books[i]
+
+		data, ok := jncApi._series[book.Serie.Id]
+		if ok {
+			data.Volumes = append(data.Volumes, VolumeAugmented{
+				Info:       book.Volume,
+				Downloads:  book.Downloads,
+				updated:    book.LastUpdated,
+				downloaded: book.LastDownload,
+			})
+			sort.Slice(data.Volumes, func(i, j int) bool {
+				return data.Volumes[i].Info.Number < data.Volumes[j].Info.Number
+			})
+			jncApi._series[book.Serie.Id] = data
+			continue
+		}
+		jncApi._series[book.Serie.Id] = SerieAugmented{
+			Info: book.Serie,
+			Volumes: []VolumeAugmented{
+				{
+					Info:       book.Volume,
+					Downloads:  book.Downloads,
+					updated:    book.LastUpdated,
+					downloaded: book.LastDownload,
+				},
+			},
+		}
+	}
+
+	return nil
+}
+
+// FetchLibrarySeries is only needed because for whatever reason the Endpoint used in FetchLibrary does not return the Series Tags
+func (jncApi *Api) FetchLibrarySeries() error {
+	progress := 1
+	fmt.Printf("Fetching Series Info: 0/%s - 0%", strconv.Itoa(len(jncApi._series)))
+	for i := range jncApi._series {
+		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())
+
+		if err != nil {
+			return err
+		}
+
+		if res.StatusCode != 200 {
+			return errors.New(res.Status)
+		}
+
+		body, err := io.ReadAll(res.Body)
+		if err != nil {
+			return err
+		}
+
+		err = json.Unmarshal(body, &serie)
+		if err != nil {
+			jncApi._series[i] = serie
+		}
+
+		progress++
+	}
+
+	return nil
+}
+
+// GetLibrarySeries returns a list of unique series found in the User's library. Tags are only included if FetchLibrarySeries was previously called.
+func (jncApi *Api) GetLibrarySeries() (seriesList []SerieAugmented, err error) {
+	arr := make([]SerieAugmented, 0, len(jncApi._series))
+	for s := range jncApi._series {
+		arr = append(arr, jncApi._series[s])
+	}
+
+	sort.Slice(arr, func(i, j int) bool {
+		return arr[i].Info.Title < arr[j].Info.Title
+	})
+
+	return arr, nil
+}
+
+func (jncApi *Api) Download(link string, targetDir string) (name string, err error) {
+	fmt.Printf("Downloading %s...", link)
+	res, err := http.Get(link)
+	if err != nil {
+		return "", err
+	}
+	defer func(Body io.ReadCloser) {
+		_ = Body.Close()
+	}(res.Body)
+
+	if res.StatusCode != 200 {
+		return "", errors.New(res.Status)
+	}
+
+	filePath := targetDir + strings.Trim(strings.Split(res.Header["Content-Disposition"][0], "=")[1], "\"")
+
+	file, err := os.Create(filePath)
+	if err != nil {
+		return "", err
+	}
+	defer func(file *os.File) {
+		err := file.Close()
+		if err != nil {
+			panic(err)
+		}
+	}(file)
+
+	_, err = io.Copy(file, res.Body)
+	if err != nil {
+		return "", err
+	}
+
+	return filePath, nil
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..f05a8ad
--- /dev/null
+++ b/main.go
@@ -0,0 +1,596 @@
+package main
+
+import (
+	"archive/zip"
+	"bufio"
+	"container/list"
+	"fmt"
+	"github.com/beevik/etree"
+	"io"
+	"kavita-helper-go/jnc"
+	"os"
+	"os/exec"
+	"regexp"
+	"slices"
+	"strconv"
+	"strings"
+)
+
+type Chapter struct {
+	numberMain int8
+	numberSub  int8 // uses 0 for regular main chapters
+	chDisplay  string
+	title      string
+	firstPage  string
+	lastPage   string
+}
+
+type FileWrapper struct {
+	fileName string
+	file     zip.File
+	fileSet  bool
+}
+
+func NewFileWrapper() FileWrapper {
+	newFileWrapper := FileWrapper{}
+	newFileWrapper.fileSet = false
+	newFileWrapper.fileName = ""
+	return newFileWrapper
+}
+
+// TODO's
+// [ ] Create Chapter .cbz's based on given Information (no ComicInfo.xml yet)
+// 		[ ] Sort Into Volume Folders (requires following step)
+// [ ] Get ComicInfo.xml data from the J-Novel Club API
+// [ ] Get the Manga from the J-Novel Club API
+
+func GetUserInput(prompt string) string {
+	reader := bufio.NewScanner(os.Stdin)
+	fmt.Print(prompt)
+	reader.Scan()
+	err := reader.Err()
+	if err != nil {
+		panic(err)
+	}
+	return reader.Text()
+}
+
+func ClearScreen() {
+	cmd := exec.Command("clear")
+	cmd.Stdout = os.Stdout
+	if err := cmd.Run(); err != nil {
+		fmt.Println("[ERROR] - ", err)
+	}
+}
+
+func main() {
+	downloadDir := "/home/neshura/Documents/Go Workspaces/Kavita Helper/"
+
+	// initialize the J-Novel Club Instance
+	jnovel := jnc.NewJNC()
+
+	var username string
+	if slices.Contains(os.Args, "-u") {
+		idx := slices.Index(os.Args, "-u")
+		username = os.Args[idx+1]
+	} else {
+		username = GetUserInput("Enter Username: ")
+	}
+	jnovel.SetUsername(username)
+
+	var password string
+	if slices.Contains(os.Args, "-p") {
+		idx := slices.Index(os.Args, "-p")
+		password = os.Args[idx+1]
+	} else {
+		password = GetUserInput("Enter Password: ")
+	}
+	jnovel.SetPassword(password)
+
+	err := jnovel.Login()
+	if err != nil {
+		panic(err)
+	}
+	defer func(jnovel *jnc.Api) {
+		err := jnovel.Logout()
+		if err != nil {
+			panic(err)
+		}
+	}(&jnovel)
+
+	err = jnovel.FetchLibrary()
+	if err != nil {
+		panic(err)
+	}
+
+	err = jnovel.FetchLibrarySeries()
+	if err != nil {
+		panic(err)
+	}
+
+	seriesList, err := jnovel.GetLibrarySeries()
+	if err != nil {
+		panic(err)
+	}
+
+	ClearScreen()
+
+	fmt.Println("[1] - Download Single Volume")
+	fmt.Println("[2] - Download Entire Series")
+	fmt.Println("[3] - Download Entire Library")
+	fmt.Println("[4] - Download Entire Library [MANGA only]")
+	fmt.Println("[5] - Download Entire Library [NOVEL only]")
+	mode := GetUserInput("Select Mode: ")
+
+	ClearScreen()
+
+	if mode == "3" || mode == "4" || mode == "5" {
+		updatedOnly := GetUserInput("Download Only Where Newer Files Are Available? [Y/N]: ")
+
+		for s := range seriesList {
+			serie := seriesList[s]
+			if mode == "3" || mode == "4" {
+				if serie.Info.Type == "MANGA" {
+					HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y")
+				}
+			}
+			if mode == "3" || mode == "5" {
+				if serie.Info.Type == "NOVEL" {
+					HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y")
+				}
+			}
+		}
+		fmt.Println("Done")
+	} else {
+		fmt.Println("\n###[Series Selection]###")
+		for s := range seriesList {
+			serie := seriesList[s].Info
+
+			fmt.Printf("[%s] - [%s] - %s\n", strconv.Itoa(s+1), serie.Type, serie.Title)
+		}
+
+		msg := "Enter Series Number: "
+		var seriesNumber int
+		for {
+			selection := GetUserInput(msg)
+			seriesNumber, err = strconv.Atoi(selection)
+			if err != nil {
+				msg = "\rInvalid. Enter VALID Series Number: "
+			}
+			break
+		}
+
+		serie := seriesList[seriesNumber-1]
+
+		if mode == "2" {
+			HandleSeries(jnovel, serie, downloadDir, false)
+		} else {
+			ClearScreen()
+			fmt.Println("\n###[Volume Selection]###")
+			for i := range serie.Volumes {
+				vol := serie.Volumes[i]
+				fmt.Printf("[%s] - %s\n", strconv.Itoa(i+1), vol.Info.Title)
+			}
+
+			msg = "Enter Volume Number: "
+			var volumeNumber int
+			for {
+				selection := GetUserInput(msg)
+				volumeNumber, err = strconv.Atoi(selection)
+				if err != nil {
+					msg = "\rInvalid. Enter VALID Volume Number: "
+				}
+				break
+			}
+
+			volume := serie.Volumes[volumeNumber-1]
+			HandleVolume(jnovel, serie, volume, downloadDir)
+		}
+	}
+}
+
+func HandleSeries(jnovel jnc.Api, serie jnc.SerieAugmented, downloadDir string, updatedOnly bool) {
+	for v := range serie.Volumes {
+		volume := serie.Volumes[v]
+		if len(volume.Downloads) != 0 && volume.UpdateAvailable() {
+			downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
+			HandleVolume(jnovel, serie, volume, downloadDir)
+		}
+	}
+}
+
+func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir 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 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
+			} else {
+				splits[len(splits)-1] = serie.Info.Title + part
+				splits = append(splits, "")
+			}
+			downloadDir = strings.Join(splits, "/")
+		} else {
+			downloadDir += serie.Info.Title + "/"
+		}
+
+		_, err := os.Stat(downloadDir)
+		if err != nil {
+			err = os.Mkdir(downloadDir, 0755)
+			if err != nil {
+				panic(err)
+			}
+		}
+	}
+
+	return downloadDir
+}
+
+func HandleVolume(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string) {
+	var downloadLink string
+	if len(volume.Downloads) == 0 {
+		fmt.Printf("Volume %s currently has no downloads available. Skipping \n", volume.Info.Title)
+		return
+	} else if len(volume.Downloads) > 1 {
+		fmt.Println("TODO: implement download selection/default select highest quality")
+		downloadLink = volume.Downloads[len(volume.Downloads)-1].Link
+	} else {
+		downloadLink = volume.Downloads[0].Link
+	}
+
+	DownloadAndProcessEpub(jnovel, serie, volume, downloadLink, downloadDir)
+}
+
+func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadLink string, downloadDirectory string) {
+	FormatNavigationFile := map[string]string{
+		"manga": "item/nav.ncx",
+		"novel": "OEBPS/toc.ncx",
+	}
+
+	FormatMetadataName := map[string]string{
+		"item/comic.opf":    "manga",
+		"OEBPS/content.opf": "novel",
+	}
+
+	MetaInf := "META-INF/container.xml"
+	file, err := jnovel.Download(downloadLink, downloadDirectory)
+	if err != nil {
+		panic(err)
+	}
+
+	epub, err := zip.OpenReader(file)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	metadata := NewFileWrapper()
+	navigation := NewFileWrapper()
+
+	for i := range epub.Reader.File {
+		file := epub.Reader.File[i]
+
+		if file.Name == MetaInf {
+			doc := etree.NewDocument()
+			reader, err := file.Open()
+			if err != nil {
+				fmt.Println(err)
+			}
+
+			if _, err := doc.ReadFrom(reader); err != nil {
+				_ = reader.Close()
+				panic(err)
+			}
+
+			metadata.fileName = doc.FindElement("//container/rootfiles/rootfile").SelectAttr("full-path").Value
+			navigation.fileName = FormatNavigationFile[FormatMetadataName[metadata.fileName]]
+		}
+
+		if len(metadata.fileName) != 0 && file.Name == metadata.fileName {
+			metadata.file = *file
+			metadata.fileSet = true
+		}
+
+		if len(navigation.fileName) != 0 && file.Name == navigation.fileName {
+			navigation.file = *file
+			navigation.fileSet = true
+		}
+
+		if metadata.fileSet && navigation.fileSet {
+			println("breaking")
+			break
+		}
+	}
+
+	// Switch based on metadata file
+
+	switch FormatMetadataName[metadata.fileName] {
+	case "manga":
+		{
+			chapterList := GenerateMangaChapterList(navigation)
+
+			for ch := chapterList.Front(); ch != nil; ch = ch.Next() {
+				chap := ch.Value.(Chapter)
+				fmt.Printf("[%s] %s: %s - %s\n", chap.chDisplay, chap.title, chap.firstPage, chap.lastPage)
+			}
+		}
+	case "novel":
+		{
+			CalibreSeriesAttr := "calibre:series"
+			CalibreSeriesIndexAttr := "calibre:series_index"
+			EpubTitleTag := "//package/metadata"
+
+			metafile, err := metadata.file.Open()
+			if err != nil {
+				fmt.Println(err)
+			}
+
+			doc := etree.NewDocument()
+			if _, err = doc.ReadFrom(metafile); err != nil {
+				fmt.Println(err)
+			}
+
+			idx := doc.FindElement(EpubTitleTag + "/dc:title").Index()
+
+			seriesTitle := etree.NewElement("meta")
+			seriesTitle.CreateAttr("name", CalibreSeriesAttr)
+			var title string
+			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]
+				number = splits[3]
+			} else {
+				title = serie.Info.Title
+				number = strconv.Itoa(volume.Info.Number)
+			}
+
+			seriesTitle.CreateAttr("content", title)
+
+			seriesNumber := etree.NewElement("meta")
+			seriesNumber.CreateAttr("name", CalibreSeriesIndexAttr)
+			seriesNumber.CreateAttr("content", number)
+
+			doc.FindElement(EpubTitleTag).InsertChildAt(idx+1, seriesTitle)
+			doc.FindElement(EpubTitleTag).InsertChildAt(idx+2, seriesNumber)
+			doc.Indent(2)
+
+			// Open the zip file for writing
+			zipfile, err := os.OpenFile(file+".new", os.O_RDWR|os.O_CREATE, 0644)
+			if err != nil {
+				panic(err)
+			}
+			defer func(zipfile *os.File) {
+				err := zipfile.Close()
+				if err != nil {
+					panic(err)
+				}
+			}(zipfile)
+
+			zipWriter := zip.NewWriter(zipfile)
+
+			str, err := doc.WriteToString()
+			if err != nil {
+				panic(err)
+			}
+
+			metawriter, err := zipWriter.Create(metadata.fileName)
+			if err != nil {
+				panic(err)
+			}
+
+			_, err = metawriter.Write([]byte(str))
+			if err != nil {
+				panic(err)
+			}
+
+			for _, f := range epub.File {
+				if f.Name != metadata.fileName {
+					writer, err := zipWriter.Create(f.Name)
+					if err != nil {
+						panic(err)
+					}
+					reader, err := f.Open()
+					if err != nil {
+						panic(err)
+					}
+
+					_, err = io.Copy(writer, reader)
+					if err != nil {
+						_ = reader.Close()
+						panic(err)
+					}
+				}
+			}
+
+			epub.Close()
+			zipWriter.Close()
+			source, _ := os.Open(file + ".new")
+			dest, _ := os.Create(file)
+			io.Copy(dest, source)
+			os.Remove(file + ".new")
+		}
+	}
+}
+
+func FinalizeChapters(chapters *list.List, pageList []*etree.Element) *list.List {
+	fmt.Println("Finalizing Chapters")
+
+	lastPage := pageList[len(pageList)-1].SelectAttr("src").Value
+
+	sortedChapters := list.New()
+
+	for ch := chapters.Back(); ch != nil; ch = ch.Prev() {
+		var newChapterData Chapter
+		currentChapter := ch.Value.(Chapter)
+
+		if currentChapter.numberMain != -1 {
+			newChapterData.title = currentChapter.title
+			newChapterData.firstPage = currentChapter.firstPage
+		} else {
+			mainChapter := GetLastValidChapterNumber(ch)
+			for sch := sortedChapters.Front(); sch != nil; sch = sch.Next() {
+				if sch.Value.(Chapter).title == mainChapter.title {
+					newChapterData = sch.Value.(Chapter)
+					newChapterData.firstPage = currentChapter.firstPage
+					sch.Value = newChapterData
+					break
+				}
+			}
+			continue
+		}
+
+		if currentChapter.numberSub != 0 {
+			numberMain, numberSub := AnalyzeMainChapter(ch)
+
+			newChapterData.numberMain = numberMain
+			newChapterData.numberSub = numberSub
+		} else {
+			newChapterData.numberMain = currentChapter.numberMain
+			newChapterData.numberSub = currentChapter.numberSub
+		}
+
+		if ch.Next() == nil {
+			newChapterData.lastPage = lastPage
+		} else {
+			var lastChapterPage string
+			for p := range pageList {
+				page := pageList[p]
+				if page.SelectAttr("src").Value == ch.Next().Value.(Chapter).firstPage {
+					lastChapterPage = pageList[p-1].SelectAttr("src").Value
+				}
+			}
+			newChapterData.lastPage = lastChapterPage
+		}
+
+		if newChapterData.numberSub != 0 {
+			newChapterData.chDisplay = strconv.FormatInt(int64(newChapterData.numberMain), 10) + "." + strconv.FormatInt(int64(newChapterData.numberSub), 10)
+		} else {
+			newChapterData.chDisplay = strconv.FormatInt(int64(newChapterData.numberMain), 10)
+		}
+
+		sortedChapters.PushFront(newChapterData)
+	}
+
+	return sortedChapters
+}
+
+func AnalyzeMainChapter(currentChapter *list.Element) (int8, int8) {
+	var currentChapterNumber int8
+	subChapterCount := 1
+
+	if currentChapter.Next() != nil {
+		ch := currentChapter.Next()
+		for {
+			if ch.Value.(Chapter).numberSub != 0 {
+				subChapterCount++
+				if ch.Next() != nil {
+					ch = ch.Next()
+				} else {
+					break
+				}
+			} else {
+				break
+			}
+		}
+	}
+
+	if currentChapter.Prev() != nil {
+		ch := currentChapter.Prev()
+		// Then Get the Current Main Chapter
+		for {
+			if ch.Value.(Chapter).numberSub != 0 {
+				subChapterCount++
+				if ch.Prev() != nil {
+					ch = ch.Prev()
+				} else {
+					break
+				}
+			} else {
+				subChapterCount++ // actual Chapter also counts in this case
+				currentChapterNumber = ch.Value.(Chapter).numberMain
+				break
+			}
+		}
+	}
+
+	// Calculate integer sub number based on total subChapterC
+	subChapterNumber := int8((float32(currentChapter.Value.(Chapter).numberSub) / float32(subChapterCount)) * 10)
+
+	// return main Chapter Number + CurrentChapter Sub Number
+	return currentChapterNumber, subChapterNumber
+}
+
+func GetLastValidChapterNumber(currentChapter *list.Element) Chapter {
+	for {
+		if currentChapter.Next() != nil {
+			currentChapter = currentChapter.Next()
+			chapterData := currentChapter.Value.(Chapter)
+			if chapterData.numberMain != -1 && chapterData.numberSub == 0 {
+				return chapterData
+			}
+		}
+		break
+	}
+	return currentChapter.Value.(Chapter)
+}
+
+func GenerateMangaChapterList(navigation FileWrapper) *list.List {
+	reader, err := navigation.file.Open()
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	doc := etree.NewDocument()
+	if _, err = doc.ReadFrom(reader); err != nil {
+		fmt.Println(err)
+	}
+
+	navMap := doc.FindElement("//ncx/navMap")
+	navChapters := navMap.FindElements("//navPoint")
+	chapters := list.New()
+
+	pageList := doc.FindElements("//ncx/pageList/pageTarget/content")
+
+	lastMainChapter := int8(-1)
+	subChapter := int8(0)
+
+	for c := range len(navChapters) {
+		navChapter := navChapters[c]
+		label := navChapter.FindElement("./navLabel/text").Text()
+		page := navChapter.FindElement("./content")
+
+		regex := "Chapter ([0-9]*)"
+		match, _ := regexp.MatchString(regex, label)
+
+		if match {
+			r, _ := regexp.Compile(regex)
+			num := r.FindStringSubmatch(label)[1]
+			parse, _ := strconv.ParseInt(num, 10, 8)
+			lastMainChapter = int8(parse)
+			subChapter = int8(0)
+		} else {
+			if lastMainChapter == -1 {
+				subChapter -= 1
+			} else {
+				subChapter += 1
+			}
+		}
+
+		chapterData := Chapter{
+			numberMain: lastMainChapter,
+			numberSub:  subChapter,
+			chDisplay:  "",
+			title:      label,
+			firstPage:  page.SelectAttr("src").Value,
+			lastPage:   "",
+		}
+
+		chapters.PushBack(chapterData)
+	}
+
+	return FinalizeChapters(chapters, pageList)
+}