Cleanup
This commit is contained in:
		| @ -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{} | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| 		} | ||||
| 	} | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Event represents a calendar event | ||||
| type Event struct { | ||||
| 	Name             string | ||||
| 	ID               string | ||||
|  | ||||
| @ -2,6 +2,7 @@ package common | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // User represents a user within BirdBot | ||||
| type User struct { | ||||
| 	ID          string | ||||
| 	AvatarURL   string | ||||
|  | ||||
| @ -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 { | ||||
| @ -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 != "" { | ||||
| 
 | ||||
| @ -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) | ||||
| @ -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) | ||||
|  | ||||
|  | ||||
| @ -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 == "" { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										11
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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) | ||||
| 		} | ||||
|  | ||||
| @ -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{} | ||||
|  | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user