Skip to main content
Glama
dialogs.go7.89 kB
package tg import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/gotd/td/tg" mcp "github.com/metoro-io/mcp-golang" "github.com/pkg/errors" "github.com/rs/zerolog/log" ) // DialogType represents the type of dialog for filtering type DialogType string const ( DialogTypeUnknown DialogType = "unknown" DialogTypeAll DialogType = "" DialogTypeUser DialogType = "user" DialogTypeBot DialogType = "bot" DialogTypeChat DialogType = "chat" DialogTypeChannel DialogType = "channel" DefaultDialogsLimit = 100 ) // nolint:lll type DialogsArguments struct { Offset string `json:"offset,omitempty" jsonschema:"description=Offset for continuation"` OnlyUnread bool `json:"only_unread,omitempty" jsonschema:"description=Include only dialogs with unread mark"` } type MessageInfo struct { Who string `json:"who,omitempty"` When string `json:"when"` Text string `json:"text,omitempty"` IsUnread bool `json:"is_unread,omitempty"` ts int } type DialogInfo struct { Name string `json:"name,omitempty"` Type string `json:"type"` Title string `json:"title"` LastMessage *MessageInfo `json:"last_message,omitempty"` Empty bool `json:"empty,omitempty"` } type DialogsResponse struct { Dialogs []DialogInfo `json:"dialogs"` Offset DialogsOffset `json:"offset"` } // GetDialogs returns a list of dialogs (chats, channels, groups) func (c *Client) GetDialogs(args DialogsArguments) (*mcp.ToolResponse, error) { var offset DialogsOffset if args.Offset != "" { if err := offset.UnmarshalJSON([]byte(args.Offset)); err != nil { return nil, errors.Wrap(err, "failed to unmarshal offset") } } if offset.Peer == nil { offset.Peer = &tg.InputPeerEmpty{} } var dc tg.MessagesDialogsClass client := c.T() if err := client.Run(context.Background(), func(ctx context.Context) (err error) { api := client.API() dc, err = api.MessagesGetDialogs(ctx, &tg.MessagesGetDialogsRequest{ OffsetPeer: offset.Peer, OffsetID: offset.MsgID, OffsetDate: offset.Date, }) if err != nil { return fmt.Errorf("failed to get dialogs: %w", err) } // Debug //jsonData, _ := json.Marshal(dc) //log.Info().RawJSON("dialogs", cleanJSON(jsonData)).Msg("dialogs") return nil }); err != nil { return nil, errors.Wrap(err, "failed to get dialogs") } d, err := newDialogs(dc, args.OnlyUnread) if err != nil { return nil, errors.Wrap(err, "failed to get dialogs") } rsp := DialogsResponse{ Dialogs: d.Info(), Offset: d.Offset(), } jsonData, err := json.Marshal(rsp) if err != nil { return nil, errors.Wrap(err, "failed to marshal response") } return mcp.NewToolResponse(mcp.NewTextContent(string(jsonData))), nil } type dialogs struct { tg.MessagesDialogs // chat id key messages map[int64]*tg.Message users map[int64]*tg.User //dialogs map[string]*tg.Dialog chats map[int64]*tg.Chat channels map[int64]*tg.Channel //opts onlyUnread bool } func newDialogs(rawD tg.MessagesDialogsClass, onlyUnread bool) (*dialogs, error) { var d dialogs switch dT := rawD.(type) { case *tg.MessagesDialogs: d = dialogs{MessagesDialogs: *dT} case *tg.MessagesDialogsSlice: d = dialogs{MessagesDialogs: tg.MessagesDialogs{ Dialogs: dT.Dialogs, Messages: dT.Messages, Chats: dT.Chats, Users: dT.Users, }} case *tg.MessagesDialogsNotModified: default: } d.messages = make(map[int64]*tg.Message) for _, m := range d.Messages { switch mT := m.(type) { case *tg.Message: d.messages[getPeerID(mT.PeerID)] = mT case *tg.MessageService, *tg.MessageEmpty: default: } } delete(d.messages, 0) d.users = make(map[int64]*tg.User) for _, uc := range d.Users { u, ok := uc.(*tg.User) if !ok { log.Debug().Msgf("newDialogs(%+v): invalid message user", uc) continue } d.users[u.GetID()] = u } d.chats = make(map[int64]*tg.Chat) d.channels = make(map[int64]*tg.Channel) for _, c := range d.Chats { switch cT := c.(type) { case *tg.Chat: d.chats[cT.ID] = cT case *tg.Channel: d.channels[cT.ID] = cT case *tg.ChatForbidden, *tg.ChannelForbidden, *tg.ChatEmpty: default: } } d.onlyUnread = onlyUnread return &d, nil } func (d *dialogs) Info() []DialogInfo { ds := make([]DialogInfo, 0, len(d.Dialogs)) for _, dItem := range d.Dialogs { dialogItem, ok := dItem.(*tg.Dialog) if !ok { continue } if d.onlyUnread && dialogItem.UnreadCount == 0 { continue } info, err := d.processDialog(dialogItem) if err != nil { log.Debug().Err(err).Str("dialog", dItem.String()).Msg("failed process dialog") continue } if info.Title == "" { continue } ds = append(ds, info) } return ds } func (d *dialogs) Offset() DialogsOffset { for i := len(d.Dialogs) - 1; i >= 0; i-- { dialogItem, ok := d.Dialogs[i].(*tg.Dialog) if !ok { continue } if dialogItem.Peer == nil { continue } msg, ok := d.messages[getPeerID(dialogItem.Peer)] if !ok { continue } return DialogsOffset{ MsgID: msg.ID, Date: msg.Date, Peer: getInputPeerID(dialogItem.Peer), } } return DialogsOffset{} } func (d *dialogs) processDialog(dialogItem *tg.Dialog) (DialogInfo, error) { var info DialogInfo if msg, ok := d.messages[getPeerID(dialogItem.Peer)]; ok { var who string if msg.FromID != nil { name, _, err := d.getNameID(msg.FromID) if err != nil { return DialogInfo{}, errors.Wrap(err, "failed to get dialog name") } who = name } // Limit message to 20 words text := msg.Message words := strings.Fields(text) if len(words) > 20 { text = strings.Join(words[:20], " ") + "..." } info.LastMessage = &MessageInfo{ Who: who, When: time.Unix(int64(msg.Date), 0).Format(time.DateTime), ts: msg.Date, Text: text, IsUnread: dialogItem.UnreadCount > 0, } } if dialogItem.Peer == nil { return DialogInfo{}, fmt.Errorf("no peer: %s", dialogItem.String()) } var err error info.Title, info.Name, err = d.getNameID(dialogItem.Peer) if err != nil { return DialogInfo{}, err } info.Type = string(d.getType(dialogItem)) if info.LastMessage == nil { info.Empty = true } return info, nil } func (d *dialogs) getNameID(pC tg.PeerClass) (string, string, error) { var name, username string switch p := pC.(type) { case *tg.PeerUser: u, ok := d.users[p.GetUserID()] if !ok { return "", "", errors.Errorf("peerid(%d): invalid message user", p.GetUserID()) } name = getTitle(u) username = getUsername(u) case *tg.PeerChannel: channel, ok := d.channels[p.GetChannelID()] if !ok { return "", "", errors.Errorf("peerid(%d): invalid message channel", p.GetChannelID()) } name = getTitle(channel) username = getUsername(channel) case *tg.PeerChat: chat, ok := d.chats[p.GetChatID()] if !ok { return "", "", errors.Errorf("peerid(%d): invalid message chat", p.GetChatID()) } name = getTitle(chat) username = getUsername(chat) default: return "", "", fmt.Errorf("chose author(%T): invalid dialog peer", p) } return name, username, nil } func (d *dialogs) getType(rawD *tg.Dialog) DialogType { switch v := rawD.Peer.(type) { case *tg.PeerChannel: return DialogTypeChannel case *tg.PeerChat: return DialogTypeChat case *tg.PeerUser: u, ok := d.users[getPeerID(rawD.Peer)] if !ok { log.Debug().Msgf("getType(%+v): user not found", v) return DialogTypeUser } if u.Bot { return DialogTypeBot } return DialogTypeUser default: log.Debug().Msgf("getType(%+v): unknown dialog type", v) return DialogTypeUnknown } } func getPeerID(p tg.PeerClass) int64 { if p == nil { return 0 } switch v := p.(type) { case *tg.PeerChannel: return v.ChannelID case *tg.PeerChat: return v.ChatID case *tg.PeerUser: return v.UserID default: return 0 } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chaindead/telegram-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server