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:
@@ -40,12 +40,14 @@ func newBus(buffer int, start bool) *Bus {
|
||||
// Publish enqueues ev. It never blocks the caller; a full buffer is dropped.
|
||||
func (b *Bus) Publish(_ context.Context, ev any) {
|
||||
if b == nil {
|
||||
CallRelease(ev)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case b.ch <- ev:
|
||||
default:
|
||||
log.Printf("events: dropped %T", ev)
|
||||
CallRelease(ev)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,3 +102,44 @@ func TestBusDropsWhenFull(t *testing.T) {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestNopReleasesPostCreated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
released := false
|
||||
Nop{}.Publish(context.Background(), PostCreated{Release: func() { released = true }})
|
||||
if !released {
|
||||
t.Fatal("Nop.Publish did not release held upload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBusDropReleasesPostCreated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bus := newBus(1, false)
|
||||
bus.Publish(context.Background(), "kept")
|
||||
released := false
|
||||
bus.Publish(context.Background(), PostCreated{Release: func() { released = true }})
|
||||
if !released {
|
||||
t.Fatal("dropped PostCreated did not release held upload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscribeReleasePostedToDiscord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bus := New()
|
||||
defer bus.Close()
|
||||
SubscribeRelease(bus)
|
||||
|
||||
released := make(chan struct{})
|
||||
bus.Publish(context.Background(), PostedToDiscord{
|
||||
PostID: "post-1",
|
||||
Release: func() { close(released) },
|
||||
})
|
||||
select {
|
||||
case <-released:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("PostedToDiscord did not release")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Image is a public photo already attached to a site post.
|
||||
// Image is a public photo or video already attached to a site post.
|
||||
type Image struct {
|
||||
URL string
|
||||
Description string
|
||||
Kind string
|
||||
}
|
||||
|
||||
// Media is a local upload still held from the create request so Discord can
|
||||
// attach the bytes. Photos carry re-encoded Bytes; videos carry an open Body.
|
||||
type Media struct {
|
||||
Name string
|
||||
ContentType string
|
||||
Kind string
|
||||
Size int64
|
||||
Body io.ReadCloser
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// PostEvent is a Discord-free snapshot of a site post after a successful write.
|
||||
@@ -29,6 +42,8 @@ type PostEvent struct {
|
||||
// PostCreated is emitted after a successful site create.
|
||||
type PostCreated struct {
|
||||
PostEvent
|
||||
Media []Media `json:"-"`
|
||||
Release func() `json:"-"`
|
||||
}
|
||||
|
||||
// PostUpdated is emitted after a successful site edit.
|
||||
@@ -36,6 +51,13 @@ type PostUpdated struct {
|
||||
PostEvent
|
||||
}
|
||||
|
||||
// PostedToDiscord is emitted after the Discord subscriber finishes with a
|
||||
// PostCreated (sent, skipped, or failed). Release deletes held upload files.
|
||||
type PostedToDiscord struct {
|
||||
PostID string
|
||||
Release func() `json:"-"`
|
||||
}
|
||||
|
||||
// Permalink builds /questions/{root}#post-{id}, prefixed by baseURL when set.
|
||||
func Permalink(baseURL, rootID, postID string) string {
|
||||
path := "/questions/" + url.PathEscape(rootID) + "#post-" + url.PathEscape(postID)
|
||||
|
||||
@@ -5,5 +5,7 @@ import "context"
|
||||
// Nop is a Publisher used when nothing is subscribed.
|
||||
type Nop struct{}
|
||||
|
||||
// Publish discards ev.
|
||||
func (Nop) Publish(context.Context, any) {}
|
||||
// Publish discards ev after releasing any held upload.
|
||||
func (Nop) Publish(_ context.Context, ev any) {
|
||||
CallRelease(ev)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Once returns fn wrapped so it runs at most once.
|
||||
func Once(fn func()) func() {
|
||||
if fn == nil {
|
||||
return func() {}
|
||||
}
|
||||
var once sync.Once
|
||||
return func() { once.Do(fn) }
|
||||
}
|
||||
|
||||
// CallRelease runs Release on PostCreated or PostedToDiscord when set.
|
||||
func CallRelease(ev any) {
|
||||
switch e := ev.(type) {
|
||||
case PostCreated:
|
||||
if e.Release != nil {
|
||||
e.Release()
|
||||
}
|
||||
case PostedToDiscord:
|
||||
if e.Release != nil {
|
||||
e.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeRelease runs PostedToDiscord.Release on the worker.
|
||||
func SubscribeRelease(bus *Bus) {
|
||||
if bus == nil {
|
||||
return
|
||||
}
|
||||
bus.Subscribe(func(_ context.Context, ev any) {
|
||||
if _, ok := ev.(PostedToDiscord); ok {
|
||||
CallRelease(ev)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user