Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
583 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
package members | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/go-faster/errors" | ||
|
||
"github.com/gotd/td/telegram/peers" | ||
"github.com/gotd/td/tg" | ||
) | ||
|
||
// ChannelMembers is channel Members. | ||
type ChannelMembers struct { | ||
m *peers.Manager | ||
channel peers.Channel | ||
} | ||
|
||
// ChannelMember is channel Member. | ||
type ChannelMember struct { | ||
parent *ChannelMembers | ||
creatorDate time.Time | ||
user peers.User | ||
inviter peers.User | ||
raw tg.ChannelParticipantClass | ||
} | ||
|
||
// 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, false | ||
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 | ||
} | ||
|
||
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: &tg.ChannelParticipantsRecent{}, | ||
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, participant := range m.Participants { | ||
var ( | ||
userID int64 | ||
inviterID int64 | ||
err error | ||
) | ||
switch p := participant.(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) | ||
} | ||
member := ChannelMember{ | ||
parent: c, | ||
creatorDate: channelDate, | ||
user: user, | ||
inviter: peers.User{}, | ||
raw: participant, | ||
} | ||
if inviterID != 0 { | ||
inviter, err := c.m.ResolveUserID(ctx, inviterID) | ||
if err != nil { | ||
return errors.Wrapf(err, "get inviter %d", inviterID) | ||
} | ||
member.inviter = inviter | ||
} | ||
|
||
if err := cb(member); 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 | ||
} | ||
|
||
// Channel returns recent channel members. | ||
func Channel(ctx context.Context, channel peers.Channel) (*ChannelMembers, error) { | ||
m := channel.Manager() | ||
return &ChannelMembers{ | ||
m: m, | ||
channel: channel, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package members | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/gotd/td/internal/testutil" | ||
"github.com/gotd/td/tg" | ||
) | ||
|
||
func TestChannelMembers_Count(t *testing.T) { | ||
a := require.New(t) | ||
ctx := context.Background() | ||
mock, m := testManager(t) | ||
|
||
ch := m.Channel(getTestChannel()) | ||
members, err := Channel(ctx, ch) | ||
a.NoError(err) | ||
|
||
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) | ||
} |
Oops, something went wrong.