Extract ActivityPub outbox walk into internal/activitypub/outbox.go with webfinger/actor resolution, Create/Note filtering and IRI dereferencing. Fix searchStatus returning wrong post, nil-account panic in SearchActors, @local handle resolution, empty Author via noteToInternalPost, protocol- relative URL handling, pagination loops, limit clamping (200) and cacheErr masking. Add per-host token-bucket throttle (1/s, cross-host concurrency) in throttle.go and background GTS warming via search?resolve for outbox- fetched posts without following remotes. Align AGENTS.md. Co-Authored-By: internal-model
58 lines
922 B
Go
58 lines
922 B
Go
package activitypub
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type hostLimiter struct {
|
|
mu sync.Mutex
|
|
buckets map[string]*bucket
|
|
}
|
|
|
|
type bucket struct {
|
|
mu sync.Mutex
|
|
last time.Time
|
|
}
|
|
|
|
func newHostLimiter() *hostLimiter {
|
|
return &hostLimiter{buckets: make(map[string]*bucket)}
|
|
}
|
|
|
|
func (h *hostLimiter) Wait(ctx context.Context, host string) error {
|
|
h.mu.Lock()
|
|
b, ok := h.buckets[host]
|
|
if !ok {
|
|
b = &bucket{}
|
|
h.buckets[host] = b
|
|
if len(h.buckets) > 1024 {
|
|
for k, v := range h.buckets {
|
|
if time.Since(v.last) > 10*time.Minute {
|
|
delete(h.buckets, k)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
h.mu.Unlock()
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if b.last.IsZero() {
|
|
b.last = time.Now()
|
|
return nil
|
|
}
|
|
wait := time.Until(b.last.Add(time.Second))
|
|
if wait <= 0 {
|
|
b.last = time.Now()
|
|
return nil
|
|
}
|
|
select {
|
|
case <-time.After(wait):
|
|
b.last = time.Now()
|
|
return nil
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|