feat: add VK channel support
- Add VK channel implementation using vksdk - Support text messages and media attachments - Implement Long Poll API for real-time messaging - Add group chat support with trigger prefixes - Add user whitelist (allow_from) configuration - Add VK channel documentation Files: - pkg/channels/vk/: VK channel implementation - pkg/config/config.go: Add VKConfig structure - pkg/channels/manager.go: Register VK channel - pkg/gateway/gateway.go: Import VK channel package - docs/channels/vk/: Usage documentation
This commit is contained in:
parent
c0464bdd5d
commit
4f9c35b145
8 changed files with 504 additions and 1 deletions
169
docs/channels/vk/README.md
Normal file
169
docs/channels/vk/README.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# VK (VKontakte)
|
||||
|
||||
The VK channel uses Bots Long Poll API for bot-based communication with VK social network. It supports text messages, media attachments (photos, videos, audio, documents, stickers), and group chat interactions.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"vk": {
|
||||
"enabled": true,
|
||||
"token": "your_vk_access_token",
|
||||
"group_id": 123456789,
|
||||
"allow_from": ["123456789"],
|
||||
"group_trigger": {
|
||||
"mention_only": false,
|
||||
"prefixes": ["/bot", "!bot"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | ------------------------------------------------------------------ |
|
||||
| enabled | bool | Yes | Whether to enable the VK channel |
|
||||
| token | string | Yes | VK Community Access Token |
|
||||
| group_id | int | Yes | VK Community ID (Group ID) |
|
||||
| allow_from | array | No | Allowlist of user IDs; empty means all users are allowed |
|
||||
| group_trigger | object | No | Configuration for group chat triggers |
|
||||
|
||||
### Group Trigger Configuration
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------ | -------- | ------------------------------------------------------------------ |
|
||||
| mention_only | bool | Only respond when bot is mentioned in group chats |
|
||||
| prefixes | []string | List of prefixes that trigger bot response in group chats |
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create a VK Community
|
||||
|
||||
1. Go to [VK](https://vk.com) and log in
|
||||
2. Create a new community or use an existing one
|
||||
3. Note your Community ID (found in the community URL, e.g., `public123456789`)
|
||||
|
||||
### 2. Enable Messages
|
||||
|
||||
1. Go to your community page
|
||||
2. Click "Manage" → "Messages" → "Community Messages"
|
||||
3. Enable community messages
|
||||
|
||||
### 3. Create Access Token
|
||||
|
||||
1. Go to "Manage" → "API usage" → "Access tokens"
|
||||
2. Click "Create token"
|
||||
3. Select the following permissions:
|
||||
- `messages` - Access to messages
|
||||
- `photos` - Access to photos (optional)
|
||||
- `docs` - Access to documents (optional)
|
||||
4. Copy the generated access token
|
||||
|
||||
### 4. Configure PicoClaw
|
||||
|
||||
1. Add the token to your PicoClaw configuration
|
||||
2. Set the `group_id` to your community ID (numeric value)
|
||||
3. (Optional) Configure `allow_from` to restrict which user IDs can interact
|
||||
|
||||
## Features
|
||||
|
||||
### Supported Message Types
|
||||
|
||||
- **Text messages**: Full support for text messages
|
||||
- **Photos**: Photos are displayed as `[photo]` placeholder
|
||||
- **Videos**: Videos are displayed as `[video]` placeholder
|
||||
- **Audio**: Audio files are displayed as `[audio]` placeholder
|
||||
- **Documents**: Documents are displayed as `[document: filename]`
|
||||
- **Stickers**: Stickers are displayed as `[sticker]` placeholder
|
||||
|
||||
### Group Chat Support
|
||||
|
||||
The VK channel supports group chats with configurable triggers:
|
||||
|
||||
- **Mention-only mode**: Bot only responds when mentioned
|
||||
- **Prefix mode**: Bot responds to messages starting with specified prefixes
|
||||
- **Permissive mode**: Bot responds to all messages (default)
|
||||
|
||||
### Message Length
|
||||
|
||||
VK has a maximum message length of 4000 characters. PicoClaw automatically splits longer messages into multiple parts.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"vk": {
|
||||
"enabled": true,
|
||||
"token": "vk1.a.abc123...",
|
||||
"group_id": 123456789
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### With User Whitelist
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"vk": {
|
||||
"enabled": true,
|
||||
"token": "vk1.a.abc123...",
|
||||
"group_id": 123456789,
|
||||
"allow_from": ["123456789", "987654321"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### With Group Chat Triggers
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"vk": {
|
||||
"enabled": true,
|
||||
"token": "vk1.a.abc123...",
|
||||
"group_id": 123456789,
|
||||
"group_trigger": {
|
||||
"prefixes": ["/bot", "!bot"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Bot Not Responding
|
||||
|
||||
1. Check that the access token is valid
|
||||
2. Verify that the `group_id` is correct
|
||||
3. Ensure the user ID is in `allow_from` if configured
|
||||
4. Check PicoClaw logs for error messages
|
||||
|
||||
### Permission Errors
|
||||
|
||||
Make sure the access token has the necessary permissions:
|
||||
- `messages` - Required for sending and receiving messages
|
||||
- `photos` - Optional, for handling photo attachments
|
||||
- `docs` - Optional, for handling document attachments
|
||||
|
||||
### Group Chat Issues
|
||||
|
||||
If the bot doesn't respond in group chats:
|
||||
1. Check `group_trigger` configuration
|
||||
2. Try using a prefix to trigger the bot
|
||||
3. Check if the bot has permission to read group messages
|
||||
|
||||
## API Reference
|
||||
|
||||
The VK channel uses the [VK SDK for Go](https://github.com/SevereCloud/vksdk) library, which supports VK API version 5.199.
|
||||
|
||||
For more information about VK API, see:
|
||||
- [VK API Documentation](https://dev.vk.com/en)
|
||||
- [VK Bots Long Poll API](https://dev.vk.com/en/api/bots-long-poll/getting-started)
|
||||
5
go.mod
5
go.mod
|
|
@ -5,6 +5,7 @@ go 1.25.8
|
|||
require (
|
||||
fyne.io/systray v1.12.0
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/SevereCloud/vksdk/v3 v3.3.1
|
||||
github.com/adhocore/gronx v1.19.6
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
|
|
@ -87,6 +88,8 @@ require (
|
|||
github.com/segmentio/encoding v0.5.4 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.mau.fi/libsignal v0.2.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
|
|
@ -124,7 +127,7 @@ require (
|
|||
golang.org/x/arch v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.42.0
|
||||
)
|
||||
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -7,6 +7,8 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
|
|||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/SevereCloud/vksdk/v3 v3.3.1 h1:O86zsp5LQnHE+O5acvuXM/s6S1LyxzVTkF6+Lup0Jyg=
|
||||
github.com/SevereCloud/vksdk/v3 v3.3.1/go.mod h1:c6WaA5aocUYsXfkcUbg2qy45V9M1VDcqHHmHIN14NAw=
|
||||
github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc=
|
||||
github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
|
||||
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||
|
|
@ -277,6 +279,10 @@ github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADT
|
|||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yeongaori/discordgo-fork v0.0.0-20260319072544-e8e546f5d532 h1:gxFHYeUDGziRb0zXYEqBFohC+NJbIW9L0tddaXMWr2o=
|
||||
|
|
|
|||
|
|
@ -425,6 +425,10 @@ func (m *Manager) initChannels(channels *config.ChannelsConfig) error {
|
|||
m.initChannel("irc", "IRC")
|
||||
}
|
||||
|
||||
if channels.VK.Enabled && channels.VK.Token.String() != "" && channels.VK.GroupID != 0 {
|
||||
m.initChannel("vk", "VK")
|
||||
}
|
||||
|
||||
logger.InfoCF("channels", "Channel initialization completed", map[string]any{
|
||||
"enabled_channels": len(m.channels),
|
||||
})
|
||||
|
|
|
|||
13
pkg/channels/vk/init.go
Normal file
13
pkg/channels/vk/init.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package vk
|
||||
|
||||
import (
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
channels.RegisterFactory("vk", func(cfg *config.Config, b *bus.MessageBus) (channels.Channel, error) {
|
||||
return NewVKChannel(cfg, b)
|
||||
})
|
||||
}
|
||||
291
pkg/channels/vk/vk.go
Normal file
291
pkg/channels/vk/vk.go
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
package vk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/SevereCloud/vksdk/v3/api"
|
||||
"github.com/SevereCloud/vksdk/v3/api/params"
|
||||
"github.com/SevereCloud/vksdk/v3/events"
|
||||
"github.com/SevereCloud/vksdk/v3/longpoll-bot"
|
||||
"github.com/SevereCloud/vksdk/v3/object"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/identity"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
type VKChannel struct {
|
||||
*channels.BaseChannel
|
||||
vk *api.VK
|
||||
lp *longpoll.LongPoll
|
||||
config *config.Config
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewVKChannel(cfg *config.Config, bus *bus.MessageBus) (*VKChannel, error) {
|
||||
vkCfg := cfg.Channels.VK
|
||||
|
||||
vk := api.NewVK(vkCfg.Token.String())
|
||||
|
||||
base := channels.NewBaseChannel(
|
||||
"vk",
|
||||
vkCfg,
|
||||
bus,
|
||||
vkCfg.AllowFrom,
|
||||
channels.WithMaxMessageLength(4000),
|
||||
channels.WithGroupTrigger(vkCfg.GroupTrigger),
|
||||
channels.WithReasoningChannelID(vkCfg.ReasoningChannelID),
|
||||
)
|
||||
|
||||
return &VKChannel{
|
||||
BaseChannel: base,
|
||||
vk: vk,
|
||||
config: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *VKChannel) Start(ctx context.Context) error {
|
||||
logger.InfoC("vk", "Starting VK bot (Long Poll mode)...")
|
||||
|
||||
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||
|
||||
groupID := c.config.Channels.VK.GroupID
|
||||
if groupID == 0 {
|
||||
c.cancel()
|
||||
return fmt.Errorf("group_id is required for VK bot")
|
||||
}
|
||||
|
||||
lp, err := longpoll.NewLongPoll(c.vk, groupID)
|
||||
if err != nil {
|
||||
c.cancel()
|
||||
return fmt.Errorf("failed to create long poll: %w", err)
|
||||
}
|
||||
c.lp = lp
|
||||
|
||||
lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
|
||||
c.handleMessage(obj.Message)
|
||||
})
|
||||
|
||||
c.SetRunning(true)
|
||||
|
||||
logger.InfoCF("vk", "VK bot connected", map[string]any{
|
||||
"group_id": groupID,
|
||||
})
|
||||
|
||||
go func() {
|
||||
if err := lp.Run(); err != nil {
|
||||
logger.ErrorCF("vk", "Long poll failed", map[string]any{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *VKChannel) Stop(ctx context.Context) error {
|
||||
logger.InfoC("vk", "Stopping VK bot...")
|
||||
c.SetRunning(false)
|
||||
|
||||
if c.lp != nil {
|
||||
c.lp.Shutdown()
|
||||
}
|
||||
|
||||
if c.cancel != nil {
|
||||
c.cancel()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *VKChannel) handleMessage(msg object.MessagesMessage) {
|
||||
if msg.Action.Type != "" {
|
||||
return
|
||||
}
|
||||
|
||||
if bool(msg.Out) {
|
||||
return
|
||||
}
|
||||
|
||||
peerID := msg.PeerID
|
||||
chatID := strconv.Itoa(peerID)
|
||||
|
||||
fromID := msg.FromID
|
||||
userID := strconv.Itoa(fromID)
|
||||
|
||||
platformID := userID
|
||||
sender := bus.SenderInfo{
|
||||
Platform: "vk",
|
||||
PlatformID: platformID,
|
||||
CanonicalID: identity.BuildCanonicalID("vk", platformID),
|
||||
DisplayName: c.getUserName(fromID),
|
||||
}
|
||||
|
||||
if !c.IsAllowedSender(sender) {
|
||||
logger.DebugCF("vk", "Message from unauthorized user", map[string]any{
|
||||
"peer_id": peerID,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
text := msg.Text
|
||||
if text == "" && len(msg.Attachments) > 0 {
|
||||
text = c.processAttachments(msg.Attachments)
|
||||
}
|
||||
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
groupTrigger := c.config.Channels.VK.GroupTrigger
|
||||
isGroupChat := peerID != fromID
|
||||
|
||||
if isGroupChat {
|
||||
isMentioned := c.isMentioned(msg)
|
||||
if isMentioned {
|
||||
text = c.stripBotMention(text)
|
||||
}
|
||||
respond, cleaned := c.ShouldRespondInGroup(isMentioned, text)
|
||||
if !respond {
|
||||
return
|
||||
}
|
||||
text = cleaned
|
||||
_ = groupTrigger
|
||||
}
|
||||
|
||||
peerKind := "direct"
|
||||
peerIDStr := userID
|
||||
if isGroupChat {
|
||||
peerKind = "group"
|
||||
peerIDStr = chatID
|
||||
}
|
||||
|
||||
peer := bus.Peer{Kind: peerKind, ID: peerIDStr}
|
||||
messageID := strconv.Itoa(msg.ConversationMessageID)
|
||||
|
||||
metadata := map[string]string{
|
||||
"user_id": userID,
|
||||
"is_group": fmt.Sprintf("%t", isGroupChat),
|
||||
}
|
||||
|
||||
c.HandleMessage(c.ctx,
|
||||
peer,
|
||||
messageID,
|
||||
userID,
|
||||
chatID,
|
||||
text,
|
||||
nil,
|
||||
metadata,
|
||||
sender,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *VKChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]string, error) {
|
||||
if !c.IsRunning() {
|
||||
return nil, channels.ErrNotRunning
|
||||
}
|
||||
|
||||
peerID, err := strconv.Atoi(msg.ChatID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid chat ID %s: %w", msg.ChatID, channels.ErrSendFailed)
|
||||
}
|
||||
|
||||
if msg.Content == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var messageIDs []string
|
||||
chunks := channels.SplitMessage(msg.Content, 4000)
|
||||
|
||||
for _, chunk := range chunks {
|
||||
if chunk == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
b := params.NewMessagesSendBuilder()
|
||||
b.Message(chunk)
|
||||
b.RandomID(0)
|
||||
b.PeerID(peerID)
|
||||
|
||||
if msg.ReplyToMessageID != "" {
|
||||
if replyID, err := strconv.Atoi(msg.ReplyToMessageID); err == nil {
|
||||
b.ReplyTo(replyID)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.vk.MessagesSend(b.Params)
|
||||
if err != nil {
|
||||
logger.ErrorCF("vk", "Failed to send message", map[string]any{
|
||||
"error": err.Error(),
|
||||
"peer_id": peerID,
|
||||
})
|
||||
return messageIDs, fmt.Errorf("failed to send message: %w", err)
|
||||
}
|
||||
|
||||
messageIDs = append(messageIDs, strconv.Itoa(resp))
|
||||
}
|
||||
|
||||
return messageIDs, nil
|
||||
}
|
||||
|
||||
func (c *VKChannel) isMentioned(msg object.MessagesMessage) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *VKChannel) stripBotMention(text string) string {
|
||||
return strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
func (c *VKChannel) stripPrefix(text string, prefixes []string) string {
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(text, prefix) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(text, prefix))
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func (c *VKChannel) getUserName(userID int) string {
|
||||
users, err := c.vk.UsersGet(api.Params{
|
||||
"user_ids": userID,
|
||||
})
|
||||
if err != nil || len(users) == 0 {
|
||||
return strconv.Itoa(userID)
|
||||
}
|
||||
|
||||
user := users[0]
|
||||
return fmt.Sprintf("%s %s", user.FirstName, user.LastName)
|
||||
}
|
||||
|
||||
func (c *VKChannel) processAttachments(attachments []object.MessagesMessageAttachment) string {
|
||||
var parts []string
|
||||
|
||||
for _, att := range attachments {
|
||||
switch att.Type {
|
||||
case "photo":
|
||||
parts = append(parts, "[photo]")
|
||||
case "video":
|
||||
parts = append(parts, "[video]")
|
||||
case "audio":
|
||||
parts = append(parts, "[audio]")
|
||||
case "doc":
|
||||
if att.Doc.Title != "" {
|
||||
parts = append(parts, fmt.Sprintf("[document: %s]", att.Doc.Title))
|
||||
} else {
|
||||
parts = append(parts, "[document]")
|
||||
}
|
||||
case "sticker":
|
||||
parts = append(parts, "[sticker]")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
|
@ -293,6 +293,7 @@ type ChannelsConfig struct {
|
|||
Pico PicoConfig `json:"pico" yaml:"pico,omitempty"`
|
||||
PicoClient PicoClientConfig `json:"pico_client" yaml:"pico_client,omitempty"`
|
||||
IRC IRCConfig `json:"irc" yaml:"irc,omitempty"`
|
||||
VK VKConfig `json:"vk" yaml:"vk,omitempty"`
|
||||
}
|
||||
|
||||
// GroupTriggerConfig controls when the bot responds in group chats.
|
||||
|
|
@ -547,6 +548,21 @@ type IRCConfig struct {
|
|||
ReasoningChannelID string `json:"reasoning_channel_id" yaml:"-"`
|
||||
}
|
||||
|
||||
type VKConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"-" env:"PICOCLAW_CHANNELS_VK_ENABLED"`
|
||||
Token SecureString `json:"token,omitzero" yaml:"token,omitempty" env:"PICOCLAW_CHANNELS_VK_TOKEN"`
|
||||
GroupID int `json:"group_id" yaml:"-" env:"PICOCLAW_CHANNELS_VK_GROUP_ID"`
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from" yaml:"-" env:"PICOCLAW_CHANNELS_VK_ALLOW_FROM"`
|
||||
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty" yaml:"-"`
|
||||
Typing TypingConfig `json:"typing,omitempty" yaml:"-"`
|
||||
Placeholder PlaceholderConfig `json:"placeholder,omitempty" yaml:"-"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" yaml:"-" env:"PICOCLAW_CHANNELS_VK_REASONING_CHANNEL_ID"`
|
||||
}
|
||||
|
||||
func (c *VKConfig) SetToken(token string) {
|
||||
c.Token = *NewSecureString(token)
|
||||
}
|
||||
|
||||
type HeartbeatConfig struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_HEARTBEAT_ENABLED"`
|
||||
Interval int `json:"interval" env:"PICOCLAW_HEARTBEAT_INTERVAL"` // minutes, min 5
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
_ "github.com/sipeed/picoclaw/pkg/channels/qq"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/slack"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/telegram"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/vk"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/weixin"
|
||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue