Add video picker and Discord video attachments.
CI / test (pull_request) Successful in 6m29s

The picker accepts one MP4 or WebM, the thread lightbox zooms photos, and Discord gets the video as a file on a follow-up message so photo embeds still render.
This commit is contained in:
2026-08-31 02:00:56 -07:00
parent 84dea8ea5d
commit f96df3222d
22 changed files with 1018 additions and 144 deletions
+59 -5
View File
@@ -1,8 +1,10 @@
package web
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"strings"
@@ -237,20 +239,72 @@ func assertPostEvent(t *testing.T, got, want events.PostEvent) {
}
}
func TestPostEventOmitsVideos(t *testing.T) {
func TestPostEventIncludesPhotosAndVideos(t *testing.T) {
t.Parallel()
srv, _ := newTestServer(t, Config{})
got := srv.postEvent(&store.Post{
ID: "root-1",
Title: "Clip",
Body: "Photo and video.",
ID: "root-1",
Title: "Clip",
Body: "Photo and video.",
Images: []store.PostImage{
{PublicURL: "https://cdn.example/a.jpg", Description: "Still", Kind: store.MediaKindImage},
{PublicURL: "https://cdn.example/a.mp4", Description: "Walkthrough", Kind: store.MediaKindVideo},
},
}, nil, nil)
if len(got.Images) != 1 || got.Images[0].URL != "https://cdn.example/a.jpg" {
if len(got.Images) != 2 ||
got.Images[0].URL != "https://cdn.example/a.jpg" || got.Images[0].Kind != store.MediaKindImage ||
got.Images[1].URL != "https://cdn.example/a.mp4" || got.Images[1].Kind != store.MediaKindVideo {
t.Fatalf("event images = %+v", got.Images)
}
}
func TestCreatePostHoldsVideoUntilRelease(t *testing.T) {
t.Parallel()
rec := &events.Recording{}
blobs := &recordingImageBlob{}
srv, mem := newTestServer(t, Config{
Events: rec,
Blob: blobs,
HoldUploadUntilDiscord: true,
})
handler := srv.Handler()
user := seedUser(t, mem, uniq("hold-video"), "hunter22", store.RoleUser)
cookies := loginUser(t, handler, user.Username, "hunter22")
csrf := csrfForCookies(t, handler, cookies)
clip := tinyMP4()
res := multipartPost(t, handler, "/submit", map[string][]string{
"_csrf": {csrf},
"title": {"Valve clip"},
"body": {"Watch the handle."},
"city": {"Oakland"},
}, []multipartTestFile{{name: "walk.mp4", body: clip}}, cookies)
if res.Code != http.StatusSeeOther {
t.Fatalf("submit status = %d: %s", res.Code, res.Body.String())
}
got := rec.Snapshot()
if len(got) != 1 {
t.Fatalf("published %d events, want 1: %#v", len(got), got)
}
created, ok := got[0].(events.PostCreated)
if !ok {
t.Fatalf("event %T, want PostCreated", got[0])
}
if len(created.Media) != 1 ||
created.Media[0].Kind != store.MediaKindVideo ||
created.Media[0].ContentType != "video/mp4" ||
created.Media[0].Body == nil {
t.Fatalf("held media = %+v", created.Media)
}
if created.Release == nil {
t.Fatal("missing Release")
}
body, err := io.ReadAll(created.Media[0].Body)
if err != nil || !bytes.Equal(body, clip) {
t.Fatalf("held video body = %d bytes err=%v", len(body), err)
}
created.Release()
}