Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
# ── Chat Channel ──────────────────────────
# TELEGRAM_BOT_TOKEN=123456:ABC...
# DISCORD_BOT_TOKEN=xxx
# Feishu (飞书)
# PICOCLAW_CHANNELS_FEISHU_APP_ID=cli_xxx
# PICOCLAW_CHANNELS_FEISHU_APP_SECRET=xxx
# PICOCLAW_CHANNELS_FEISHU_RANDOM_REACTION_EMOJI=Typing,OneSecond

# ── Web Search (optional) ────────────────
# BRAVE_SEARCH_API_KEY=BSA...
Expand Down
3 changes: 2 additions & 1 deletion config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
"encrypt_key": "",
"verification_token": "",
"allow_from": [],
"reasoning_channel_id": ""
"reasoning_channel_id": "",
"random_reaction_emoji": []
},
"dingtalk": {
"enabled": false,
Expand Down
4 changes: 3 additions & 1 deletion docs/channels/feishu/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
| app_secret | string | 是 | 飞书应用的 App Secret |
| encrypt_key | string | 否 | 事件回调加密密钥 |
| verification_token | string | 否 | 用于Webhook事件验证的Token |
| allow_from | array | 否 | 用户ID白名单,空表示允许所有用户 |
| allow_from | array | 否 | 用户ID白名单,空表示所有用户 |
| random_reaction_emoji | array | 否 | 随机添加的表情列表,空则使用默认 "Pin" |

## 设置流程

Expand All @@ -35,3 +36,4 @@
3. 配置事件订阅和Webhook URL
4. 设置加密(可选,生产环境建议启用)
5. 将 App ID、App Secret、Encrypt Key 和 Verification Token(如果启用加密) 填入配置文件中
6. 自定义你希望 PicoClaw react 你消息时的表情(可选, Reference URL: [Feishu Emoji List](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce))
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/bwmarrin/discordgo v0.29.0
github.com/caarlos0/env/v11 v11.3.1
github.com/chzyer/readline v1.5.1
github.com/ergochat/irc-go v0.5.0
github.com/gdamore/tcell/v2 v2.13.8
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
Expand Down Expand Up @@ -37,7 +38,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/ergochat/irc-go v0.5.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
Expand Down
24 changes: 22 additions & 2 deletions pkg/channels/feishu/feishu_64.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ package feishu

import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -195,25 +197,43 @@ func (c *FeishuChannel) SendPlaceholder(ctx context.Context, chatID string) (str
}

// ReactToMessage implements channels.ReactionCapable.
// Adds an "Pin" reaction and returns an undo function to remove it.
// Adds a reaction (randomly chosen from config) and returns an undo function to remove it.
func (c *FeishuChannel) ReactToMessage(ctx context.Context, chatID, messageID string) (func(), error) {
// Get emoji list from config
emojiList := c.config.RandomReactionEmoji
if len(emojiList) == 0 {
// Default to "Pin" if no config
emojiList = []string{"Pin"}
}

// Randomly choose one from the list using crypto/rand for better distribution
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(emojiList))))
var chosenEmoji string
if err != nil {
chosenEmoji = emojiList[0]
} else {
chosenEmoji = emojiList[idx.Int64()]
}

req := larkim.NewCreateMessageReactionReqBuilder().
MessageId(messageID).
Body(larkim.NewCreateMessageReactionReqBodyBuilder().
ReactionType(larkim.NewEmojiBuilder().EmojiType("Pin").Build()).
ReactionType(larkim.NewEmojiBuilder().EmojiType(chosenEmoji).Build()).
Build()).
Build()

resp, err := c.client.Im.V1.MessageReaction.Create(ctx, req)
if err != nil {
logger.ErrorCF("feishu", "Failed to add reaction", map[string]any{
"emoji": chosenEmoji,
"message_id": messageID,
"error": err.Error(),
})
return func() {}, fmt.Errorf("feishu react: %w", err)
}
if !resp.Success() {
logger.ErrorCF("feishu", "Reaction API error", map[string]any{
"emoji": chosenEmoji,
"message_id": messageID,
"code": resp.Code,
"msg": resp.Msg,
Expand Down
19 changes: 10 additions & 9 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,16 @@ type TelegramConfig struct {
}

type FeishuConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_FEISHU_ENABLED"`
AppID string `json:"app_id" env:"PICOCLAW_CHANNELS_FEISHU_APP_ID"`
AppSecret string `json:"app_secret" env:"PICOCLAW_CHANNELS_FEISHU_APP_SECRET"`
EncryptKey string `json:"encrypt_key" env:"PICOCLAW_CHANNELS_FEISHU_ENCRYPT_KEY"`
VerificationToken string `json:"verification_token" env:"PICOCLAW_CHANNELS_FEISHU_VERIFICATION_TOKEN"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_FEISHU_ALLOW_FROM"`
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
Placeholder PlaceholderConfig `json:"placeholder,omitempty"`
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_FEISHU_REASONING_CHANNEL_ID"`
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_FEISHU_ENABLED"`
AppID string `json:"app_id" env:"PICOCLAW_CHANNELS_FEISHU_APP_ID"`
AppSecret string `json:"app_secret" env:"PICOCLAW_CHANNELS_FEISHU_APP_SECRET"`
EncryptKey string `json:"encrypt_key" env:"PICOCLAW_CHANNELS_FEISHU_ENCRYPT_KEY"`
VerificationToken string `json:"verification_token" env:"PICOCLAW_CHANNELS_FEISHU_VERIFICATION_TOKEN"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_FEISHU_ALLOW_FROM"`
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
Placeholder PlaceholderConfig `json:"placeholder,omitempty"`
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_FEISHU_REASONING_CHANNEL_ID"`
RandomReactionEmoji FlexibleStringSlice `json:"random_reaction_emoji" env:"PICOCLAW_CHANNELS_FEISHU_RANDOM_REACTION_EMOJI"`
}

type DiscordConfig struct {
Expand Down
Loading