mirror of
https://github.com/Luzifer/discord-community.git
synced 2024-12-29 23:01:18 +00:00
Add reactionrole module. handle optional thumbnail in streamschedule
better Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
f83598f387
commit
e5e0da7375
3 changed files with 242 additions and 7 deletions
217
mod_reactionRole.go
Normal file
217
mod_reactionRole.go
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Luzifer/go_helpers/v2/env"
|
||||||
|
"github.com/Luzifer/go_helpers/v2/str"
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @module reactionrole
|
||||||
|
* @module_desc Creates a post with pre-set reactions and assigns roles on reaction
|
||||||
|
*/
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterModule("reactionrole", func() module { return &modReactionRole{} })
|
||||||
|
}
|
||||||
|
|
||||||
|
type modReactionRole struct {
|
||||||
|
attrs moduleAttributeStore
|
||||||
|
discord *discordgo.Session
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m modReactionRole) ID() string { return m.id }
|
||||||
|
|
||||||
|
func (m *modReactionRole) Initialize(id string, crontab *cron.Cron, discord *discordgo.Session, attrs moduleAttributeStore) error {
|
||||||
|
m.attrs = attrs
|
||||||
|
m.discord = discord
|
||||||
|
m.id = id
|
||||||
|
|
||||||
|
if err := attrs.Expect(
|
||||||
|
"discord_channel_id",
|
||||||
|
"embed_title",
|
||||||
|
"reaction_roles",
|
||||||
|
); err != nil {
|
||||||
|
return errors.Wrap(err, "validating attributes")
|
||||||
|
}
|
||||||
|
|
||||||
|
discord.AddHandler(m.handleMessageReactionAdd)
|
||||||
|
discord.AddHandler(m.handleMessageReactionRemove)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m modReactionRole) Setup() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// @attr discord_channel_id required string "" ID of the Discord channel to post the message to
|
||||||
|
channelID := m.attrs.MustString("discord_channel_id", nil)
|
||||||
|
|
||||||
|
msgEmbed := &discordgo.MessageEmbed{
|
||||||
|
// @attr embed_color optional int64 "0x2ECC71" Integer representation of the hex color for the embed
|
||||||
|
Color: int(m.attrs.MustInt64("embed_color", ptrInt64(streamScheduleDefaultColor))),
|
||||||
|
// @attr embed_description optional string "" Description for the embed block
|
||||||
|
Description: strings.TrimSpace(m.attrs.MustString("embed_description", ptrStringEmpty)),
|
||||||
|
Timestamp: time.Now().Format(time.RFC3339),
|
||||||
|
// @attr embed_title required string "" Title of the embed
|
||||||
|
Title: m.attrs.MustString("embed_title", nil),
|
||||||
|
Type: discordgo.EmbedTypeRich,
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.attrs.MustString("embed_thumbnail_url", ptrStringEmpty) != "" {
|
||||||
|
msgEmbed.Thumbnail = &discordgo.MessageEmbedThumbnail{
|
||||||
|
// @attr embed_thumbnail_url optional string "" Publically hosted image URL to use as thumbnail
|
||||||
|
URL: m.attrs.MustString("embed_thumbnail_url", ptrStringEmpty),
|
||||||
|
// @attr embed_thumbnail_width optional int64 "" Width of the thumbnail
|
||||||
|
Width: int(m.attrs.MustInt64("embed_thumbnail_width", ptrInt64Zero)),
|
||||||
|
// @attr embed_thumbnail_height optional int64 "" Height of the thumbnail
|
||||||
|
Height: int(m.attrs.MustInt64("embed_thumbnail_height", ptrInt64Zero)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reactionListRaw, err := m.attrs.StringSlice("reaction_roles")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting role list")
|
||||||
|
}
|
||||||
|
var reactionList []string
|
||||||
|
for _, r := range reactionListRaw {
|
||||||
|
reactionList = append(reactionList, strings.Split(r, "=")[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
var managedMsg *discordgo.Message
|
||||||
|
if err = store.ReadWithLock(m.id, func(a moduleAttributeStore) error {
|
||||||
|
mid, err := a.String("message_id")
|
||||||
|
if err == errValueNotSet {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
managedMsg, err = m.discord.ChannelMessage(channelID, mid)
|
||||||
|
return errors.Wrap(err, "fetching managed message")
|
||||||
|
}); err != nil && !strings.Contains(err.Error(), "404") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if managedMsg == nil {
|
||||||
|
managedMsg, err = m.discord.ChannelMessageSendEmbed(channelID, msgEmbed)
|
||||||
|
} else if !isDiscordMessageEmbedEqual(managedMsg.Embeds[0], msgEmbed) {
|
||||||
|
_, err = m.discord.ChannelMessageEditEmbed(channelID, managedMsg.ID, msgEmbed)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "updating / creating message")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = store.Set(m.id, "message_id", managedMsg.ID); err != nil {
|
||||||
|
return errors.Wrap(err, "storing managed message id")
|
||||||
|
}
|
||||||
|
|
||||||
|
var addedReactions []string
|
||||||
|
|
||||||
|
for _, r := range managedMsg.Reactions {
|
||||||
|
okName := str.StringInSlice(r.Emoji.Name, reactionList)
|
||||||
|
|
||||||
|
compiledName := fmt.Sprintf(":%s:%s", r.Emoji.Name, r.Emoji.ID)
|
||||||
|
okCode := str.StringInSlice(compiledName, reactionList)
|
||||||
|
|
||||||
|
if !okCode && !okName {
|
||||||
|
if err = m.discord.MessageReactionsRemoveEmoji(channelID, managedMsg.ID, r.Emoji.ID); err != nil {
|
||||||
|
return errors.Wrap(err, "removing reaction emoji")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addedReactions = append(addedReactions, compiledName, r.Emoji.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, emoji := range reactionList {
|
||||||
|
if !str.StringInSlice(emoji, addedReactions) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"emote": emoji,
|
||||||
|
"message": managedMsg.ID,
|
||||||
|
"module": m.id,
|
||||||
|
}).Trace("Adding emoji reaction")
|
||||||
|
if err = m.discord.MessageReactionAdd(channelID, managedMsg.ID, emoji); err != nil {
|
||||||
|
return errors.Wrap(err, "adding reaction emoji")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m modReactionRole) extractRoles() (map[string]string, error) {
|
||||||
|
// @attr reaction_roles required []string "" List of strings in format `emote=role-id`
|
||||||
|
list, err := m.attrs.StringSlice("reaction_roles")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "getting role list")
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.ListToMap(list), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m modReactionRole) handleMessageReaction(d *discordgo.Session, e *discordgo.MessageReaction, add bool) {
|
||||||
|
if e.UserID == m.discord.State.User.ID {
|
||||||
|
// Reaction was manipulated by the bot, ignore
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
messageID string
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = store.ReadWithLock(m.id, func(a moduleAttributeStore) error {
|
||||||
|
messageID, err = a.String("message_id")
|
||||||
|
return errors.Wrap(err, "reading message ID")
|
||||||
|
}); err != nil {
|
||||||
|
log.WithError(err).Error("Unable to get managed message ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if messageID == "" || e.MessageID != messageID {
|
||||||
|
// This is not our managed message (or we don't have one), we don't care
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, err := m.extractRoles()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Unable to extract role mapping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, check := range []string{e.Emoji.Name, fmt.Sprintf(":%s:%s", e.Emoji.Name, e.Emoji.ID)} {
|
||||||
|
role, ok := roles[check]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if add {
|
||||||
|
if err = m.discord.GuildMemberRoleAdd(config.GuildID, e.UserID, strings.Split(role, ":")[0]); err != nil {
|
||||||
|
log.WithError(err).Error("Unable to add role to user")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(role, ":set") {
|
||||||
|
if err = m.discord.GuildMemberRoleRemove(config.GuildID, e.UserID, strings.Split(role, ":")[0]); err != nil {
|
||||||
|
log.WithError(err).Error("Unable to remove role to user")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m modReactionRole) handleMessageReactionAdd(d *discordgo.Session, e *discordgo.MessageReactionAdd) {
|
||||||
|
m.handleMessageReaction(d, e.MessageReaction, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m modReactionRole) handleMessageReactionRemove(d *discordgo.Session, e *discordgo.MessageReactionRemove) {
|
||||||
|
m.handleMessageReaction(d, e.MessageReaction, false)
|
||||||
|
}
|
|
@ -91,20 +91,23 @@ func (m modStreamSchedule) cronUpdateSchedule() {
|
||||||
// @attr embed_color optional int64 "0x2ECC71" Integer representation of the hex color for the embed
|
// @attr embed_color optional int64 "0x2ECC71" Integer representation of the hex color for the embed
|
||||||
Color: int(m.attrs.MustInt64("embed_color", ptrInt64(streamScheduleDefaultColor))),
|
Color: int(m.attrs.MustInt64("embed_color", ptrInt64(streamScheduleDefaultColor))),
|
||||||
// @attr embed_description optional string "" Description for the embed block
|
// @attr embed_description optional string "" Description for the embed block
|
||||||
Description: m.attrs.MustString("embed_description", ptrStringEmpty),
|
Description: strings.TrimSpace(m.attrs.MustString("embed_description", ptrStringEmpty)),
|
||||||
Fields: []*discordgo.MessageEmbedField{},
|
Fields: []*discordgo.MessageEmbedField{},
|
||||||
Thumbnail: &discordgo.MessageEmbedThumbnail{
|
Timestamp: time.Now().Format(time.RFC3339),
|
||||||
|
// @attr embed_title required string "" Title of the embed
|
||||||
|
Title: m.attrs.MustString("embed_title", nil),
|
||||||
|
Type: discordgo.EmbedTypeRich,
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.attrs.MustString("embed_thumbnail_url", ptrStringEmpty) != "" {
|
||||||
|
msgEmbed.Thumbnail = &discordgo.MessageEmbedThumbnail{
|
||||||
// @attr embed_thumbnail_url optional string "" Publically hosted image URL to use as thumbnail
|
// @attr embed_thumbnail_url optional string "" Publically hosted image URL to use as thumbnail
|
||||||
URL: m.attrs.MustString("embed_thumbnail_url", ptrStringEmpty),
|
URL: m.attrs.MustString("embed_thumbnail_url", ptrStringEmpty),
|
||||||
// @attr embed_thumbnail_width optional int64 "" Width of the thumbnail
|
// @attr embed_thumbnail_width optional int64 "" Width of the thumbnail
|
||||||
Width: int(m.attrs.MustInt64("embed_thumbnail_width", ptrInt64Zero)),
|
Width: int(m.attrs.MustInt64("embed_thumbnail_width", ptrInt64Zero)),
|
||||||
// @attr embed_thumbnail_height optional int64 "" Height of the thumbnail
|
// @attr embed_thumbnail_height optional int64 "" Height of the thumbnail
|
||||||
Height: int(m.attrs.MustInt64("embed_thumbnail_height", ptrInt64Zero)),
|
Height: int(m.attrs.MustInt64("embed_thumbnail_height", ptrInt64Zero)),
|
||||||
},
|
}
|
||||||
Timestamp: time.Now().Format(time.RFC3339),
|
|
||||||
// @attr embed_title required string "" Title of the embed
|
|
||||||
Title: m.attrs.MustString("embed_title", nil),
|
|
||||||
Type: discordgo.EmbedTypeRich,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, seg := range data.Data.Segments {
|
for _, seg := range data.Data.Segments {
|
||||||
|
|
15
wiki/Home.md
15
wiki/Home.md
|
@ -106,6 +106,21 @@ Updates the presence status of the bot to display the next stream
|
||||||
| `cron` | | string | `* * * * *` | When to execute the module |
|
| `cron` | | string | `* * * * *` | When to execute the module |
|
||||||
| `schedule_past_time` | | duration | `15m` | How long in the past should the schedule contain an entry |
|
| `schedule_past_time` | | duration | `15m` | How long in the past should the schedule contain an entry |
|
||||||
|
|
||||||
|
## Type: `reactionrole`
|
||||||
|
|
||||||
|
Creates a post with pre-set reactions and assigns roles on reaction
|
||||||
|
|
||||||
|
| Attribute | Req. | Type | Default Value | Description |
|
||||||
|
| --------- | :--: | ---- | ------------- | ----------- |
|
||||||
|
| `discord_channel_id` | ✅ | string | | ID of the Discord channel to post the message to |
|
||||||
|
| `embed_title` | ✅ | string | | Title of the embed |
|
||||||
|
| `reaction_roles` | ✅ | []string | | List of strings in format `emote=role-id` |
|
||||||
|
| `embed_color` | | int64 | `0x2ECC71` | Integer representation of the hex color for the embed |
|
||||||
|
| `embed_description` | | string | | Description for the embed block |
|
||||||
|
| `embed_thumbnail_height` | | int64 | | Height of the thumbnail |
|
||||||
|
| `embed_thumbnail_url` | | string | | Publically hosted image URL to use as thumbnail |
|
||||||
|
| `embed_thumbnail_width` | | int64 | | Width of the thumbnail |
|
||||||
|
|
||||||
## Type: `schedule`
|
## Type: `schedule`
|
||||||
|
|
||||||
Posts stream schedule derived from Twitch schedule as embed in Discord channel
|
Posts stream schedule derived from Twitch schedule as embed in Discord channel
|
||||||
|
|
Loading…
Reference in a new issue