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:
+37
-14
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user