mirror of
https://github.com/tinode/chat.git
synced 2025-03-14 10:05:07 +00:00
feat: add link-preview handler
This commit is contained in:
2
go.mod
2
go.mod
@ -21,6 +21,7 @@ require (
|
||||
github.com/tinode/snowflake v1.0.0
|
||||
go.mongodb.org/mongo-driver v1.12.1
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/api v0.148.0
|
||||
@ -68,7 +69,6 @@ require (
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
|
113
server/linkpreview.go
Normal file
113
server/linkpreview.go
Normal file
@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"golang.org/x/net/html"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LinkPreview struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Image string `json:"image"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// PreviewLink handles the HTTP request, fetches the URL, and returns the link preview
|
||||
func PreviewLink(w http.ResponseWriter, r *http.Request) {
|
||||
url := r.URL.Query().Get("url")
|
||||
if url == "" {
|
||||
http.Error(w, "Missing 'url' query parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
http.Error(w, "Non-OK HTTP status", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
metadata := extractMetadata(doc)
|
||||
|
||||
linkPreview := LinkPreview{
|
||||
Title: metadata["og:title"],
|
||||
Description: metadata["og:description"],
|
||||
Image: metadata["og:image"],
|
||||
URL: url,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(linkPreview); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func extractMetadata(n *html.Node) map[string]string {
|
||||
metaTags := map[string]string{}
|
||||
var traverse func(*html.Node)
|
||||
traverse = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "meta" {
|
||||
attrs := make(map[string]string)
|
||||
for _, attr := range n.Attr {
|
||||
attrs[attr.Key] = attr.Val
|
||||
}
|
||||
name := attrs["name"]
|
||||
property := attrs["property"]
|
||||
content := attrs["content"]
|
||||
|
||||
if strings.HasPrefix(property, "og:") && content != "" {
|
||||
metaTags[property] = content
|
||||
} else if name != "" && content != "" {
|
||||
metaTags[name] = content
|
||||
}
|
||||
}
|
||||
|
||||
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
||||
traverse(child)
|
||||
}
|
||||
}
|
||||
|
||||
traverse(n)
|
||||
|
||||
if _, exists := metaTags["og:title"]; !exists {
|
||||
metaTags["og:title"] = extractTitle(n)
|
||||
}
|
||||
return metaTags
|
||||
}
|
||||
|
||||
func extractTitle(n *html.Node) string {
|
||||
if n.Type == html.ElementNode && n.Data == "title" {
|
||||
if n.FirstChild != nil {
|
||||
return n.FirstChild.Data
|
||||
}
|
||||
}
|
||||
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
||||
title := extractTitle(child)
|
||||
if title != "" {
|
||||
return title
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
@ -734,6 +734,8 @@ func main() {
|
||||
mux.HandleFunc("/", serve404)
|
||||
}
|
||||
|
||||
mux.HandleFunc(config.ApiPath+"v0/preview-link", PreviewLink)
|
||||
|
||||
if err = listenAndServe(config.Listen, mux, tlsConfig, signalHandler()); err != nil {
|
||||
logs.Err.Fatal(err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user