Compentize Workload (#4)
This commit is contained in:
		
							
								
								
									
										154
									
								
								app/bot.go
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								app/bot.go
									
									
									
									
									
								
							@ -1,13 +1,10 @@
 | 
			
		||||
package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/ilyakaznacheev/cleanenv"
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
	"github.com/yeslayla/birdbot/core"
 | 
			
		||||
	"github.com/yeslayla/birdbot/discord"
 | 
			
		||||
	"github.com/yeslayla/birdbot/mastodon"
 | 
			
		||||
@ -17,34 +14,29 @@ var Version string
 | 
			
		||||
var Build string
 | 
			
		||||
 | 
			
		||||
type Bot struct {
 | 
			
		||||
	session  *discord.Discord
 | 
			
		||||
	mastodon *mastodon.Mastodon
 | 
			
		||||
	Session  *discord.Discord
 | 
			
		||||
	Mastodon *mastodon.Mastodon
 | 
			
		||||
 | 
			
		||||
	// Discord Objects
 | 
			
		||||
	guildID               string
 | 
			
		||||
	eventCategoryID       string
 | 
			
		||||
	archiveCategoryID     string
 | 
			
		||||
	notificationChannelID string
 | 
			
		||||
 | 
			
		||||
	onReadyHandlers  [](func() error)
 | 
			
		||||
	onNotifyHandlers [](func(string) error)
 | 
			
		||||
 | 
			
		||||
	onEventCreatedHandlers   [](func(common.Event) error)
 | 
			
		||||
	onEventDeletedHandlers   [](func(common.Event) error)
 | 
			
		||||
	onEventUpdatedHandlers   [](func(common.Event) error)
 | 
			
		||||
	onEventCompletedHandlers [](func(common.Event) error)
 | 
			
		||||
 | 
			
		||||
	gameModules []common.GameModule
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Initalize creates the discord session and registers handlers
 | 
			
		||||
func (app *Bot) Initialize(config_path string) error {
 | 
			
		||||
	log.Printf("Using config: %s", config_path)
 | 
			
		||||
	cfg := &core.Config{}
 | 
			
		||||
func (app *Bot) Initialize(cfg *core.Config) error {
 | 
			
		||||
 | 
			
		||||
	_, err := os.Stat(config_path)
 | 
			
		||||
	if errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
		log.Printf("Config file not found: '%s'", config_path)
 | 
			
		||||
		err := cleanenv.ReadEnv(cfg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		err := cleanenv.ReadConfig(config_path, cfg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// Load directly from config
 | 
			
		||||
	app.guildID = cfg.Discord.GuildID
 | 
			
		||||
	app.eventCategoryID = cfg.Discord.EventCategory
 | 
			
		||||
@ -58,30 +50,30 @@ func (app *Bot) Initialize(config_path string) error {
 | 
			
		||||
	if cfg.Mastodon.ClientID != "" && cfg.Mastodon.ClientSecret != "" &&
 | 
			
		||||
		cfg.Mastodon.Username != "" && cfg.Mastodon.Password != "" &&
 | 
			
		||||
		cfg.Mastodon.Server != "" {
 | 
			
		||||
		app.mastodon = mastodon.NewMastodon(cfg.Mastodon.Server, cfg.Mastodon.ClientID, cfg.Mastodon.ClientSecret,
 | 
			
		||||
		app.Mastodon = mastodon.NewMastodon(cfg.Mastodon.Server, cfg.Mastodon.ClientID, cfg.Mastodon.ClientSecret,
 | 
			
		||||
			cfg.Mastodon.Username, cfg.Mastodon.Password)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	app.session = discord.New(app.guildID, cfg.Discord.Token)
 | 
			
		||||
	app.Session = discord.New(app.guildID, cfg.Discord.Token)
 | 
			
		||||
 | 
			
		||||
	// Register Event Handlers
 | 
			
		||||
	app.session.OnReady(app.onReady)
 | 
			
		||||
	app.session.OnEventCreate(app.onEventCreate)
 | 
			
		||||
	app.session.OnEventDelete(app.onEventDelete)
 | 
			
		||||
	app.session.OnEventUpdate(app.onEventUpdate)
 | 
			
		||||
	app.Session.OnReady(app.onReady)
 | 
			
		||||
	app.Session.OnEventCreate(app.onEventCreate)
 | 
			
		||||
	app.Session.OnEventDelete(app.onEventDelete)
 | 
			
		||||
	app.Session.OnEventUpdate(app.onEventUpdate)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run opens the session with Discord until exit
 | 
			
		||||
func (app *Bot) Run() error {
 | 
			
		||||
	return app.session.Run()
 | 
			
		||||
	return app.Session.Run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Stop triggers a graceful shutdown of the app
 | 
			
		||||
func (app *Bot) Stop() {
 | 
			
		||||
	log.Print("Shuting down...")
 | 
			
		||||
	app.session.Stop()
 | 
			
		||||
	app.Session.Stop()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Notify sends a message to the notification channe;
 | 
			
		||||
@ -93,110 +85,76 @@ func (app *Bot) Notify(message string) {
 | 
			
		||||
 | 
			
		||||
	log.Print("Notification: ", message)
 | 
			
		||||
 | 
			
		||||
	channel := app.session.NewChannelFromID(app.notificationChannelID)
 | 
			
		||||
	channel := app.Session.NewChannelFromID(app.notificationChannelID)
 | 
			
		||||
	if channel == nil {
 | 
			
		||||
		log.Printf("Failed notification: channel was not found with ID '%v'", app.notificationChannelID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := app.session.SendMessage(channel, message)
 | 
			
		||||
	err := app.Session.SendMessage(channel, message)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print("Failed notification: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, handler := range app.onNotifyHandlers {
 | 
			
		||||
		if err := handler(message); err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *Bot) onReady(d *discord.Discord) {
 | 
			
		||||
	app.session.SetStatus(fmt.Sprintf("with fire! (%s)", Version))
 | 
			
		||||
	app.Session.SetStatus(fmt.Sprintf("with fire! (%s)", Version))
 | 
			
		||||
 | 
			
		||||
	for _, handler := range app.onReadyHandlers {
 | 
			
		||||
		if err := handler(); err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *Bot) onEventCreate(d *discord.Discord, event *core.Event) {
 | 
			
		||||
func (app *Bot) onEventCreate(d *discord.Discord, event common.Event) {
 | 
			
		||||
 | 
			
		||||
	log.Print("Event Created: '", event.Name, "':'", event.Location, "'")
 | 
			
		||||
 | 
			
		||||
	channel, err := app.session.NewChannelFromName(event.Channel().Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print("Failed to create channel for event: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if app.eventCategoryID != "" {
 | 
			
		||||
		err = app.session.MoveChannelToCategory(channel, app.eventCategoryID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("Failed to move channel to events category '%s': %v", channel.Name, err)
 | 
			
		||||
	for _, handler := range app.onEventCreatedHandlers {
 | 
			
		||||
		if err := handler(event); err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	eventURL := fmt.Sprintf("https://discordapp.com/events/%s/%s", app.guildID, event.ID)
 | 
			
		||||
	app.Notify(fmt.Sprintf("%s is organizing an event '%s': %s", event.Organizer.Mention(), event.Name, eventURL))
 | 
			
		||||
 | 
			
		||||
	if app.mastodon != nil {
 | 
			
		||||
		err = app.mastodon.Toot(fmt.Sprintf("A new event has been organized '%s': %s", event.Name, eventURL))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("Failed to send Mastodon Toot:", err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *Bot) onEventDelete(d *discord.Discord, event common.Event) {
 | 
			
		||||
 | 
			
		||||
	for _, handler := range app.onEventDeletedHandlers {
 | 
			
		||||
		if err := handler(event); err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func (app *Bot) onEventDelete(d *discord.Discord, event *core.Event) {
 | 
			
		||||
 | 
			
		||||
	_, err := app.session.DeleteChannel(event.Channel())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print("Failed to create channel for event: ", err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	app.Notify(fmt.Sprintf("%s cancelled '%s' on %s, %d!", event.Organizer.Mention(), event.Name, event.DateTime.Month().String(), event.DateTime.Day()))
 | 
			
		||||
func (app *Bot) onEventUpdate(d *discord.Discord, event common.Event) {
 | 
			
		||||
 | 
			
		||||
	if app.mastodon != nil {
 | 
			
		||||
		err = app.mastodon.Toot(fmt.Sprintf("'%s' cancelled on %s, %d!", event.Name, event.DateTime.Month().String(), event.DateTime.Day()))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("Failed to send Mastodon Toot:", err)
 | 
			
		||||
		}
 | 
			
		||||
	for _, handler := range app.onEventUpdatedHandlers {
 | 
			
		||||
		if err := handler(event); err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func (app *Bot) onEventUpdate(d *discord.Discord, event *core.Event) {
 | 
			
		||||
	// Pass event onwards
 | 
			
		||||
	if event.Completed {
 | 
			
		||||
		app.onEventComplete(d, event)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *Bot) onEventComplete(d *discord.Discord, event *core.Event) {
 | 
			
		||||
func (app *Bot) onEventComplete(d *discord.Discord, event common.Event) {
 | 
			
		||||
 | 
			
		||||
	channel := event.Channel()
 | 
			
		||||
 | 
			
		||||
	if app.archiveCategoryID != "" {
 | 
			
		||||
 | 
			
		||||
		if err := app.session.MoveChannelToCategory(channel, app.archiveCategoryID); err != nil {
 | 
			
		||||
			log.Print("Failed to move channel to archive category: ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := app.session.ArchiveChannel(channel); err != nil {
 | 
			
		||||
			log.Print("Failed to archive channel: ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Printf("Archived channel: '%s'", channel.Name)
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		// Delete Channel
 | 
			
		||||
		_, err := app.session.DeleteChannel(channel)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Print("Failed to delete channel: ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Printf("Deleted channel: '%s'", channel.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(strings.ToLower(event.Description), "recurring weekly") {
 | 
			
		||||
		startTime := event.DateTime.AddDate(0, 0, 7)
 | 
			
		||||
		finishTime := event.CompleteTime.AddDate(0, 0, 7)
 | 
			
		||||
		nextEvent := event
 | 
			
		||||
		nextEvent.DateTime = startTime
 | 
			
		||||
		nextEvent.CompleteTime = finishTime
 | 
			
		||||
 | 
			
		||||
		if err := app.session.CreateEvent(nextEvent); err != nil {
 | 
			
		||||
			log.Print("Failed to create recurring event: ", err)
 | 
			
		||||
	for _, handler := range app.onEventCompletedHandlers {
 | 
			
		||||
		if err := handler(event); err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewBot() *Bot {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								app/component_loader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								app/component_loader.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ComponentLoader struct {
 | 
			
		||||
	bot *Bot
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewComponentLoader(bot *Bot) *ComponentLoader {
 | 
			
		||||
	return &ComponentLoader{
 | 
			
		||||
		bot: bot,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) LoadComponent(component common.Component) {
 | 
			
		||||
	if err := component.Initialize(loader); err != nil {
 | 
			
		||||
		log.Print("Failed to load component: ", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) OnReady(handler func() error) error {
 | 
			
		||||
	loader.bot.onReadyHandlers = append(loader.bot.onReadyHandlers, handler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) OnNotify(handler func(string) error) error {
 | 
			
		||||
	loader.bot.onNotifyHandlers = append(loader.bot.onNotifyHandlers, handler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) OnEventCreate(handler func(common.Event) error) error {
 | 
			
		||||
	loader.bot.onEventCreatedHandlers = append(loader.bot.onEventCreatedHandlers, handler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (loader *ComponentLoader) OnEventDelete(handler func(common.Event) error) error {
 | 
			
		||||
	loader.bot.onEventDeletedHandlers = append(loader.bot.onEventDeletedHandlers, handler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (loader *ComponentLoader) OnEventUpdate(handler func(common.Event) error) error {
 | 
			
		||||
	loader.bot.onEventUpdatedHandlers = append(loader.bot.onEventUpdatedHandlers, handler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (loader *ComponentLoader) OnEventComplete(handler func(common.Event) error) error {
 | 
			
		||||
	loader.bot.onEventCompletedHandlers = append(loader.bot.onEventCompletedHandlers, handler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) RegisterGameModule(ID string, plugin common.GameModule) error {
 | 
			
		||||
	return fmt.Errorf("unimplemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) CreateEvent(event common.Event) error {
 | 
			
		||||
	return loader.bot.Session.CreateEvent(event)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (loader *ComponentLoader) Notify(message string) error {
 | 
			
		||||
	loader.bot.Notify(message)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								common/component.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								common/component.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
type Component interface {
 | 
			
		||||
	Initialize(birdbot ComponentManager) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComponentManager interface {
 | 
			
		||||
	OnReady(func() error) error
 | 
			
		||||
 | 
			
		||||
	OnNotify(func(string) error) error
 | 
			
		||||
 | 
			
		||||
	// Event events
 | 
			
		||||
	OnEventCreate(func(Event) error) error
 | 
			
		||||
	OnEventDelete(func(Event) error) error
 | 
			
		||||
	OnEventUpdate(func(Event) error) error
 | 
			
		||||
	OnEventComplete(func(Event) error) error
 | 
			
		||||
 | 
			
		||||
	RegisterGameModule(ID string, plugin GameModule) error
 | 
			
		||||
 | 
			
		||||
	CreateEvent(event Event) error
 | 
			
		||||
	Notify(message string) error
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								common/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								common/event.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Event struct {
 | 
			
		||||
	Name             string
 | 
			
		||||
	ID               string
 | 
			
		||||
	Location         string
 | 
			
		||||
	Completed        bool
 | 
			
		||||
	DateTime         time.Time
 | 
			
		||||
	CompleteDateTime time.Time
 | 
			
		||||
	Description      string
 | 
			
		||||
	ImageURL         string
 | 
			
		||||
 | 
			
		||||
	Organizer User
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								common/game_plugin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/game_plugin.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
type GameModule interface {
 | 
			
		||||
	SendMessage(user string, message string)
 | 
			
		||||
	RecieveMessage(user User, message string)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								common/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								common/user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
	ID          string
 | 
			
		||||
	AvatarURL   string
 | 
			
		||||
	DisplayName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DiscordMention generated a Discord mention string for the user
 | 
			
		||||
func (user *User) DiscordMention() string {
 | 
			
		||||
	if user == nil {
 | 
			
		||||
		return "<NULL>"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("<@%s>", user.ID)
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,43 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Channel struct {
 | 
			
		||||
	Name     string
 | 
			
		||||
	ID       string
 | 
			
		||||
	Verified bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenerateEventChannelName(eventName string, location string, dateTime time.Time) string {
 | 
			
		||||
	month := GetMonthPrefix(dateTime)
 | 
			
		||||
	day := dateTime.Day()
 | 
			
		||||
	city := GetCityFromLocation(location)
 | 
			
		||||
	year := dateTime.Year()
 | 
			
		||||
 | 
			
		||||
	channel := fmt.Sprint(month, "-", day, city, "-", eventName, "-", year)
 | 
			
		||||
	channel = strings.ReplaceAll(channel, " ", "-")
 | 
			
		||||
	channel = strings.ToLower(channel)
 | 
			
		||||
 | 
			
		||||
	re, _ := regexp.Compile(`[^\w\-]`)
 | 
			
		||||
	channel = re.ReplaceAllString(channel, "")
 | 
			
		||||
 | 
			
		||||
	return channel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateChannel returns a channel object associated with an event
 | 
			
		||||
func GenerateChannel(event common.Event) *Channel {
 | 
			
		||||
 | 
			
		||||
	channelName := GenerateEventChannelName(event.Name, event.Location, event.DateTime)
 | 
			
		||||
 | 
			
		||||
	return &Channel{
 | 
			
		||||
		Name:     channelName,
 | 
			
		||||
		Verified: false,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package core
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Discord  DiscordConfig  `yaml:"discord"`
 | 
			
		||||
	Mastodon MastodonConfig `yaml:"mastodon"`
 | 
			
		||||
	Features Features       `yaml:"features"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DiscordConfig struct {
 | 
			
		||||
@ -21,3 +22,10 @@ type MastodonConfig struct {
 | 
			
		||||
	ClientID     string `yaml:"client_id" env:"MASTODON_CLIENT_ID"`
 | 
			
		||||
	ClientSecret string `yaml:"client_secret" env:"MASTODON_CLIENT_SECRET"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Features struct {
 | 
			
		||||
	ManageEventChannels bool `yaml:"manage_event_channels" env:"BIRD_EVENT_CHANNELS" env-default:"true"`
 | 
			
		||||
	AnnounceEvents      bool `yaml:"announce_events" env:"BIRD_ANNOUNCE_EVENTS" env-default:"true"`
 | 
			
		||||
	ReccurringEvents    bool `yaml:"recurring_events" env:"BIRD_RECURRING_EVENTS" env-default:"true"`
 | 
			
		||||
	LoadGamePlugins     bool `yaml:"load_game_plugins" env:"BIRD_LOAD_GAME_PLUGINS" env-default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,99 +0,0 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const REMOTE_LOCATION string = "online"
 | 
			
		||||
 | 
			
		||||
type Event struct {
 | 
			
		||||
	Name         string
 | 
			
		||||
	ID           string
 | 
			
		||||
	Location     string
 | 
			
		||||
	Completed    bool
 | 
			
		||||
	DateTime     time.Time
 | 
			
		||||
	CompleteTime time.Time
 | 
			
		||||
	Description  string
 | 
			
		||||
	Image        string
 | 
			
		||||
 | 
			
		||||
	Organizer *User
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Channel returns a channel object associated with an event
 | 
			
		||||
func (event *Event) Channel() *Channel {
 | 
			
		||||
 | 
			
		||||
	month := event.GetMonthPrefix()
 | 
			
		||||
	day := event.DateTime.Day()
 | 
			
		||||
	city := event.GetCityFromLocation()
 | 
			
		||||
	year := event.DateTime.Year()
 | 
			
		||||
 | 
			
		||||
	channel := fmt.Sprint(month, "-", day, city, "-", event.Name, "-", year)
 | 
			
		||||
	channel = strings.ReplaceAll(channel, " ", "-")
 | 
			
		||||
	channel = strings.ToLower(channel)
 | 
			
		||||
 | 
			
		||||
	re, _ := regexp.Compile(`[^\w\-]`)
 | 
			
		||||
	channel = re.ReplaceAllString(channel, "")
 | 
			
		||||
 | 
			
		||||
	return &Channel{
 | 
			
		||||
		Name:     channel,
 | 
			
		||||
		Verified: false,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCityFromLocation returns the city name of an event's location
 | 
			
		||||
func (event *Event) GetCityFromLocation() string {
 | 
			
		||||
 | 
			
		||||
	if event.Location == REMOTE_LOCATION {
 | 
			
		||||
		return fmt.Sprint("-", REMOTE_LOCATION)
 | 
			
		||||
	}
 | 
			
		||||
	parts := strings.Split(event.Location, " ")
 | 
			
		||||
	index := -1
 | 
			
		||||
	loc := event.Location
 | 
			
		||||
 | 
			
		||||
	for i, v := range parts {
 | 
			
		||||
		part := strings.ToLower(v)
 | 
			
		||||
		if part == "mi" || part == "michigan" {
 | 
			
		||||
			index = i - 1
 | 
			
		||||
			if index < 0 {
 | 
			
		||||
				return ""
 | 
			
		||||
			}
 | 
			
		||||
			if index > 0 && parts[index] == "," {
 | 
			
		||||
				index -= 1
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if index > 1 && strings.Contains(parts[index-2], ",") {
 | 
			
		||||
				loc = fmt.Sprintf("%s-%s", parts[index-1], parts[index])
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			loc = parts[index]
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprint("-", loc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMonthPrefix returns a month in short form
 | 
			
		||||
func (event *Event) GetMonthPrefix() string {
 | 
			
		||||
	month := event.DateTime.Month()
 | 
			
		||||
	data := map[time.Month]string{
 | 
			
		||||
		time.January:   "jan",
 | 
			
		||||
		time.February:  "feb",
 | 
			
		||||
		time.March:     "march",
 | 
			
		||||
		time.April:     "april",
 | 
			
		||||
		time.May:       "may",
 | 
			
		||||
		time.June:      "june",
 | 
			
		||||
		time.July:      "july",
 | 
			
		||||
		time.August:    "aug",
 | 
			
		||||
		time.September: "sept",
 | 
			
		||||
		time.October:   "oct",
 | 
			
		||||
		time.November:  "nov",
 | 
			
		||||
		time.December:  "dec",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return data[month]
 | 
			
		||||
}
 | 
			
		||||
@ -1,62 +0,0 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetChannelName(t *testing.T) {
 | 
			
		||||
	assert := assert.New(t)
 | 
			
		||||
 | 
			
		||||
	// Test Valid Address
 | 
			
		||||
	event := Event{
 | 
			
		||||
		Name:     "Hello World",
 | 
			
		||||
		Location: "1234 Place Rd, Ann Arbor, MI 00000",
 | 
			
		||||
		DateTime: time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal("jan-5-ann-arbor-hello-world-2022", event.Channel().Name)
 | 
			
		||||
 | 
			
		||||
	// Test Unparsable Location
 | 
			
		||||
	// lmanley: Note it'd be nice to expand support for this
 | 
			
		||||
	event = Event{
 | 
			
		||||
		Name:     "Hello World",
 | 
			
		||||
		Location: "Michigan Theater, Ann Arbor",
 | 
			
		||||
		DateTime: time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal("jan-5-hello-world-2022", event.Channel().Name)
 | 
			
		||||
 | 
			
		||||
	// Test Short Location
 | 
			
		||||
	event = Event{
 | 
			
		||||
		Name:     "Hello World",
 | 
			
		||||
		Location: "Monroe, MI",
 | 
			
		||||
		DateTime: time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal("jan-5-monroe-hello-world-2022", event.Channel().Name)
 | 
			
		||||
 | 
			
		||||
	// Test Short Location
 | 
			
		||||
	event = Event{
 | 
			
		||||
		Name:     "Hello World",
 | 
			
		||||
		Location: "Monroe St, Monroe , MI",
 | 
			
		||||
		DateTime: time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal("jan-5-monroe-hello-world-2022", event.Channel().Name)
 | 
			
		||||
 | 
			
		||||
	// Test Remote Event
 | 
			
		||||
	event = Event{
 | 
			
		||||
		Name:     "Hello World",
 | 
			
		||||
		Location: REMOTE_LOCATION,
 | 
			
		||||
		DateTime: time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal("jan-5-online-hello-world-2022", event.Channel().Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMonthPrefix(t *testing.T) {
 | 
			
		||||
	assert := assert.New(t)
 | 
			
		||||
 | 
			
		||||
	event := Event{
 | 
			
		||||
		DateTime: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal("jan", event.GetMonthPrefix())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								core/location.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								core/location.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const REMOTE_LOCATION string = "online"
 | 
			
		||||
 | 
			
		||||
// GetCityFromLocation returns the city name of an event's location
 | 
			
		||||
func GetCityFromLocation(location string) string {
 | 
			
		||||
 | 
			
		||||
	if location == REMOTE_LOCATION {
 | 
			
		||||
		return fmt.Sprint("-", REMOTE_LOCATION)
 | 
			
		||||
	}
 | 
			
		||||
	parts := strings.Split(location, " ")
 | 
			
		||||
	index := -1
 | 
			
		||||
	loc := location
 | 
			
		||||
 | 
			
		||||
	for i, v := range parts {
 | 
			
		||||
		part := strings.ToLower(v)
 | 
			
		||||
		if part == "mi" || part == "michigan" {
 | 
			
		||||
			index = i - 1
 | 
			
		||||
			if index < 0 {
 | 
			
		||||
				return ""
 | 
			
		||||
			}
 | 
			
		||||
			if index > 0 && parts[index] == "," {
 | 
			
		||||
				index -= 1
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if index > 1 && strings.Contains(parts[index-2], ",") {
 | 
			
		||||
				loc = fmt.Sprintf("%s-%s", parts[index-1], parts[index])
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			loc = parts[index]
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprint("-", loc)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								core/time.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								core/time.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
// GetMonthPrefix returns a month in short form
 | 
			
		||||
func GetMonthPrefix(dateTime time.Time) string {
 | 
			
		||||
	month := dateTime.Month()
 | 
			
		||||
	data := map[time.Month]string{
 | 
			
		||||
		time.January:   "jan",
 | 
			
		||||
		time.February:  "feb",
 | 
			
		||||
		time.March:     "march",
 | 
			
		||||
		time.April:     "april",
 | 
			
		||||
		time.May:       "may",
 | 
			
		||||
		time.June:      "june",
 | 
			
		||||
		time.July:      "july",
 | 
			
		||||
		time.August:    "aug",
 | 
			
		||||
		time.September: "sept",
 | 
			
		||||
		time.October:   "oct",
 | 
			
		||||
		time.November:  "nov",
 | 
			
		||||
		time.December:  "dec",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return data[month]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								core/user.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								core/user.go
									
									
									
									
									
								
							@ -1,16 +0,0 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
	ID string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mention generated a Discord mention string for the user
 | 
			
		||||
func (user *User) Mention() string {
 | 
			
		||||
	if user == nil {
 | 
			
		||||
		return "<NULL>"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("<@%s>", user.ID)
 | 
			
		||||
}
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUserMention(t *testing.T) {
 | 
			
		||||
	assert := assert.New(t)
 | 
			
		||||
 | 
			
		||||
	// Create user object
 | 
			
		||||
	user := &User{
 | 
			
		||||
		ID: "sample_id",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal("<@sample_id>", user.Mention())
 | 
			
		||||
 | 
			
		||||
	// Test null user
 | 
			
		||||
	var nullUser *User = nil
 | 
			
		||||
	assert.NotEmpty(nullUser.Mention())
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -8,7 +8,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
	"github.com/yeslayla/birdbot/core"
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Discord struct {
 | 
			
		||||
@ -64,7 +64,7 @@ func (discord *Discord) OnReady(handler func(*Discord)) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnEventCreate registers a handler when a guild scheduled event is created
 | 
			
		||||
func (discord *Discord) OnEventCreate(handler func(*Discord, *core.Event)) {
 | 
			
		||||
func (discord *Discord) OnEventCreate(handler func(*Discord, common.Event)) {
 | 
			
		||||
	discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.GuildScheduledEventCreate) {
 | 
			
		||||
		if r.GuildID != discord.guildID {
 | 
			
		||||
			return
 | 
			
		||||
@ -75,7 +75,7 @@ func (discord *Discord) OnEventCreate(handler func(*Discord, *core.Event)) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnEventDelete registers a handler when a guild scheduled event is deleted
 | 
			
		||||
func (discord *Discord) OnEventDelete(handler func(*Discord, *core.Event)) {
 | 
			
		||||
func (discord *Discord) OnEventDelete(handler func(*Discord, common.Event)) {
 | 
			
		||||
	discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.GuildScheduledEventDelete) {
 | 
			
		||||
		if r.GuildID != discord.guildID {
 | 
			
		||||
			return
 | 
			
		||||
@ -86,7 +86,7 @@ func (discord *Discord) OnEventDelete(handler func(*Discord, *core.Event)) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnEventUpdate registers a handler when a guild scheduled event is updated
 | 
			
		||||
func (discord *Discord) OnEventUpdate(handler func(*Discord, *core.Event)) {
 | 
			
		||||
func (discord *Discord) OnEventUpdate(handler func(*Discord, common.Event)) {
 | 
			
		||||
	discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.GuildScheduledEventUpdate) {
 | 
			
		||||
		if r.GuildID != discord.guildID {
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
@ -4,27 +4,28 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
	"github.com/yeslayla/birdbot/core"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewEvent converts a discordgo.GuildScheduledEvent to birdbot event
 | 
			
		||||
func NewEvent(guildEvent *discordgo.GuildScheduledEvent) *core.Event {
 | 
			
		||||
	event := &core.Event{
 | 
			
		||||
func NewEvent(guildEvent *discordgo.GuildScheduledEvent) common.Event {
 | 
			
		||||
	event := common.Event{
 | 
			
		||||
		Name:        guildEvent.Name,
 | 
			
		||||
		Description: guildEvent.Description,
 | 
			
		||||
		ID:          guildEvent.ID,
 | 
			
		||||
		Organizer: &core.User{
 | 
			
		||||
		Organizer: common.User{
 | 
			
		||||
			ID: guildEvent.CreatorID,
 | 
			
		||||
		},
 | 
			
		||||
		DateTime: guildEvent.ScheduledStartTime,
 | 
			
		||||
		Image:    guildEvent.Image,
 | 
			
		||||
		ImageURL: guildEvent.Image,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if guildEvent.ScheduledEndTime != nil {
 | 
			
		||||
		event.CompleteTime = *guildEvent.ScheduledEndTime
 | 
			
		||||
		event.CompleteDateTime = *guildEvent.ScheduledEndTime
 | 
			
		||||
	} else {
 | 
			
		||||
		year, month, day := guildEvent.ScheduledStartTime.Date()
 | 
			
		||||
		event.CompleteTime = time.Date(year, month, day, 0, 0, 0, 0, guildEvent.ScheduledStartTime.Location())
 | 
			
		||||
		event.CompleteDateTime = time.Date(year, month, day, 0, 0, 0, 0, guildEvent.ScheduledStartTime.Location())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	event.Completed = guildEvent.Status == discordgo.GuildScheduledEventStatusCompleted
 | 
			
		||||
@ -38,14 +39,14 @@ func NewEvent(guildEvent *discordgo.GuildScheduledEvent) *core.Event {
 | 
			
		||||
	return event
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (discord *Discord) CreateEvent(event *core.Event) error {
 | 
			
		||||
func (discord *Discord) CreateEvent(event common.Event) error {
 | 
			
		||||
 | 
			
		||||
	params := &discordgo.GuildScheduledEventParams{
 | 
			
		||||
		Name:               event.Name,
 | 
			
		||||
		Description:        event.Description,
 | 
			
		||||
		ScheduledStartTime: &event.DateTime,
 | 
			
		||||
		ScheduledEndTime:   &event.CompleteTime,
 | 
			
		||||
		Image:              event.Image,
 | 
			
		||||
		ScheduledEndTime:   &event.CompleteDateTime,
 | 
			
		||||
		Image:              event.ImageURL,
 | 
			
		||||
		EntityType:         discordgo.GuildScheduledEventEntityTypeExternal,
 | 
			
		||||
		PrivacyLevel:       discordgo.GuildScheduledEventPrivacyLevelGuildOnly,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -4,17 +4,19 @@ import (
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
	"github.com/yeslayla/birdbot/core"
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewUser creates a new user object from a discordgo.User object
 | 
			
		||||
func NewUser(user *discordgo.User) *core.User {
 | 
			
		||||
func NewUser(user *discordgo.User) common.User {
 | 
			
		||||
	if user == nil {
 | 
			
		||||
		log.Print("Cannot user object, user is nil!")
 | 
			
		||||
		return nil
 | 
			
		||||
		return common.User{
 | 
			
		||||
			ID: "-1",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &core.User{
 | 
			
		||||
	return common.User{
 | 
			
		||||
		ID: user.ID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										56
									
								
								events/announce_events.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								events/announce_events.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
package events
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
	"github.com/yeslayla/birdbot/mastodon"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AnnounceEventsComponent struct {
 | 
			
		||||
	bot      common.ComponentManager
 | 
			
		||||
	mastodon *mastodon.Mastodon
 | 
			
		||||
	guildID  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAnnounceEventsComponent(mastodon *mastodon.Mastodon, guildID string) *AnnounceEventsComponent {
 | 
			
		||||
	return &AnnounceEventsComponent{
 | 
			
		||||
		mastodon: mastodon,
 | 
			
		||||
		guildID:  guildID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *AnnounceEventsComponent) Initialize(birdbot common.ComponentManager) error {
 | 
			
		||||
	c.bot = birdbot
 | 
			
		||||
 | 
			
		||||
	_ = birdbot.OnEventCreate(c.OnEventCreate)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *AnnounceEventsComponent) OnEventCreate(e common.Event) error {
 | 
			
		||||
	eventURL := fmt.Sprintf("https://discordapp.com/events/%s/%s", c.guildID, e.ID)
 | 
			
		||||
	c.bot.Notify(fmt.Sprintf("%s is organizing an event '%s': %s", e.Organizer.DiscordMention(), e.Name, eventURL))
 | 
			
		||||
 | 
			
		||||
	if c.mastodon != nil {
 | 
			
		||||
		err := c.mastodon.Toot(fmt.Sprintf("A new event has been organized '%s': %s", e.Name, eventURL))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("Failed to send Mastodon Toot:", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *AnnounceEventsComponent) OnEventDelete(e common.Event) error {
 | 
			
		||||
	_ = c.bot.Notify(fmt.Sprintf("%s cancelled '%s' on %s, %d!", e.Organizer.DiscordMention(), e.Name, e.DateTime.Month().String(), e.DateTime.Day()))
 | 
			
		||||
 | 
			
		||||
	if c.mastodon != nil {
 | 
			
		||||
		err := c.mastodon.Toot(fmt.Sprintf("'%s' cancelled on %s, %d!", e.Name, e.DateTime.Month().String(), e.DateTime.Day()))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("Failed to send Mastodon Toot:", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								events/manage_event_channels.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								events/manage_event_channels.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
package events
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
	"github.com/yeslayla/birdbot/core"
 | 
			
		||||
	"github.com/yeslayla/birdbot/discord"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ManageEventChannelsComponent struct {
 | 
			
		||||
	session    *discord.Discord
 | 
			
		||||
	categoryID string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewManageEventChannelsComponent(categoryID string, session *discord.Discord) *ManageEventChannelsComponent {
 | 
			
		||||
	return &ManageEventChannelsComponent{
 | 
			
		||||
		session:    session,
 | 
			
		||||
		categoryID: categoryID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *ManageEventChannelsComponent) Initialize(birdbot common.ComponentManager) error {
 | 
			
		||||
	_ = birdbot.OnEventCreate(c.OnEventCreate)
 | 
			
		||||
	_ = birdbot.OnEventComplete(c.OnEventComplete)
 | 
			
		||||
	_ = birdbot.OnEventDelete(c.OnEventDelete)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *ManageEventChannelsComponent) OnEventCreate(e common.Event) error {
 | 
			
		||||
	channel, err := c.session.NewChannelFromName(core.GenerateChannel(e).Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print("Failed to create channel for event: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.categoryID != "" {
 | 
			
		||||
		err = c.session.MoveChannelToCategory(channel, c.categoryID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("Failed to move channel to events category '%s': %v", channel.Name, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *ManageEventChannelsComponent) OnEventDelete(e common.Event) error {
 | 
			
		||||
	_, err := c.session.DeleteChannel(core.GenerateChannel(e))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print("Failed to create channel for event: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *ManageEventChannelsComponent) OnEventComplete(e common.Event) error {
 | 
			
		||||
	channel := core.GenerateChannel(e)
 | 
			
		||||
 | 
			
		||||
	if c.categoryID != "" {
 | 
			
		||||
 | 
			
		||||
		if err := c.session.MoveChannelToCategory(channel, c.categoryID); err != nil {
 | 
			
		||||
			log.Print("Failed to move channel to archive category: ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := c.session.ArchiveChannel(channel); err != nil {
 | 
			
		||||
			log.Print("Failed to archive channel: ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Printf("Archived channel: '%s'", channel.Name)
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		// Delete Channel
 | 
			
		||||
		_, err := c.session.DeleteChannel(channel)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Print("Failed to delete channel: ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Printf("Deleted channel: '%s'", channel.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								events/recurring_events.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								events/recurring_events.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
package events
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
	"github.com/yeslayla/birdbot/discord"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type RecurringEventsComponent struct {
 | 
			
		||||
	session *discord.Discord
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRecurringEventsComponent() *RecurringEventsComponent {
 | 
			
		||||
	return &RecurringEventsComponent{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *RecurringEventsComponent) Initialize(birdbot common.ComponentManager) error {
 | 
			
		||||
	_ = birdbot.OnEventComplete(c.OnEventComplete)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *RecurringEventsComponent) OnEventComplete(e common.Event) error {
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(strings.ToLower(e.Description), "recurring weekly") {
 | 
			
		||||
		startTime := e.DateTime.AddDate(0, 0, 7)
 | 
			
		||||
		finishTime := e.CompleteDateTime.AddDate(0, 0, 7)
 | 
			
		||||
		nextEvent := e
 | 
			
		||||
		nextEvent.DateTime = startTime
 | 
			
		||||
		nextEvent.CompleteDateTime = finishTime
 | 
			
		||||
 | 
			
		||||
		if err := c.session.CreateEvent(nextEvent); err != nil {
 | 
			
		||||
			log.Print("Failed to create recurring event: ", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								main.go
									
									
									
									
									
								
							@ -1,13 +1,17 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
	"github.com/ilyakaznacheev/cleanenv"
 | 
			
		||||
	"github.com/yeslayla/birdbot/app"
 | 
			
		||||
	"github.com/yeslayla/birdbot/core"
 | 
			
		||||
	"github.com/yeslayla/birdbot/events"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
@ -27,12 +31,41 @@ func main() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Printf("Using config: %s", config_file)
 | 
			
		||||
	cfg := &core.Config{}
 | 
			
		||||
 | 
			
		||||
	_, err := os.Stat(config_file)
 | 
			
		||||
	if errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
		log.Printf("Config file not found: '%s'", config_file)
 | 
			
		||||
		err := cleanenv.ReadEnv(cfg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		err := cleanenv.ReadConfig(config_file, cfg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bot := app.NewBot()
 | 
			
		||||
 | 
			
		||||
	if err := bot.Initialize(config_file); err != nil {
 | 
			
		||||
	if err := bot.Initialize(cfg); err != nil {
 | 
			
		||||
		log.Fatal("Failed to initialize: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	loader := app.NewComponentLoader(bot)
 | 
			
		||||
 | 
			
		||||
	if cfg.Features.AnnounceEvents {
 | 
			
		||||
		loader.LoadComponent(events.NewAnnounceEventsComponent(bot.Mastodon, cfg.Discord.NotificationChannel))
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.Features.ManageEventChannels {
 | 
			
		||||
		loader.LoadComponent(events.NewManageEventChannelsComponent(cfg.Discord.EventCategory, bot.Session))
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.Features.ReccurringEvents {
 | 
			
		||||
		loader.LoadComponent(events.NewRecurringEventsComponent())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := bot.Run(); err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user