add dialogs
This commit is contained in:
parent
125eab031f
commit
f29d0f81f9
6 changed files with 222 additions and 12 deletions
|
|
@ -13,11 +13,10 @@ The Model Context Protocol (MCP) is a system that lets AI apps, like Claude Desk
|
||||||
## What does this server do?
|
## What does this server do?
|
||||||
|
|
||||||
- [x] Get current user data
|
- [x] Get current user data
|
||||||
- [ ] Get the list of dialogs (chats, channels, groups)
|
- [x] Get the list of dialogs (chats, channels, groups)
|
||||||
- [ ] Get the list of (unread) messages in the given dialog
|
- [ ] Get the list of (unread) messages in the given dialog
|
||||||
- [ ] Mark chanel as read
|
- [ ] Mark chanel as read
|
||||||
- [ ] Retrieve messages by date and time
|
- [ ] Retrieve messages by date and time
|
||||||
- [ ] Download media files
|
|
||||||
- [ ] Get the list of contacts
|
- [ ] Get the list of contacts
|
||||||
- [ ] Draft a message
|
- [ ] Draft a message
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func main() {
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
client := tg.New(appID, apiHash, sessionPath).T
|
client := tg.New(appID, apiHash, sessionPath).T()
|
||||||
|
|
||||||
if err := client.Run(ctx, func(ctx context.Context) error {
|
if err := client.Run(ctx, func(ctx context.Context) error {
|
||||||
self, err := client.Self(ctx)
|
self, err := client.Self(ctx)
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,24 @@ package tg
|
||||||
import "github.com/gotd/td/telegram"
|
import "github.com/gotd/td/telegram"
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
T *telegram.Client
|
appID int
|
||||||
|
appHash string
|
||||||
|
sessionPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(appID int, appHash, sessionPath string) *Client {
|
func New(appID int, appHash, sessionPath string) *Client {
|
||||||
client := telegram.NewClient(appID, appHash, telegram.Options{
|
return &Client{
|
||||||
|
appID: appID,
|
||||||
|
appHash: appHash,
|
||||||
|
sessionPath: sessionPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) T() *telegram.Client {
|
||||||
|
return telegram.NewClient(c.appID, c.appHash, telegram.Options{
|
||||||
SessionStorage: &telegram.FileSessionStorage{
|
SessionStorage: &telegram.FileSessionStorage{
|
||||||
Path: sessionPath,
|
Path: c.sessionPath,
|
||||||
},
|
},
|
||||||
NoUpdates: true,
|
NoUpdates: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return &Client{
|
|
||||||
T: client,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
187
internal/tg/dialogs.go
Normal file
187
internal/tg/dialogs.go
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
package tg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/gotd/td/tg"
|
||||||
|
mcp "github.com/metoro-io/mcp-golang"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialogType represents the type of dialog for filtering
|
||||||
|
type DialogType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DialogTypeAll represents all types of dialogs
|
||||||
|
DialogTypeAll DialogType = ""
|
||||||
|
// DialogTypeUser represents user chats
|
||||||
|
DialogTypeUser DialogType = "user"
|
||||||
|
// DialogTypeChat represents group chats
|
||||||
|
DialogTypeChat DialogType = "chat"
|
||||||
|
// DialogTypeChannel represents channels
|
||||||
|
DialogTypeChannel DialogType = "channel"
|
||||||
|
|
||||||
|
// DefaultDialogsLimit is the default limit for dialogs
|
||||||
|
DefaultDialogsLimit = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialogsArguments contains parameters for getting dialogs
|
||||||
|
type DialogsArguments struct {
|
||||||
|
Type DialogType `json:"type,omitempty" jsonschema:"description=Filter dialogs by type (user, chat, channel or empty for all),enum=,enum=user,enum=chat,enum=channel"`
|
||||||
|
Limit int `json:"limit,omitempty" jsonschema:"description=Maximum number of dialogs to return (max: 100),default=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialogInfo represents a simplified dialog structure
|
||||||
|
type DialogInfo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
UnreadCount int `json:"unread_count"`
|
||||||
|
LastMessageID int `json:"last_message_id"`
|
||||||
|
IsVerified bool `json:"is_verified,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDialogs returns a list of dialogs (chats, channels, groups)
|
||||||
|
func (c *Client) GetDialogs(args DialogsArguments) (*mcp.ToolResponse, error) {
|
||||||
|
var result []DialogInfo
|
||||||
|
|
||||||
|
if args.Limit <= 0 || args.Limit > DefaultDialogsLimit {
|
||||||
|
args.Limit = DefaultDialogsLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Type == "" {
|
||||||
|
args.Type = DialogTypeAll
|
||||||
|
}
|
||||||
|
|
||||||
|
client := c.T()
|
||||||
|
if err := client.Run(context.Background(), func(ctx context.Context) error {
|
||||||
|
api := client.API()
|
||||||
|
dialogsClass, err := api.MessagesGetDialogs(ctx, &tg.MessagesGetDialogsRequest{
|
||||||
|
OffsetPeer: &tg.InputPeerEmpty{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get dialogs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialogs *tg.MessagesDialogs
|
||||||
|
switch d := dialogsClass.(type) {
|
||||||
|
case *tg.MessagesDialogs:
|
||||||
|
dialogs = d
|
||||||
|
case *tg.MessagesDialogsSlice:
|
||||||
|
dialogs = &tg.MessagesDialogs{
|
||||||
|
Dialogs: d.Dialogs,
|
||||||
|
Messages: d.Messages,
|
||||||
|
Chats: d.Chats,
|
||||||
|
Users: d.Users,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected dialogs response type")
|
||||||
|
}
|
||||||
|
|
||||||
|
result = make([]DialogInfo, 0, len(dialogs.Dialogs))
|
||||||
|
|
||||||
|
for _, dialog := range dialogs.Dialogs {
|
||||||
|
dialogItem, ok := dialog.(*tg.Dialog)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var info DialogInfo
|
||||||
|
info.UnreadCount = dialogItem.UnreadCount
|
||||||
|
info.LastMessageID = dialogItem.TopMessage
|
||||||
|
|
||||||
|
switch peer := dialogItem.Peer.(type) {
|
||||||
|
case *tg.PeerUser:
|
||||||
|
if args.Type != DialogTypeAll && args.Type != DialogTypeUser {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userItem := range dialogs.Users {
|
||||||
|
user, ok := userItem.(*tg.User)
|
||||||
|
if !ok || user.ID != peer.UserID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info.ID = user.ID
|
||||||
|
info.Type = "user"
|
||||||
|
info.Title = getUserName(user)
|
||||||
|
info.IsVerified = user.Verified
|
||||||
|
|
||||||
|
result = append(result, info)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case *tg.PeerChat:
|
||||||
|
if args.Type != DialogTypeAll && args.Type != DialogTypeChat {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chatItem := range dialogs.Chats {
|
||||||
|
chat, ok := chatItem.(*tg.Chat)
|
||||||
|
if !ok || chat.ID != peer.ChatID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info.ID = chat.ID
|
||||||
|
info.Type = "chat"
|
||||||
|
info.Title = chat.Title
|
||||||
|
|
||||||
|
result = append(result, info)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case *tg.PeerChannel:
|
||||||
|
if args.Type != DialogTypeAll && args.Type != DialogTypeChannel {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, channelItem := range dialogs.Chats {
|
||||||
|
channel, ok := channelItem.(*tg.Channel)
|
||||||
|
if !ok || channel.ID != peer.ChannelID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info.ID = channel.ID
|
||||||
|
info.Type = "channel"
|
||||||
|
info.Title = channel.Title
|
||||||
|
info.IsVerified = channel.Verified
|
||||||
|
|
||||||
|
result = append(result, info)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get dialogs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert response to JSON
|
||||||
|
jsonData, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to marshal response")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
return result[i].LastMessageID > result[j].LastMessageID
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(result) > args.Limit {
|
||||||
|
result = result[:args.Limit]
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewToolResponse(mcp.NewTextContent(string(jsonData))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get user's name
|
||||||
|
func getUserName(user *tg.User) string {
|
||||||
|
name := user.FirstName
|
||||||
|
if user.LastName != "" {
|
||||||
|
name += " " + user.LastName
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
@ -20,8 +20,9 @@ type EmptyArguments struct{}
|
||||||
func (c *Client) GetMe(_ EmptyArguments) (*mcp.ToolResponse, error) {
|
func (c *Client) GetMe(_ EmptyArguments) (*mcp.ToolResponse, error) {
|
||||||
var toolResponse *mcp.ToolResponse
|
var toolResponse *mcp.ToolResponse
|
||||||
|
|
||||||
if err := c.T.Run(context.Background(), func(ctx context.Context) error {
|
client := c.T()
|
||||||
self, err := c.T.Self(ctx)
|
if err := client.Run(context.Background(), func(ctx context.Context) error {
|
||||||
|
self, err := client.Self(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to get self info")
|
return errors.Wrap(err, "failed to get self info")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
serve.go
17
serve.go
|
|
@ -41,6 +41,18 @@ func serve(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
log.Info().RawJSON("answer", data).Msg("Check GetMe: OK")
|
log.Info().RawJSON("answer", data).Msg("Check GetMe: OK")
|
||||||
|
|
||||||
|
answer, err = client.GetDialogs(tg.DialogsArguments{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get dialogs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err = json.MarshalIndent(answer, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().RawJSON("answer", data).Msg("Check GetDialogs: OK")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,6 +61,11 @@ func serve(ctx context.Context, cmd *cli.Command) error {
|
||||||
return fmt.Errorf("register tool: %w", err)
|
return fmt.Errorf("register tool: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = server.RegisterTool("dialogs", "Get list of dialogs (chats, channels, groups)", client.GetDialogs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("register dialogs tool: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := server.Serve(); err != nil {
|
if err := server.Serve(); err != nil {
|
||||||
return fmt.Errorf("serve: %w", err)
|
return fmt.Errorf("serve: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue