Skip to content

Commit d98eed4

Browse files
author
cuiko
committed
feat(chat): Extend chat delete to remove a user DM from the chat list
`tg chat delete <ref>` previously only deleted supergroups/channels via channels.deleteChannel and errored on anything else. Now a user/bot ref removes that conversation from the account via messages.deleteHistory, so a DM (e.g. a blocked spammer's) can be cleared from the chat list. --revoke also deletes the history on the other side. Basic groups stay unsupported (use `tg chat leave`).
1 parent d956fb8 commit d98eed4

5 files changed

Lines changed: 50 additions & 16 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ client commands route through when present).
8989
| `tg chat list` | List dialogs. |
9090
| `tg chat info <ref>` | Show one user, chat, bot, or channel. `--full` adds members/admins/online counts, about, linked discussion group, pinned message, and slow mode (supergroups/channels only). |
9191
| `tg chat create <title>` | Create a supergroup. `--forum` enables topics; `--about` sets the description. |
92-
| `tg chat delete <ref>` | Delete a supergroup or channel (irreversible). Prompts unless `--yes`. |
92+
| `tg chat delete <ref>` | Delete a supergroup/channel (irreversible), or remove a user DM from your chat list (`--revoke` also deletes it on the other side). Prompts unless `--yes`. |
9393
| `tg chat edit <ref>` | Edit a supergroup: `--title`, `--about`, `--public <name>` / `--private`; toggles: `--forum`/`--no-forum`, `--hide-members`/`--show-members`, `--hide-history`/`--show-history`, `--slow-mode <s>`, `--no-forwards`/`--allow-forwards`, `--need-approval`/`--no-need-approval` (public only). |
9494
| `tg chat topic list <ref>` | List a forum supergroup's topics. `--search` filters by title; `--limit` caps results (single page only, ~100 max — pagination not implemented). |
9595
| `tg chat topic create <ref> <title>` | Create a topic. `--icon-color`, `--icon-emoji`, and `--random-id` (idempotent retry). |

