mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
appservice: heartbeat presence online so bot shows green while running
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"maunium.net/go/mautrix"
|
||||
@@ -16,6 +17,14 @@ import (
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// presenceHeartbeatInterval is how often the appservice re-asserts its online
|
||||
// presence. In masdevice mode the /sync long-poll refreshed presence implicitly
|
||||
// (~30s); appservice mode has no /sync, so we push presence explicitly. Synapse
|
||||
// decays an "online" user to unavailable after ~5min of no update, so a 1min tick
|
||||
// keeps the bot reliably green while running — and lets it decay to offline on its
|
||||
// own if the process dies without a graceful shutdown.
|
||||
const presenceHeartbeatInterval = time.Minute
|
||||
|
||||
// cryptoToDeviceTypes are the to-device event types the crypto machine must see
|
||||
// to establish Olm/Megolm sessions and share/receive room keys. In /sync mode
|
||||
// the cryptohelper gets these automatically; under the appservice transaction
|
||||
@@ -85,6 +94,12 @@ func newAppserviceSession(cfg Config) (*Session, error) {
|
||||
}
|
||||
|
||||
client := as.BotClient() // as_token auth + SetAppServiceUserID (?user_id=) assertion
|
||||
// Assert the device via ?org.matrix.msc3202.device_id= on E2EE requests, and
|
||||
// satisfy cryptohelper.Init's syncer check: with this set, Init permits a nil
|
||||
// Syncer (we drive the crypto machine from transactions, not /sync). The param
|
||||
// is only emitted once DeviceID is non-empty (url.go), so setting it now is a
|
||||
// no-op for the whoami/device-create calls below; CreateDeviceMSC4190 re-sets it.
|
||||
client.SetAppServiceDeviceID = true
|
||||
|
||||
// Validate the token + identity before we start listening.
|
||||
whoami, err := client.Whoami(ctx)
|
||||
@@ -119,6 +134,25 @@ func newAppserviceSession(cfg Config) (*Session, error) {
|
||||
ep := appservice.NewEventProcessor(as)
|
||||
mach := ch.Machine()
|
||||
|
||||
// Best-effort cross-signing bootstrap so the bot's device shows as verified to
|
||||
// users who trust its master key. Mirrors the masdevice path (client.go); the
|
||||
// appservice path historically omitted it, so a fresh crypto.db has no
|
||||
// cross-signing identity and the device shows unverified. Best-effort: under MAS
|
||||
// the key upload may be refused, which we log and ignore — E2EE still functions.
|
||||
if recoveryKey, _, err := mach.GenerateAndUploadCrossSigningKeys(ctx, func(ui *mautrix.RespUserInteractive) interface{} {
|
||||
return map[string]interface{}{"session": ui.Session}
|
||||
}, ""); err != nil {
|
||||
slog.Warn("cross-signing: key upload skipped (may already exist or need UIA)", "err", err)
|
||||
} else {
|
||||
slog.Info("cross-signing: keys uploaded", "recovery_key", recoveryKey)
|
||||
}
|
||||
if err := mach.SignOwnDevice(ctx, mach.OwnIdentity()); err != nil {
|
||||
slog.Warn("cross-signing: sign own device failed", "err", err)
|
||||
}
|
||||
if err := mach.SignOwnMasterKey(ctx); err != nil {
|
||||
slog.Warn("cross-signing: sign master key failed", "err", err)
|
||||
}
|
||||
|
||||
// Crypto plumbing that /sync would otherwise carry:
|
||||
ep.OnOTK(mach.HandleOTKCounts)
|
||||
ep.OnDeviceList(mach.HandleDeviceLists)
|
||||
@@ -193,6 +227,8 @@ func (s *Session) runAppservice(ctx context.Context) error {
|
||||
close(errCh)
|
||||
}()
|
||||
|
||||
go s.runPresenceHeartbeat(ctx)
|
||||
|
||||
slog.Info("appservice listener started", "address", s.as.Host.Address())
|
||||
|
||||
select {
|
||||
@@ -202,3 +238,33 @@ func (s *Session) runAppservice(ctx context.Context) error {
|
||||
return fmt.Errorf("appservice HTTP listener exited unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
// runPresenceHeartbeat keeps the bot's Matrix presence "online" while the
|
||||
// appservice runs. Without /sync, nothing else refreshes presence, so it would
|
||||
// otherwise freeze at its last value. On graceful shutdown it best-effort sets
|
||||
// presence "offline"; on a hard crash the heartbeat simply stops and Synapse
|
||||
// decays the stale "online" state on its own.
|
||||
func (s *Session) runPresenceHeartbeat(ctx context.Context) {
|
||||
setPresence := func(ctx context.Context, presence event.Presence) {
|
||||
if err := s.Client.SetPresence(ctx, mautrix.ReqPresence{Presence: presence}); err != nil {
|
||||
slog.Warn("appservice: set presence failed", "presence", presence, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
setPresence(ctx, event.PresenceOnline)
|
||||
|
||||
ticker := time.NewTicker(presenceHeartbeatInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Detach from the cancelled ctx so the final PUT still goes out.
|
||||
offCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
setPresence(offCtx, event.PresenceOffline)
|
||||
cancel()
|
||||
return
|
||||
case <-ticker.C:
|
||||
setPresence(ctx, event.PresenceOnline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user