diff --git a/app/bot.go b/app/bot.go index 983c3b0..28eb839 100644 --- a/app/bot.go +++ b/app/bot.go @@ -157,6 +157,7 @@ func (app *Bot) onEventComplete(d *discord.Discord, event common.Event) { } +// NewBot creates a new bot instance func NewBot() *Bot { return &Bot{} } diff --git a/app/plugin_loader.go b/app/plugins.go similarity index 72% rename from app/plugin_loader.go rename to app/plugins.go index 4ad2d49..8193273 100644 --- a/app/plugin_loader.go +++ b/app/plugins.go @@ -8,25 +8,23 @@ import ( "github.com/yeslayla/birdbot/common" ) -type PluginLoader struct{} +// LoadPlugin loads a plugin and returns its component if successful +func LoadPlugin(pluginPath string) common.Component { -func NewPluginLoader() PluginLoader { - return PluginLoader{} -} - -func (loader PluginLoader) LoadPlugin(pluginPath string) common.Component { plug, err := plugin.Open(pluginPath) if err != nil { log.Printf("Failed to load plugin '%s': %s", pluginPath, err) return nil } + // Lookup component symbol sym, err := plug.Lookup("Component") if err != nil { log.Printf("Failed to load plugin '%s': failed to get Component: %s", pluginPath, err) return nil } + // Validate component type var component common.Component component, ok := sym.(common.Component) if !ok { @@ -36,7 +34,8 @@ func (loader PluginLoader) LoadPlugin(pluginPath string) common.Component { return component } -func (loader PluginLoader) LoadPlugins(directory string) []common.Component { +// LoadPlugins loads all plugins and componenets in a directory +func LoadPlugins(directory string) []common.Component { paths, err := os.ReadDir(directory) if err != nil { @@ -50,7 +49,7 @@ func (loader PluginLoader) LoadPlugins(directory string) []common.Component { continue } - if comp := loader.LoadPlugin(path.Name()); comp != nil { + if comp := LoadPlugin(path.Name()); comp != nil { components = append(components, comp) } } diff --git a/common/component.go b/common/component.go index 35e4c0c..0b4223e 100644 --- a/common/component.go +++ b/common/component.go @@ -4,6 +4,8 @@ type Component interface { Initialize(birdbot ComponentManager) error } +// ComponentManager is the primary way for a component to interact with BirdBot +// by listening to events and committing actions type ComponentManager interface { OnReady(func() error) error @@ -15,8 +17,9 @@ type ComponentManager interface { OnEventUpdate(func(Event) error) error OnEventComplete(func(Event) error) error - RegisterGameModule(ID string, plugin GameModule) error - + // Actions CreateEvent(event Event) error Notify(message string) error + + RegisterGameModule(ID string, plugin GameModule) error } diff --git a/common/event.go b/common/event.go index 502feca..6424ab4 100644 --- a/common/event.go +++ b/common/event.go @@ -4,6 +4,7 @@ import ( "time" ) +// Event represents a calendar event type Event struct { Name string ID string diff --git a/common/user.go b/common/user.go index a19bf06..498dfd7 100644 --- a/common/user.go +++ b/common/user.go @@ -2,6 +2,7 @@ package common import "fmt" +// User represents a user within BirdBot type User struct { ID string AvatarURL string diff --git a/events/announce_events.go b/components/announce_events.go similarity index 70% rename from events/announce_events.go rename to components/announce_events.go index 10a7648..387db71 100644 --- a/events/announce_events.go +++ b/components/announce_events.go @@ -1,4 +1,4 @@ -package events +package components import ( "fmt" @@ -7,20 +7,22 @@ import ( "github.com/yeslayla/birdbot/mastodon" ) -type AnnounceEventsComponent struct { +type announceEventsComponent struct { bot common.ComponentManager mastodon *mastodon.Mastodon guildID string } -func NewAnnounceEventsComponent(mastodon *mastodon.Mastodon, guildID string) *AnnounceEventsComponent { - return &AnnounceEventsComponent{ +// NewAnnounceEventsComponent creates a new component +func NewAnnounceEventsComponent(mastodon *mastodon.Mastodon, guildID string) common.Component { + return &announceEventsComponent{ mastodon: mastodon, guildID: guildID, } } -func (c *AnnounceEventsComponent) Initialize(birdbot common.ComponentManager) error { +// Initialize registers event listeners +func (c *announceEventsComponent) Initialize(birdbot common.ComponentManager) error { c.bot = birdbot _ = birdbot.OnEventCreate(c.OnEventCreate) @@ -29,10 +31,12 @@ func (c *AnnounceEventsComponent) Initialize(birdbot common.ComponentManager) er return nil } -func (c *AnnounceEventsComponent) OnEventCreate(e common.Event) error { +// OnEventCreate notifies about the event creation to given providers +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)) + // Toot an announcement if Mastodon is configured if c.mastodon != nil { err := c.mastodon.Toot(fmt.Sprintf("A new event has been organized '%s': %s", e.Name, eventURL)) if err != nil { @@ -43,7 +47,7 @@ func (c *AnnounceEventsComponent) OnEventCreate(e common.Event) error { return nil } -func (c *AnnounceEventsComponent) OnEventDelete(e common.Event) error { +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 { diff --git a/events/manage_event_channels.go b/components/manage_event_channels.go similarity index 63% rename from events/manage_event_channels.go rename to components/manage_event_channels.go index d431b41..710bdfe 100644 --- a/events/manage_event_channels.go +++ b/components/manage_event_channels.go @@ -1,4 +1,4 @@ -package events +package components import ( "log" @@ -8,21 +8,23 @@ import ( "github.com/yeslayla/birdbot/discord" ) -type ManageEventChannelsComponent struct { +type manageEventChannelsComponent struct { session *discord.Discord categoryID string archiveCategoryID string } -func NewManageEventChannelsComponent(categoryID string, archiveCategoryID string, session *discord.Discord) *ManageEventChannelsComponent { - return &ManageEventChannelsComponent{ +// NewManageEventChannelsComponent creates a new component +func NewManageEventChannelsComponent(categoryID string, archiveCategoryID string, session *discord.Discord) common.Component { + return &manageEventChannelsComponent{ session: session, categoryID: categoryID, archiveCategoryID: archiveCategoryID, } } -func (c *ManageEventChannelsComponent) Initialize(birdbot common.ComponentManager) error { +// Initialize registers event listeners +func (c *manageEventChannelsComponent) Initialize(birdbot common.ComponentManager) error { _ = birdbot.OnEventCreate(c.OnEventCreate) _ = birdbot.OnEventComplete(c.OnEventComplete) _ = birdbot.OnEventDelete(c.OnEventDelete) @@ -30,8 +32,9 @@ func (c *ManageEventChannelsComponent) Initialize(birdbot common.ComponentManage return nil } -func (c *ManageEventChannelsComponent) OnEventCreate(e common.Event) error { - channel, err := c.session.NewChannelFromName(core.GenerateChannel(e).Name) +// OnEventCreate creates a new channel for an event and moves it to a given category +func (c *manageEventChannelsComponent) OnEventCreate(e common.Event) error { + channel, err := c.session.NewChannelFromName(core.GenerateChannelFromEvent(e).Name) if err != nil { log.Print("Failed to create channel for event: ", err) } @@ -45,16 +48,19 @@ func (c *ManageEventChannelsComponent) OnEventCreate(e common.Event) error { return nil } -func (c *ManageEventChannelsComponent) OnEventDelete(e common.Event) error { - _, err := c.session.DeleteChannel(core.GenerateChannel(e)) +// OnEventDelete deletes the channel associated with the given event +func (c *manageEventChannelsComponent) OnEventDelete(e common.Event) error { + _, err := c.session.DeleteChannel(core.GenerateChannelFromEvent(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) +// OnEventComplete archives a given event channel if not given +// an archive category will delete the channel instead +func (c *manageEventChannelsComponent) OnEventComplete(e common.Event) error { + channel := core.GenerateChannelFromEvent(e) if c.archiveCategoryID != "" { diff --git a/events/recurring_events.go b/components/recurring_events.go similarity index 58% rename from events/recurring_events.go rename to components/recurring_events.go index 8fc44fc..cdfffea 100644 --- a/events/recurring_events.go +++ b/components/recurring_events.go @@ -1,4 +1,4 @@ -package events +package components import ( "log" @@ -8,21 +8,24 @@ import ( "github.com/yeslayla/birdbot/discord" ) -type RecurringEventsComponent struct { +type recurringEventsComponent struct { session *discord.Discord } -func NewRecurringEventsComponent() *RecurringEventsComponent { - return &RecurringEventsComponent{} +// NewRecurringEventsComponent creates a new component instance +func NewRecurringEventsComponent() common.Component { + return &recurringEventsComponent{} } -func (c *RecurringEventsComponent) Initialize(birdbot common.ComponentManager) error { +// Initialize registers event listeners +func (c *recurringEventsComponent) Initialize(birdbot common.ComponentManager) error { _ = birdbot.OnEventComplete(c.OnEventComplete) return nil } -func (c *RecurringEventsComponent) OnEventComplete(e common.Event) error { +// OnEventComplete checks for keywords before creating a new event +func (c *recurringEventsComponent) OnEventComplete(e common.Event) error { if strings.Contains(strings.ToLower(e.Description), "recurring weekly") { startTime := e.DateTime.AddDate(0, 0, 7) diff --git a/core/channel.go b/core/channel.go index ea7ee4e..7e8cb59 100644 --- a/core/channel.go +++ b/core/channel.go @@ -15,6 +15,7 @@ type Channel struct { Verified bool } +// GenerateEventChannelName deciphers a channel name from a given set of event data func GenerateEventChannelName(eventName string, location string, dateTime time.Time) string { month := GetMonthPrefix(dateTime) day := dateTime.Day() @@ -31,8 +32,8 @@ func GenerateEventChannelName(eventName string, location string, dateTime time.T return channel } -// GenerateChannel returns a channel object associated with an event -func GenerateChannel(event common.Event) *Channel { +// GenerateChannelFromEvent returns a channel object associated with an event +func GenerateChannelFromEvent(event common.Event) *Channel { channelName := GenerateEventChannelName(event.Name, event.Location, event.DateTime) diff --git a/core/configuration.go b/core/configuration.go index a98cedb..897fec7 100644 --- a/core/configuration.go +++ b/core/configuration.go @@ -2,12 +2,14 @@ package core import "strings" +// Config is used to modify the behavior of birdbot externally type Config struct { Discord DiscordConfig `yaml:"discord"` Mastodon MastodonConfig `yaml:"mastodon"` Features Features `yaml:"features"` } +// DiscordConfig contains discord specific configuration type DiscordConfig struct { Token string `yaml:"token" env:"DISCORD_TOKEN"` GuildID string `yaml:"guild_id" env:"DISCORD_GUILD_ID"` @@ -17,6 +19,7 @@ type DiscordConfig struct { NotificationChannel string `yaml:"notification_channel" env:"DISCORD_NOTIFICATION_CHANNEL"` } +// MastodonConfig contains mastodon specific configuration type MastodonConfig struct { Server string `yaml:"server" env:"MASTODON_SERVER"` Username string `yaml:"user" env:"MASTODON_USER"` @@ -25,6 +28,7 @@ type MastodonConfig struct { ClientSecret string `yaml:"client_secret" env:"MASTODON_CLIENT_SECRET"` } +// Features contains all features flags that can be used to modify functionality type Features struct { ManageEventChannels Feature `yaml:"manage_event_channels" env:"BIRD_EVENT_CHANNELS"` AnnounceEvents Feature `yaml:"announce_events" env:"BIRD_ANNOUNCE_EVENTS"` @@ -32,12 +36,16 @@ type Features struct { LoadGamePlugins Feature `yaml:"load_game_plugins" env:"BIRD_LOAD_GAME_PLUGINS"` } +// Feature is a boolean string used to toggle functionality type Feature string +// IsEnabled returns true when a feature is set to be true func (value Feature) IsEnabled() bool { return strings.ToLower(string(value)) == "true" } +// IsEnabled returns true when a feature is set to be true +// or if the feature flag is not set at all func (value Feature) IsEnabledByDefault() bool { v := strings.ToLower(string(value)) if v == "" { diff --git a/core/location.go b/core/location.go index 1c4f5e8..c2511e0 100644 --- a/core/location.go +++ b/core/location.go @@ -5,13 +5,14 @@ import ( "strings" ) -const REMOTE_LOCATION string = "online" +// RemoteLocation is the string used to identify a online event +const RemoteLocation 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) + if location == RemoteLocation { + return fmt.Sprint("-", RemoteLocation) } parts := strings.Split(location, " ") index := -1 diff --git a/discord/event.go b/discord/event.go index 76e4927..6cad111 100644 --- a/discord/event.go +++ b/discord/event.go @@ -31,7 +31,7 @@ func NewEvent(guildEvent *discordgo.GuildScheduledEvent) common.Event { event.Completed = guildEvent.Status == discordgo.GuildScheduledEventStatusCompleted if guildEvent.EntityType != discordgo.GuildScheduledEventEntityTypeExternal { - event.Location = core.REMOTE_LOCATION + event.Location = core.RemoteLocation } else { event.Location = guildEvent.EntityMetadata.Location } diff --git a/main.go b/main.go index 64bf43e..898fd0c 100644 --- a/main.go +++ b/main.go @@ -10,8 +10,8 @@ import ( "github.com/ilyakaznacheev/cleanenv" "github.com/yeslayla/birdbot/app" + "github.com/yeslayla/birdbot/components" "github.com/yeslayla/birdbot/core" - "github.com/yeslayla/birdbot/events" ) const PluginsDirectory = "./plugins" @@ -59,18 +59,17 @@ func main() { loader := app.NewComponentLoader(bot) if cfg.Features.AnnounceEvents.IsEnabledByDefault() { - loader.LoadComponent(events.NewAnnounceEventsComponent(bot.Mastodon, cfg.Discord.NotificationChannel)) + loader.LoadComponent(components.NewAnnounceEventsComponent(bot.Mastodon, cfg.Discord.NotificationChannel)) } if cfg.Features.ManageEventChannels.IsEnabledByDefault() { - loader.LoadComponent(events.NewManageEventChannelsComponent(cfg.Discord.EventCategory, cfg.Discord.ArchiveCategory, bot.Session)) + loader.LoadComponent(components.NewManageEventChannelsComponent(cfg.Discord.EventCategory, cfg.Discord.ArchiveCategory, bot.Session)) } if cfg.Features.ReccurringEvents.IsEnabledByDefault() { - loader.LoadComponent(events.NewRecurringEventsComponent()) + loader.LoadComponent(components.NewRecurringEventsComponent()) } if _, err := os.Stat(PluginsDirectory); !os.IsNotExist(err) { - pluginLoader := app.NewPluginLoader() - components := pluginLoader.LoadPlugins(PluginsDirectory) + components := app.LoadPlugins(PluginsDirectory) for _, comp := range components { loader.LoadComponent(comp) } diff --git a/mastodon/mastodon.go b/mastodon/mastodon.go index 0ca74e1..91abae5 100644 --- a/mastodon/mastodon.go +++ b/mastodon/mastodon.go @@ -11,6 +11,7 @@ type Mastodon struct { client *mastodon.Client } +// NewMastodon initializes a new Mastodon client func NewMastodon(server string, clientID string, clientSecret string, username string, password string) *Mastodon { m := &Mastodon{} diff --git a/mastodon/toot.go b/mastodon/toot.go index 84be582..e70bb25 100644 --- a/mastodon/toot.go +++ b/mastodon/toot.go @@ -6,6 +6,7 @@ import ( "github.com/mattn/go-mastodon" ) +// Toot publishes a toot on Mastodon func (m *Mastodon) Toot(message string) error { _, err := m.client.PostStatus(context.Background(), &mastodon.Toot{ Status: message,