Add customizable embed timestamps

This commit is contained in:
Elara 2023-12-05 13:28:47 -08:00
parent 20c6771e7b
commit dd550b10cf
11 changed files with 130 additions and 62 deletions

View File

@ -75,6 +75,7 @@ The eventlog listens for important events such as kicks, bans, role changes, etc
- `/eventlog channel` can be used by anyone with the `Manage Server` permission to set the channel for the event log
- `/eventlog ticket_channel` can be used by anyone with the `Manage Server` permission to set the channel in which ticket conversations logs will be sent
- `/eventlog time_format` can be used by anyone with the `Manage Server` permission to set the time format for embeds. You can either use `discord` for a timezone-agnostic timestamp or a [strftime string](https://github.com/lestrrat-go/strftime#supported-conversion-specifications), which will be in UTC.
### Reactions

View File

@ -48,7 +48,6 @@ func loadConfig() (*Config, error) {
}
fl.Close()
}
return cfg, env.ParseWithOptions(cfg, env.Options{Prefix: "OWOBOT_"})
}

2
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/bwmarrin/discordgo v0.27.1
github.com/caarlos0/env/v10 v10.0.0
github.com/jmoiron/sqlx v1.3.5
github.com/lestrrat-go/strftime v1.0.6
github.com/pelletier/go-toml/v2 v2.1.0
github.com/rivo/uniseg v0.4.4
github.com/valyala/fasttemplate v1.2.2
@ -22,6 +23,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mvdan/xurls v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect

7
go.sum
View File

@ -23,6 +23,10 @@ github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
@ -34,6 +38,8 @@ github.com/mvdan/xurls v1.1.0 h1:OpuDelGQ1R1ueQ6sSryzi6P+1RtBpfQHM8fJwlE45ww=
github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@ -43,6 +49,7 @@ github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=

View File

