feat(discord): add mention_only option for @-mention responses
Add MentionOnly config option to Discord channel. When enabled, the bot only responds when explicitly @-mentioned, useful for shared servers. - Add MentionOnly bool field to DiscordConfig - Store botUserID on startup for mention checking - Check m.Mentions before processing messages when MentionOnly is true - Update config example and README documentation
This commit is contained in:
parent
e599573ed4
commit
9d0903515a
5 changed files with 40 additions and 13 deletions
|
|
@ -334,7 +334,8 @@ picoclaw gateway
|
||||||
"discord": {
|
"discord": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"token": "YOUR_BOT_TOKEN",
|
"token": "YOUR_BOT_TOKEN",
|
||||||
"allow_from": ["YOUR_USER_ID"]
|
"allow_from": ["YOUR_USER_ID"],
|
||||||
|
"mention_only": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -347,6 +348,10 @@ picoclaw gateway
|
||||||
* Bot Permissions: `Send Messages`, `Read Message History`
|
* Bot Permissions: `Send Messages`, `Read Message History`
|
||||||
* Open the generated invite URL and add the bot to your server
|
* Open the generated invite URL and add the bot to your server
|
||||||
|
|
||||||
|
**Optional: Mention-only mode**
|
||||||
|
|
||||||
|
Set `mention_only: true` to make the bot respond only when @-mentioned. Useful for shared servers where you want the bot to respond only when explicitly called.
|
||||||
|
|
||||||
**6. Run**
|
**6. Run**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@
|
||||||
"discord": {
|
"discord": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"token": "YOUR_DISCORD_BOT_TOKEN",
|
"token": "YOUR_DISCORD_BOT_TOKEN",
|
||||||
"allow_from": []
|
"allow_from": [],
|
||||||
|
"mention_only": false
|
||||||
},
|
},
|
||||||
"maixcam": {
|
"maixcam": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ type DiscordChannel struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
typingMu sync.Mutex
|
typingMu sync.Mutex
|
||||||
typingStop map[string]chan struct{} // chatID → stop signal
|
typingStop map[string]chan struct{} // chatID → stop signal
|
||||||
|
botUserID string // stored for mention checking
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
|
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
|
||||||
|
|
@ -75,6 +76,7 @@ func (c *DiscordChannel) Start(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get bot user: %w", err)
|
return fmt.Errorf("failed to get bot user: %w", err)
|
||||||
}
|
}
|
||||||
|
c.botUserID = botUser.ID
|
||||||
logger.InfoCF("discord", "Discord bot connected", map[string]any{
|
logger.InfoCF("discord", "Discord bot connected", map[string]any{
|
||||||
"username": botUser.Username,
|
"username": botUser.Username,
|
||||||
"user_id": botUser.ID,
|
"user_id": botUser.ID,
|
||||||
|
|
@ -131,7 +133,7 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DiscordChannel) sendChunk(ctx context.Context, channelID, content string) error {
|
func (c *DiscordChannel) sendChunk(ctx context.Context, channelID, content string) error {
|
||||||
// 使用传入的 ctx 进行超时控制
|
// Use the passed ctx for timeout control
|
||||||
sendCtx, cancel := context.WithTimeout(ctx, sendTimeout)
|
sendCtx, cancel := context.WithTimeout(ctx, sendTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -152,7 +154,7 @@ func (c *DiscordChannel) sendChunk(ctx context.Context, channelID, content strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendContent 安全地追加内容到现有文本
|
// appendContent safely appends content to existing text
|
||||||
func appendContent(content, suffix string) string {
|
func appendContent(content, suffix string) string {
|
||||||
if content == "" {
|
if content == "" {
|
||||||
return suffix
|
return suffix
|
||||||
|
|
@ -169,7 +171,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查白名单,避免为被拒绝的用户下载附件和转录
|
// Check allowlist first to avoid downloading attachments and transcribing for rejected users
|
||||||
if !c.IsAllowed(m.Author.ID) {
|
if !c.IsAllowed(m.Author.ID) {
|
||||||
logger.DebugCF("discord", "Message rejected by allowlist", map[string]any{
|
logger.DebugCF("discord", "Message rejected by allowlist", map[string]any{
|
||||||
"user_id": m.Author.ID,
|
"user_id": m.Author.ID,
|
||||||
|
|
@ -177,6 +179,23 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If configured to only respond to mentions, check if bot is mentioned
|
||||||
|
if c.config.MentionOnly {
|
||||||
|
isMentioned := false
|
||||||
|
for _, mention := range m.Mentions {
|
||||||
|
if mention.ID == c.botUserID {
|
||||||
|
isMentioned = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isMentioned {
|
||||||
|
logger.DebugCF("discord", "Message ignored - bot not mentioned", map[string]any{
|
||||||
|
"user_id": m.Author.ID,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
senderID := m.Author.ID
|
senderID := m.Author.ID
|
||||||
senderName := m.Author.Username
|
senderName := m.Author.Username
|
||||||
if m.Author.Discriminator != "" && m.Author.Discriminator != "0" {
|
if m.Author.Discriminator != "" && m.Author.Discriminator != "0" {
|
||||||
|
|
@ -187,7 +206,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
mediaPaths := make([]string, 0, len(m.Attachments))
|
mediaPaths := make([]string, 0, len(m.Attachments))
|
||||||
localFiles := make([]string, 0, len(m.Attachments))
|
localFiles := make([]string, 0, len(m.Attachments))
|
||||||
|
|
||||||
// 确保临时文件在函数返回时被清理
|
// Ensure temp files are cleaned up when function returns
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, file := range localFiles {
|
for _, file := range localFiles {
|
||||||
if err := os.Remove(file); err != nil {
|
if err := os.Remove(file); err != nil {
|
||||||
|
|
@ -211,7 +230,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
if c.transcriber != nil && c.transcriber.IsAvailable() {
|
if c.transcriber != nil && c.transcriber.IsAvailable() {
|
||||||
ctx, cancel := context.WithTimeout(c.getContext(), transcriptionTimeout)
|
ctx, cancel := context.WithTimeout(c.getContext(), transcriptionTimeout)
|
||||||
result, err := c.transcriber.Transcribe(ctx, localPath)
|
result, err := c.transcriber.Transcribe(ctx, localPath)
|
||||||
cancel() // 立即释放context资源,避免在for循环中泄漏
|
cancel() // Release context resources immediately to avoid leaks in for loop
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorCF("discord", "Voice transcription failed", map[string]any{
|
logger.ErrorCF("discord", "Voice transcription failed", map[string]any{
|
||||||
|
|
|
||||||
|
|
@ -215,9 +215,10 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,10 @@ func DefaultConfig() *Config {
|
||||||
AllowFrom: FlexibleStringSlice{},
|
AllowFrom: FlexibleStringSlice{},
|
||||||
},
|
},
|
||||||
Discord: DiscordConfig{
|
Discord: DiscordConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Token: "",
|
Token: "",
|
||||||
AllowFrom: FlexibleStringSlice{},
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
MentionOnly: false,
|
||||||
},
|
},
|
||||||
MaixCam: MaixCamConfig{
|
MaixCam: MaixCamConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue