All checks were successful
release-tag / release-image (push) Successful in 2m8s
193 lines
5.8 KiB
Go
193 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
const richPresenceSettingKey = "rich_presence_config"
|
|
|
|
func defaultRichPresenceConfig() RichPresenceConfig {
|
|
return RichPresenceConfig{
|
|
Enabled: false,
|
|
Status: "online",
|
|
ActivityType: "watching",
|
|
Name: "SF Trading",
|
|
State: "Gemeinsame Session",
|
|
Details: "Bereit für Orga-Aktivitäten",
|
|
InviteButtonLabel: "Channel beitreten",
|
|
InvitePostTitle: "Gemeinsame Session",
|
|
InvitePostText: "Tritt dem Discord-Channel bei und schließe dich der Session an.",
|
|
InviteChannelID: "",
|
|
InvitePostChannelID: "",
|
|
}
|
|
}
|
|
|
|
func (s *Store) GetRichPresenceConfig() (RichPresenceConfig, error) {
|
|
cfg := defaultRichPresenceConfig()
|
|
raw, err := s.GetSetting(richPresenceSettingKey)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return cfg, nil
|
|
}
|
|
return cfg, err
|
|
}
|
|
if strings.TrimSpace(raw) == "" {
|
|
return cfg, nil
|
|
}
|
|
if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
|
|
return cfg, err
|
|
}
|
|
if cfg.Status == "" {
|
|
cfg.Status = "online"
|
|
}
|
|
if cfg.ActivityType == "" {
|
|
cfg.ActivityType = "watching"
|
|
}
|
|
if cfg.InviteButtonLabel == "" {
|
|
cfg.InviteButtonLabel = "Channel beitreten"
|
|
}
|
|
if cfg.InvitePostTitle == "" {
|
|
cfg.InvitePostTitle = "Gemeinsame Session"
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (s *Store) SaveRichPresenceConfig(cfg RichPresenceConfig) error {
|
|
b, err := json.Marshal(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.SetSetting(richPresenceSettingKey, string(b))
|
|
}
|
|
|
|
func (b *Bot) ApplySavedRichPresence() {
|
|
cfg, err := b.store.GetRichPresenceConfig()
|
|
if err != nil {
|
|
log.Println("rich presence config load failed:", err)
|
|
return
|
|
}
|
|
if err := b.SetRichPresence(cfg); err != nil {
|
|
log.Println("rich presence apply failed:", err)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) SetRichPresence(cfg RichPresenceConfig) error {
|
|
if b == nil || b.dg == nil {
|
|
return fmt.Errorf("bot session nicht verfügbar")
|
|
}
|
|
if !cfg.Enabled {
|
|
return b.dg.UpdateStatusComplex(discordgo.UpdateStatusData{Status: cfg.Status, Activities: []*discordgo.Activity{}})
|
|
}
|
|
activity := &discordgo.Activity{
|
|
Name: firstNonEmpty(cfg.Name, "SF Trading"),
|
|
Type: activityTypeFromString(cfg.ActivityType),
|
|
State: strings.TrimSpace(cfg.State),
|
|
Details: strings.TrimSpace(cfg.Details),
|
|
Assets: discordgo.Assets{
|
|
LargeImageID: strings.TrimSpace(cfg.LargeImage),
|
|
LargeText: strings.TrimSpace(cfg.LargeText),
|
|
SmallImageID: strings.TrimSpace(cfg.SmallImage),
|
|
SmallText: strings.TrimSpace(cfg.SmallText),
|
|
},
|
|
Timestamps: discordgo.TimeStamps{StartTimestamp: cfg.StartTimestamp, EndTimestamp: cfg.EndTimestamp},
|
|
}
|
|
// Discord beschränkt Bot-Presences offiziell auf name/state/type/url. Details, Assets und Timestamps
|
|
// werden mitgesendet, können clientseitig aber ignoriert werden. Der Session-Invite-Post enthält
|
|
// deshalb den zuverlässigen Button-Link.
|
|
return b.dg.UpdateStatusComplex(discordgo.UpdateStatusData{Status: firstNonEmpty(cfg.Status, "online"), Activities: []*discordgo.Activity{activity}})
|
|
}
|
|
|
|
func (b *Bot) SendRichPresenceInvite(cfg RichPresenceConfig) (*discordgo.Message, error) {
|
|
if strings.TrimSpace(cfg.InvitePostChannelID) == "" {
|
|
return nil, fmt.Errorf("Invite-Post-Channel fehlt")
|
|
}
|
|
link := channelLink(b.cfg.GuildID, cfg.InviteChannelID)
|
|
if link == "" {
|
|
return nil, fmt.Errorf("Invite-Channel fehlt")
|
|
}
|
|
buttonLabel := firstNonEmpty(cfg.InviteButtonLabel, "Channel beitreten")
|
|
emb := &discordgo.MessageEmbed{
|
|
Title: firstNonEmpty(cfg.InvitePostTitle, "Gemeinsame Session"),
|
|
Description: firstNonEmpty(cfg.InvitePostText, "Tritt dem Discord-Channel bei und schließe dich der Session an."),
|
|
Color: 0x38bdf8,
|
|
Timestamp: time.Now().Format(time.RFC3339),
|
|
Fields: []*discordgo.MessageEmbedField{
|
|
{Name: "Channel", Value: fmt.Sprintf("<#%s>", cfg.InviteChannelID), Inline: true},
|
|
{Name: "Status", Value: firstNonEmpty(cfg.State, cfg.Details, cfg.Name, "Session aktiv"), Inline: true},
|
|
},
|
|
}
|
|
msg, err := b.dg.ChannelMessageSendComplex(cfg.InvitePostChannelID, &discordgo.MessageSend{
|
|
Embeds: []*discordgo.MessageEmbed{emb},
|
|
Components: []discordgo.MessageComponent{discordgo.ActionsRow{Components: []discordgo.MessageComponent{discordgo.Button{Label: buttonLabel, Style: discordgo.LinkButton, URL: link}}}},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg.LastInviteChannelID = msg.ChannelID
|
|
cfg.LastInviteMessageID = msg.ID
|
|
_ = b.store.SaveRichPresenceConfig(cfg)
|
|
return msg, nil
|
|
}
|
|
|
|
func activityTypeFromString(v string) discordgo.ActivityType {
|
|
switch strings.ToLower(strings.TrimSpace(v)) {
|
|
case "playing", "game", "spielt":
|
|
return discordgo.ActivityTypeGame
|
|
case "streaming":
|
|
return discordgo.ActivityTypeStreaming
|
|
case "listening", "hört":
|
|
return discordgo.ActivityTypeListening
|
|
case "competing":
|
|
return discordgo.ActivityTypeCompeting
|
|
case "custom":
|
|
return discordgo.ActivityTypeCustom
|
|
default:
|
|
return discordgo.ActivityTypeWatching
|
|
}
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, v := range values {
|
|
if strings.TrimSpace(v) != "" {
|
|
return strings.TrimSpace(v)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func channelLink(guildID, channelID string) string {
|
|
guildID = strings.TrimSpace(guildID)
|
|
channelID = strings.TrimSpace(channelID)
|
|
if guildID == "" || channelID == "" {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("https://discord.com/channels/%s/%s", guildID, channelID)
|
|
}
|
|
|
|
func unixMillisFromDatetimeLocal(v string) int64 {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return 0
|
|
}
|
|
for _, layout := range []string{"2006-01-02T15:04", "2006-01-02T15:04:05", time.RFC3339} {
|
|
if t, err := time.ParseInLocation(layout, v, time.Local); err == nil {
|
|
return t.UnixMilli()
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func datetimeLocalFromUnixMillis(ms int64) string {
|
|
if ms <= 0 {
|
|
return ""
|
|
}
|
|
return time.UnixMilli(ms).Local().Format("2006-01-02T15:04")
|
|
}
|