2021-07-12 16:04:31 +00:00
package main
import (
"context"
"fmt"
2021-07-12 17:08:10 +00:00
"strings"
2021-07-12 16:04:31 +00:00
"time"
"github.com/bwmarrin/discordgo"
"github.com/pkg/errors"
2021-07-22 22:54:11 +00:00
"github.com/robfig/cron/v3"
2021-07-12 16:04:31 +00:00
log "github.com/sirupsen/logrus"
)
2021-07-25 11:43:41 +00:00
/ *
* @ module schedule
* @ module_desc Posts stream schedule derived from Twitch schedule as embed in Discord channel
* /
2021-07-31 18:58:11 +00:00
const (
2021-08-07 15:00:49 +00:00
streamScheduleDefaultColor = 0x2ECC71
2021-07-31 18:58:11 +00:00
)
2021-07-25 11:43:41 +00:00
var (
2021-07-31 18:58:11 +00:00
defaultStreamScheduleEntries = ptrInt64 ( 5 ) //nolint: gomnd // This is already the "constant"
defaultStreamSchedulePastTime = ptrDuration ( 15 * time . Minute ) //nolint: gomnd // This is already the "constant"
2021-07-12 16:04:31 +00:00
)
2021-07-22 22:54:11 +00:00
func init ( ) {
RegisterModule ( "schedule" , func ( ) module { return & modStreamSchedule { } } )
2021-07-12 16:04:31 +00:00
}
2021-07-29 19:13:19 +00:00
type modStreamSchedule struct {
attrs moduleAttributeStore
discord * discordgo . Session
2021-08-07 14:16:14 +00:00
id string
2021-07-29 19:13:19 +00:00
}
2021-07-22 22:54:11 +00:00
2021-08-07 14:16:14 +00:00
func ( m modStreamSchedule ) ID ( ) string { return m . id }
func ( m * modStreamSchedule ) Initialize ( id string , crontab * cron . Cron , discord * discordgo . Session , attrs moduleAttributeStore ) error {
2021-07-22 22:54:11 +00:00
m . attrs = attrs
m . discord = discord
2021-08-07 14:16:14 +00:00
m . id = id
2021-07-22 22:54:11 +00:00
2021-07-25 11:43:41 +00:00
if err := attrs . Expect (
"discord_channel_id" ,
"embed_title" ,
"twitch_channel_id" ,
"twitch_client_id" ,
2021-08-20 23:25:19 +00:00
"twitch_client_secret" ,
2021-07-25 11:43:41 +00:00
) ; err != nil {
return errors . Wrap ( err , "validating attributes" )
}
// @attr cron optional string "*/10 * * * *" When to execute the schedule transfer
2021-07-22 22:54:11 +00:00
if _ , err := crontab . AddFunc ( attrs . MustString ( "cron" , ptrString ( "*/10 * * * *" ) ) , m . cronUpdateSchedule ) ; err != nil {
2021-07-25 11:43:41 +00:00
return errors . Wrap ( err , "adding cron function" )
2021-07-12 16:04:31 +00:00
}
2021-07-22 22:54:11 +00:00
return nil
2021-07-12 16:04:31 +00:00
}
2021-08-07 13:24:11 +00:00
func ( m modStreamSchedule ) Setup ( ) error { return nil }
2021-08-20 23:14:21 +00:00
//nolint:funlen // Seeing no sense to split for 5 lines
2021-07-22 22:54:11 +00:00
func ( m modStreamSchedule ) cronUpdateSchedule ( ) {
2021-07-29 19:13:19 +00:00
twitch := newTwitchAdapter (
// @attr twitch_client_id required string "" Twitch client ID the token was issued for
m . attrs . MustString ( "twitch_client_id" , nil ) ,
2021-08-20 23:25:19 +00:00
// @attr twitch_client_secret required string "" Secret for the Twitch app identified with twitch_client_id
m . attrs . MustString ( "twitch_client_secret" , nil ) ,
"" , // No User-Token used
2021-07-29 19:13:19 +00:00
)
2021-07-12 16:04:31 +00:00
2021-07-29 19:13:19 +00:00
data , err := twitch . GetChannelStreamSchedule (
context . Background ( ) ,
2021-07-25 11:43:41 +00:00
// @attr twitch_channel_id required string "" ID (not name) of the channel to fetch the schedule from
2021-07-29 19:13:19 +00:00
m . attrs . MustString ( "twitch_channel_id" , nil ) ,
2021-07-25 11:43:41 +00:00
// @attr schedule_past_time optional duration "15m" How long in the past should the schedule contain an entry
2021-07-29 19:13:19 +00:00
ptrTime ( time . Now ( ) . Add ( - m . attrs . MustDuration ( "schedule_past_time" , defaultStreamSchedulePastTime ) ) ) ,
)
if err != nil {
2021-07-12 16:04:31 +00:00
log . WithError ( err ) . Error ( "Unable to fetch stream schedule" )
return
}
2021-08-07 15:00:49 +00:00
// @attr discord_channel_id required string "" ID of the Discord channel to post the message to
channelID := m . attrs . MustString ( "discord_channel_id" , nil )
2021-07-12 16:04:31 +00:00
msgEmbed := & discordgo . MessageEmbed {
2021-08-07 21:45:34 +00:00
// @attr embed_color optional int64 "0x2ECC71" Integer / HEX representation of the color for the embed
2021-07-31 18:58:11 +00:00
Color : int ( m . attrs . MustInt64 ( "embed_color" , ptrInt64 ( streamScheduleDefaultColor ) ) ) ,
2021-07-25 11:43:41 +00:00
// @attr embed_description optional string "" Description for the embed block
2021-08-07 20:14:01 +00:00
Description : strings . TrimSpace ( m . attrs . MustString ( "embed_description" , ptrStringEmpty ) ) ,
2021-07-12 16:04:31 +00:00
Fields : [ ] * discordgo . MessageEmbedField { } ,
2021-08-07 20:14:01 +00:00
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 {
2021-08-07 17:26:12 +00:00
// @attr embed_thumbnail_url optional string "" Publically hosted image URL to use as thumbnail
2021-07-25 11:43:41 +00:00
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 ) ) ,
2021-08-07 20:14:01 +00:00
}
2021-07-12 16:04:31 +00:00
}
for _ , seg := range data . Data . Segments {
title := seg . Title
if seg . Category != nil && seg . Category . Name != seg . Title {
title = fmt . Sprintf ( "%s (%s)" , seg . Title , seg . Category . Name )
}
2021-07-17 12:03:17 +00:00
if seg . StartTime == nil || seg . CanceledUntil != nil {
2021-07-12 17:08:10 +00:00
continue
}
2021-07-12 16:04:31 +00:00
msgEmbed . Fields = append ( msgEmbed . Fields , & discordgo . MessageEmbedField {
2021-08-27 21:19:44 +00:00
Name : m . formatTime ( * seg . StartTime ) ,
2021-07-12 16:04:31 +00:00
Value : title ,
Inline : false ,
} )
2021-07-17 12:11:13 +00:00
2021-07-25 11:43:41 +00:00
// @attr schedule_entries optional int64 "5" How many schedule entries to add to the embed as fields
if len ( msgEmbed . Fields ) == int ( m . attrs . MustInt64 ( "schedule_entries" , defaultStreamScheduleEntries ) ) {
2021-07-17 12:11:13 +00:00
break
}
2021-07-12 16:04:31 +00:00
}
2021-07-12 17:08:10 +00:00
var managedMsg * discordgo . Message
2021-08-07 15:00:49 +00:00
if err = store . ReadWithLock ( m . id , func ( a moduleAttributeStore ) error {
mid , err := a . String ( "message_id" )
if err == errValueNotSet {
return nil
2021-07-12 16:04:31 +00:00
}
2021-08-07 15:00:49 +00:00
managedMsg , err = m . discord . ChannelMessage ( channelID , mid )
return errors . Wrap ( err , "fetching managed message" )
} ) ; err != nil {
log . WithError ( err ) . Error ( "Unable to fetch managed message for stream schedule" )
return
2021-07-12 16:04:31 +00:00
}
2021-07-12 17:08:10 +00:00
if managedMsg != nil {
oldEmbed := managedMsg . Embeds [ 0 ]
2021-08-07 17:27:20 +00:00
if isDiscordMessageEmbedEqual ( oldEmbed , msgEmbed ) {
2021-07-12 17:08:10 +00:00
log . Debug ( "Stream Schedule is up-to-date" )
return
}
2021-08-07 15:00:49 +00:00
_ , err = m . discord . ChannelMessageEditEmbed ( channelID , managedMsg . ID , msgEmbed )
2021-07-12 16:04:31 +00:00
} else {
2021-08-07 15:00:49 +00:00
managedMsg , err = m . discord . ChannelMessageSendEmbed ( channelID , msgEmbed )
2021-07-12 16:04:31 +00:00
}
if err != nil {
log . WithError ( err ) . Error ( "Unable to announce streamplan" )
return
}
2021-07-12 17:08:10 +00:00
2021-08-07 15:00:49 +00:00
if err = store . Set ( m . id , "message_id" , managedMsg . ID ) ; err != nil {
log . WithError ( err ) . Error ( "Unable to store managed message id" )
return
}
2021-07-12 17:08:10 +00:00
log . Info ( "Updated Stream Schedule" )
}
2021-08-27 21:19:44 +00:00
func ( m modStreamSchedule ) formatTime ( t time . Time ) string {
2021-08-27 21:26:29 +00:00
// @attr timezone optional string "UTC" Timezone to display the times in (e.g. `Europe/Berlin`)
2021-08-27 21:19:44 +00:00
tz , err := time . LoadLocation ( m . attrs . MustString ( "timezone" , ptrString ( "UTC" ) ) )
2021-07-12 17:08:10 +00:00
if err != nil {
2021-08-27 21:19:44 +00:00
log . WithError ( err ) . Fatal ( "Unable to load timezone" )
2021-07-12 17:08:10 +00:00
}
2021-08-27 21:19:44 +00:00
return localeStrftime (
t . In ( tz ) ,
2021-08-27 21:26:29 +00:00
// @attr time_format optional string "%b %d, %Y %I:%M %p" Time format in [limited strftime format](https://github.com/Luzifer/discord-community/blob/master/strftime.go) to use (e.g. `%a. %d.%m. %H:%M Uhr`)
2021-08-27 21:19:44 +00:00
m . attrs . MustString ( "time_format" , ptrString ( "%b %d, %Y %I:%M %p" ) ) ,
// @attr locale optional string "en_US" Locale to translate the date to ([supported locales](https://github.com/goodsign/monday/blob/24c0b92f25dca51152defe82cefc7f7fc1c92009/locale.go#L9-L49))
m . attrs . MustString ( "locale" , ptrString ( "en_US" ) ) ,
)
2021-07-12 17:08:10 +00:00
}