internal/action/chat/delete.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,24 @@ import (
1313
// DeleteChatRequest is the raw request for `tg chat delete`.
1414
type DeleteChatRequest struct {
1515
RawRef string
16+
Revoke bool
1617
Yes bool
1718
Prompter ui.Prompter
1819
}
1920

2021
// DeleteChatQuery is the normalized payload passed to Telegram.
2122
type DeleteChatQuery struct {
22-
Ref ref.Ref
23+
Ref ref.Ref
24+
Revoke bool
2325
}
2426

25-
// DeleteChatFunc deletes a supergroup or channel.
27+
// DeleteChatFunc deletes a channel/supergroup or removes a user DM.
2628
type DeleteChatFunc func(context.Context, DeleteChatQuery) (output.PeerRef, error)
2729

2830
// DeleteChat validates, confirms, and dispatches a chat-delete request.
29-
// Deleting a channel/supergroup is irreversible and affects every member, so
30-
// it always confirms unless Yes is set.
31+
// Deleting a channel/supergroup is irreversible and affects every member;
32+
// deleting a user DM removes the conversation from the account (and, with
33+
// Revoke, the other side too). Either way it confirms unless Yes is set.
3134
func DeleteChat(ctx context.Context, req DeleteChatRequest, do DeleteChatFunc) (output.PeerRef, error) {
3235
parsed, err := ref.ParseRef(req.RawRef)
3336
if err != nil {
@@ -36,8 +39,12 @@ func DeleteChat(ctx context.Context, req DeleteChatRequest, do DeleteChatFunc) (
3639
if do == nil {
3740
return output.PeerRef{}, fmt.Errorf("%w: chat delete called without delete function", command.ErrPrecondition)
3841
}
39-
if err := ui.ConfirmDestructive(req.Prompter, fmt.Sprintf("delete %s for everyone? this cannot be undone", parsed.String()), req.Yes); err != nil {
42+
prompt := fmt.Sprintf("delete %s? this cannot be undone", parsed.String())
43+
if req.Revoke {
44+
prompt = fmt.Sprintf("delete %s for everyone? this cannot be undone", parsed.String())
45+
}
46+
if err := ui.ConfirmDestructive(req.Prompter, prompt, req.Yes); err != nil {
4047
return output.PeerRef{}, err
4148
}
42-
return do(ctx, DeleteChatQuery{Ref: parsed})
49+
return do(ctx, DeleteChatQuery{Ref: parsed, Revoke: req.Revoke})
4350
}

internal/action/chat/delete_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,16 @@ func TestDeleteChat_DeclineSkipsDispatch(t *testing.T) {
3636
})
3737
require.ErrorIs(t, err, command.ErrNotConfirmed)
3838
}
39+
40+
func TestDeleteChat_RevokePassesThrough(t *testing.T) {
41+
_, err := actionchat.DeleteChat(context.Background(), actionchat.DeleteChatRequest{
42+
RawRef: "@bob",
43+
Revoke: true,
44+
Prompter: &ui.StubPrompter{Answers: []any{true}},
45+
}, func(_ context.Context, q actionchat.DeleteChatQuery) (output.PeerRef, error) {
46+
require.Equal(t, "bob", q.Ref.Value)
47+
require.True(t, q.Revoke)
48+
return output.PeerRef{}, nil
49+
})
50+
require.NoError(t, err)
51+
}

internal/cli/chat/delete/delete.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
// Options holds the resolved flags and injected dependencies for Run.
2323
type Options struct {
2424
RawRef string
25+
Revoke bool
2526
Yes bool
2627
Exporter output.Exporter
2728
IOStreams *ui.IOStreams
@@ -31,7 +32,7 @@ type Options struct {
3132

3233
// New builds the cobra command for "tg chat delete".
3334
func New(f *runtime.Invocation, runF func(*Options) error) *cobra.Command {
34-
return NewWith(f, runF, "Delete a supergroup or channel (irreversible)")
35+
return NewWith(f, runF, "Delete a supergroup/channel, or remove a user DM from your chat list")
3536
}
3637

3738
// NewWith builds the delete command with a caller-supplied short description so
@@ -55,6 +56,7 @@ func NewWith(f *runtime.Invocation, runF func(*Options) error, short string) *co
5556
return Run(cmd.Context(), opts)
5657
},
5758
}
59+
cmd.Flags().BoolVar(&opts.Revoke, "revoke", false, "For a user DM, also delete the conversation on the other side")
5860
cmd.Flags().BoolVarP(&opts.Yes, "yes", "y", false, "Skip confirmation prompt")
5961
command.SetMeta(cmd, command.Meta{NeedsAccount: true, NeedsClient: true})
6062
output.AddJSONFlags(cmd, &opts.Exporter, []string{"id", "ref", "kind", "title", "username"})
@@ -65,6 +67,7 @@ func NewWith(f *runtime.Invocation, runF func(*Options) error, short string) *co
6567
func Run(ctx context.Context, opts *Options) error {
6668
pr, err := actionchat.DeleteChat(ctx, actionchat.DeleteChatRequest{
6769
RawRef: opts.RawRef,
70+
Revoke: opts.Revoke,
6871
Yes: opts.Yes,
6972
Prompter: opts.Prompter,
7073
}, opts.Delete)

internal/telegram/chat_delete.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,30 @@ import (
1212
"github.com/vika2603/telegram-cli/internal/telegram/peer"
1313
)
1414

15-
// DeleteChat performs the RPC for `tg chat delete`: it deletes a supergroup or
16-
// channel via channels.deleteChannel (irreversible, creator-only).
15+
// DeleteChat performs the RPC for `tg chat delete`. A supergroup/channel is
16+
// deleted via channels.deleteChannel (irreversible, creator-only). A user/bot
17+
// DM is removed from the account via messages.deleteHistory (with q.Revoke it
18+
// also deletes the history on the other side). Basic groups are not handled —
19+
// use `tg chat leave`.
1720
func DeleteChat(ctx context.Context, api *tg.Client, resolver *peer.Resolver, q actionchat.DeleteChatQuery) (output.PeerRef, error) {
1821
resolved, err := resolver.Resolve(ctx, q.Ref)
1922
if err != nil {
2023
return output.PeerRef{}, err
2124
}
22-
inCh, ok := inputChannelFromPeer(resolved.InputPeer)
23-
if !ok {
24-
return output.PeerRef{}, fmt.Errorf("%w: only supergroups and channels can be deleted by ref", command.ErrUnsupported)
25+
if inCh, ok := inputChannelFromPeer(resolved.InputPeer); ok {
26+
if _, err := api.ChannelsDeleteChannel(ctx, inCh); err != nil {
27+
return output.PeerRef{}, err
28+
}
29+
return output.PeerRefFromResolved(resolved), nil
2530
}
26-
if _, err := api.ChannelsDeleteChannel(ctx, inCh); err != nil {
27-
return output.PeerRef{}, err
31+
if resolved.Kind == "user" || resolved.Kind == "bot" {
32+
if _, err := api.MessagesDeleteHistory(ctx, &tg.MessagesDeleteHistoryRequest{
33+
Peer: resolved.InputPeer,
34+
Revoke: q.Revoke,
35+
}); err != nil {
36+
return output.PeerRef{}, err
37+
}
38+
return output.PeerRefFromResolved(resolved), nil
2839
}
29-
return output.PeerRefFromResolved(resolved), nil
40+
return output.PeerRef{}, fmt.Errorf("%w: only channels, supergroups, and user DMs can be deleted by ref (use `tg chat leave` for basic groups)", command.ErrUnsupported)
3041
}

0 commit comments

Comments
 (0)