-
-
Notifications
You must be signed in to change notification settings - Fork 137
Expand file tree
/
Copy pathslackdump.go
More file actions
229 lines (194 loc) · 6.82 KB
/
slackdump.go
File metadata and controls
229 lines (194 loc) · 6.82 KB
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright (c) 2021-2026 Rustam Gilyazov and Contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package slackdump
import (
"context"
"errors"
"fmt"
"log/slog"
"runtime/trace"
"time"
"golang.org/x/time/rate"
"github.com/rusq/fsadapter"
"github.com/rusq/slack"
st "github.com/rusq/slackdump/v4/internal/structures"
"github.com/rusq/slackdump/v4/auth"
"github.com/rusq/slackdump/v4/internal/client"
"github.com/rusq/slackdump/v4/internal/network"
"github.com/rusq/slackdump/v4/stream"
)
//go:generate mockgen -destination internal/mocks/mock_os/mock_os.go os FileInfo
// Session stores basic session parameters. Zero value is not usable, must be
// initialised with New.
type Session struct {
client client.Slack // client is the Slack client to use for API calls.
uc *usercache // usercache contains the list of users.
fs fsadapter.FS // filesystem adapter
log *slog.Logger // logger
wspInfo *WorkspaceInfo // workspace info
cfg config
}
// WorkspaceInfo is an type alias for [slack.AuthTestResponse].
type WorkspaceInfo = slack.AuthTestResponse
// 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 12/2025.
//
// [types]: https://api.slack.com/methods/conversations.list#arg_types
var AllChanTypes = []string{st.CMPIM, st.CIM, st.CPublic, st.CPrivate}
// 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 network.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 *slog.Logger) 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 client.Slack) Option {
return func(s *Session) {
s.client = cl
}
}
func WithForceEnterprise(b bool) Option {
return func(s *Session) {
s.cfg.forceEnterprise = b
}
}
// 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)
}
return NewNoValidate(ctx, prov, opts...)
}
// NewNoValidate creates new Slackdump session with provided options, and
// populates the internal cache of users and channels for lookups. This
// function does not validate the auth.Provider.
func NewNoValidate(ctx context.Context, prov auth.Provider, opts ...Option) (*Session, error) {
sd := &Session{
cfg: defConfig,
uc: new(usercache),
log: slog.Default(),
}
for _, opt := range opts {
opt(sd)
}
if err := sd.initClient(ctx, prov, sd.cfg.forceEnterprise); err != nil {
return nil, err
}
return sd, nil
}
// initWorkspaceInfo gets from the API and sets the workspace information for
// the session.
func (s *Session) initWorkspaceInfo(ctx context.Context, cl client.Slack) error {
info, err := cl.AuthTestContext(ctx)
if err != nil {
return err
}
s.wspInfo = info
return nil
}
// initClient initialises the client that is appropriate for the current
// workspace. It will use the initialised auth.Provider for credentials. If
// forceEdge is true, it will use th edge client regardless of whether it
// detects the enterprise instance or not. If the client was set by the
// WithClient option, it will not override it.
func (s *Session) initClient(ctx context.Context, prov auth.Provider, forceEdge bool) error {
if s.client == nil {
cl, err := client.New(ctx, prov, client.WithEnterprise(forceEdge))
if err != nil {
return err
}
s.client = cl
}
return s.initWorkspaceInfo(ctx, s.client)
}
// ErrNotAClient is returned by Client() when the underlying client is not a
// slack.Client.
var ErrNotAClient = errors.New("programming error: underlying client is not a slack.Client")
// Client returns the underlying slack.Client. If the underlying client does
// not wrap a *slack.Client, ErrNotAClient is returned.
func (s *Session) Client() (*slack.Client, error) {
cl, ok := s.client.(*client.Client)
if !ok {
return nil, ErrNotAClient
}
return cl.Client, nil
}
// 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 network.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 returns the new data streamer with the current session parameters.
func (s *Session) Stream(opts ...stream.Option) *stream.Stream {
return stream.New(s.client, s.cfg.limits, opts...)
}