forked from vika2603/telegram-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreply.go
More file actions
163 lines (154 loc) · 5.24 KB
/
Copy pathreply.go
File metadata and controls
163 lines (154 loc) · 5.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Package reply implements the top-level "tg reply" command.
package reply
import (
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"github.com/gotd/td/telegram/peers"
"github.com/gotd/td/tg"
"github.com/spf13/cobra"
"github.com/vika2603/telegram-cli/internal/account"
actionmessage "github.com/vika2603/telegram-cli/internal/action/message"
"github.com/vika2603/telegram-cli/internal/cli/complete"
"github.com/vika2603/telegram-cli/internal/command"
"github.com/vika2603/telegram-cli/internal/output"
"github.com/vika2603/telegram-cli/internal/ref"
"github.com/vika2603/telegram-cli/internal/runtime"
"github.com/vika2603/telegram-cli/internal/telegram"
"github.com/vika2603/telegram-cli/internal/telegram/peer"
"github.com/vika2603/telegram-cli/internal/ui"
)
// Options holds resolved flags and injectable dependencies for Run.
type Options struct {
RawMessageRef string
Text string
Files []string
Names []string
Silent bool
Parse string
RandomID int64
Exporter output.Exporter
IOStreams *ui.IOStreams
Stdin io.Reader
Send actionmessage.SendFunc
}
// New builds the cobra command for "tg reply".
func New(f *runtime.Invocation, runF func(*Options) error) *cobra.Command {
opts := &Options{}
cmd := &cobra.Command{
Use: "reply <msg-ref> [text...]",
Short: "Reply to a message",
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: complete.MessageRefs(f),
RunE: func(cmd *cobra.Command, args []string) error {
opts.RawMessageRef = args[0]
opts.Text = strings.Join(args[1:], " ")
opts.IOStreams = f.IOStreams
opts.Stdin = f.IOStreams.In
if runF != nil {
return runF(opts)
}
opts.Send = newSend(f)
return Run(cmd.Context(), opts)
},
}
cmd.Flags().StringArrayVar(&opts.Files, "file", nil, `File attachment; repeat for multiple files; "-" reads stdin bytes`)
cmd.Flags().StringArrayVar(&opts.Names, "name", nil, "Upload filename override; repeat to match --file")
cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Send without notification")
cmd.Flags().StringVar(&opts.Parse, "parse", "", "Parse mode for text or caption (only: html)")
cmd.Flags().Int64Var(&opts.RandomID, "random-id", 0, "Idempotency key (int64): reusing it on retry dedupes the reply server-side")
command.SetMeta(cmd, command.Meta{NeedsAccount: true, NeedsClient: true})
output.AddJSONFlags(cmd, &opts.Exporter,
[]string{"action", "message_id", "chat_id", "date"})
return cmd
}
// Run parses the target message ref, sends a reply, and renders the result.
func Run(ctx context.Context, opts *Options) error {
msgRef, err := ref.ParseMessageRef(opts.RawMessageRef)
if err != nil {
return fmt.Errorf("%w: %s", command.ErrUsage, err.Error())
}
rows, err := actionmessage.Send(ctx, actionmessage.SendRequest{
RawRef: msgRef.Peer.String(),
Text: opts.Text,
Files: opts.Files,
Names: opts.Names,
ReplyTo: msgRef.MessageID,
Silent: opts.Silent,
Parse: opts.Parse,
RandomID: opts.RandomID,
Stdin: opts.Stdin,
}, opts.Send)
if err != nil {
return err
}
if opts.Exporter != nil {
return opts.Exporter.Write(opts.IOStreams, rows)
}
return output.RenderSendResults(opts.IOStreams, rows)
}
func newSend(f *runtime.Invocation) actionmessage.SendFunc {
return func(ctx context.Context, q actionmessage.SendQuery) ([]output.SendResultRow, error) {
acct, err := f.Account("")
if err != nil {
return nil, err
}
// Daemon fast-path: text-only / no attachments. Stdin alone is
// not a gate (CLI plumbs IOStreams.In into every Query, but
// telegram.SendMessage only consumes it via Attachment.Path=="-").
// reply has no sticker/GIF flags, so the attachment check is the
// whole gate; see newSend in internal/cli/msg/send for the
// media-capability negotiation that path adds.
if len(q.Attachments) == 0 {
if cl, _ := runtime.MaybeDialDaemon(ctx, f, acct); cl != nil {
defer func() { _ = cl.Close() }()
// Strip Stdin io.Reader before wire encode — JSON cannot
// rehydrate the interface.
wire := q
wire.Stdin = nil
raw, err := cl.Call(ctx, "msg.send", wire)
if err != nil {
return nil, err
}
var rows []output.SendResultRow
if err := json.Unmarshal(raw, &rows); err != nil {
return nil, err
}
if store, err := account.OpenRecentStore(acct.Meta.Name); err == nil {
recordSentMessages(store, q.Ref.String(), q.Text, rows)
}
return rows, nil
}
}
var rows []output.SendResultRow
err = f.WithPeers(ctx, acct, runtime.ClientOptsFrom(f, acct),
func(ctx context.Context, api *tg.Client, _ *peers.Manager, res *peer.Resolver) error {
rows, err = telegram.SendMessage(ctx, api, res, q)
if err == nil {
recordSentMessages(res.Store(), q.Ref.String(), q.Text, rows)
}
return err
})
return rows, err
}
}
func recordSentMessages(store *account.PeerStore, peerRef, text string, rows []output.SendResultRow) {
if store == nil {
return
}
for _, row := range rows {
if row.MessageID <= 0 {
continue
}
_ = store.RecordRecentMessage(account.RecentMessage{
Ref: peerRef + ":" + strconv.Itoa(row.MessageID),
PeerRef: peerRef,
MessageID: row.MessageID,
Date: row.Date,
Text: text,
})
}
}