Add Pushover outbound notification channel

This commit is contained in:
Vernon Stinebaker 2026-02-16 22:48:32 +08:00
parent dc85d0929b
commit c85faddc89
3 changed files with 112 additions and 5 deletions

View file

@ -48,7 +48,7 @@ func (m *Manager) initChannels() error {
if m.config.Channels.Telegram.Enabled && m.config.Channels.Telegram.Token != "" { if m.config.Channels.Telegram.Enabled && m.config.Channels.Telegram.Token != "" {
logger.DebugC("channels", "Attempting to initialize Telegram channel") logger.DebugC("channels", "Attempting to initialize Telegram channel")
telegram, err := NewTelegramChannel(m.config, m.bus) telegram, err := NewTelegramChannel(m.config.Channels.Telegram, m.bus)
if err != nil { if err != nil {
logger.ErrorCF("channels", "Failed to initialize Telegram channel", map[string]interface{}{ logger.ErrorCF("channels", "Failed to initialize Telegram channel", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@ -176,6 +176,19 @@ func (m *Manager) initChannels() error {
} }
} }
if m.config.Channels.Pushover.Enabled && m.config.Channels.Pushover.AppToken != "" && m.config.Channels.Pushover.UserKey != "" {
logger.DebugC("channels", "Attempting to initialize Pushover channel")
pushover, err := NewPushoverChannel(m.config.Channels.Pushover, m.bus)
if err != nil {
logger.ErrorCF("channels", "Failed to initialize Pushover channel", map[string]interface{}{
"error": err.Error(),
})
} else {
m.channels["pushover"] = pushover
logger.InfoC("channels", "Pushover channel enabled successfully")
}
}
logger.InfoCF("channels", "Channel initialization completed", map[string]interface{}{ logger.InfoCF("channels", "Channel initialization completed", map[string]interface{}{
"enabled_channels": len(m.channels), "enabled_channels": len(m.channels),
}) })

88
pkg/channels/pushover.go Normal file
View file

@ -0,0 +1,88 @@
package channels
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/logger"
)
type PushoverChannel struct {
*BaseChannel
config config.PushoverConfig
client *http.Client
}
func NewPushoverChannel(cfg config.PushoverConfig, bus *bus.MessageBus) (*PushoverChannel, error) {
base := NewBaseChannel("pushover", cfg, bus, nil)
return &PushoverChannel{
BaseChannel: base,
config: cfg,
client: &http.Client{},
}, nil
}
func (c *PushoverChannel) Name() string {
return "pushover"
}
func (c *PushoverChannel) Start(ctx context.Context) error {
logger.InfoC("pushover", "Starting Pushover channel")
c.setRunning(true)
return nil
}
func (c *PushoverChannel) Stop(ctx context.Context) error {
logger.InfoC("pushover", "Stopping Pushover channel")
c.setRunning(false)
return nil
}
func (c *PushoverChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
if !c.IsRunning() {
return fmt.Errorf("pushover channel not running")
}
if c.config.AppToken == "" || c.config.UserKey == "" {
return fmt.Errorf("pushover app_token and user_key are required")
}
data := url.Values{}
data.Set("token", c.config.AppToken)
data.Set("user", c.config.UserKey)
data.Set("message", msg.Content)
// Truncate message if too long (Pushover limit is 1024 chars)
if len(msg.Content) > 1024 {
data.Set("message", msg.Content[:1021]+"...")
}
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.pushover.net/1/messages.json", strings.NewReader(data.Encode()))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("failed to send notification: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("pushover API returned status %d", resp.StatusCode)
}
logger.DebugCF("pushover", "Notification sent", map[string]any{
"content_length": len(msg.Content),
})
return nil
}

View file

@ -79,6 +79,7 @@ type ChannelsConfig struct {
Slack SlackConfig `json:"slack"` Slack SlackConfig `json:"slack"`
LINE LINEConfig `json:"line"` LINE LINEConfig `json:"line"`
OneBot OneBotConfig `json:"onebot"` OneBot OneBotConfig `json:"onebot"`
Pushover PushoverConfig `json:"pushover"`
} }
type WhatsAppConfig struct { type WhatsAppConfig struct {
@ -104,10 +105,9 @@ type FeishuConfig struct {
} }
type DiscordConfig struct { type DiscordConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DISCORD_ENABLED"` Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DISCORD_ENABLED"`
Token string `json:"token" env:"PICOCLAW_CHANNELS_DISCORD_TOKEN"` Token string `json:"token" env:"PICOCLAW_CHANNELS_DISCORD_TOKEN"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_DISCORD_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_DISCORD_ALLOW_FROM"`
MentionOnly bool `json:"mention_only" env:"PICOCLAW_CHANNELS_DISCORD_MENTION_ONLY"`
} }
type MaixCamConfig struct { type MaixCamConfig struct {
@ -157,6 +157,12 @@ type OneBotConfig struct {
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_ONEBOT_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_ONEBOT_ALLOW_FROM"`
} }
type PushoverConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_PUSHOVER_ENABLED"`
AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_PUSHOVER_APP_TOKEN"`
UserKey string `json:"user_key" env:"PICOCLAW_CHANNELS_PUSHOVER_USER_KEY"`
}
type HeartbeatConfig struct { type HeartbeatConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_HEARTBEAT_ENABLED"` Enabled bool `json:"enabled" env:"PICOCLAW_HEARTBEAT_ENABLED"`
Interval int `json:"interval" env:"PICOCLAW_HEARTBEAT_INTERVAL"` // minutes, min 5 Interval int `json:"interval" env:"PICOCLAW_HEARTBEAT_INTERVAL"` // minutes, min 5