@@ -19,22 +19,49 @@ package slackdump
1919
2020import (
2121 "context"
22+ "errors"
23+ "iter"
24+ "log/slog"
2225 "runtime/trace"
23- "time"
2426
2527 "github.com/rusq/slack"
2628
2729 "github.com/rusq/slackdump/v4/internal/network"
30+ "github.com/rusq/slackdump/v4/stream"
2831 "github.com/rusq/slackdump/v4/types"
2932)
3033
34+ // GetChannelsParameters holds the parameters for [GetChannelsEx] and
35+ // [StreamChannelsEx] functions.
36+ type GetChannelsParameters struct {
37+ // ChannelTypes allows to specify the channel types to fetch. If the slice
38+ // is empty, all channel types will be fetched.
39+ ChannelTypes []string
40+ // OnlyMyChannels restricts the channels only to the channels that the user
41+ // is a member of.
42+ OnlyMyChannels bool
43+ }
44+
3145// GetChannels list all conversations for a user. `chanTypes` specifies the
32- // type of messages to fetch. See github.com/rusq/slack docs for possible
46+ // type of channels to fetch. See github.com/rusq/slack docs for possible
3347// values. If large number of channels is to be returned, consider using
34- // StreamChannels.
48+ // [StreamChannelsEx]. It is a wrapper for [GetChannelsEx].
49+ //
50+ // Deprecated; Use [GetChannelsEx]. This function Will be removed in v5.
3551func (s * Session ) GetChannels (ctx context.Context , chanTypes ... string ) (types.Channels , error ) {
52+ p := GetChannelsParameters {
53+ ChannelTypes : chanTypes ,
54+ OnlyMyChannels : false ,
55+ }
56+ return s .GetChannelsEx (ctx , p )
57+ }
58+
59+ // GetChannelsEx list all conversations for a user. GetChannelParameters should
60+ // contain the fetch criteria. If large number of channels is to be returned,
61+ // consider using [StreamChannelsEx].
62+ func (s * Session ) GetChannelsEx (ctx context.Context , p GetChannelsParameters ) (types.Channels , error ) {
3663 var allChannels types.Channels
37- if err := s .getChannels (ctx , chanTypes , func (cc types.Channels ) error {
64+ if err := s .getChannels (ctx , p , func (ctx context. Context , cc types.Channels ) error {
3865 allChannels = append (allChannels , cc ... )
3966 return nil
4067 }); err != nil {
@@ -44,74 +71,92 @@ func (s *Session) GetChannels(ctx context.Context, chanTypes ...string) (types.C
4471}
4572
4673// StreamChannels requests the channels from the API and calls the callback
47- // function cb for each.
74+ // function cb for each. It is a wrapper for [StreamChannelsEx].
75+ //
76+ // Deprecated: Use [StreamChannelsEx]. This function Will be removed in v5.
4877func (s * Session ) StreamChannels (ctx context.Context , chanTypes []string , cb func (ch slack.Channel ) error ) error {
49- return s .getChannels (ctx , chanTypes , func (chans types.Channels ) error {
78+ p := GetChannelsParameters {
79+ ChannelTypes : chanTypes ,
80+ OnlyMyChannels : false ,
81+ }
82+ for chans , err := range s .StreamChannelsEx (ctx , p ) {
83+ if err != nil {
84+ return err
85+ }
5086 for _ , ch := range chans {
5187 if err := cb (ch ); err != nil {
5288 return err
5389 }
5490 }
55- return nil
56- })
91+ }
92+ return nil
93+ }
94+
95+ // StreamChannelsEx requests the channels from the API and returns an iterator
96+ // of channel chunks.
97+ func (s * Session ) StreamChannelsEx (ctx context.Context , p GetChannelsParameters ) iter.Seq2 [[]slack.Channel , error ] {
98+ return func (yield func (ch []slack.Channel , err error ) bool ) {
99+ err := s .getChannels (ctx , p , func (ctx context.Context , chans types.Channels ) error {
100+ if ! yield (chans , nil ) {
101+ return ErrStop
102+ }
103+ return nil
104+ })
105+ if err != nil {
106+ if errors .Is (err , ErrStop ) {
107+ return
108+ }
109+ _ = yield (nil , err )
110+ }
111+ }
57112}
58113
114+ type chanProcFunc func (ctx context.Context , ch types.Channels ) error
115+
116+ func (f chanProcFunc ) Channels (ctx context.Context , ch []slack.Channel ) error {
117+ return f (ctx , ch )
118+ }
119+
120+ // ErrStop instructs early stop to streaming function, when returned from a
121+ // callback function.
122+ var ErrStop = errors .New ("stop" )
123+
59124// getChannels list all channels for a user. `chanTypes` specifies
60125// the type of messages to fetch. See github.com/rusq/slack docs for possible
61- // values
62- func (s * Session ) getChannels (ctx context.Context , chanTypes [] string , cb func (types. Channels ) error ) error {
126+ // values. If the cb function returns [ErrStop], the iteration will stop.
127+ func (s * Session ) getChannels (ctx context.Context , gcp GetChannelsParameters , cb chanProcFunc ) error {
63128 ctx , task := trace .NewTask (ctx , "getChannels" )
64129 defer task .End ()
65130
66- limiter := s .limiter (network .Tier2 )
67-
68- if len (chanTypes ) == 0 {
69- chanTypes = AllChanTypes
131+ if len (gcp .ChannelTypes ) == 0 {
132+ gcp .ChannelTypes = AllChanTypes
70133 }
71134
72- params := & slack.GetConversationsParameters {Types : chanTypes , Limit : s .cfg .limits .Request .Channels }
73- fetchStart := time .Now ()
74- var total int
75- for i := 1 ; ; i ++ {
76- var (
77- chans []slack.Channel
78- nextcur string
79- )
80- reqStart := time .Now ()
81- if err := network .WithRetry (ctx , limiter , s .cfg .limits .Tier3 .Retries , func (ctx context.Context ) error {
82- var err error
83- trace .WithRegion (ctx , "GetConversationsContext" , func () {
84- chans , nextcur , err = s .client .GetConversationsContext (ctx , params )
85- })
86- return err
87- }); err != nil {
88- return err
135+ st := s .Stream ()
136+ params := & slack.GetConversationsParameters {Types : gcp .ChannelTypes , Limit : s .cfg .limits .Request .Channels }
137+ if err := st .ListChannelsEx (ctx , cb , params , gcp .OnlyMyChannels ); err != nil {
138+ if errors .Is (err , ErrStop ) {
139+ // early stop indicated
140+ return nil
89141 }
90142
91- if err := cb ( chans ); err != nil {
143+ if ! shouldFallbackToListChannels ( err ) {
92144 return err
93145 }
94- total += len (chans )
95-
96- s .log .InfoContext (ctx , "channels" , "request" , i , "fetched" , len (chans ), "total" , total ,
97- "speed" , float64 (len (chans ))/ time .Since (reqStart ).Seconds (),
98- "avg" , float64 (total )/ time .Since (fetchStart ).Seconds (),
99- )
100-
101- if nextcur == "" {
102- s .log .InfoContext (ctx , "channels fetch complete" , "total" , total )
103- break
104- }
105-
106- params .Cursor = nextcur
107-
108- if err := limiter .Wait (ctx ); err != nil {
146+ slog .DebugContext (ctx , "falling back to simple List Channels" , "err" , err )
147+ if err := st .ListChannels (ctx , cb , params ); err != nil {
148+ if errors .Is (err , ErrStop ) {
149+ // early stop indicated
150+ return nil
151+ }
109152 return err
110153 }
111154 }
112155 return nil
113156}
114157
158+ func shouldFallbackToListChannels (err error ) bool { return errors .Is (err , stream .ErrOpNotSupported ) }
159+
115160// GetChannelMembers returns a list of all members in a channel.
116161func (sd * Session ) GetChannelMembers (ctx context.Context , channelID string ) ([]string , error ) {
117162 var ids []string
0 commit comments