forked from rusq/slackdump
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslackdump.go
202 lines (172 loc) · 6.44 KB
/
slackdump.go
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package slackdump
import (
"context"
"errors"
"fmt"
"io"
"runtime/trace"
"time"
"github.com/go-playground/validator/v10"
"github.com/slack-go/slack"
"golang.org/x/time/rate"
"github.com/rusq/fsadapter"
"github.com/rusq/slackdump/v3/auth"
"github.com/rusq/slackdump/v3/internal/network"
"github.com/rusq/slackdump/v3/logger"
)
//go:generate mockgen -destination internal/mocks/mock_os/mock_os.go os FileInfo
//go:generate mockgen -source slackdump.go -destination clienter_mock_test.go -package slackdump -mock_names clienter=mockClienter,Reporter=mockReporter
// Session stores basic session parameters. Zero value is not usable, must be
// initialised with New.
type Session struct {
client clienter // Slack client
uc *usercache // usercache contains the list of users.
fs fsadapter.FS // filesystem adapter
log logger.Interface // logger
wspInfo *WorkspaceInfo // workspace info
cfg config
}
// WorkspaceInfo is an type alias for [slack.AuthTestResponse].
type WorkspaceInfo = slack.AuthTestResponse
// Slacker is the interface with some functions of slack.Client.
type Slacker interface {
AuthTestContext(context.Context) (response *slack.AuthTestResponse, err error)
GetConversationInfoContext(ctx context.Context, input *slack.GetConversationInfoInput) (*slack.Channel, error)
GetConversationHistoryContext(ctx context.Context, params *slack.GetConversationHistoryParameters) (*slack.GetConversationHistoryResponse, error)
GetConversationRepliesContext(ctx context.Context, params *slack.GetConversationRepliesParameters) (msgs []slack.Message, hasMore bool, nextCursor string, err error)
GetConversationsContext(ctx context.Context, params *slack.GetConversationsParameters) (channels []slack.Channel, nextCursor string, err error)
GetStarredContext(ctx context.Context, params slack.StarsParameters) ([]slack.StarredItem, *slack.Paging, error)
GetUsersPaginated(options ...slack.GetUsersOption) slack.UserPagination
GetUsersInConversationContext(ctx context.Context, params *slack.GetUsersInConversationParameters) ([]string, string, error)
ListBookmarks(channelID string) ([]slack.Bookmark, error)
}
// clienter is the interface with some functions of slack.Client with the sole
// purpose of mocking in tests (see client_mock.go)
type clienter interface {
Slacker
GetFile(downloadURL string, writer io.Writer) error
GetUsersContext(ctx context.Context, options ...slack.GetUsersOption) ([]slack.User, error)
GetEmojiContext(ctx context.Context) (map[string]string, error)
}
// ErrNoUserCache is returned when the user cache is not initialised.
var ErrNoUserCache = errors.New("user cache unavailable")
// AllChanTypes enumerates all API-supported channel [types] as of 03/2023.
//
// [types]: https://api.slack.com/methods/conversations.list#arg_types
var AllChanTypes = []string{"mpim", "im", "public_channel", "private_channel"}
// Option is the signature of the option-setting function.
type Option func(*Session)
// WithFilesystem sets the filesystem adapter to use for the session. If this
// option is not given, the default filesystem adapter is initialised with the
// base location specified in the Config.
func WithFilesystem(fs fsadapter.FS) Option {
return func(s *Session) {
if fs != nil {
s.fs = fs
}
}
}
// WithLimits sets the API limits to use for the session. If this option is
// not given, the default limits are initialised with the values specified in
// DefLimits.
func WithLimits(l Limits) Option {
return func(s *Session) {
if l.Validate() == nil {
s.cfg.limits = l
}
}
}
// WithLogger sets the logger to use for the session. If this option is not
// given, the default logger is initialised with the filename specified in
// Config.Logfile. If the Config.Logfile is empty, the default logger writes
// to STDERR.
func WithLogger(l logger.Interface) Option {
return func(s *Session) {
if l != nil {
s.log = l
}
}
}
// WithUserCacheRetention sets the retention period for the user cache. If this
// option is not given, the default value is 60 minutes.
func WithUserCacheRetention(d time.Duration) Option {
return func(s *Session) {
s.cfg.cacheRetention = d
}
}
// WithSlackClient sets the Slack client to use for the session. If this
func WithSlackClient(cl clienter) Option {
return func(s *Session) {
s.client = cl
}
}
// New creates new Slackdump session with provided options, and populates the
// internal cache of users and channels for lookups. If it fails to
// authenticate, AuthError is returned.
func New(ctx context.Context, prov auth.Provider, opts ...Option) (*Session, error) {
ctx, task := trace.NewTask(ctx, "New")
defer task.End()
if err := prov.Validate(); err != nil {
return nil, fmt.Errorf("auth provider validation error: %s", err)
}
httpCl, err := prov.HTTPClient()
if err != nil {
return nil, err
}
cl := slack.New(prov.SlackToken(), slack.OptionHTTPClient(httpCl))
sd := &Session{
client: cl,
cfg: defConfig,
uc: new(usercache),
log: logger.Default,
}
for _, opt := range opts {
opt(sd)
}
network.SetLogger(sd.log) // set the logger for the network package
if err := sd.cfg.limits.Validate(); err != nil {
var vErr validator.ValidationErrors
if errors.As(err, &vErr) {
return nil, fmt.Errorf("API limits failed validation: %s", vErr.Translate(OptErrTranslations))
}
return nil, err
}
authResp, err := sd.client.AuthTestContext(ctx)
if err != nil {
return nil, &auth.Error{Err: err}
}
sd.wspInfo = authResp
return sd, nil
}
// Client returns the underlying slack.Client.
func (s *Session) Client() *slack.Client {
return s.client.(*slack.Client)
}
// CurrentUserID returns the user ID of the authenticated user.
func (s *Session) CurrentUserID() string {
return s.wspInfo.UserID
}
func (s *Session) limiter(t network.Tier) *rate.Limiter {
var tl TierLimit
switch t {
case network.Tier2:
tl = s.cfg.limits.Tier2
case network.Tier3:
tl = s.cfg.limits.Tier3
case network.Tier4:
tl = s.cfg.limits.Tier4
default:
tl = s.cfg.limits.Tier3
}
return network.NewLimiter(t, tl.Burst, int(tl.Boost))
}
// Info returns a workspace information. Slackdump retrieves workspace
// information during the initialisation when performing authentication test,
// so no API call is involved at this point.
func (s *Session) Info() *WorkspaceInfo {
return s.wspInfo
}
// Stream streams the channel, calling proc functions for each chunk.
func (s *Session) Stream(opts ...StreamOption) *Stream {
return NewStream(s.client, &s.cfg.limits, opts...)
}