mirror of
https://github.com/Luzifer/discord-community.git
synced 2024-12-20 10:21:22 +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
|
||||
Color: int(m.attrs.MustInt64("embed_color", ptrInt64(streamScheduleDefaultColor))),
|
||||
// @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{},
|
||||
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
|
||||
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)),
|
||||
},
|
||||
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 {
|
||||
|
|
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 |
|
||||
| `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`
|
||||
|
||||
Posts stream schedule derived from Twitch schedule as embed in Discord channel
|
||||
|
|
Loading…
Reference in a new issue