diff --git a/README.md b/README.md index 3251abf..b3e32d3 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The Model Context Protocol (MCP) is a system that lets AI apps, like Claude Desk - [x] Get the list of dialogs (chats, channels, groups) - [x] Get the list of messages in the given dialog - [x] Draft a message -- [ ] Mark chanel as read +- [x] Mark dialog as read - [ ] Retrieve messages by date and time - [ ] Get the list of contacts diff --git a/internal/tg/read.go b/internal/tg/read.go new file mode 100644 index 0000000..c702bb5 --- /dev/null +++ b/internal/tg/read.go @@ -0,0 +1,86 @@ +package tg + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/gotd/td/telegram/message" + "github.com/gotd/td/tg" + mcp "github.com/metoro-io/mcp-golang" + "github.com/pkg/errors" +) + +type ReadArguments struct { + Name string `json:"name" jsonschema:"description=Name of the dialog"` +} + +type ReadResponse struct { + Result string `json:"result"` +} + +func (c *Client) ReadHistory(args ReadArguments) (*mcp.ToolResponse, error) { + ctx := context.Background() + + var affectedMsgs *tg.MessagesAffectedMessages + client := c.T() + if err := client.Run(ctx, func(ctx context.Context) error { + api := client.API() + + sender := message.NewSender(api) + inputPeer, err := sender.Resolve(args.Name).AsInputPeer(ctx) + if err != nil { + return fmt.Errorf("failed to resolve name: %w", err) + } + + switch p := inputPeer.(type) { + case *tg.InputPeerUser: + affectedMsgs, err = api.MessagesReadHistory(ctx, &tg.MessagesReadHistoryRequest{ + Peer: inputPeer, + }) + + case *tg.InputPeerChannel: + var ok bool + ok, err = api.ChannelsReadHistory(ctx, &tg.ChannelsReadHistoryRequest{ + Channel: &tg.InputChannel{ + ChannelID: p.ChannelID, + AccessHash: p.AccessHash, + }, + }) + if err != nil { + return fmt.Errorf("failed to read channels: %w", err) + } + + affectedMsgs = &tg.MessagesAffectedMessages{} + if ok { + affectedMsgs = &tg.MessagesAffectedMessages{ + Pts: 1, + PtsCount: 1, + } + } + + default: + return fmt.Errorf("unexpected input peer type: %T", p) + } + + if err != nil { + return fmt.Errorf("read history: %w", err) + } + + return nil + }); err != nil { + return nil, fmt.Errorf("run client: %w", err) + } + + res := "done" + if affectedMsgs.PtsCount == 0 { + res = "unread messages not found" + } + + jsonData, err := json.Marshal(ReadResponse{Result: res}) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal response") + } + + return mcp.NewToolResponse(mcp.NewTextContent(string(jsonData))), nil +} diff --git a/serve.go b/serve.go index 526d977..2e4d2ac 100644 --- a/serve.go +++ b/serve.go @@ -57,10 +57,17 @@ func serve(ctx context.Context, cmd *cli.Command) error { answer, err = client.SendDraft(tg.DraftArguments{Name: os.Getenv("TG_TEST_USERNAME"), Text: "test draft"}) if err != nil { - return fmt.Errorf("send draft: %w", err) + log.Err(err).Msg("Check SendDraft: FAIL") + } else { + log.Info().RawJSON("answer", []byte(answer.Content[0].TextContent.Text)).Msg("Check SendDraft: OK") } - log.Info().RawJSON("answer", []byte(answer.Content[0].TextContent.Text)).Msg("Check SendDraft: OK") + answer, err = client.ReadHistory(tg.ReadArguments{Name: os.Getenv("TG_TEST_USERNAME")}) + if err != nil { + log.Err(err).Msg("Check ReadHistory: FAIL") + } else { + log.Info().RawJSON("answer", []byte(answer.Content[0].TextContent.Text)).Msg("Check ReadHistory: OK") + } return nil } @@ -85,6 +92,11 @@ func serve(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("register dialogs tool: %w", err) } + err = server.RegisterTool("tg_read", "Mark dialog messages as read (channel, user)", client.ReadHistory) + if err != nil { + return fmt.Errorf("register read tool: %w", err) + } + if err := server.Serve(); err != nil { return fmt.Errorf("serve: %w", err) }