Commit all code. Should be mostly functional.
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/go,goland
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=go,goland
|
||||
|
||||
### Go ###
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
### GoLand ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### GoLand Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# Cache file creation bug
|
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
# CodeStream plugin
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/go,goland
|
||||
|
||||
manga/*
|
||||
Generated
+8
@@ -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
|
||||
Generated
+9
@@ -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>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/opds-pse-server.iml" filepath="$PROJECT_DIR$/.idea/opds-pse-server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Shane Clark
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,30 @@
|
||||
# go-opds-pse
|
||||
|
||||
A tiny lightweight OPDS server with PSE support. Mainly written for those using Panels on iOS.
|
||||
|
||||
### Usage
|
||||
To use this, just download the [Latest Release](https://github.com/sclark-dev/go-opds-pse/releases/latest) and run it via terminal.
|
||||
|
||||
```shell
|
||||
go-opds-pse -m "./manga/" serve -p 8080
|
||||
```
|
||||
|
||||
### Building
|
||||
To build this you'll want the latest version of Go. Doesn't use any external libraries.
|
||||
|
||||
### Attributions
|
||||
Thanks to [dir2opds](https://github.com/dubyte/dir2opds) for the inspiration for this.
|
||||
|
||||
### FAQs
|
||||
- Why Go?
|
||||
|
||||
Well, Go is just very memory efficient. Yes, writing this in something like C would be even better, but that's a pain. My choice for Go was a cross-platform server that can be run on embedded hardware for local access to your comic/manga collection.
|
||||
|
||||
- Is there a Web UI for this?
|
||||
|
||||
No. This is designed to be a very lightweight server and a UI would add a lot of bulk. However since this server just serves a basic OPDS feed, there should be plenty of clients available. Alas, the PSE support is kinda lacking. It seems like there just isn't much of a market for that.
|
||||
|
||||
- Does streaming use more bandwidth?
|
||||
|
||||
Not really. While CBZ files are compressed, *generally* the bandwidth saved by compressing JPGs and non-lossless PNGs is pretty minimal. In fact, I don'
|
||||
t compress my CBZ files at all. The few hundred kilobytes it saves just isn't enough to justify the extra battery usage to decompress it on a mobile device.
|
||||
@@ -0,0 +1,19 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
func IntFlag(name string, value int, usage string) *cli.IntFlag {
|
||||
return &cli.IntFlag{
|
||||
Name: name,
|
||||
Usage: usage,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func StringFlag(name string, value string, usage string) *cli.StringFlag {
|
||||
return &cli.StringFlag{
|
||||
Name: name,
|
||||
Usage: usage,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/urfave/cli/v2"
|
||||
"net/http"
|
||||
"opds-pse-server/app/routes"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Serve = cli.Command{
|
||||
Name: "serve",
|
||||
Usage: "start the http server",
|
||||
Flags: []cli.Flag{
|
||||
IntFlag("port, p", 3000, "web server port"),
|
||||
},
|
||||
Action: runServe,
|
||||
}
|
||||
|
||||
func runServe(ctx *cli.Context) error {
|
||||
r := mux.NewRouter()
|
||||
|
||||
fs := http.FileServer(http.Dir("manga/"))
|
||||
r.PathPrefix("/manga/").Handler(http.StripPrefix("/manga", fs))
|
||||
|
||||
c := cache.New(5*time.Minute, 10*time.Minute)
|
||||
|
||||
routes.Init(ctx, r, c)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: r,
|
||||
Addr: fmt.Sprintf(":%d", ctx.Int("port")),
|
||||
WriteTimeout: 15 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
return srv.ListenAndServe()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package comicinfo
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type ComicInfo struct {
|
||||
XMLName xml.Name `xml:"ComicInfo"`
|
||||
XmlnsXsd string `xml:"xmlns:xsd,attr"`
|
||||
XmlnsXsi string `xml:"xmlns:xsi,attr"`
|
||||
Title string `xml:"Title"`
|
||||
Summary string `xml:"Summary"`
|
||||
Writer string `xml:"Writer"`
|
||||
Publisher string `xml:"Publisher"`
|
||||
Imprint string `xml:"Imprint"`
|
||||
Tags string `xml:"Tags"`
|
||||
PageCount int `xml:"PageCount"`
|
||||
Manga string `xml:"Manga"`
|
||||
AgeRating string `xml:"AgeRating"`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package opds
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"golang.org/x/tools/blog/atom"
|
||||
)
|
||||
|
||||
type Feed struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
|
||||
XmlLang string `xml:"xml:lang,attr" default:"en"`
|
||||
XmlnsDc string `xml:"xmlns:dc,attr" default:"http://purl.org/dc/terms/"`
|
||||
XmlnsPse string `xml:"xmlns:pse,attr" default:"http://vaemendis.net/opds-pse/ns"`
|
||||
XmlnsOpds string `xml:"xmlns:opds,attr" default:"http://opds-spec.org/2010/catalog"`
|
||||
XmlnsOpenSearch string `xml:"xmlns:opensearch,attr" default:"http://a9.com/-/spec/opensearch/1.1/"`
|
||||
ID string `xml:"id"`
|
||||
Link []Link `xml:"link"`
|
||||
Title string `xml:"title"`
|
||||
Updated atom.TimeStr `xml:"updated"`
|
||||
Author *Author `xml:"author,omitempty"`
|
||||
Entry []*Entry `xml:"entry"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string `xml:"name,omitempty"`
|
||||
URI string `xml:"uri,omitempty"`
|
||||
Email string `xml:"email,omitempty"`
|
||||
InnerXML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Title string `xml:"title"`
|
||||
ID string `xml:"id"`
|
||||
Author *Author `xml:"author,omitempty"`
|
||||
Published atom.TimeStr `xml:"published,omitempty"`
|
||||
Updated atom.TimeStr `xml:"updated,omitempty"`
|
||||
DcPublisher string `xml:"dc:publisher,omitempty"`
|
||||
Link []Link `xml:"link"`
|
||||
Summary *Text `xml:"summary,omitempty"`
|
||||
Content *Text `xml:"content,omitempty"`
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Rel string `xml:"rel,attr,omitempty"`
|
||||
Href string `xml:"href,attr"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
HrefLang string `xml:"hreflang,attr,omitempty"`
|
||||
Title string `xml:"title,attr,omitempty"`
|
||||
Length uint `xml:"length,attr,omitempty"`
|
||||
PseCount uint `xml:"pse:count,attr,omitempty"`
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Body string `xml:",chardata"`
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/samber/lo"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/tools/blog/atom"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"opds-pse-server/app/comicinfo"
|
||||
"opds-pse-server/app/opds"
|
||||
"opds-pse-server/app/utils"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ctx *cli.Context
|
||||
var c *cache.Cache
|
||||
|
||||
func Init(cctx *cli.Context, r *mux.Router, cc *cache.Cache) {
|
||||
ctx = cctx
|
||||
c = cc
|
||||
r.HandleFunc("/", Index).Methods("GET")
|
||||
r.HandleFunc("/stream/{manga:.*}/{page:[0-9]+}", Stream).Methods("GET")
|
||||
}
|
||||
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
feed := opds.Feed{
|
||||
ID: "feed",
|
||||
Title: "go-opds-pse",
|
||||
Updated: atom.Time(time.Now()),
|
||||
Author: &opds.Author{
|
||||
Name: "go-opds-pse",
|
||||
URI: "https://github.com/sclark-dev/go-opds-pse",
|
||||
},
|
||||
Link: []opds.Link{
|
||||
{
|
||||
Rel: "start",
|
||||
Href: "/",
|
||||
Type: "application/atom+xml;profile=opds-catalog;kind=navigation",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
feed.Entry = append(feed.Entry)
|
||||
|
||||
_ = filepath.WalkDir(ctx.String("manga"), func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() || filepath.Ext(d.Name()) != ".cbz" {
|
||||
return err
|
||||
}
|
||||
|
||||
hrefPath := strings.TrimPrefix(path, ctx.String("manga"))
|
||||
|
||||
// Check if ComicInfo is cached
|
||||
var ComicInfo comicinfo.ComicInfo
|
||||
ci, found := c.Get(path)
|
||||
if found {
|
||||
ComicInfo = ci.(comicinfo.ComicInfo)
|
||||
} else {
|
||||
zf, _ := zip.OpenReader(path)
|
||||
for i := range zf.File {
|
||||
if strings.ToLower(zf.File[i].Name) == "comicinfo.xml" {
|
||||
cif, err := zf.File[i].Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cib, _ := io.ReadAll(cif)
|
||||
_ = cif.Close()
|
||||
_ = xml.Unmarshal(cib, &ComicInfo)
|
||||
c.Set(path, ComicInfo, cache.NoExpiration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entry := &opds.Entry{
|
||||
ID: utils.FileBasename(d.Name()),
|
||||
Title: utils.FileBasename(d.Name()),
|
||||
Author: &opds.Author{
|
||||
Name: ComicInfo.Writer,
|
||||
},
|
||||
Summary: &opds.Text{Body: ComicInfo.Summary},
|
||||
DcPublisher: ComicInfo.Publisher,
|
||||
Link: []opds.Link{
|
||||
{
|
||||
Rel: "http://opds-spec.org/acquisition",
|
||||
Href: "/manga" + hrefPath,
|
||||
Type: "application/x-cbz",
|
||||
Title: utils.FileBasename(d.Name()),
|
||||
},
|
||||
{
|
||||
Rel: "http://opds-spec.org/image",
|
||||
Href: "/stream" + hrefPath + "/0",
|
||||
Type: "image/png",
|
||||
},
|
||||
{
|
||||
Rel: "http://opds-spec.org/image/thumbnail",
|
||||
Href: "/stream" + hrefPath + "/0",
|
||||
Type: "image/png",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if ComicInfo != (comicinfo.ComicInfo{}) {
|
||||
entry.Link = append(entry.Link, opds.Link{
|
||||
Rel: "http://vaemendis.net/opds-pse/stream",
|
||||
Href: "/stream" + hrefPath + "/{pageNumber}",
|
||||
Type: "image/png",
|
||||
PseCount: uint(ComicInfo.PageCount),
|
||||
})
|
||||
}
|
||||
|
||||
feed.Entry = append(feed.Entry, entry)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = defaults.Set(&feed)
|
||||
data, _ := xml.MarshalIndent(feed, "", " ")
|
||||
http.ServeContent(w, r, "feed.xml", time.Now(), bytes.NewReader(data))
|
||||
}
|
||||
|
||||
func Stream(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse all variables
|
||||
vars := mux.Vars(r)
|
||||
path := ctx.String("manga") + "/" + vars["manga"]
|
||||
page, err := strconv.Atoi(vars["page"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Open zip file
|
||||
zf, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer zf.Close()
|
||||
|
||||
// Filter to just accepted image files
|
||||
pages := lo.Filter(zf.File, func(f *zip.File, i int) bool {
|
||||
if slices.Contains([]string{".png", ".jpg", ".webp"}, filepath.Ext(f.Name)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Check if page number exists
|
||||
if page > len(pages)-1 {
|
||||
http.Error(w, fmt.Sprintf("page %d out of range", page), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Sort pages lexically
|
||||
sort.Slice(pages, func(i, j int) bool {
|
||||
return pages[i].Name < pages[j].Name
|
||||
})
|
||||
|
||||
// Open page for reading
|
||||
imf, err := pages[page].Open()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer imf.Close()
|
||||
|
||||
// Read all data from page
|
||||
imageBytes, err := io.ReadAll(imf)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve page data
|
||||
http.ServeContent(w, r, pages[page].Name, time.Now(), bytes.NewReader(imageBytes))
|
||||
}
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FileBasename(path string) string {
|
||||
return strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
module opds-pse-server
|
||||
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.22.4
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/urfave/cli/v2 v2.27.4
|
||||
golang.org/x/tools v0.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/creasty/defaults v1.8.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
||||
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"log"
|
||||
"mime"
|
||||
"opds-pse-server/app/cmd"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_ = mime.AddExtensionType(".cbz", "application/x-cbz")
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "go-opds-pse"
|
||||
app.Usage = "A lightweight OPDS server with PSE support."
|
||||
app.Version = "0.0.1+dev"
|
||||
app.Flags = []cli.Flag{
|
||||
cmd.StringFlag("manga, m", "manga", "default manga folder"),
|
||||
}
|
||||
app.Commands = []*cli.Command{
|
||||
&cmd.Serve,
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalf("Failed to start application: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user