move gotd fork into repo. (#111)
- update to latest telegram layer - remove some references to fields in tg.Entities that don't exist in the schema - originally added here: https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e - referenced here - https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3 - https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// ChannelMembers is channel Members.
|
||||
type ChannelMembers struct {
|
||||
m *peers.Manager
|
||||
filter tg.ChannelParticipantsFilterClass
|
||||
channel peers.Channel
|
||||
}
|
||||
|
||||
func (c *ChannelMembers) query(ctx context.Context, offset, limit int) (*tg.ChannelsChannelParticipants, error) {
|
||||
raw := c.m.API()
|
||||
p, err := raw.ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{
|
||||
Channel: c.channel.InputChannel(),
|
||||
Filter: c.filter,
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get members")
|
||||
}
|
||||
|
||||
m, ok := p.AsModified()
|
||||
if !ok {
|
||||
return nil, errors.Errorf("unexpected type %T", p)
|
||||
}
|
||||
if err := c.m.Apply(ctx, m.Users, m.Chats); err != nil {
|
||||
return nil, errors.Wrap(err, "apply entities")
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ForEach calls cb for every member of channel.
|
||||
//
|
||||
// May return ChannelInfoUnavailableError.
|
||||
func (c *ChannelMembers) ForEach(ctx context.Context, cb Callback) error {
|
||||
const limit = 100
|
||||
|
||||
full, err := c.channel.FullRaw(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get full")
|
||||
}
|
||||
if !full.CanViewParticipants {
|
||||
return &ChannelInfoUnavailableError{}
|
||||
}
|
||||
channelDate := time.Unix(int64(c.channel.Raw().Date), 0)
|
||||
|
||||
offset := 0
|
||||
for {
|
||||
m, err := c.query(ctx, offset, limit)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "query")
|
||||
}
|
||||
|
||||
if len(m.Participants) < 1 {
|
||||
return nil
|
||||
}
|
||||
for i, member := range m.Participants {
|
||||
var (
|
||||
userID int64
|
||||
inviterID int64
|
||||
err error
|
||||
)
|
||||
switch p := member.(type) {
|
||||
case *tg.ChannelParticipant:
|
||||
userID = p.UserID
|
||||
case *tg.ChannelParticipantSelf:
|
||||
userID = p.UserID
|
||||
inviterID = p.InviterID
|
||||
case *tg.ChannelParticipantCreator:
|
||||
userID = p.UserID
|
||||
case *tg.ChannelParticipantAdmin:
|
||||
userID = p.UserID
|
||||
inviterID = p.InviterID
|
||||
case *tg.ChannelParticipantBanned:
|
||||
userPeer, ok := p.Peer.(*tg.PeerUser)
|
||||
if !ok {
|
||||
return errors.Errorf("unexpected type %T", p.Peer)
|
||||
}
|
||||
userID = userPeer.UserID
|
||||
case *tg.ChannelParticipantLeft:
|
||||
userPeer, ok := p.Peer.(*tg.PeerUser)
|
||||
if !ok {
|
||||
return errors.Errorf("unexpected type %T", p.Peer)
|
||||
}
|
||||
userID = userPeer.UserID
|
||||
default:
|
||||
return errors.Errorf("unexpected type %T", p)
|
||||
}
|
||||
|
||||
user, err := c.m.ResolveUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get member %d", userID)
|
||||
}
|
||||
chm := ChannelMember{
|
||||
parent: c,
|
||||
creatorDate: channelDate,
|
||||
user: user,
|
||||
inviter: peers.User{},
|
||||
raw: member,
|
||||
}
|
||||
if inviterID != 0 {
|
||||
inviter, err := c.m.ResolveUserID(ctx, inviterID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get inviter %d", inviterID)
|
||||
}
|
||||
chm.inviter = inviter
|
||||
}
|
||||
|
||||
if err := cb(chm); err != nil {
|
||||
return errors.Wrapf(err, "callback (index: %d)", i)
|
||||
}
|
||||
}
|
||||
|
||||
offset += limit
|
||||
}
|
||||
}
|
||||
|
||||
// Count returns total count of members.
|
||||
func (c *ChannelMembers) Count(ctx context.Context) (int, error) {
|
||||
m, err := c.query(ctx, 0, 1)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "query")
|
||||
}
|
||||
return m.Count, nil
|
||||
}
|
||||
|
||||
// Peer returns chat object.
|
||||
func (c *ChannelMembers) Peer() peers.Peer {
|
||||
return c.channel
|
||||
}
|
||||
|
||||
// Kick kicks user member.
|
||||
//
|
||||
// Needed for parity with ChatMembers to define common interface.
|
||||
//
|
||||
// If revokeHistory is set, will delete all messages from this member.
|
||||
func (c *ChannelMembers) Kick(ctx context.Context, member tg.InputUserClass, revokeHistory bool) error {
|
||||
p := convertInputUserToInputPeer(member)
|
||||
if revokeHistory {
|
||||
if _, err := c.m.API().ChannelsDeleteParticipantHistory(ctx, &tg.ChannelsDeleteParticipantHistoryRequest{
|
||||
Channel: c.channel.InputChannel(),
|
||||
Participant: p,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "revoke history")
|
||||
}
|
||||
}
|
||||
return c.KickMember(ctx, p)
|
||||
}
|
||||
|
||||
// KickMember kicks member.
|
||||
//
|
||||
// Unlike Kick, KickMember can be used to kick chat member that uses send-as-channel mode.
|
||||
func (c *ChannelMembers) KickMember(ctx context.Context, member tg.InputPeerClass) error {
|
||||
return c.EditMemberRights(ctx, member, MemberRights{
|
||||
DenyViewMessages: true,
|
||||
})
|
||||
}
|
||||
|
||||
// EditMemberRights edits member rights in this channel.
|
||||
func (c *ChannelMembers) EditMemberRights(
|
||||
ctx context.Context,
|
||||
member tg.InputPeerClass,
|
||||
options MemberRights,
|
||||
) error {
|
||||
return c.editMemberRights(ctx, member, options)
|
||||
}
|
||||
|
||||
func (c *ChannelMembers) editMemberRights(ctx context.Context, p tg.InputPeerClass, options MemberRights) error {
|
||||
if _, err := c.m.API().ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{
|
||||
Channel: c.channel.InputChannel(),
|
||||
Participant: p,
|
||||
BannedRights: options.IntoChatBannedRights(),
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "edit member rights")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EditRights edits rights of all members in this channel.
|
||||
func (c *ChannelMembers) EditRights(ctx context.Context, options MemberRights) error {
|
||||
return editDefaultRights(ctx, c.m.API(), c.channel.InputPeer(), options)
|
||||
}
|
||||
|
||||
// EditAdminRights edits admin rights of given user in this channel.
|
||||
func (c *ChannelMembers) EditAdminRights(
|
||||
ctx context.Context,
|
||||
admin tg.InputUserClass,
|
||||
options AdminRights,
|
||||
) error {
|
||||
if _, err := c.m.API().ChannelsEditAdmin(ctx, &tg.ChannelsEditAdminRequest{
|
||||
Channel: c.channel.InputChannel(),
|
||||
UserID: admin,
|
||||
AdminRights: options.IntoChatAdminRights(),
|
||||
Rank: options.Rank,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "edit admin rights")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Channel returns recent channel members.
|
||||
func Channel(channel peers.Channel) *ChannelMembers {
|
||||
return ChannelQuery{Channel: channel}.Recent()
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// ChannelMember is channel Member.
|
||||
type ChannelMember struct {
|
||||
parent *ChannelMembers
|
||||
creatorDate time.Time
|
||||
user peers.User
|
||||
inviter peers.User
|
||||
raw tg.ChannelParticipantClass
|
||||
}
|
||||
|
||||
// Raw returns raw member object.
|
||||
func (c ChannelMember) Raw() tg.ChannelParticipantClass {
|
||||
return c.raw
|
||||
}
|
||||
|
||||
// Status returns member Status.
|
||||
func (c ChannelMember) Status() Status {
|
||||
switch c.raw.(type) {
|
||||
case *tg.ChannelParticipant:
|
||||
return Plain
|
||||
case *tg.ChannelParticipantSelf:
|
||||
return Plain
|
||||
case *tg.ChannelParticipantCreator:
|
||||
return Creator
|
||||
case *tg.ChannelParticipantAdmin:
|
||||
return Admin
|
||||
case *tg.ChannelParticipantBanned:
|
||||
return Banned
|
||||
case *tg.ChannelParticipantLeft:
|
||||
return Left
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Rank returns admin "rank".
|
||||
func (c ChannelMember) Rank() (string, bool) {
|
||||
switch p := c.raw.(type) {
|
||||
case *tg.ChannelParticipant:
|
||||
return "", false
|
||||
case *tg.ChannelParticipantSelf:
|
||||
return "", false
|
||||
case *tg.ChannelParticipantCreator:
|
||||
return p.GetRank()
|
||||
case *tg.ChannelParticipantAdmin:
|
||||
return p.GetRank()
|
||||
case *tg.ChannelParticipantBanned:
|
||||
return "", false
|
||||
case *tg.ChannelParticipantLeft:
|
||||
return "", false
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// JoinDate returns member join date, if it is available.
|
||||
func (c ChannelMember) JoinDate() (time.Time, bool) {
|
||||
switch p := c.raw.(type) {
|
||||
case *tg.ChannelParticipant:
|
||||
return time.Unix(int64(p.Date), 0), true
|
||||
case *tg.ChannelParticipantSelf:
|
||||
return time.Unix(int64(p.Date), 0), true
|
||||
case *tg.ChannelParticipantCreator:
|
||||
return c.creatorDate, true
|
||||
case *tg.ChannelParticipantAdmin:
|
||||
return time.Unix(int64(p.Date), 0), true
|
||||
case *tg.ChannelParticipantBanned:
|
||||
return time.Unix(int64(p.Date), 0), true
|
||||
case *tg.ChannelParticipantLeft:
|
||||
return time.Time{}, false
|
||||
default:
|
||||
return time.Time{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// InvitedBy returns user that invited this member.
|
||||
func (c ChannelMember) InvitedBy() (peers.User, bool) {
|
||||
switch p := c.raw.(type) {
|
||||
case *tg.ChannelParticipant:
|
||||
return peers.User{}, false
|
||||
case *tg.ChannelParticipantSelf:
|
||||
return c.inviter, true
|
||||
case *tg.ChannelParticipantCreator:
|
||||
return peers.User{}, false
|
||||
case *tg.ChannelParticipantAdmin:
|
||||
_, has := p.GetInviterID()
|
||||
return c.inviter, has
|
||||
case *tg.ChannelParticipantBanned:
|
||||
return peers.User{}, false
|
||||
case *tg.ChannelParticipantLeft:
|
||||
return peers.User{}, false
|
||||
default:
|
||||
return peers.User{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// User returns member User object.
|
||||
func (c ChannelMember) User() peers.User {
|
||||
return c.user
|
||||
}
|
||||
|
||||
// Kick kicks this member.
|
||||
//
|
||||
// If revokeHistory is set, will delete all messages from this member.
|
||||
func (c ChannelMember) Kick(ctx context.Context, revokeHistory bool) error {
|
||||
return c.parent.Kick(ctx, c.user.InputUser(), revokeHistory)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// ChannelQuery is builder for channel members querying.
|
||||
type ChannelQuery struct {
|
||||
Channel peers.Channel
|
||||
}
|
||||
|
||||
func (q ChannelQuery) query(filter tg.ChannelParticipantsFilterClass) *ChannelMembers {
|
||||
return &ChannelMembers{
|
||||
m: q.Channel.Manager(),
|
||||
filter: filter,
|
||||
channel: q.Channel,
|
||||
}
|
||||
}
|
||||
|
||||
// Recent queries recent members.
|
||||
func (q ChannelQuery) Recent() *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsRecent{})
|
||||
}
|
||||
|
||||
// Admins queries admins members.
|
||||
func (q ChannelQuery) Admins() *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsAdmins{})
|
||||
}
|
||||
|
||||
// Kicked queries kicked members.
|
||||
func (q ChannelQuery) Kicked(query string) *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsKicked{Q: query})
|
||||
}
|
||||
|
||||
// Bots queries bots members.
|
||||
func (q ChannelQuery) Bots() *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsBots{})
|
||||
}
|
||||
|
||||
// Banned queries banned members.
|
||||
func (q ChannelQuery) Banned(query string) *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsBanned{Q: query})
|
||||
}
|
||||
|
||||
// Search queries members by given name.
|
||||
func (q ChannelQuery) Search(query string) *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsSearch{Q: query})
|
||||
}
|
||||
|
||||
// Contacts queries members that are also contacts.
|
||||
func (q ChannelQuery) Contacts(query string) *ChannelMembers {
|
||||
return q.query(&tg.ChannelParticipantsContacts{Q: query})
|
||||
}
|
||||
|
||||
// Custom creates query with custom filter.
|
||||
func (q ChannelQuery) Custom(filter tg.ChannelParticipantsFilterClass) *ChannelMembers {
|
||||
return q.query(filter)
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func TestChannelMembers_Count(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
ch := m.Channel(getTestChannel())
|
||||
members := Channel(ch)
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Filter: &tg.ChannelParticipantsRecent{},
|
||||
Offset: 0,
|
||||
Limit: 1,
|
||||
}).ThenErr(testutil.TestError())
|
||||
_, err := members.Count(ctx)
|
||||
a.Error(err)
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Filter: &tg.ChannelParticipantsRecent{},
|
||||
Offset: 0,
|
||||
Limit: 1,
|
||||
}).ThenResult(&tg.ChannelsChannelParticipantsNotModified{})
|
||||
_, err = members.Count(ctx)
|
||||
a.Error(err)
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Filter: &tg.ChannelParticipantsRecent{},
|
||||
Offset: 0,
|
||||
Limit: 1,
|
||||
}).ThenResult(&tg.ChannelsChannelParticipants{
|
||||
Count: 10,
|
||||
})
|
||||
count, err := members.Count(ctx)
|
||||
a.NoError(err)
|
||||
a.Equal(10, count)
|
||||
}
|
||||
|
||||
func TestChannelMembers_ForEach(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
date := int(now.Unix())
|
||||
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
mock, m := testManager(t)
|
||||
|
||||
rawCh := getTestChannel()
|
||||
rawCh.Date = date
|
||||
ch := m.Channel(rawCh)
|
||||
members := Channel(ch)
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsGetFullChannelRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
}).ThenResult(&tg.MessagesChatFull{
|
||||
FullChat: getTestChannelFull(),
|
||||
})
|
||||
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Filter: &tg.ChannelParticipantsRecent{},
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
}).ThenResult(&tg.ChannelsChannelParticipants{
|
||||
Count: 10,
|
||||
Participants: []tg.ChannelParticipantClass{
|
||||
&tg.ChannelParticipant{
|
||||
UserID: 10,
|
||||
Date: date,
|
||||
},
|
||||
&tg.ChannelParticipantSelf{
|
||||
UserID: 10,
|
||||
InviterID: 11,
|
||||
Date: date,
|
||||
},
|
||||
&tg.ChannelParticipantCreator{
|
||||
UserID: 10,
|
||||
Rank: "rank",
|
||||
},
|
||||
&tg.ChannelParticipantAdmin{
|
||||
UserID: 10,
|
||||
InviterID: 11,
|
||||
Date: date,
|
||||
Rank: "rank",
|
||||
},
|
||||
&tg.ChannelParticipantBanned{
|
||||
Peer: &tg.PeerUser{UserID: 10},
|
||||
Date: date,
|
||||
},
|
||||
&tg.ChannelParticipantLeft{
|
||||
Peer: &tg.PeerUser{UserID: 10},
|
||||
},
|
||||
},
|
||||
Users: []tg.UserClass{
|
||||
&tg.User{
|
||||
ID: 10,
|
||||
AccessHash: 10,
|
||||
},
|
||||
&tg.User{
|
||||
ID: 11,
|
||||
AccessHash: 10,
|
||||
},
|
||||
},
|
||||
}).ExpectCall(&tg.ChannelsGetParticipantsRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Filter: &tg.ChannelParticipantsRecent{},
|
||||
Offset: 100,
|
||||
Limit: 100,
|
||||
}).ThenResult(&tg.ChannelsChannelParticipants{
|
||||
Count: 10,
|
||||
})
|
||||
|
||||
expected := []struct {
|
||||
Status Status
|
||||
JoinDate time.Time
|
||||
JoinDateSet bool
|
||||
Rank string
|
||||
RankSet bool
|
||||
InviterID int64
|
||||
}{
|
||||
{Status: Plain, JoinDate: now, JoinDateSet: true},
|
||||
{Status: Plain, JoinDate: now, JoinDateSet: true, InviterID: 11},
|
||||
{Status: Creator, Rank: "rank", RankSet: true, JoinDate: now, JoinDateSet: true},
|
||||
{Status: Admin, Rank: "rank", RankSet: true, JoinDate: now, JoinDateSet: true, InviterID: 11},
|
||||
{Status: Banned, JoinDate: now, JoinDateSet: true},
|
||||
{Status: Left},
|
||||
}
|
||||
|
||||
i := 0
|
||||
a.NoError(members.ForEach(ctx, func(m Member) error {
|
||||
p := m.(ChannelMember)
|
||||
e := expected[i]
|
||||
|
||||
a.Equal(e.Status, p.Status(), i)
|
||||
a.Equal(int64(10), p.User().ID())
|
||||
if join, ok := p.JoinDate(); e.JoinDateSet {
|
||||
a.True(ok, i)
|
||||
a.Equal(e.JoinDate.Unix(), join.Unix(), i)
|
||||
} else {
|
||||
a.False(ok, i)
|
||||
}
|
||||
|
||||
if rank, ok := p.Rank(); e.RankSet {
|
||||
a.True(ok, i)
|
||||
a.Equal(e.Rank, rank, i)
|
||||
} else {
|
||||
a.False(ok, i)
|
||||
}
|
||||
|
||||
if inviter, ok := p.InvitedBy(); e.InviterID != 0 {
|
||||
a.True(ok, i)
|
||||
a.Equal(e.InviterID, inviter.ID())
|
||||
} else {
|
||||
a.False(ok, i)
|
||||
}
|
||||
|
||||
i++
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
t.Run("ChannelInfoUnavailableError", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
mock, m := testManager(t)
|
||||
|
||||
rawCh := getTestChannel()
|
||||
rawCh.Date = date
|
||||
ch := m.Channel(rawCh)
|
||||
|
||||
rawFull := &tg.ChannelFull{
|
||||
HasScheduled: true,
|
||||
ID: 11,
|
||||
About: "garfield blog",
|
||||
ParticipantsCount: 1,
|
||||
ChatPhoto: &tg.PhotoEmpty{},
|
||||
}
|
||||
rawFull.SetFlags()
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsGetFullChannelRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
}).ThenResult(&tg.MessagesChatFull{
|
||||
FullChat: rawFull,
|
||||
})
|
||||
members := Channel(ch)
|
||||
|
||||
var targetErr *ChannelInfoUnavailableError
|
||||
a.ErrorAs(members.ForEach(ctx, func(p Member) error {
|
||||
return nil
|
||||
}), &targetErr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChannelMembers_Kick(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
u := m.User(getTestUser())
|
||||
ch := m.Channel(getTestChannel())
|
||||
members := Channel(ch)
|
||||
rights := tg.ChatBannedRights{
|
||||
ViewMessages: true,
|
||||
}
|
||||
rights.SetFlags()
|
||||
member := ChannelMember{
|
||||
parent: members,
|
||||
user: u,
|
||||
raw: &tg.ChannelParticipant{},
|
||||
}
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsEditBannedRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Participant: u.InputPeer(),
|
||||
BannedRights: rights,
|
||||
}).ThenRPCErr(getTestError())
|
||||
a.Error(member.Kick(ctx, false))
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsDeleteParticipantHistoryRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Participant: u.InputPeer(),
|
||||
}).ThenRPCErr(getTestError())
|
||||
a.Error(member.Kick(ctx, true))
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsEditBannedRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Participant: u.InputPeer(),
|
||||
BannedRights: rights,
|
||||
}).ThenResult(&tg.Updates{})
|
||||
a.NoError(member.Kick(ctx, false))
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsDeleteParticipantHistoryRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Participant: u.InputPeer(),
|
||||
}).ThenResult(&tg.MessagesAffectedHistory{})
|
||||
mock.ExpectCall(&tg.ChannelsEditBannedRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
Participant: u.InputPeer(),
|
||||
BannedRights: rights,
|
||||
}).ThenResult(&tg.Updates{})
|
||||
a.NoError(member.Kick(ctx, true))
|
||||
}
|
||||
|
||||
func TestChannelMembers_EditAdminRights(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
u := m.User(getTestUser())
|
||||
ch := m.Channel(getTestChannel())
|
||||
members := Channel(ch)
|
||||
rights := tg.ChatAdminRights{
|
||||
AddAdmins: true,
|
||||
}
|
||||
rights.SetFlags()
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsEditAdminRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
UserID: u.InputUser(),
|
||||
AdminRights: rights,
|
||||
Rank: "rank",
|
||||
}).ThenRPCErr(getTestError())
|
||||
a.Error(members.EditAdminRights(ctx, u.InputUser(), AdminRights{
|
||||
Rank: "rank",
|
||||
AddAdmins: true,
|
||||
}))
|
||||
|
||||
mock.ExpectCall(&tg.ChannelsEditAdminRequest{
|
||||
Channel: ch.InputChannel(),
|
||||
UserID: u.InputUser(),
|
||||
AdminRights: rights,
|
||||
Rank: "rank",
|
||||
}).ThenResult(&tg.Updates{})
|
||||
a.NoError(members.EditAdminRights(ctx, u.InputUser(), AdminRights{
|
||||
Rank: "rank",
|
||||
AddAdmins: true,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// ChatMembers is chat Members.
|
||||
type ChatMembers struct {
|
||||
m *peers.Manager
|
||||
chat peers.Chat
|
||||
}
|
||||
|
||||
func (c *ChatMembers) queryParticipants(ctx context.Context) (*tg.ChatParticipants, error) {
|
||||
full, err := c.chat.FullRaw(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get full")
|
||||
}
|
||||
switch p := full.Participants.(type) {
|
||||
case *tg.ChatParticipantsForbidden:
|
||||
return nil, &ChatInfoUnavailableError{Info: p}
|
||||
case *tg.ChatParticipants:
|
||||
return p, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unexpected type %T", p)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEach calls cb for every member of chat.
|
||||
//
|
||||
// May return ChatInfoUnavailableError.
|
||||
func (c *ChatMembers) ForEach(ctx context.Context, cb Callback) error {
|
||||
chatDate := time.Unix(int64(c.chat.Raw().Date), 0)
|
||||
p, err := c.queryParticipants(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "query")
|
||||
}
|
||||
|
||||
for i, participant := range p.Participants {
|
||||
userID := participant.GetUserID()
|
||||
user, err := c.m.ResolveUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get member %d", userID)
|
||||
}
|
||||
|
||||
var inviter peers.User
|
||||
switch p := participant.(type) {
|
||||
case *tg.ChatParticipant:
|
||||
inviter, err = c.m.ResolveUserID(ctx, p.InviterID)
|
||||
case *tg.ChatParticipantAdmin:
|
||||
inviter, err = c.m.ResolveUserID(ctx, p.InviterID)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get inviter")
|
||||
}
|
||||
|
||||
if err := cb(ChatMember{
|
||||
parent: c,
|
||||
creatorDate: chatDate,
|
||||
user: user,
|
||||
inviter: inviter,
|
||||
raw: participant,
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "callback (index: %d)", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count returns total count of members.
|
||||
func (c *ChatMembers) Count(ctx context.Context) (int, error) {
|
||||
p, err := c.queryParticipants(ctx)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "query")
|
||||
}
|
||||
return len(p.Participants), nil
|
||||
}
|
||||
|
||||
// Peer returns chat object.
|
||||
func (c *ChatMembers) Peer() peers.Peer {
|
||||
return c.chat
|
||||
}
|
||||
|
||||
// Kick kicks user member.
|
||||
//
|
||||
// If revokeHistory is set, will delete all messages from this member.
|
||||
func (c *ChatMembers) Kick(ctx context.Context, member tg.InputUserClass, revokeHistory bool) error {
|
||||
if _, err := c.m.API().MessagesDeleteChatUser(ctx, &tg.MessagesDeleteChatUserRequest{
|
||||
RevokeHistory: revokeHistory,
|
||||
ChatID: c.chat.ID(),
|
||||
UserID: member,
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "delete user (revoke: %v)", revokeHistory)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EditRights edits rights of all members in this chat.
|
||||
func (c *ChatMembers) EditRights(ctx context.Context, options MemberRights) error {
|
||||
return editDefaultRights(ctx, c.m.API(), c.chat.InputPeer(), options)
|
||||
}
|
||||
|
||||
// EditAdmin edits admin rights for given user.
|
||||
func (c *ChatMembers) EditAdmin(ctx context.Context, user tg.InputUserClass, isAdmin bool) error {
|
||||
if _, err := c.m.API().MessagesEditChatAdmin(ctx, &tg.MessagesEditChatAdminRequest{
|
||||
ChatID: c.chat.ID(),
|
||||
UserID: user,
|
||||
IsAdmin: isAdmin,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "edit admin")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Chat returns recent chat members.
|
||||
func Chat(chat peers.Chat) *ChatMembers {
|
||||
return &ChatMembers{
|
||||
m: chat.Manager(),
|
||||
chat: chat,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// ChatMember is chat Member.
|
||||
type ChatMember struct {
|
||||
parent *ChatMembers
|
||||
creatorDate time.Time
|
||||
user peers.User
|
||||
inviter peers.User
|
||||
raw tg.ChatParticipantClass
|
||||
}
|
||||
|
||||
// Raw returns raw member object.
|
||||
func (c ChatMember) Raw() tg.ChatParticipantClass {
|
||||
return c.raw
|
||||
}
|
||||
|
||||
// Status returns member Status.
|
||||
func (c ChatMember) Status() Status {
|
||||
switch c.raw.(type) {
|
||||
case *tg.ChatParticipant:
|
||||
return Plain
|
||||
case *tg.ChatParticipantCreator:
|
||||
return Creator
|
||||
case *tg.ChatParticipantAdmin:
|
||||
return Admin
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// JoinDate returns member join date, if it is available.
|
||||
func (c ChatMember) JoinDate() (time.Time, bool) {
|
||||
switch p := c.raw.(type) {
|
||||
case *tg.ChatParticipant:
|
||||
return time.Unix(int64(p.Date), 0), true
|
||||
case *tg.ChatParticipantCreator:
|
||||
return c.creatorDate, true
|
||||
case *tg.ChatParticipantAdmin:
|
||||
return time.Unix(int64(p.Date), 0), true
|
||||
default:
|
||||
return time.Time{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// InvitedBy returns user that invited this member.
|
||||
func (c ChatMember) InvitedBy() (peers.User, bool) {
|
||||
switch c.raw.(type) {
|
||||
case *tg.ChatParticipant:
|
||||
return c.inviter, true
|
||||
case *tg.ChatParticipantCreator:
|
||||
return peers.User{}, false
|
||||
case *tg.ChatParticipantAdmin:
|
||||
return c.inviter, true
|
||||
default:
|
||||
return peers.User{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// User returns member User object.
|
||||
func (c ChatMember) User() peers.User {
|
||||
return c.user
|
||||
}
|
||||
|
||||
// Kick kicks this member.
|
||||
//
|
||||
// If revokeHistory is set, will delete all messages from this member.
|
||||
func (c ChatMember) Kick(ctx context.Context, revokeHistory bool) error {
|
||||
return c.parent.Kick(ctx, c.user.InputUser(), revokeHistory)
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func TestChatMembers_Count(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
now := time.Now()
|
||||
date := int(now.Unix())
|
||||
|
||||
rawCh := getTestChat()
|
||||
rawCh.Date = date
|
||||
ch := m.Chat(rawCh)
|
||||
members := Chat(ch)
|
||||
|
||||
mock.ExpectCall(&tg.MessagesGetFullChatRequest{
|
||||
ChatID: ch.ID(),
|
||||
}).ThenRPCErr(getTestError())
|
||||
_, err := members.Count(ctx)
|
||||
a.Error(err)
|
||||
|
||||
mock.ExpectCall(&tg.MessagesGetFullChatRequest{
|
||||
ChatID: ch.ID(),
|
||||
}).ThenResult(&tg.MessagesChatFull{
|
||||
FullChat: getTestChatFull(&tg.ChatParticipants{
|
||||
ChatID: 10,
|
||||
Participants: []tg.ChatParticipantClass{
|
||||
&tg.ChatParticipant{
|
||||
UserID: 10,
|
||||
InviterID: 11,
|
||||
Date: date,
|
||||
},
|
||||
&tg.ChatParticipantCreator{
|
||||
UserID: 10,
|
||||
},
|
||||
&tg.ChatParticipantAdmin{
|
||||
UserID: 10,
|
||||
InviterID: 11,
|
||||
Date: date,
|
||||
},
|
||||
},
|
||||
Version: 1,
|
||||
}),
|
||||
})
|
||||
count, err := members.Count(ctx)
|
||||
a.Equal(3, count)
|
||||
a.NoError(err)
|
||||
}
|
||||
|
||||
func TestChatMembers_ForEach(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
date := int(now.Unix())
|
||||
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
mock, m := testManager(t)
|
||||
|
||||
rawCh := getTestChat()
|
||||
rawCh.Date = date
|
||||
ch := m.Chat(rawCh)
|
||||
|
||||
mock.ExpectCall(&tg.MessagesGetFullChatRequest{
|
||||
ChatID: ch.ID(),
|
||||
}).ThenResult(&tg.MessagesChatFull{
|
||||
FullChat: getTestChatFull(&tg.ChatParticipants{
|
||||
ChatID: 10,
|
||||
Participants: []tg.ChatParticipantClass{
|
||||
&tg.ChatParticipant{
|
||||
UserID: 10,
|
||||
InviterID: 11,
|
||||
Date: date,
|
||||
},
|
||||
&tg.ChatParticipantCreator{
|
||||
UserID: 10,
|
||||
},
|
||||
&tg.ChatParticipantAdmin{
|
||||
UserID: 10,
|
||||
InviterID: 11,
|
||||
Date: date,
|
||||
},
|
||||
},
|
||||
Version: 1,
|
||||
}),
|
||||
Users: []tg.UserClass{
|
||||
&tg.User{
|
||||
ID: 10,
|
||||
AccessHash: 10,
|
||||
},
|
||||
&tg.User{
|
||||
ID: 11,
|
||||
AccessHash: 10,
|
||||
},
|
||||
},
|
||||
})
|
||||
members := Chat(ch)
|
||||
|
||||
count, err := members.Count(ctx)
|
||||
a.Equal(3, count)
|
||||
a.NoError(err)
|
||||
|
||||
expected := []struct {
|
||||
Status Status
|
||||
JoinDate time.Time
|
||||
JoinDateSet bool
|
||||
InviterID int64
|
||||
}{
|
||||
{Status: Plain, JoinDate: now, JoinDateSet: true, InviterID: 11},
|
||||
{Status: Creator, JoinDate: now, JoinDateSet: true},
|
||||
{Status: Admin, JoinDate: now, JoinDateSet: true, InviterID: 11},
|
||||
}
|
||||
|
||||
i := 0
|
||||
a.NoError(members.ForEach(ctx, func(m Member) error {
|
||||
p := m.(ChatMember)
|
||||
e := expected[i]
|
||||
|
||||
a.Equal(e.Status, p.Status(), i)
|
||||
a.Equal(int64(10), p.User().ID())
|
||||
if join, ok := p.JoinDate(); e.JoinDateSet {
|
||||
a.True(ok, i)
|
||||
a.Equal(e.JoinDate.Unix(), join.Unix(), i)
|
||||
} else {
|
||||
a.False(ok, i)
|
||||
}
|
||||
|
||||
if inviter, ok := p.InvitedBy(); e.InviterID != 0 {
|
||||
a.True(ok, i)
|
||||
a.Equal(e.InviterID, inviter.ID())
|
||||
} else {
|
||||
a.False(ok, i)
|
||||
}
|
||||
|
||||
i++
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
t.Run("ChatInfoUnavailableError", func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
mock, m := testManager(t)
|
||||
|
||||
rawCh := getTestChat()
|
||||
rawCh.Date = date
|
||||
ch := m.Chat(rawCh)
|
||||
|
||||
mock.ExpectCall(&tg.MessagesGetFullChatRequest{
|
||||
ChatID: ch.ID(),
|
||||
}).ThenResult(&tg.MessagesChatFull{
|
||||
FullChat: getTestChatFull(&tg.ChatParticipantsForbidden{}),
|
||||
})
|
||||
members := Chat(ch)
|
||||
|
||||
var targetErr *ChatInfoUnavailableError
|
||||
a.ErrorAs(members.ForEach(ctx, func(p Member) error {
|
||||
return nil
|
||||
}), &targetErr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChatMembers_Kick(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
u := m.User(getTestUser())
|
||||
ch := m.Chat(getTestChat())
|
||||
members := Chat(ch)
|
||||
member := ChatMember{
|
||||
parent: members,
|
||||
user: u,
|
||||
raw: &tg.ChatParticipant{},
|
||||
}
|
||||
|
||||
mock.ExpectCall(&tg.MessagesDeleteChatUserRequest{
|
||||
RevokeHistory: true,
|
||||
ChatID: ch.ID(),
|
||||
UserID: u.InputUser(),
|
||||
}).ThenRPCErr(getTestError())
|
||||
a.Error(member.Kick(ctx, true))
|
||||
|
||||
mock.ExpectCall(&tg.MessagesDeleteChatUserRequest{
|
||||
RevokeHistory: true,
|
||||
ChatID: ch.ID(),
|
||||
UserID: u.InputUser(),
|
||||
}).ThenResult(&tg.Updates{})
|
||||
a.NoError(member.Kick(ctx, true))
|
||||
}
|
||||
|
||||
func TestChatMembers_EditAdmin(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
u := m.User(getTestUser())
|
||||
ch := m.Chat(getTestChat())
|
||||
members := Chat(ch)
|
||||
|
||||
mock.ExpectCall(&tg.MessagesEditChatAdminRequest{
|
||||
IsAdmin: true,
|
||||
ChatID: ch.ID(),
|
||||
UserID: u.InputUser(),
|
||||
}).ThenRPCErr(getTestError())
|
||||
a.Error(members.EditAdmin(ctx, u.InputUser(), true))
|
||||
|
||||
mock.ExpectCall(&tg.MessagesEditChatAdminRequest{
|
||||
IsAdmin: true,
|
||||
ChatID: ch.ID(),
|
||||
UserID: u.InputUser(),
|
||||
}).ThenTrue()
|
||||
a.NoError(members.EditAdmin(ctx, u.InputUser(), true))
|
||||
|
||||
mock.ExpectCall(&tg.MessagesEditChatAdminRequest{
|
||||
IsAdmin: false,
|
||||
ChatID: ch.ID(),
|
||||
UserID: u.InputUser(),
|
||||
}).ThenTrue()
|
||||
a.NoError(members.EditAdmin(ctx, u.InputUser(), false))
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func convertInputUserToInputPeer(p tg.InputUserClass) tg.InputPeerClass {
|
||||
switch p := p.(type) {
|
||||
case *tg.InputUserSelf:
|
||||
return &tg.InputPeerSelf{}
|
||||
case *tg.InputUser:
|
||||
return &tg.InputPeerUser{
|
||||
UserID: p.UserID,
|
||||
AccessHash: p.AccessHash,
|
||||
}
|
||||
case *tg.InputUserFromMessage:
|
||||
return &tg.InputPeerUserFromMessage{
|
||||
Peer: p.Peer,
|
||||
MsgID: p.MsgID,
|
||||
UserID: p.UserID,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func editDefaultRights(ctx context.Context, api *tg.Client, p tg.InputPeerClass, rights MemberRights) error {
|
||||
if _, err := api.MessagesEditChatDefaultBannedRights(ctx, &tg.MessagesEditChatDefaultBannedRightsRequest{
|
||||
Peer: p,
|
||||
BannedRights: rights.IntoChatBannedRights(),
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "edit default rights")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// ChatInfoUnavailableError reports that chat members info is not available.
|
||||
type ChatInfoUnavailableError struct {
|
||||
Info *tg.ChatParticipantsForbidden
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (c *ChatInfoUnavailableError) Error() string {
|
||||
return "chat members info is unavailable"
|
||||
}
|
||||
|
||||
// ChannelInfoUnavailableError reports that channel members info is not available.
|
||||
type ChannelInfoUnavailableError struct {
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (c *ChannelInfoUnavailableError) Error() string {
|
||||
return "channel members info is unavailable"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChatInfoUnavailableError_Error(t *testing.T) {
|
||||
require.Equal(t, (&ChatInfoUnavailableError{}).Error(), "chat members info is unavailable")
|
||||
}
|
||||
|
||||
func TestChannelInfoUnavailableError_Error(t *testing.T) {
|
||||
require.Equal(t, (&ChannelInfoUnavailableError{}).Error(), "channel members info is unavailable")
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Package members defines interfaces for working with chat/channel members.
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
var _ = []Member{
|
||||
ChatMember{},
|
||||
ChannelMember{},
|
||||
}
|
||||
|
||||
// Member represents chat/channel member.
|
||||
type Member interface {
|
||||
// Status returns member Status.
|
||||
Status() Status
|
||||
// JoinDate returns member join date, if it is available.
|
||||
JoinDate() (time.Time, bool)
|
||||
// InvitedBy returns user that invited this member.
|
||||
InvitedBy() (peers.User, bool)
|
||||
// User returns member User object.
|
||||
User() peers.User
|
||||
// Kick kicks this member.
|
||||
//
|
||||
// If revokeHistory is set, will delete all messages from this member.
|
||||
Kick(ctx context.Context, revokeHistory bool) error
|
||||
}
|
||||
|
||||
// Callback is type for member iterator callback.
|
||||
type Callback = func(p Member) error
|
||||
|
||||
var _ = []Members{
|
||||
&ChatMembers{},
|
||||
&ChannelMembers{},
|
||||
}
|
||||
|
||||
// Members represents chat/channel members.
|
||||
type Members interface {
|
||||
// ForEach calls cb for every member of chat/channel.
|
||||
ForEach(ctx context.Context, cb Callback) error
|
||||
// Count returns total count of members.
|
||||
Count(ctx context.Context) (int, error)
|
||||
// Peer returns chat object.
|
||||
Peer() peers.Peer
|
||||
// Kick kicks user member.
|
||||
//
|
||||
// If revokeHistory is set, will delete all messages from this member.
|
||||
Kick(ctx context.Context, member tg.InputUserClass, revokeHistory bool) error
|
||||
// EditRights edits rights of all members in this chat/channel.
|
||||
EditRights(ctx context.Context, options MemberRights) error
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tgerr"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tgmock"
|
||||
)
|
||||
|
||||
func testManager(t *testing.T) (*tgmock.Mock, *peers.Manager) {
|
||||
mock := tgmock.New(t)
|
||||
return mock, peers.Options{
|
||||
Logger: zaptest.NewLogger(t),
|
||||
Cache: &peers.InmemoryCache{},
|
||||
}.Build(tg.NewClient(mock))
|
||||
}
|
||||
|
||||
func getTestChannel() *tg.Channel {
|
||||
return &tg.Channel{
|
||||
Broadcast: true,
|
||||
Noforwards: true,
|
||||
ID: 11,
|
||||
AccessHash: 11,
|
||||
Title: "I hate mondays",
|
||||
Username: "",
|
||||
Photo: &tg.ChatPhotoEmpty{},
|
||||
Date: int(time.Now().Unix()),
|
||||
RestrictionReason: nil,
|
||||
AdminRights: tg.ChatAdminRights{},
|
||||
BannedRights: tg.ChatBannedRights{},
|
||||
DefaultBannedRights: tg.ChatBannedRights{},
|
||||
ParticipantsCount: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func getTestChannelFull() *tg.ChannelFull {
|
||||
u := &tg.ChannelFull{
|
||||
CanViewParticipants: true,
|
||||
HasScheduled: true,
|
||||
ID: 11,
|
||||
About: "garfield blog",
|
||||
ParticipantsCount: 1,
|
||||
ChatPhoto: &tg.PhotoEmpty{},
|
||||
}
|
||||
u.SetFlags()
|
||||
return u
|
||||
}
|
||||
|
||||
func getTestChat() *tg.Chat {
|
||||
u := &tg.Chat{
|
||||
Noforwards: true,
|
||||
ID: 10,
|
||||
Title: "I hate mondays",
|
||||
ParticipantsCount: 1,
|
||||
Date: int(time.Now().Unix()),
|
||||
Version: 1,
|
||||
Photo: &tg.ChatPhotoEmpty{},
|
||||
}
|
||||
u.SetFlags()
|
||||
return u
|
||||
}
|
||||
|
||||
func getTestChatFull(participants tg.ChatParticipantsClass) *tg.ChatFull {
|
||||
u := &tg.ChatFull{
|
||||
CanSetUsername: false,
|
||||
HasScheduled: true,
|
||||
ID: 10,
|
||||
About: "garfield blog",
|
||||
Participants: participants,
|
||||
}
|
||||
u.SetFlags()
|
||||
return u
|
||||
}
|
||||
|
||||
func getTestUser() *tg.User {
|
||||
u := &tg.User{
|
||||
Self: false,
|
||||
Bot: false,
|
||||
ID: 11,
|
||||
AccessHash: 10,
|
||||
FirstName: "Julia",
|
||||
LastName: "Ann",
|
||||
Username: "aboba",
|
||||
}
|
||||
u.SetFlags()
|
||||
return u
|
||||
}
|
||||
|
||||
func getTestError() *tgerr.Error {
|
||||
return &tgerr.Error{
|
||||
Code: 1337,
|
||||
Message: "TEST_ERROR",
|
||||
Type: "TEST_ERROR",
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditRights(t *testing.T) {
|
||||
a := require.New(t)
|
||||
ctx := context.Background()
|
||||
mock, m := testManager(t)
|
||||
|
||||
rights := tg.ChatBannedRights{
|
||||
SendInline: true,
|
||||
}
|
||||
rights.SetFlags()
|
||||
req := func(p Members) *tgmock.RequestBuilder {
|
||||
return mock.ExpectCall(&tg.MessagesEditChatDefaultBannedRightsRequest{
|
||||
Peer: p.Peer().InputPeer(),
|
||||
BannedRights: rights,
|
||||
})
|
||||
}
|
||||
for _, p := range []Members{
|
||||
Chat(m.Chat(getTestChat())),
|
||||
Channel(m.Channel(getTestChannel())),
|
||||
} {
|
||||
req(p).ThenRPCErr(getTestError())
|
||||
a.Error(p.EditRights(ctx, MemberRights{DenySendInline: true}))
|
||||
req(p).ThenResult(&tg.Updates{})
|
||||
a.NoError(p.EditRights(ctx, MemberRights{DenySendInline: true}))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// AdminRights represents admin right settings.
|
||||
type AdminRights struct {
|
||||
// Indicates the role (rank) of the admin in the group: just an arbitrary string.
|
||||
//
|
||||
// If empty, will not be used.
|
||||
Rank string
|
||||
// If set, allows the admin to modify the description of the channel/supergroup.
|
||||
ChangeInfo bool
|
||||
// If set, allows the admin to post messages in the channel.
|
||||
PostMessages bool
|
||||
// If set, allows the admin to also edit messages from other admins in the channel.
|
||||
EditMessages bool
|
||||
// If set, allows the admin to also delete messages from other admins in the channel.
|
||||
DeleteMessages bool
|
||||
// If set, allows the admin to ban users from the channel/supergroup.
|
||||
BanUsers bool
|
||||
// If set, allows the admin to invite users in the channel/supergroup.
|
||||
InviteUsers bool
|
||||
// If set, allows the admin to pin messages in the channel/supergroup.
|
||||
PinMessages bool
|
||||
// If set, allows the admin to add other admins with the same (or more limited)
|
||||
// permissions in the channel/supergroup.
|
||||
AddAdmins bool
|
||||
// Whether this admin is anonymous.
|
||||
Anonymous bool
|
||||
// If set, allows the admin to change group call/livestream settings.
|
||||
ManageCall bool
|
||||
// Set this flag if none of the other flags are set, but you still want the user to be an
|
||||
// admin.
|
||||
Other bool
|
||||
}
|
||||
|
||||
// IntoChatAdminRights converts AdminRights into tg.ChatAdminRights.
|
||||
func (b AdminRights) IntoChatAdminRights() (r tg.ChatAdminRights) {
|
||||
r.ChangeInfo = b.ChangeInfo
|
||||
r.PostMessages = b.PostMessages
|
||||
r.EditMessages = b.EditMessages
|
||||
r.DeleteMessages = b.DeleteMessages
|
||||
r.BanUsers = b.BanUsers
|
||||
r.InviteUsers = b.InviteUsers
|
||||
r.PinMessages = b.PinMessages
|
||||
r.AddAdmins = b.AddAdmins
|
||||
r.Anonymous = b.Anonymous
|
||||
r.ManageCall = b.ManageCall
|
||||
r.Other = b.Other
|
||||
r.SetFlags()
|
||||
return r
|
||||
}
|
||||
|
||||
// MemberRights represents member right settings.
|
||||
type MemberRights struct {
|
||||
// If set, does not allow a user to view messages in a supergroup/channel/chat.
|
||||
//
|
||||
// In fact, user will be kicked.
|
||||
DenyViewMessages bool
|
||||
// If set, does not allow a user to send messages in a supergroup/chat.
|
||||
DenySendMessages bool
|
||||
// If set, does not allow a user to send any media in a supergroup/chat.
|
||||
DenySendMedia bool
|
||||
// If set, does not allow a user to send stickers in a supergroup/chat.
|
||||
DenySendStickers bool
|
||||
// If set, does not allow a user to send gifs in a supergroup/chat.
|
||||
DenySendGifs bool
|
||||
// If set, does not allow a user to send games in a supergroup/chat.
|
||||
DenySendGames bool
|
||||
// If set, does not allow a user to use inline bots in a supergroup/chat.
|
||||
DenySendInline bool
|
||||
// If set, does not allow a user to embed links in the messages of a supergroup/chat.
|
||||
DenyEmbedLinks bool
|
||||
// If set, does not allow a user to send polls in a supergroup/chat.
|
||||
DenySendPolls bool
|
||||
// If set, does not allow any user to change the description of a supergroup/chat.
|
||||
DenyChangeInfo bool
|
||||
// If set, does not allow any user to invite users in a supergroup/chat.
|
||||
DenyInviteUsers bool
|
||||
// If set, does not allow any user to pin messages in a supergroup/chat.
|
||||
DenyPinMessages bool
|
||||
// Validity of said permissions (it is considered forever any value less than 30 seconds or more than 366 days).
|
||||
//
|
||||
// If value is zero, value will not be used.
|
||||
UntilDate time.Time
|
||||
}
|
||||
|
||||
// ApplyFor sets duration of validity of set rights.
|
||||
func (b *MemberRights) ApplyFor(d time.Duration) {
|
||||
b.UntilDate = time.Now().Add(d)
|
||||
}
|
||||
|
||||
// IntoChatBannedRights converts MemberRights into tg.ChatBannedRights.
|
||||
func (b MemberRights) IntoChatBannedRights() (r tg.ChatBannedRights) {
|
||||
r = tg.ChatBannedRights{
|
||||
ViewMessages: b.DenyViewMessages,
|
||||
SendMessages: b.DenySendMessages,
|
||||
SendMedia: b.DenySendMedia,
|
||||
SendStickers: b.DenySendStickers,
|
||||
SendGifs: b.DenySendGifs,
|
||||
SendGames: b.DenySendGames,
|
||||
SendInline: b.DenySendInline,
|
||||
EmbedLinks: b.DenyEmbedLinks,
|
||||
SendPolls: b.DenySendPolls,
|
||||
ChangeInfo: b.DenyChangeInfo,
|
||||
InviteUsers: b.DenyInviteUsers,
|
||||
PinMessages: b.DenyPinMessages,
|
||||
}
|
||||
if !b.UntilDate.IsZero() {
|
||||
r.UntilDate = int(b.UntilDate.Unix())
|
||||
}
|
||||
r.SetFlags()
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func TestMemberRights_ApplyFor(t *testing.T) {
|
||||
var r MemberRights
|
||||
r.ApplyFor(time.Second)
|
||||
require.False(t, r.UntilDate.IsZero())
|
||||
}
|
||||
|
||||
func TestMemberRights_IntoChatBannedRights(t *testing.T) {
|
||||
r := MemberRights{
|
||||
DenyViewMessages: true,
|
||||
DenySendMessages: true,
|
||||
DenySendMedia: true,
|
||||
DenySendStickers: true,
|
||||
DenySendGifs: true,
|
||||
DenySendGames: true,
|
||||
DenySendInline: true,
|
||||
DenyEmbedLinks: true,
|
||||
DenySendPolls: true,
|
||||
DenyChangeInfo: true,
|
||||
DenyInviteUsers: true,
|
||||
DenyPinMessages: true,
|
||||
UntilDate: time.Time{},
|
||||
}
|
||||
|
||||
rights := r.IntoChatBannedRights()
|
||||
expected := tg.ChatBannedRights{
|
||||
ViewMessages: true,
|
||||
SendMessages: true,
|
||||
SendMedia: true,
|
||||
SendStickers: true,
|
||||
SendGifs: true,
|
||||
SendGames: true,
|
||||
SendInline: true,
|
||||
EmbedLinks: true,
|
||||
SendPolls: true,
|
||||
ChangeInfo: true,
|
||||
InviteUsers: true,
|
||||
PinMessages: true,
|
||||
UntilDate: 0,
|
||||
}
|
||||
expected.SetFlags()
|
||||
require.Equal(t, expected, rights)
|
||||
|
||||
r.ApplyFor(time.Second)
|
||||
rights = r.IntoChatBannedRights()
|
||||
require.NotZero(t, rights.UntilDate)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package members
|
||||
|
||||
//go:generate go run -modfile=../../../_tools/go.mod golang.org/x/tools/cmd/stringer -type=Status
|
||||
|
||||
// Status defines participant status.
|
||||
type Status int
|
||||
|
||||
const (
|
||||
// Plain is status for plain participant.
|
||||
Plain Status = iota
|
||||
// Creator is status for chat/channel creator.
|
||||
Creator
|
||||
// Admin is status for chat/channel admin.
|
||||
Admin
|
||||
// Banned is status for banned user.
|
||||
Banned
|
||||
// Left is status for user that left chat/channel.
|
||||
Left
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
// Code generated by "stringer -type=Status"; DO NOT EDIT.
|
||||
|
||||
package members
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Plain-0]
|
||||
_ = x[Creator-1]
|
||||
_ = x[Admin-2]
|
||||
_ = x[Banned-3]
|
||||
_ = x[Left-4]
|
||||
}
|
||||
|
||||
const _Status_name = "PlainCreatorAdminBannedLeft"
|
||||
|
||||
var _Status_index = [...]uint8{0, 5, 12, 17, 23, 27}
|
||||
|
||||
func (i Status) String() string {
|
||||
if i < 0 || i >= Status(len(_Status_index)-1) {
|
||||
return "Status(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Status_name[_Status_index[i]:_Status_index[i+1]]
|
||||
}
|
||||
Reference in New Issue
Block a user