Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ client commands route through when present).
| `tg chat list` | List dialogs. |
| `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). |
| `tg chat create <title>` | Create a supergroup. `--forum` enables topics; `--about` sets the description. |
| `tg chat delete <ref>` | Delete a supergroup or channel (irreversible). Prompts unless `--yes`. |
| `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`. |
| `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). |
| `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). |
| `tg chat topic create <ref> <title>` | Create a topic. `--icon-color`, `--icon-emoji`, and `--random-id` (idempotent retry). |
Expand Down
19 changes: 13 additions & 6 deletions internal/action/chat/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ import (
// DeleteChatRequest is the raw request for `tg chat delete`.
type DeleteChatRequest struct {
RawRef string
Revoke bool
Yes bool
Prompter ui.Prompter
}

// DeleteChatQuery is the normalized payload passed to Telegram.
type DeleteChatQuery struct {
Ref ref.Ref
Ref ref.Ref
Revoke bool
}

// DeleteChatFunc deletes a supergroup or channel.
// DeleteChatFunc deletes a channel/supergroup or removes a user DM.
type DeleteChatFunc func(context.Context, DeleteChatQuery) (output.PeerRef, error)

// DeleteChat validates, confirms, and dispatches a chat-delete request.
// Deleting a channel/supergroup is irreversible and affects every member, so
// it always confirms unless Yes is set.
// Deleting a channel/supergroup is irreversible and affects every member;
// deleting a user DM removes the conversation from the account (and, with
// Revoke, the other side too). Either way it confirms unless Yes is set.
func DeleteChat(ctx context.Context, req DeleteChatRequest, do DeleteChatFunc) (output.PeerRef, error) {
parsed, err := ref.ParseRef(req.RawRef)
if err != nil {
Expand All @@ -36,8 +39,12 @@ func DeleteChat(ctx context.Context, req DeleteChatRequest, do DeleteChatFunc) (
if do == nil {
return output.PeerRef{}, fmt.Errorf("%w: chat delete called without delete function", command.ErrPrecondition)
}
if err := ui.ConfirmDestructive(req.Prompter, fmt.Sprintf("delete %s for everyone? this cannot be undone", parsed.String()), req.Yes); err != nil {
prompt := fmt.Sprintf("delete %s? this cannot be undone", parsed.String())
if req.Revoke {
prompt = fmt.Sprintf("delete %s for everyone? this cannot be undone", parsed.String())
}
if err := ui.ConfirmDestructive(req.Prompter, prompt, req.Yes); err != nil {
return output.PeerRef{}, err
}
return do(ctx, DeleteChatQuery{Ref: parsed})
return do(ctx, DeleteChatQuery{Ref: parsed, Revoke: req.Revoke})
}
13 changes: 13 additions & 0 deletions internal/action/chat/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,16 @@ func TestDeleteChat_DeclineSkipsDispatch(t *testing.T) {
})
require.ErrorIs(t, err, command.ErrNotConfirmed)
}

func TestDeleteChat_RevokePassesThrough(t *testing.T) {
_, err := actionchat.DeleteChat(context.Background(), actionchat.DeleteChatRequest{
RawRef: "@bob",
Revoke: true,
Prompter: &ui.StubPrompter{Answers: []any{true}},
}, func(_ context.Context, q actionchat.DeleteChatQuery) (output.PeerRef, error) {
require.Equal(t, "bob", q.Ref.Value)
require.True(t, q.Revoke)
return output.PeerRef{}, nil
})
require.NoError(t, err)
}
5 changes: 4 additions & 1 deletion internal/cli/chat/delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
// Options holds the resolved flags and injected dependencies for Run.
type Options struct {
RawRef string
Revoke bool
Yes bool
Exporter output.Exporter
IOStreams *ui.IOStreams
Expand All @@ -31,7 +32,7 @@ type Options struct {

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

// NewWith builds the delete command with a caller-supplied short description so
Expand All @@ -55,6 +56,7 @@ func NewWith(f *runtime.Invocation, runF func(*Options) error, short string) *co
return Run(cmd.Context(), opts)
},
}
cmd.Flags().BoolVar(&opts.Revoke, "revoke", false, "For a user DM, also delete the conversation on the other side")
cmd.Flags().BoolVarP(&opts.Yes, "yes", "y", false, "Skip confirmation prompt")
command.SetMeta(cmd, command.Meta{NeedsAccount: true, NeedsClient: true})
output.AddJSONFlags(cmd, &opts.Exporter, []string{"id", "ref", "kind", "title", "username"})
Expand All @@ -65,6 +67,7 @@ func NewWith(f *runtime.Invocation, runF func(*Options) error, short string) *co
func Run(ctx context.Context, opts *Options) error {
pr, err := actionchat.DeleteChat(ctx, actionchat.DeleteChatRequest{
RawRef: opts.RawRef,
Revoke: opts.Revoke,
Yes: opts.Yes,
Prompter: opts.Prompter,
}, opts.Delete)
Expand Down
27 changes: 19 additions & 8 deletions internal/telegram/chat_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,30 @@ import (
"github.com/vika2603/telegram-cli/internal/telegram/peer"
)

// DeleteChat performs the RPC for `tg chat delete`: it deletes a supergroup or
// channel via channels.deleteChannel (irreversible, creator-only).
// DeleteChat performs the RPC for `tg chat delete`. A supergroup/channel is
// deleted via channels.deleteChannel (irreversible, creator-only). A user/bot
// DM is removed from the account via messages.deleteHistory (with q.Revoke it
// also deletes the history on the other side). Basic groups are not handled —
// use `tg chat leave`.
func DeleteChat(ctx context.Context, api *tg.Client, resolver *peer.Resolver, q actionchat.DeleteChatQuery) (output.PeerRef, error) {
resolved, err := resolver.Resolve(ctx, q.Ref)
if err != nil {
return output.PeerRef{}, err
}
inCh, ok := inputChannelFromPeer(resolved.InputPeer)
if !ok {
return output.PeerRef{}, fmt.Errorf("%w: only supergroups and channels can be deleted by ref", command.ErrUnsupported)
if inCh, ok := inputChannelFromPeer(resolved.InputPeer); ok {
if _, err := api.ChannelsDeleteChannel(ctx, inCh); err != nil {
return output.PeerRef{}, err
}
return output.PeerRefFromResolved(resolved), nil
}
if _, err := api.ChannelsDeleteChannel(ctx, inCh); err != nil {
return output.PeerRef{}, err
if resolved.Kind == "user" || resolved.Kind == "bot" {
if _, err := api.MessagesDeleteHistory(ctx, &tg.MessagesDeleteHistoryRequest{
Peer: resolved.InputPeer,
Revoke: q.Revoke,
}); err != nil {
return output.PeerRef{}, err
}
return output.PeerRefFromResolved(resolved), nil
}
return output.PeerRefFromResolved(resolved), nil
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)
}