/
channel.go
140 lines (128 loc) · 4.19 KB
/
channel.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
package ircslack
import (
"fmt"
"strings"
"time"
"github.com/slack-go/slack"
)
// Constants for public, private, and multi-party conversation prefixes.
// Channel threads are prefixed with "+" but they are not conversation types
// so they do not belong here. A thread is just a message whose destination
// is within another message in a public, private, or multi-party conversation.
const (
ChannelPrefixPublicChannel = "#"
ChannelPrefixPrivateChannel = "@"
ChannelPrefixMpIM = "&"
// NOTE: a thread is not a channel type
ChannelPrefixThread = "+"
)
// HasChannelPrefix returns true if the channel name starts with one of the
// supproted channel prefixes.
func HasChannelPrefix(name string) bool {
if len(name) == 0 {
return false
}
switch string(name[0]) {
case ChannelPrefixPublicChannel, ChannelPrefixPrivateChannel, ChannelPrefixMpIM, ChannelPrefixThread:
return true
default:
return false
}
}
// StripChannelPrefix returns a channel name without its channel prefix. If no
// channel prefix is present, the string is returned unchanged.
func StripChannelPrefix(name string) string {
if HasChannelPrefix(name) {
return name[1:]
}
return name
}
// ChannelMembers returns a list of users in the given conversation.
func ChannelMembers(ctx *IrcContext, channelID string) ([]slack.User, error) {
var (
members, m []string
nextCursor string
err error
page int
)
for {
attempt := 0
for {
// retry if rate-limited, no more than MaxSlackAPIAttempts times
if attempt >= MaxSlackAPIAttempts {
return nil, fmt.Errorf("ChannelMembers: exceeded the maximum number of attempts (%d) with the Slack API", MaxSlackAPIAttempts)
}
log.Debugf("ChannelMembers: page %d attempt #%d nextCursor=%s", page, attempt, nextCursor)
m, nextCursor, err = ctx.SlackClient.GetUsersInConversation(&slack.GetUsersInConversationParameters{ChannelID: channelID, Cursor: nextCursor, Limit: 1000})
if err != nil {
log.Errorf("Failed to get users in conversation '%s': %v", channelID, err)
if rlErr, ok := err.(*slack.RateLimitedError); ok {
// we were rate-limited. Let's wait as much as Slack
// instructs us to do
log.Warningf("Hit Slack API rate limiter. Waiting %v", rlErr.RetryAfter)
time.Sleep(rlErr.RetryAfter)
attempt++
continue
}
return nil, fmt.Errorf("Cannot get member list for conversation %s: %v", channelID, err)
}
break
}
members = append(members, m...)
log.Debugf("Fetched %d user IDs for channel %s (fetched so far: %d)", len(m), channelID, len(members))
// TODO call ctx.Users.FetchByID here in a goroutine to see if this
// speeds up
if nextCursor == "" {
break
}
page++
}
log.Debugf("Retrieving user information for %d users", len(members))
users, err := ctx.Users.FetchByIDs(ctx.SlackClient, false, members...)
if err != nil {
return nil, fmt.Errorf("Failed to fetch users by their IDs: %v", err)
}
return users, nil
}
// Channel wraps a Slack conversation with a few utility functions.
type Channel slack.Channel
// IsPublicChannel returns true if the channel is public.
func (c *Channel) IsPublicChannel() bool {
return c.IsChannel && !c.IsPrivate
}
// IsPrivateChannel returns true if the channel is private.
func (c *Channel) IsPrivateChannel() bool {
return c.IsGroup && c.IsPrivate
}
// IsMP returns true if it is a multi-party conversation.
func (c *Channel) IsMP() bool {
return c.IsMpIM
}
// IRCName returns the channel name as it would appear on IRC.
// Examples:
// * #channel for public groups
// * @channel for private groups
// * &Gxxxx|nick1-nick2-nick3 for multi-party IMs
func (c *Channel) IRCName() string {
switch {
case c.IsPublicChannel():
return ChannelPrefixPublicChannel + c.Name
case c.IsPrivateChannel():
return ChannelPrefixPrivateChannel + c.Name
case c.IsMP():
name := ChannelPrefixMpIM + c.ID + "|" + c.Name
name = strings.Replace(name, "mpdm-", "", -1)
name = strings.Replace(name, "--", "-", -1)
if len(name) >= 30 {
return name[:29] + "…"
}
return name
default:
log.Warningf("Unknown channel type for channel %+v", c)
return "<unknow-channel-type>"
}
}
// SlackName returns the slack.Channel.Name field.
func (c *Channel) SlackName() string {
return c.Name
}