tdl/app/forward/iter.go

217 lines
4.4 KiB
Go

package forward
import (
"context"
"strings"
"time"
"github.com/expr-lang/expr/vm"
"github.com/go-faster/errors"
"github.com/gotd/td/telegram/message/entity"
"github.com/gotd/td/telegram/message/html"
"github.com/gotd/td/telegram/peers"
"github.com/gotd/td/tg"
"github.com/mitchellh/mapstructure"
"github.com/iyear/tdl/core/dcpool"
"github.com/iyear/tdl/core/forwarder"
"github.com/iyear/tdl/core/util/tutil"
"github.com/iyear/tdl/pkg/texpr"
"github.com/iyear/tdl/pkg/tmessage"
)
type iterOptions struct {
manager *peers.Manager
pool dcpool.Pool
to *vm.Program
edit *vm.Program
dialogs []*tmessage.Dialog
mode forwarder.Mode
silent bool
dryRun bool
grouped bool
delay time.Duration
}
type iter struct {
opts iterOptions
i, j int
elem forwarder.Elem
err error
}
type env struct {
From struct {
ID int64 `comment:"ID of dialog"`
Username string `comment:"Username of dialog"`
VisibleName string `comment:"Title of channel and group, first and last name of user"`
}
Message texpr.EnvMessage
}
func exprEnv(from peers.Peer, msg *tg.Message) env {
e := env{}
if from != nil {
e.From.ID = from.ID()
e.From.Username, _ = from.Username()
e.From.VisibleName = from.VisibleName()
}
if msg != nil {
e.Message = texpr.ConvertEnvMessage(msg)
}
return e
}
type dest struct {
Peer string
Thread int
}
func newIter(opts iterOptions) *iter {
return &iter{
opts: opts,
i: 0,
j: 0,
elem: nil,
err: nil,
}
}
func (i *iter) Next(ctx context.Context) bool {
select {
case <-ctx.Done():
i.err = ctx.Err()
return false
default:
}
// end of iteration or error occurred
if i.i >= len(i.opts.dialogs) || i.err != nil {
return false
}
// if delay is set, sleep for a while for each iteration
if i.opts.delay > 0 && (i.i+i.j) > 0 { // skip first delay
time.Sleep(i.opts.delay)
}
p, m := i.opts.dialogs[i.i].Peer, i.opts.dialogs[i.i].Messages[i.j]
if i.j++; i.j >= len(i.opts.dialogs[i.i].Messages) {
i.i++
i.j = 0
}
from, err := i.opts.manager.FromInputPeer(ctx, p)
if err != nil {
i.err = errors.Wrap(err, "get from peer")
return false
}
msg, err := tutil.GetSingleMessage(ctx, i.opts.pool.Default(ctx), from.InputPeer(), m)
if err != nil {
i.err = errors.Wrapf(err, "get message: %d", m)
return false
}
// message routing
result, err := texpr.Run(i.opts.to, exprEnv(from, msg))
if err != nil {
i.err = errors.Wrap(err, "message routing")
return false
}
var (
to peers.Peer
thread int
)
switch r := result.(type) {
case string:
// pure chat, no reply to, which is a compatible with old version
// and a convenient way to send message to self
to, err = i.resolvePeer(ctx, r)
case map[string]interface{}:
// chat with reply to topic or message
var d dest
if err = mapstructure.WeakDecode(r, &d); err != nil {
i.err = errors.Wrapf(err, "decode dest: %v", result)
return false
}
to, err = i.resolvePeer(ctx, d.Peer)
thread = d.Thread
default:
i.err = errors.Errorf("message router must return string or dest: %T", result)
return false
}
var modeOverride forwarder.Mode = -1 // default value is invalid
// edit message
if i.opts.edit != nil {
result, err = texpr.Run(i.opts.edit, exprEnv(from, msg))
if err != nil {
i.err = errors.Wrap(err, "edit message")
return false
}
r, ok := result.(string)
if !ok {
i.err = errors.Errorf("edit must return string: %T", result)
return false
}
eb := entity.Builder{}
if err = html.HTML(strings.NewReader(r), &eb, html.Options{
UserResolver: nil,
DisableTelegramEscape: false,
}); err != nil {
i.err = errors.Wrap(err, "parse edited message")
return false
}
// modify message
msg.Message, msg.Entities = eb.Raw()
// direct mode can't modify message content, so we force it to be clone mode
modeOverride = forwarder.ModeClone
}
if err != nil {
i.err = errors.Wrapf(err, "resolve dest: %v", result)
return false
}
i.elem = &iterElem{
from: from,
msg: msg,
to: to,
thread: thread,
modeOverride: modeOverride,
opts: i.opts,
}
return true
}
func (i *iter) resolvePeer(ctx context.Context, peer string) (peers.Peer, error) {
if peer == "" { // self
return i.opts.manager.Self(ctx)
}
return tutil.GetInputPeer(ctx, i.opts.manager, peer)
}
func (i *iter) Value() forwarder.Elem {
return i.elem
}
func (i *iter) Err() error {
return i.err
}