feedwatch: tell the owner when a feed breaks, comes back, or goes quiet
Every poll's outcome has always been written to source_health and shown on /status, but only to someone who went and looked. Nobody did: Siliconera has been refused on 3,769 polls in a row and Digital Foundry on 1,900, and neither has ever fetched successfully. SendAdminWarning existed and nothing called it. A watcher now reads source_health every 15 minutes and sends one notice to matrix.admin_room covering everything that changed since it last spoke: feeds that have failed 5 polls in a row, feeds that have fetched since they were reported down, and feeds that fetch fine but have kept nothing new for 7 days (every healthy source produces something inside about 4). What has been said is stored on the row (down_alerted_at, quiet_alerted_at), so each episode is reported once and a restart doesn't repeat it. A notice that fails to send is not marked, and goes out on the next pass. last_new_at is new, moved only when a poll keeps a story. Existing rows start from their newest retained story, or from now if they have none, and a new source starts at its first poll: nothing on disk says whether a source with no stories is long dead or was added yesterday, so it gets a full window before it can be called quiet. With no admin room configured the watcher doesn't run at all, rather than marking alerts as delivered to nowhere.
This commit is contained in:
@@ -508,23 +508,26 @@ func (c *Client) sendImage(ctx context.Context, roomID id.RoomID, imageURL strin
|
||||
return err
|
||||
}
|
||||
|
||||
// SendAdminWarning sends a warning message to the admin room, if configured.
|
||||
func (c *Client) SendAdminWarning(msg string) {
|
||||
// SendAdminNotice sends a notice to the admin room. It returns an error when no
|
||||
// admin room is configured or the send fails, so a caller that must not lose
|
||||
// the message (the feed watcher) can try again later.
|
||||
func (c *Client) SendAdminNotice(ctx context.Context, msg string) error {
|
||||
if c.adminRoom == nil {
|
||||
slog.Warn("admin warning (no admin room configured)", "msg", msg)
|
||||
return
|
||||
return fmt.Errorf("no admin room configured")
|
||||
}
|
||||
|
||||
content := &event.MessageEventContent{
|
||||
MsgType: event.MsgText,
|
||||
MsgType: event.MsgNotice,
|
||||
Body: msg,
|
||||
}
|
||||
|
||||
if _, err := c.mx.SendMessageEvent(context.Background(), *c.adminRoom, event.EventMessage, content); err != nil {
|
||||
slog.Error("failed to send admin warning", "err", err, "msg", msg)
|
||||
if _, err := c.mx.SendMessageEvent(ctx, *c.adminRoom, event.EventMessage, content); err != nil {
|
||||
return fmt.Errorf("send admin notice: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasAdminRoom reports whether an admin room is configured.
|
||||
func (c *Client) HasAdminRoom() bool { return c.adminRoom != nil }
|
||||
|
||||
// ChannelRoomID returns the room ID for a named channel.
|
||||
// Returns false if the channel is not configured or has an empty room ID.
|
||||
func (c *Client) ChannelRoomID(channel string) (id.RoomID, bool) {
|
||||
|
||||
Reference in New Issue
Block a user