mirror of https://github.com/usememos/memos.git
118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package v1
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
"github.com/usememos/memos/plugin/httpgetter"
|
|
)
|
|
|
|
// RegisterLinkRoutes registers lightweight HTTP routes for link previews.
|
|
// We keep this as a REST handler (not gRPC) to avoid schema churn
|
|
// and to reuse existing safety checks in the httpgetter plugin.
|
|
func (s *APIV1Service) RegisterLinkRoutes(g *echo.Group) {
|
|
g.GET("/api/v1/link:preview", s.handleGetLinkPreview)
|
|
}
|
|
|
|
type linkPreviewResponse struct {
|
|
Preview linkPreview `json:"preview"`
|
|
}
|
|
|
|
type linkPreview struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
ImageURL string `json:"imageUrl"`
|
|
SiteName string `json:"siteName"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func (s *APIV1Service) handleGetLinkPreview(c echo.Context) error {
|
|
_ = s
|
|
rawURL := c.QueryParam("url")
|
|
if rawURL == "" {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "url is required")
|
|
}
|
|
|
|
meta, err := httpgetter.GetHTMLMeta(rawURL)
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
parsedURL, _ := url.Parse(rawURL)
|
|
siteName := ""
|
|
if parsedURL != nil {
|
|
siteName = parsedURL.Hostname()
|
|
}
|
|
|
|
imageURL := meta.Image
|
|
if parsedURL != nil && imageURL != "" {
|
|
if u, err := url.Parse(imageURL); err == nil {
|
|
if !u.IsAbs() {
|
|
// handle protocol-relative
|
|
if u.Host != "" {
|
|
u.Scheme = parsedURL.Scheme
|
|
imageURL = u.String()
|
|
} else {
|
|
// relative path -> join with base
|
|
u.Scheme = parsedURL.Scheme
|
|
u.Host = parsedURL.Host
|
|
if !path.IsAbs(u.Path) {
|
|
u.Path = path.Join(parsedURL.Path, "..", u.Path)
|
|
}
|
|
imageURL = u.String()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If meta image missing, try first <img> on page.
|
|
if imageURL == "" {
|
|
if firstImg, err := httpgetter.GetFirstImageURL(rawURL); err == nil && firstImg != "" {
|
|
if parsedURL != nil {
|
|
imageURL = toAbsoluteFromBase(parsedURL, firstImg)
|
|
} else {
|
|
imageURL = firstImg
|
|
}
|
|
}
|
|
}
|
|
|
|
resp := linkPreviewResponse{
|
|
Preview: linkPreview{
|
|
Title: meta.Title,
|
|
Description: meta.Description,
|
|
ImageURL: imageURL,
|
|
SiteName: siteName,
|
|
URL: rawURL,
|
|
},
|
|
}
|
|
return c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
func toAbsoluteFromBase(base *url.URL, raw string) string {
|
|
if raw == "" || base == nil {
|
|
return raw
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return raw
|
|
}
|
|
if u.IsAbs() {
|
|
return u.String()
|
|
}
|
|
// Protocol-relative //host/path
|
|
if u.Host != "" && u.Scheme == "" {
|
|
u.Scheme = base.Scheme
|
|
return u.String()
|
|
}
|
|
// Pure relative path
|
|
u.Scheme = base.Scheme
|
|
u.Host = base.Host
|
|
if !path.IsAbs(u.Path) {
|
|
u.Path = path.Join(path.Dir(base.Path), u.Path)
|
|
}
|
|
return u.String()
|
|
}
|