Add meta tags to show cover and share description in social platforms

This commit is contained in:
Deluan 2023-01-24 15:35:23 -05:00
parent cab43c89e6
commit 69b36c75a5
5 changed files with 54 additions and 19 deletions

View File

@ -1,7 +1,10 @@
package model package model
import ( import (
"strings"
"time" "time"
"github.com/navidrome/navidrome/utils/number"
) )
type Share struct { type Share struct {
@ -21,6 +24,25 @@ type Share struct {
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt,omitempty"` UpdatedAt time.Time `structs:"updated_at" json:"updatedAt,omitempty"`
Tracks MediaFiles `structs:"-" json:"tracks,omitempty" orm:"-"` Tracks MediaFiles `structs:"-" json:"tracks,omitempty" orm:"-"`
Albums Albums `structs:"-" json:"albums,omitempty" orm:"-"` Albums Albums `structs:"-" json:"albums,omitempty" orm:"-"`
URL string `structs:"-" json:"-" orm:"-"`
ImageURL string `structs:"-" json:"-" orm:"-"`
}
func (s Share) CoverArtID() ArtworkID {
ids := strings.SplitN(s.ResourceIDs, ",", 2)
if len(ids) == 0 {
return ArtworkID{}
}
switch s.ResourceType {
case "album":
return Album{ID: ids[0]}.CoverArtID()
case "playlist":
return Playlist{ID: ids[0]}.CoverArtID()
case "artist":
return Artist{ID: ids[0]}.CoverArtID()
}
rnd := number.RandomInt64(int64(len(s.Tracks)))
return s.Tracks[rnd].CoverArtID()
} }
type Shares []Share type Shares []Share

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server" "github.com/navidrome/navidrome/server"
@ -41,17 +42,15 @@ func (p *Router) handleShares(w http.ResponseWriter, r *http.Request) {
return return
} }
s = p.mapShareInfo(*s) s = p.mapShareInfo(r, *s)
server.IndexWithShare(p.ds, ui.BuildAssets(), s)(w, r) server.IndexWithShare(p.ds, ui.BuildAssets(), s)(w, r)
} }
func (p *Router) mapShareInfo(s model.Share) *model.Share { func (p *Router) mapShareInfo(r *http.Request, s model.Share) *model.Share {
mapped := &model.Share{ s.URL = ShareURL(r, s.ID)
Description: s.Description, s.ImageURL = ImageURL(r, s.CoverArtID(), consts.UICoverArtSize)
Tracks: s.Tracks,
}
for i := range s.Tracks { for i := range s.Tracks {
mapped.Tracks[i].ID = encodeMediafileShare(s, s.Tracks[i].ID) s.Tracks[i].ID = encodeMediafileShare(s, s.Tracks[i].ID)
} }
return mapped return &s
} }

View File

@ -41,6 +41,7 @@ func (p *Router) routes() http.Handler {
if conf.Server.DevEnableShare { if conf.Server.DevEnableShare {
r.HandleFunc("/s/{id}", p.handleStream) r.HandleFunc("/s/{id}", p.handleStream)
r.HandleFunc("/{id}", p.handleShares) r.HandleFunc("/{id}", p.handleShares)
r.HandleFunc("/", p.handleShares)
r.Handle("/*", p.assetsHandler) r.Handle("/*", p.assetsHandler)
} }
}) })

View File

@ -1,7 +1,6 @@
package server package server
import ( import (
"context"
"encoding/json" "encoding/json"
"html/template" "html/template"
"io" "io"
@ -80,8 +79,6 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl
log.Trace(r, "Injecting config in index.html", "config", string(appConfigJson)) log.Trace(r, "Injecting config in index.html", "config", string(appConfigJson))
} }
shareInfoJson := marshalShareData(r.Context(), shareInfo)
log.Debug("UI configuration", "appConfig", appConfig) log.Debug("UI configuration", "appConfig", appConfig)
version := consts.Version version := consts.Version
if version != "dev" { if version != "dev" {
@ -89,9 +86,10 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl
} }
data := map[string]interface{}{ data := map[string]interface{}{
"AppConfig": string(appConfigJson), "AppConfig": string(appConfigJson),
"ShareInfo": string(shareInfoJson),
"Version": version, "Version": version,
} }
addShareData(r, data, shareInfo)
w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Type", "text/html")
err = t.Execute(w, data) err = t.Execute(w, data)
if err != nil { if err != nil {
@ -134,14 +132,15 @@ type shareTrack struct {
Duration float32 `json:"duration,omitempty"` Duration float32 `json:"duration,omitempty"`
} }
func marshalShareData(ctx context.Context, shareInfo *model.Share) []byte { func addShareData(r *http.Request, data map[string]interface{}, shareInfo *model.Share) {
if shareInfo == nil { ctx := r.Context()
return nil if shareInfo == nil || shareInfo.ID == "" {
return
} }
data := shareData{ sd := shareData{
Description: shareInfo.Description, Description: shareInfo.Description,
} }
data.Tracks = slice.Map(shareInfo.Tracks, func(mf model.MediaFile) shareTrack { sd.Tracks = slice.Map(shareInfo.Tracks, func(mf model.MediaFile) shareTrack {
return shareTrack{ return shareTrack{
ID: mf.ID, ID: mf.ID,
Title: mf.Title, Title: mf.Title,
@ -152,11 +151,19 @@ func marshalShareData(ctx context.Context, shareInfo *model.Share) []byte {
} }
}) })
shareInfoJson, err := json.Marshal(data) shareInfoJson, err := json.Marshal(sd)
if err != nil { if err != nil {
log.Error(ctx, "Error converting shareInfo to JSON", "config", shareInfo, err) log.Error(ctx, "Error converting shareInfo to JSON", "config", shareInfo, err)
} else { } else {
log.Trace(ctx, "Injecting shareInfo in index.html", "config", string(shareInfoJson)) log.Trace(ctx, "Injecting shareInfo in index.html", "config", string(shareInfoJson))
} }
return shareInfoJson
if shareInfo.Description != "" {
data["ShareDescription"] = shareInfo.Description
} else {
data["ShareDescription"] = shareInfo.Contents
}
data["ShareURL"] = shareInfo.URL
data["ShareImageURL"] = shareInfo.ImageURL
data["ShareInfo"] = string(shareInfoJson)
} }

View File

@ -29,6 +29,12 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<meta property="og:site_name" content="Navidrome">
<meta property="og:url" content="{{ .ShareURL }}">
<meta property="og:title" content="{{ .ShareDescription }}">
<meta property="og:image" content="{{ .ShareImageURL }}">
<meta property="og:image:width" content="300">
<meta property="og:image:height" content="300">
<title>Navidrome</title> <title>Navidrome</title>
<script> <script>
window.__APP_CONFIG__ = {{ .AppConfig }} window.__APP_CONFIG__ = {{ .AppConfig }}