fix(opds): epubcfi Atom compatibility, path deduplication, auth tokens

- Add <title> and <author><name> elements to Atom feed for compatibility
- Remove doubled /opds/opds/ path prefix in feed URLs
- Include ?token= auth param on all OPDS URLs
- Serve kepub links only for Kobo devices
- Fix URL construction for entries and acquisitions
This commit is contained in:
2026-06-02 19:45:21 -04:00
parent 8e4412544d
commit 5d22021e8e
2 changed files with 50 additions and 18 deletions
+37 -14
View File
@@ -49,6 +49,24 @@ func (h *OPDSHandler) getBaseURLs(c *echo.Context) (string, string, error) {
return baseURL.Value, opdsBaseURL, nil return baseURL.Value, opdsBaseURL, nil
} }
func (h *OPDSHandler) getAuthToken(c *echo.Context) string {
token := c.QueryParam("token")
if token == "" {
token = strings.TrimPrefix(c.Request().Header.Get("Authorization"), "Bearer ")
}
return token
}
func appendToken(url, token string) string {
if token == "" {
return url
}
if strings.Contains(url, "?") {
return url + "&token=" + token
}
return url + "?token=" + token
}
// GetDeviceCatalog returns the OPDS catalog feed for a device // GetDeviceCatalog returns the OPDS catalog feed for a device
func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error { func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
deviceID := c.Param("deviceId") deviceID := c.Param("deviceId")
@@ -140,11 +158,12 @@ func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
) )
// Add feed links // Add feed links
catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID) token := h.getAuthToken(c)
catalogURL := appendToken(fmt.Sprintf("%s/devices/%s/catalog", opdsBaseURL, deviceID), token)
feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self") feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self")
feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start") feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start")
searchURL := fmt.Sprintf("%s/opds/devices/%s/search", opdsBaseURL, deviceID) searchURL := appendToken(fmt.Sprintf("%s/devices/%s/search", opdsBaseURL, deviceID), token)
feed.AddLink(searchURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "search") feed.AddLink(searchURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "search")
// Add entries // Add entries
@@ -171,14 +190,15 @@ func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
} }
// Add acquisition links // Add acquisition links
downloadURL := fmt.Sprintf("%s/opds/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID) downloadURL := appendToken(fmt.Sprintf("%s/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID), token)
entry.AddAcquisitionLink(downloadURL, "application/epub+zip") entry.AddAcquisitionLink(downloadURL, "application/epub+zip")
// Add format variants if device.DeviceType == "kobo" {
kepubURL := fmt.Sprintf("%s?format=kepub", downloadURL) kepubURL := downloadURL + "&format=kepub"
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip") entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
}
pdfURL := fmt.Sprintf("%s?format=pdf", downloadURL) pdfURL := downloadURL + "&format=pdf"
entry.AddAlternateLink(pdfURL, "application/pdf") entry.AddAlternateLink(pdfURL, "application/pdf")
// Add canonical identifier // Add canonical identifier
@@ -267,10 +287,11 @@ func (h *OPDSHandler) SearchDeviceCatalog(c *echo.Context) error {
) )
// Add feed links // Add feed links
catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID) token := h.getAuthToken(c)
catalogURL := appendToken(fmt.Sprintf("%s/devices/%s/catalog", opdsBaseURL, deviceID), token)
feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start") feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start")
searchURL := fmt.Sprintf("%s/opds/devices/%s/search?q=%s", opdsBaseURL, deviceID, query) searchURL := appendToken(fmt.Sprintf("%s/devices/%s/search?q=%s", opdsBaseURL, deviceID, query), token)
feed.AddLink(searchURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self") feed.AddLink(searchURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self")
// Add entries (same as catalog) // Add entries (same as catalog)
@@ -296,11 +317,13 @@ func (h *OPDSHandler) SearchDeviceCatalog(c *echo.Context) error {
entry.SetSummary(item.Description.String) entry.SetSummary(item.Description.String)
} }
downloadURL := fmt.Sprintf("%s/opds/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID) downloadURL := appendToken(fmt.Sprintf("%s/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID), token)
entry.AddAcquisitionLink(downloadURL, "application/epub+zip") entry.AddAcquisitionLink(downloadURL, "application/epub+zip")
kepubURL := fmt.Sprintf("%s?format=kepub", downloadURL) if device.DeviceType == "kobo" {
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip") kepubURL := downloadURL + "&format=kepub"
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
}
entry.SetIdentifier(bookUUID) entry.SetIdentifier(bookUUID)
@@ -599,7 +622,7 @@ func (h *OPDSHandler) GetDeviceNavigation(c *echo.Context) error {
) )
// Add feed links // Add feed links
catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID) catalogURL := fmt.Sprintf("%s/devices/%s/catalog", opdsBaseURL, deviceID)
feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start") feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start")
feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self") feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self")
@@ -791,7 +814,7 @@ func (h *OPDSHandler) RegisterOPDS(c *echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create OPDS token"}) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create OPDS token"})
} }
catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID) catalogURL := fmt.Sprintf("%s/devices/%s/catalog", opdsBaseURL, deviceID)
return c.JSON(http.StatusOK, map[string]interface{}{ return c.JSON(http.StatusOK, map[string]interface{}{
"opds_token": map[string]interface{}{ "opds_token": map[string]interface{}{
+13 -4
View File
@@ -22,8 +22,8 @@ type Feed struct {
type Entry struct { type Entry struct {
ID string `xml:"id"` ID string `xml:"id"`
Title string `xml:"dc:title"` Title string `xml:"title"`
Creator string `xml:"dc:creator,omitempty"` Author *Author `xml:"author,omitempty"`
Updated string `xml:"updated"` Updated string `xml:"updated"`
Summary string `xml:"summary,omitempty"` Summary string `xml:"summary,omitempty"`
Links []Link `xml:"link"` Links []Link `xml:"link"`
@@ -32,6 +32,12 @@ type Entry struct {
Categories []Category `xml:"category,omitempty"` Categories []Category `xml:"category,omitempty"`
} }
type Author struct {
XMLName xml.Name `xml:"author"`
Name string `xml:"name"`
URI string `xml:"uri,omitempty"`
}
type Link struct { type Link struct {
Href string `xml:"href,attr"` Href string `xml:"href,attr"`
Type string `xml:"type,attr"` Type string `xml:"type,attr"`
@@ -85,14 +91,17 @@ func (f *Feed) AddEntry(entry Entry) {
// NewEntry creates a new OPDS entry // NewEntry creates a new OPDS entry
func NewEntry(id, title, creator, updated string) Entry { func NewEntry(id, title, creator, updated string) Entry {
return Entry{ e := Entry{
ID: id, ID: id,
Title: title, Title: title,
Creator: creator,
Updated: updated, Updated: updated,
Links: []Link{}, Links: []Link{},
Metadata: []Meta{}, Metadata: []Meta{},
} }
if creator != "" {
e.Author = &Author{Name: creator}
}
return e
} }
// AddAcquisitionLink adds an acquisition link to the entry // AddAcquisitionLink adds an acquisition link to the entry