@ -32,6 +32,7 @@ type Guild struct {
TicketCategoryID string `db:"ticket_category_id"`
VettingReqChanID string `db:"vetting_req_chan_id"`
VettingRoleID string `db:"vetting_role_id"`
TimeFormat string `db:"time_format"`
}
func AllGuilds() ([]Guild, error) {
@ -86,6 +87,11 @@ func SetVettingRoleID(guildID, roleID string) error {
return err
}
func SetTimeFormat(guildID, timeFmt string) error {
_, err := db.Exec("UPDATE guilds SET time_format = ? WHERE id = ?", timeFmt, guildID)
return err
}
func IsVettingMsg(msgID string) (bool, error) {
var out bool
err := db.QueryRow("SELECT 1 FROM guild WHERE vetting_msg_id = ?", msgID).Scan(&out)

View File

@ -12,7 +12,8 @@ CREATE TABLE guilds (
ticket_log_chan_id TEXT NOT NULL DEFAULT '',
ticket_category_id TEXT NOT NULL DEFAULT '',
vetting_req_chan_id TEXT NOT NULL DEFAULT '',
vetting_role_id TEXT NOT NULL DEFAULT ''
vetting_role_id TEXT NOT NULL DEFAULT '',
time_format TEXT NOT NULL DEFAULT 'discord'
);
/* tickets holds all open tickets */

View File

@ -21,7 +21,6 @@ package eventlog
import (
"fmt"
"io"
"time"
"github.com/bwmarrin/discordgo"
"go.elara.ws/owobot/internal/db"
@ -63,6 +62,19 @@ func Init(s *discordgo.Session) error {
},
},
},
{
Name: "time_format",
Description: "Set the time format for embeds",
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "format",
Description: "The time format to use",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
},
},
},
})
@ -76,6 +88,8 @@ func eventlogCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
return channelCmd(s, i)
case "ticket_channel":
return ticketChannelCmd(s, i)
case "time_format":
return timeFormatCmd(s, i)
default:
return fmt.Errorf("unknown eventlog subcommand: %s", name)
}
@ -107,6 +121,19 @@ func ticketChannelCmd(s *discordgo.Session, i *discordgo.InteractionCreate) erro
return util.RespondEphemeral(s, i.Interaction, fmt.Sprintf("Successfully set ticket log channel to <#%s>!", c.ID))
}
func timeFormatCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
// Get the subcommand options
args := i.ApplicationCommandData().Options[0].Options
timeFmt := args[0].StringValue()
err := db.SetTimeFormat(i.GuildID, timeFmt)
if err != nil {
return err
}
return util.RespondEphemeral(s, i.Interaction, "Successfully set the time format!")
}
type Entry struct {
Title string
Description string
@ -127,9 +154,6 @@ func Log(s *discordgo.Session, guildID string, e Entry) error {
embed := &discordgo.MessageEmbed{
Title: e.Title,
Description: e.Description,
Footer: &discordgo.MessageEmbedFooter{
Text: util.FormatJucheTime(time.Now()),
},
}
if e.Author != nil {
@ -143,6 +167,8 @@ func Log(s *discordgo.Session, guildID string, e Entry) error {
embed.Image = &discordgo.MessageEmbedImage{URL: e.ImageURL}
}
AddTimeToEmbed(guild.TimeFormat, embed)
_, err = s.ChannelMessageSendEmbed(guild.LogChanID, embed)
return err
}

View File

@ -0,0 +1,38 @@
package eventlog
import (
"fmt"
"time"
"github.com/bwmarrin/discordgo"
"github.com/lestrrat-go/strftime"
)
// AddTimeToEmbed formats the current time using timeFmt and adds it to e.
// The timeFmt can either be "discord", "juche", or a strftime string.
func AddTimeToEmbed(timeFmt string, e *discordgo.MessageEmbed) *discordgo.MessageEmbed {
t := time.Now().In(time.UTC)
switch timeFmt {
case "discord":
e.Timestamp = t.Format(time.RFC3339)
case "juche":
e.Footer = &discordgo.MessageEmbedFooter{Text: formatJuche(t)}
default:
e.Footer = &discordgo.MessageEmbedFooter{Text: format(timeFmt, t)}
}
return e
}
// formatJuche formats the given time in Juche calendar format
func formatJuche(t time.Time) string {
return fmt.Sprintf("%02d:%02d %02d-%02d Juche %d", t.Hour(), t.Minute(), t.Day(), t.Month(), t.Year()-1911)
}
// format formats t using timeFmt
func format(timeFmt string, t time.Time) string {
timeStr, err := strftime.Format(timeFmt, t)
if err != nil {
return "ERROR: " + err.Error()
}
return timeStr
}

View File

@ -25,7 +25,6 @@ import (
"net/url"
"path"
"strings"
"time"
"mvdan.cc/xurls"
@ -33,6 +32,7 @@ import (
"go.elara.ws/logger/log"
"go.elara.ws/owobot/internal/db"
"go.elara.ws/owobot/internal/systems/commands"
"go.elara.ws/owobot/internal/systems/eventlog"
"go.elara.ws/owobot/internal/util"
)
@ -200,11 +200,10 @@ func onReaction(s *discordgo.Session, mra *discordgo.MessageReactionAdd) {
msg.ID,
),
Color: embedColor,
Footer: &discordgo.MessageEmbedFooter{
Text: util.FormatJucheTime(time.Now()),
},
}
eventlog.AddTimeToEmbed(guild.TimeFormat, embed)
if imageURL := getImageURL(msg); imageURL != "" {
// If the message has an image, add it to the embed
embed.Image = &discordgo.MessageEmbedImage{URL: imageURL}

View File

@ -24,7 +24,6 @@ import (
"fmt"
"slices"
"strings"
"time"
"github.com/bwmarrin/discordgo"
"go.elara.ws/logger/log"
@ -151,18 +150,19 @@ func onVettingRequest(s *discordgo.Session, i *discordgo.InteractionCreate) erro
return errors.New("you do not have the vetting role")
}
_, err = s.ChannelMessageSendComplex(guild.VettingReqChanID, &discordgo.MessageSend{
Embed: &discordgo.MessageEmbed{
Title: "Vetting Request",
Author: &discordgo.MessageEmbedAuthor{
Name: i.Member.User.Username,
IconURL: i.Member.User.AvatarURL(""),
},
Description: "Accept the vetting request to create a ticket, or reject it to kick the user.",
Footer: &discordgo.MessageEmbedFooter{
Text: util.FormatJucheTime(time.Now()),
},
embed := &discordgo.MessageEmbed{
Title: "Vetting Request",
Author: &discordgo.MessageEmbedAuthor{
Name: i.Member.User.Username,
IconURL: i.Member.User.AvatarURL(""),
},
Description: "Accept the vetting request to create a ticket, or reject it to kick the user.",
}
eventlog.AddTimeToEmbed(guild.TimeFormat, embed)
_, err = s.ChannelMessageSendComplex(guild.VettingReqChanID, &discordgo.MessageSend{
Embeds: []*discordgo.MessageEmbed{embed},
Components: []discordgo.MessageComponent{
discordgo.ActionsRow{Components: []discordgo.MessageComponent{
discordgo.Button{
@ -270,6 +270,11 @@ func onVettingResponse(s *discordgo.Session, i *discordgo.InteractionCreate) err
return nil
}
guild, err := db.GuildByID(i.GuildID)
if err != nil {
return err
}
executor := i.Member
member, err := cache.Member(s, i.GuildID, userID)
if err != nil {
@ -283,22 +288,21 @@ func onVettingResponse(s *discordgo.Session, i *discordgo.InteractionCreate) err
return err
}
embed := &discordgo.MessageEmbed{
Title: "Vetting Request Accepted!",
Description: fmt.Sprintf("This vetting request has been accepted and a vetting ticket has been created at <#%s>.\n\n**Accepted By:** <@%s>", channelID, executor.User.ID),
Author: &discordgo.MessageEmbedAuthor{
Name: member.User.Username,
IconURL: member.User.AvatarURL(""),
},
}
eventlog.AddTimeToEmbed(guild.TimeFormat, embed)
err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseUpdateMessage,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "Vetting Request Accepted!",
Description: fmt.Sprintf("This vetting request has been accepted and a vetting ticket has been created at <#%s>.\n\n**Accepted By:** <@%s>", channelID, executor.User.ID),
Author: &discordgo.MessageEmbedAuthor{
Name: member.User.Username,
IconURL: member.User.AvatarURL(""),
},
Footer: &discordgo.MessageEmbedFooter{
Text: util.FormatJucheTime(time.Now()),
},
},
},
Embeds: []*discordgo.MessageEmbed{embed},
},
})
if err != nil {
@ -310,22 +314,21 @@ func onVettingResponse(s *discordgo.Session, i *discordgo.InteractionCreate) err
return err
}
embed := &discordgo.MessageEmbed{
Title: "Vetting Request Rejected",
Description: fmt.Sprintf("This vetting request has been rejected and <@%s> has been kicked from the server.\n\n**Rejected By:** <@%s>", member.User.ID, executor.User.ID),
Author: &discordgo.MessageEmbedAuthor{
Name: member.User.Username,
IconURL: member.User.AvatarURL(""),
},
}
eventlog.AddTimeToEmbed(guild.TimeFormat, embed)
err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseUpdateMessage,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "Vetting Request Rejected",
Description: fmt.Sprintf("This vetting request has been rejected and <@%s> has been kicked from the server.\n\n**Rejected By:** <@%s>", member.User.ID, executor.User.ID),
Author: &discordgo.MessageEmbedAuthor{
Name: member.User.Username,
IconURL: member.User.AvatarURL(""),
},
Footer: &discordgo.MessageEmbedFooter{
Text: util.FormatJucheTime(time.Now()),
},
},
},
Embeds: []*discordgo.MessageEmbed{embed},
},
})
if err != nil {

View File

@ -19,9 +19,6 @@
package util
import (
"fmt"
"time"
"github.com/bwmarrin/discordgo"
"go.elara.ws/logger/log"
)
@ -33,17 +30,6 @@ func Pointer[T any](v T) *T {
return &v
}
// FormatJucheTime formats the given time in Juche calendar format,
// using pyongyang time if it's available, and otherwise UTC.
func FormatJucheTime(t time.Time) string {
tz, err := time.LoadLocation("Asia/Pyongyang")
if err != nil {
tz = time.UTC
}
t = t.In(tz)
return fmt.Sprintf("%02d:%02d %02d-%02d Juche %d", t.Hour(), t.Minute(), t.Day(), t.Month(), t.Year()-1911)
}
// RespondEphemeral responds to an interaction with an ephemeral message.
func RespondEphemeral(s *discordgo.Session, i *discordgo.Interaction, content string) error {
return s.InteractionRespond(i, &discordgo.InteractionResponse{