/
stream_events.go
266 lines (253 loc) · 10.1 KB
/
stream_events.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
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package bot
import (
"fmt"
"strings"
"time"
"github.com/bwmarrin/discordgo"
"github.com/callummance/nazuna/messages"
"github.com/callummance/nia/guildmodels"
"github.com/sirupsen/logrus"
)
const twitchColourHex = 0x6441a5
//HandleTwitchStreamOffline handles a streamoffline event generated by the Twitch EventSub API
func (b *NiaBot) HandleTwitchStreamOffline(e *messages.StreamOfflineEvent) {
//Lookup which member(s) have this stream registered for them
connectionLookup := guildmodels.MemberConnections{
TwitchConnection: &guildmodels.TwitchStream{
TwitchUID: e.BroadcasterUID,
},
}
matchingMembers, err := b.DBConnection.GetMemberByConnection(connectionLookup, nil, nil)
if err != nil {
logrus.Errorf("Failed to fetch members for streamoffline event %v due to error %v", e, err)
}
//Lookup any relevant role assignments in each members' guild
guildUpdates := make(map[string][]string) //Maps each guild to a slice of userIDs which need to be updated
for _, member := range matchingMembers {
guildUpdates[member.GuildID] = append(guildUpdates[member.GuildID], member.UserID)
}
//Update each of the members
for guild, members := range guildUpdates {
for _, member := range members {
err := b.unassignLiveRoles(member, guild)
if err != nil {
logrus.Errorf("Failed to unassign user id %v role because %v.", member, err)
}
}
}
//Delete alert posts
err = b.removeStreamAlertPosts(e.BroadcasterUID)
if err != nil {
logrus.Errorf("Failed to remove guild alert posts for twitch event %v due to error %v", e, err)
}
}
//HandleTwitchStreamOnline handles a streamonline event generated by the Twitch EventSub API
func (b *NiaBot) HandleTwitchStreamOnline(e *messages.StreamOnlineEvent) {
//Lookup which member(s) have this stream registered for them
connectionLookup := guildmodels.MemberConnections{
TwitchConnection: &guildmodels.TwitchStream{
TwitchUID: e.BroadcasterUID,
},
}
matchingMembers, err := b.DBConnection.GetMemberByConnection(connectionLookup, nil, nil)
if err != nil {
logrus.Errorf("Failed to fetch members for streamonline event %v due to error %v", e, err)
}
//Lookup any relevant role assignments in each members' guild
guildUpdates := make(map[string][]string) //Maps each guild to a slice of userIDs which need to be updated
for _, member := range matchingMembers {
guildUpdates[member.GuildID] = append(guildUpdates[member.GuildID], member.UserID)
}
//Update each of the members
for guild, members := range guildUpdates {
for _, member := range members {
err := b.assignLiveRoles(member, guild)
if err != nil {
logrus.Errorf("Failed to assign user id %v role because %v.", member, err)
}
}
//Make alert posts
err := b.makeGuildAlertPosts(e.BroadcasterUID, e.BroadcasterUserName, guild)
if err != nil {
logrus.Errorf("Failed to make guild alert posts for GID %v in response to twitch event %v due to error %v", guild, e, err)
}
}
}
//SetUserStreaming assigns the provided correct role and makes an announcement post (if needed) for the
//given stream in the given discord guild
func (b *NiaBot) SetUserStreaming(tuid, uid, gid string) error {
//Fetch stream data from twitch
t, errResp := b.getTwitchClient("", "")
if errResp != nil {
return fmt.Errorf("twitch support is not enabled on this bot instance")
}
stream, err := t.GetStream(tuid)
if err != nil {
logrus.Warnf("Failed to get stream data for twitch user %v due to error %v", tuid, err)
return err
} else if stream == nil {
logrus.Warnf("Attempted to SetUserStreaming on a streamer that is not currently live.")
return fmt.Errorf("twitch user %v does not appear to be live currently", tuid)
}
//Assign role
err = b.assignLiveRoles(uid, gid)
if err != nil {
logrus.Errorf("Failed to assign stream live role to user %v in guild %v due to error %v", uid, gid, err)
}
//Make alert post if needed
err = b.makeGuildAlertPosts(tuid, stream.UserName, gid)
if err != nil {
logrus.Errorf("Failed to make stream announcement post for stream %v in guild %v due to error %v", tuid, gid, err)
}
return nil
}
//assignLiveRoles assigns any roles specified to be assigned when a stream is live from the provided
//member
func (b *NiaBot) assignLiveRoles(uid, gid string) error {
//Get roles which will be assigned via twitch stream live alert
roles, err := b.DBConnection.LookupNowLiveRoles(gid)
if err != nil {
logrus.Errorf("Failed to lookup stream roles for guild %v due to error %v", gid, err)
return err
}
//Assign each of the roles we found
for _, role := range roles {
logrus.Infof("Adding role %v for user %v based as they have gone online on twitch.", role, uid)
err := b.DiscordSession().GuildMemberRoleAdd(gid, uid, role.RoleID)
if err != nil {
logrus.Errorf("Failed to assign user id %v role %v because %v.", uid, role.RoleID, err)
}
}
return nil
}
//assignLiveRoles removes any roles specified to be assigned when a stream is live from the provided
//member
func (b *NiaBot) unassignLiveRoles(uid, gid string) error {
//Get roles which have been assigned via twitch stream live alert
roles, err := b.DBConnection.LookupNowLiveRoles(gid)
if err != nil {
logrus.Errorf("Failed to lookup stream roles for guild %v due to error %v", gid, err)
return err
}
//Remove each of the roles we found
for _, role := range roles {
logrus.Infof("Removing role %v for user %v as they have gone offline on twitch.", role, uid)
err := b.DiscordSession().GuildMemberRoleRemove(gid, uid, role.RoleID)
if err != nil {
logrus.Errorf("Failed to assign user id %v role %v because %v.", uid, role.RoleID, err)
}
}
return nil
}
//removeStreamAlertPosts attempts to remove all alert posts created for the provided stream
func (b *NiaBot) removeStreamAlertPosts(twitchUID string) error {
stream, err := b.DBConnection.GetTwitchStream(twitchUID)
if err != nil {
logrus.Warnf("Failed to look up data on twitch stream %v in DB due to error %v", err)
return err
}
statusPosts := stream.DiscordStatusPosts
b.removeAlertPosts(statusPosts)
return nil
}
//removeAlertPosts attempts to remove all of the provided discord posts.
func (b *NiaBot) removeAlertPosts(posts []guildmodels.MessageRef) {
disc := b.DiscordSession()
for _, post := range posts {
err := disc.ChannelMessageDelete(post.ChannelID, post.MessageID)
if err != nil {
logrus.Errorf("Failed to remove bot post %v whilst removing stream alert posts, due to error %v.", post, err)
}
}
}
//makeGuildAlertPosts makes a post in the channel assigned for stream notifications in a guild, announcing the stream with UID provided.
//It then adds a message reference to the DB
func (b *NiaBot) makeGuildAlertPosts(twitchUID, twitchName, gid string) error {
guild, err := b.DBConnection.GetOrCreateGuild(gid)
if err != nil {
logrus.Warnf("Failed to look up guild details for gid %v when trying to make stream alert posts due to error %v", gid, err)
return err
}
stream, err := b.DBConnection.GetTwitchStream(twitchUID)
var statusPosts []guildmodels.MessageRef
if err == nil {
statusPosts = []guildmodels.MessageRef{}
} else {
statusPosts = stream.DiscordStatusPosts
}
//Return early if we have already made a status post in the provided discord guild
for _, post := range statusPosts {
if post.GuildID == gid {
logrus.Debugf("Skipping alert post creation on guild %v as it already has an alert post active", gid)
return nil
}
}
if guild.NotificationChannels != nil && guild.NotificationChannels.StreamNotificationsChannel != nil {
chans := []string{*guild.NotificationChannels.StreamNotificationsChannel}
msgIDs, err := b.postAlerts(twitchUID, twitchName, chans)
if err != nil {
logrus.Warnf("Failed to make stream alert posts for twitch stream %v in guild %v due to error %v", twitchName, gid, err)
}
for _, msgID := range msgIDs {
msgRef := guildmodels.MessageRef{
GuildID: gid,
ChannelID: chans[0],
MessageID: msgID,
}
err := b.DBConnection.AddDiscordStatusPost(twitchUID, &msgRef)
if err != nil {
logrus.Warnf("Failed to take note of stream alert posts due to error %v", err)
return err
}
}
}
return nil
}
//postAlerts makes posts accouncing the twitchUID stream has gone online in each of the provided channels. If successful,
//it returns messageIDs for each of the created posts
func (b *NiaBot) postAlerts(twitchUID, twitchName string, channels []string) ([]string, error) {
msgIDs := make([]string, len(channels))
t, res := b.getTwitchClient("", "")
if res != nil {
return nil, fmt.Errorf("twitch client features are not enabled, so twitch alert cannot be enabled")
}
stream, err := t.GetStream(twitchUID)
if err != nil {
logrus.Warnf("Failed to fetch stream details for twitch stream %v due to error %v", twitchUID, err)
return nil, err
} else if stream == nil {
logrus.Warnf("Twitch stream %v seems to have gone offline, skipping notification posts.", twitchUID)
return nil, fmt.Errorf("stream %v was offline", twitchUID)
}
broadcaster, err := t.GetBroadcasterDeets(twitchName)
if err != nil {
logrus.Warnf("Failed to fetch user details for twitch broadcaster %v due to error %v", twitchName, err)
return nil, err
}
thumb := strings.Replace(stream.ThumbnailURL, "{width}", "1920", 1)
thumb = strings.Replace(thumb, "{height}", "1080", 1)
notificationEmbed := discordgo.MessageEmbed{
Title: stream.Title,
Type: "rich",
Description: fmt.Sprintf("%v is streaming %v for %d users", twitchName, stream.GameName, stream.ViewerCount),
URL: fmt.Sprintf("https://twitch.tv/%v", twitchName),
Timestamp: stream.StartedAt.Format(time.RFC3339),
Color: twitchColourHex,
Image: &discordgo.MessageEmbedImage{URL: thumb},
Author: &discordgo.MessageEmbedAuthor{
URL: fmt.Sprintf("https://twitch.tv/%v", twitchName),
Name: twitchName,
IconURL: broadcaster.ProfileImageURL,
},
}
for _, tgtChan := range channels {
msg, err := b.DiscordSession().ChannelMessageSendEmbed(tgtChan, ¬ificationEmbed)
if err != nil {
logrus.Errorf("Failed to post notification message %v in channel %v due to error %v", notificationEmbed, tgtChan, err)
msgIDs = append(msgIDs, "")
} else {
msgIDs = append(msgIDs, msg.ID)
}
}
return msgIDs, nil
}