fix: dialogs tool

This commit is contained in:
John Doe 2025-04-03 02:22:46 +03:00
parent 5a37b27d7c
commit 421f6874d3
6 changed files with 190 additions and 26 deletions

View file

@ -46,7 +46,10 @@ tasks:
desc: Run linter
cmd: golangci-lint run --fix ./...
# git tag -a v0.1.1 -m "Added releases"
# git push origin v0.1.0
tag:
desc: Create a new tag
cmds:
- git tag -a v0.1.6 -m "Dialog fix"
- git push origin v0.1.6

2
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.34.0
github.com/spf13/pflag v1.0.6
github.com/tidwall/gjson v1.18.0
github.com/urfave/cli/v3 v3.1.0
golang.org/x/time v0.11.0
)
@ -34,7 +35,6 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ogen-go/ogen v1.10.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"github.com/gotd/td/tg"
@ -70,11 +71,16 @@ func (c *Client) GetDialogs(args DialogsArguments) (*mcp.ToolResponse, error) {
api := client.API()
dialogsClass, err := api.MessagesGetDialogs(ctx, &tg.MessagesGetDialogsRequest{
OffsetPeer: &tg.InputPeerEmpty{},
Limit: 20,
})
if err != nil {
return fmt.Errorf("failed to get dialogs: %w", err)
}
// Debug
// jsonData, _ := json.Marshal(dialogsClass)
// log.Info().RawJSON("dialogs", cleanJSON(jsonData)).Msg("dialogs")
var dialogs *tg.MessagesDialogs
switch d := dialogsClass.(type) {
case *tg.MessagesDialogs:
@ -90,6 +96,25 @@ func (c *Client) GetDialogs(args DialogsArguments) (*mcp.ToolResponse, error) {
return errors.New("unexpected dialogs response type")
}
messageMap := make(map[string][]*tg.Message)
for _, m := range dialogs.Messages {
msg, ok := m.(*tg.Message)
if !ok {
continue
}
if msg.PeerID == nil {
continue
}
messageMap[msg.PeerID.String()] = append(messageMap[msg.PeerID.String()], msg)
}
usersMap := make(map[string]tg.UserClass)
for _, u := range dialogs.Users {
usersMap["Peer"+u.String()] = u
}
result = make([]DialogInfo, 0, len(dialogs.Dialogs))
for _, dialog := range dialogs.Dialogs {
@ -103,22 +128,27 @@ func (c *Client) GetDialogs(args DialogsArguments) (*mcp.ToolResponse, error) {
info.LastMessageID = dialogItem.TopMessage
if args.WithLastMessages {
for _, msg := range dialogs.Messages {
message, ok := msg.(*tg.Message)
if !ok {
continue
msgs := messageMap[dialogItem.Peer.String()]
for _, msg := range msgs {
var who string
if msg.FromID != nil {
if u, ok := usersMap[msg.FromID.String()]; ok {
who = u.String()
}
}
var who string
if message.FromID != nil {
who = message.FromID.String()
// Limit message to 20 words
text := msg.Message
words := strings.Fields(text)
if len(words) > 20 {
text = strings.Join(words[:20], " ") + "..."
}
info.LastMessages = append(info.LastMessages, MessageInfo{
Who: who,
When: time.Unix(int64(message.Date), 0).Format(time.DateTime),
Text: message.Message,
IsUnread: !message.Out,
When: time.Unix(int64(msg.Date), 0).Format(time.DateTime),
Text: text,
IsUnread: dialogItem.UnreadCount > 0,
})
}
}
@ -206,15 +236,6 @@ func (c *Client) GetDialogs(args DialogsArguments) (*mcp.ToolResponse, error) {
return nil, errors.Wrap(err, "failed to marshal response")
}
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
cleanedData := cleanJSON(jsonData)
return mcp.NewToolResponse(mcp.NewTextContent(string(cleanedData))), nil
}

88
internal/tg/helpers.go Normal file
View file

@ -0,0 +1,88 @@
package tg
import (
"encoding/json"
"github.com/gotd/td/tg"
"github.com/tidwall/gjson"
)
func getUserName(user *tg.User) string {
if username, ok := user.GetUsername(); ok && username != "" {
return "@" + username
}
name := user.FirstName
if user.LastName != "" {
name += " " + user.LastName
}
return name
}
// cleanJSON removes empty/default fields from JSON
func cleanJSON(data []byte) []byte {
result := gjson.ParseBytes(data)
cleaned := cleanValue(result)
if cleaned == nil {
return data // Return original if cleaning failed
}
cleanedJSON, err := json.Marshal(cleaned)
if err != nil {
return data // Return original if marshaling failed
}
return cleanedJSON
}
func cleanValue(v gjson.Result) interface{} {
switch v.Type {
case gjson.String:
if v.String() == "" {
return nil
}
return v.String()
case gjson.Number:
// return nil
if v.Int() == 0 && v.Float() == 0 {
return nil
}
return v.Value()
case gjson.True:
return nil
// return true
case gjson.False:
return nil
case gjson.Null:
return nil
case gjson.JSON:
if v.IsArray() {
arr := make([]interface{}, 0)
v.ForEach(func(_, item gjson.Result) bool {
if cleaned := cleanValue(item); cleaned != nil {
arr = append(arr, cleaned)
}
return true
})
if len(arr) == 0 {
return nil
}
return arr
}
if v.IsObject() {
obj := make(map[string]interface{})
v.ForEach(func(key, val gjson.Result) bool {
if cleaned := cleanValue(val); cleaned != nil {
obj[key.String()] = cleaned
}
return true
})
if len(obj) == 0 {
return nil
}
return obj
}
}
return nil
}

47
resourse.go Normal file
View file

@ -0,0 +1,47 @@
package main
import (
"encoding/json"
"fmt"
mcp "github.com/metoro-io/mcp-golang"
)
func sampleResource() (*mcp.ResourceResponse, error) {
type Chat struct {
ID int64 `json:"id,omitempty"`
Type string `json:"type"`
Title string `json:"title"`
UnreadCount int `json:"unread_count"`
}
chats := []Chat{
{
ID: 123456789,
Type: "channel",
Title: "Sample Channel",
UnreadCount: 5,
},
{
ID: 987654321,
Type: "group",
Title: "Test Group",
UnreadCount: 2,
},
}
rss := make([]*mcp.EmbeddedResource, 0, len(chats))
for _, chat := range chats {
chat.ID = 0
uri := fmt.Sprintf("telegram://chats/%d", chat.ID)
content, err := json.Marshal(chat)
if err != nil {
return nil, err
}
rss = append(rss, mcp.NewTextEmbeddedResource(uri, string(content), "application/json"))
}
return mcp.NewResourceResponse(rss...), nil
}

View file

@ -56,16 +56,21 @@ func serve(ctx context.Context, cmd *cli.Command) error {
return nil
}
err = server.RegisterTool("me", "Get current Telegram account info", client.GetMe)
err = server.RegisterTool("tg_me", "Get current account info", client.GetMe)
if err != nil {
return fmt.Errorf("register tool: %w", err)
}
err = server.RegisterTool("dialogs", "Get list of dialogs (chats, channels, groups)", client.GetDialogs)
err = server.RegisterTool("tg_dialogs", "Get list of dialogs (chats, channels, groups)", client.GetDialogs)
if err != nil {
return fmt.Errorf("register dialogs tool: %w", err)
}
err = server.RegisterResource("telegram://chats", "tg_chats", "List of telegram chats", "application/json", sampleResource)
if err != nil {
return fmt.Errorf("register chats resource: %w", err)
}
if err := server.Serve(); err != nil {
return fmt.Errorf("serve: %w", err)
}