Linkvorschau für den ersten Link eines Beitrags
Der Server holt zum ersten Link Titel, Seiten- bzw. Kanalname und ein kleines Vorschaubild (320 px) und speichert das Bild lokal; der Browser lädt nur von uns, Besucher-IPs gehen nie an die verlinkte Seite. YouTube über oEmbed (alle Linkformen kanonisch als watch?v=<id>), alle übrigen Seiten über OpenGraph bzw. <title>. Abruf asynchron in einem Worker, eine Zeile je Link in link_preview, Auffrischen nach 30 Tagen. Der Abruf-Client lässt nur öffentliche IPs auf Port 80/443 zu, geprüft nach DNS-Auflösung und bei jeder Weiterleitung (SSRF). Bestehende Beiträge: kver link-previews. Neue Abhängigkeit golang.org/x/net, damit go 1.26. DEV bekommt ein beschreibbares Volume für die Vorschaubilder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a8f392edba
commit
26b0182391
+56
-1
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -16,8 +17,13 @@ func runCommand(args []string) error {
|
||||
return errors.New("Verwendung: kver reset-link <nutzername>")
|
||||
}
|
||||
return cmdResetLink(args[1])
|
||||
case "link-previews":
|
||||
if len(args) != 1 {
|
||||
return errors.New("Verwendung: kver link-previews")
|
||||
}
|
||||
return cmdLinkPreviews()
|
||||
default:
|
||||
return fmt.Errorf("unbekannter Befehl %q (bekannt: reset-link)", args[0])
|
||||
return fmt.Errorf("unbekannter Befehl %q (bekannt: reset-link, link-previews)", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,3 +45,52 @@ func cmdResetLink(username string) error {
|
||||
fmt.Println("Ältere Reset-Links dieses Nutzers sind damit ungültig.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdLinkPreviews trägt bei bestehenden Beiträgen den ersten Link nach
|
||||
// (entry.link_url) und holt alle offenen Vorschauen nacheinander. Läuft der
|
||||
// Server parallel, holt er höchstens dieselben Links noch einmal -- harmlos.
|
||||
func cmdLinkPreviews() error {
|
||||
rows, err := db.Query(`SELECT pid, content FROM entry WHERE deleted = 0 AND link_url = '' AND content LIKE '%http%'`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
links := map[int64]string{}
|
||||
for rows.Next() {
|
||||
var pid int64
|
||||
var content string
|
||||
if err := rows.Scan(&pid, &content); err != nil {
|
||||
rows.Close()
|
||||
return err
|
||||
}
|
||||
if key := previewKey(content); key != "" {
|
||||
links[pid] = key
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
for pid, key := range links {
|
||||
if _, err := db.Exec(`UPDATE entry SET link_url = $1 WHERE pid = $2`, key, pid); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(`INSERT INTO link_preview (url) VALUES ($1) ON CONFLICT DO NOTHING`, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Printf("%d Beiträge mit Link nachgetragen.\n", len(links))
|
||||
|
||||
pending := pendingPreviews(100000)
|
||||
for i, key := range pending {
|
||||
fmt.Printf("[%d/%d] %s\n", i+1, len(pending), key)
|
||||
fetchPreview(context.Background(), key)
|
||||
}
|
||||
var ok, none int
|
||||
if err := db.QueryRow(
|
||||
`SELECT COUNT(*) FILTER (WHERE status = 'ok'), COUNT(*) FILTER (WHERE status = 'none') FROM link_preview`).
|
||||
Scan(&ok, &none); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Vorschauen: %d fertig, %d ohne Vorschau.\n", ok, none)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user