Compare commits
22 Commits
v0.3.1
...
ee1bb762fb
Author | SHA1 | Date | |
---|---|---|---|
ee1bb762fb | |||
355b95e544 | |||
6d0c98f62b | |||
63edfbc268 | |||
7a01a77986 | |||
44d45b7394 | |||
ca9a183a52 | |||
6ceceba221 | |||
d52e3e1c3c | |||
c13c0225c3 | |||
6cbe4df36c | |||
5e4698495c | |||
73a63fbf4d | |||
b252d5e62e | |||
7b0c8351a8 | |||
204803cd0b | |||
a86bdc4a65 | |||
91a886a81e | |||
b98fc73ea1 | |||
aaeac5bbfe | |||
012bb4ebb0 | |||
3cba6f1881 |
5
.github/workflows/publish_release.yaml
vendored
5
.github/workflows/publish_release.yaml
vendored
@ -9,7 +9,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
name: Build & Publish
|
name: Build & Publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '>=1.19.0'
|
go-version: '1.21'
|
||||||
- name: Version
|
- name: Version
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
- name: Test
|
- name: Test
|
||||||
@ -33,6 +33,7 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
|
draft: false
|
||||||
files: |
|
files: |
|
||||||
build/birdbot
|
build/birdbot
|
||||||
sample_config.yaml
|
sample_config.yaml
|
||||||
|
2
.github/workflows/validate.yaml
vendored
2
.github/workflows/validate.yaml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '>=1.19.0'
|
go-version: '1.21'
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
FROM ubuntu:22.04
|
FROM golang:1.21-bullseye
|
||||||
|
|
||||||
|
RUN apt update -y
|
||||||
|
|
||||||
COPY build/birdbot /usr/bin/birdbot
|
COPY build/birdbot /usr/bin/birdbot
|
||||||
|
|
||||||
|
10
Makefile
10
Makefile
@ -16,7 +16,7 @@ go-full-build: go-clean go-get go-build
|
|||||||
go-build:
|
go-build:
|
||||||
@echo " > Building binary..."
|
@echo " > Building binary..."
|
||||||
@mkdir -p $(GOBIN)
|
@mkdir -p $(GOBIN)
|
||||||
@CGO_ENABLED=1 go build -ldflags "-X github.com/yeslayla/birdbot/app.Version=$(VERSION) -X github.com/yeslayla/birdbot/app.Build=$(BUILD_NUMBER)" -o $(GOBIN)/$(PROJECT_BIN) $(GOFILES)
|
@GOOS=linux CGO_ENABLED=1 go build -ldflags "-X github.com/yeslayla/birdbot/app.Version=$(VERSION) -X github.com/yeslayla/birdbot/app.Build=$(BUILD_NUMBER)" -o $(GOBIN)/$(PROJECT_BIN) $(GOFILES)
|
||||||
@chmod 755 $(GOBIN)/$(PROJECT_BIN)
|
@chmod 755 $(GOBIN)/$(PROJECT_BIN)
|
||||||
|
|
||||||
go-generate:
|
go-generate:
|
||||||
@ -24,10 +24,13 @@ go-generate:
|
|||||||
@go generate $(generate)
|
@go generate $(generate)
|
||||||
|
|
||||||
go-get:
|
go-get:
|
||||||
@go env -w GOPRIVATE=github.com/meteoritesolutions
|
|
||||||
@echo " > Checking if there is any missing dependencies..."
|
@echo " > Checking if there is any missing dependencies..."
|
||||||
@go get $(get)
|
@go get $(get)
|
||||||
|
|
||||||
|
go-get-upgrade:
|
||||||
|
@echo " > Updating dependencies..."
|
||||||
|
@go get $(get) -u
|
||||||
|
|
||||||
go-install:
|
go-install:
|
||||||
@echo " > Running go install..."
|
@echo " > Running go install..."
|
||||||
@go install $(GOFILES)
|
@go install $(GOFILES)
|
||||||
@ -56,6 +59,9 @@ docker-push: docker-build
|
|||||||
## install: Download and install dependencies
|
## install: Download and install dependencies
|
||||||
install: go-get
|
install: go-get
|
||||||
|
|
||||||
|
## upgrade: Update Go packages
|
||||||
|
upgrade: go-get-upgrade
|
||||||
|
|
||||||
# clean: Runs go clean
|
# clean: Runs go clean
|
||||||
clean: go-clean
|
clean: go-clean
|
||||||
|
|
||||||
|
15
ReadMe.md
15
ReadMe.md
@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
Bird Bot is a discord bot for managing and organizing events for a small discord community.
|
Bird Bot is a discord bot for managing and organizing events for a small discord community.
|
||||||
|
|
||||||
### Features
|
## Features
|
||||||
|
|
||||||
- Creating text channels for events
|
- Creating text channels for events
|
||||||
|
- Delete/archive text channels after events
|
||||||
- Notifying when events are created & cancelled
|
- Notifying when events are created & cancelled
|
||||||
- Delete text channels after events
|
|
||||||
- Archive text channels after events
|
|
||||||
- Create recurring weekly events
|
|
||||||
- Role selection
|
- Role selection
|
||||||
|
- Plugin support
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -20,6 +19,7 @@ To get up and running, install go and you can run `make run`!
|
|||||||
The container is expecting the config file to be located at `/etc/birdbot/birdbot.yaml`. The easily solution here is to mount the config with a volume.
|
The container is expecting the config file to be located at `/etc/birdbot/birdbot.yaml`. The easily solution here is to mount the config with a volume.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it -v `pwd`:/etc/birdbot yeslayla/birdbot:latest
|
docker run -it -v `pwd`:/etc/birdbot yeslayla/birdbot:latest
|
||||||
```
|
```
|
||||||
@ -28,3 +28,10 @@ In this example, your config is in the current directory and call `birdbot.yaml`
|
|||||||
|
|
||||||
### Persistant Data
|
### Persistant Data
|
||||||
|
|
||||||
|
The default location for container data is `/var/lib/birdbot/` so you can mount it like:
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -it -v `pwd`:/var/lib/birdbot/ yeslayla/birdbot:latest
|
||||||
|
```
|
||||||
|
74
app/bot.go
74
app/bot.go
@ -4,10 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
"github.com/yeslayla/birdbot/core"
|
"github.com/yeslayla/birdbot/core"
|
||||||
"github.com/yeslayla/birdbot/discord"
|
"github.com/yeslayla/birdbot/discord"
|
||||||
"github.com/yeslayla/birdbot/mastodon"
|
"github.com/yeslayla/birdbot/mastodon"
|
||||||
|
"github.com/yeslayla/birdbot/persistence"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string
|
var Version string
|
||||||
@ -17,6 +18,8 @@ type Bot struct {
|
|||||||
Session *discord.Discord
|
Session *discord.Discord
|
||||||
Mastodon *mastodon.Mastodon
|
Mastodon *mastodon.Mastodon
|
||||||
|
|
||||||
|
Database persistence.Database
|
||||||
|
|
||||||
// Discord Objects
|
// Discord Objects
|
||||||
guildID string
|
guildID string
|
||||||
eventCategoryID string
|
eventCategoryID string
|
||||||
@ -31,7 +34,9 @@ type Bot struct {
|
|||||||
onEventUpdatedHandlers [](func(common.Event) error)
|
onEventUpdatedHandlers [](func(common.Event) error)
|
||||||
onEventCompletedHandlers [](func(common.Event) error)
|
onEventCompletedHandlers [](func(common.Event) error)
|
||||||
|
|
||||||
gameModules []common.ChatSyncModule
|
chatLinks map[string][]string
|
||||||
|
chatHandlers map[string]common.ExternalChatModule
|
||||||
|
channelChats map[string][]common.ExternalChatModule
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initalize creates the discord session and registers handlers
|
// Initalize creates the discord session and registers handlers
|
||||||
@ -42,10 +47,14 @@ func (app *Bot) Initialize(cfg *core.Config) error {
|
|||||||
app.eventCategoryID = cfg.Discord.EventCategory
|
app.eventCategoryID = cfg.Discord.EventCategory
|
||||||
app.archiveCategoryID = cfg.Discord.ArchiveCategory
|
app.archiveCategoryID = cfg.Discord.ArchiveCategory
|
||||||
app.notificationChannelID = cfg.Discord.NotificationChannel
|
app.notificationChannelID = cfg.Discord.NotificationChannel
|
||||||
|
app.chatLinks = cfg.Discord.ChatLinks
|
||||||
|
|
||||||
if app.guildID == "" {
|
if app.guildID == "" {
|
||||||
return fmt.Errorf("discord Guild ID is not set")
|
return fmt.Errorf("discord Guild ID is not set")
|
||||||
}
|
}
|
||||||
|
if cfg.Discord.ApplicationID == "" {
|
||||||
|
return fmt.Errorf("discord Application ID is not set")
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Mastodon.ClientID != "" && cfg.Mastodon.ClientSecret != "" &&
|
if cfg.Mastodon.ClientID != "" && cfg.Mastodon.ClientSecret != "" &&
|
||||||
cfg.Mastodon.Username != "" && cfg.Mastodon.Password != "" &&
|
cfg.Mastodon.Username != "" && cfg.Mastodon.Password != "" &&
|
||||||
@ -54,7 +63,7 @@ func (app *Bot) Initialize(cfg *core.Config) error {
|
|||||||
cfg.Mastodon.Username, cfg.Mastodon.Password)
|
cfg.Mastodon.Username, cfg.Mastodon.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Session = discord.New(app.guildID, cfg.Discord.Token)
|
app.Session = discord.New(cfg.Discord.ApplicationID, app.guildID, cfg.Discord.Token, app.Database)
|
||||||
|
|
||||||
// Register Event Handlers
|
// Register Event Handlers
|
||||||
app.Session.OnReady(app.onReady)
|
app.Session.OnReady(app.onReady)
|
||||||
@ -62,16 +71,15 @@ func (app *Bot) Initialize(cfg *core.Config) error {
|
|||||||
app.Session.OnEventDelete(app.onEventDelete)
|
app.Session.OnEventDelete(app.onEventDelete)
|
||||||
app.Session.OnEventUpdate(app.onEventUpdate)
|
app.Session.OnEventUpdate(app.onEventUpdate)
|
||||||
|
|
||||||
btn := app.Session.NewButton("test", "Click Me")
|
|
||||||
btn.OnClick(func(user common.User) {
|
|
||||||
print("clicked")
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run opens the session with Discord until exit
|
// Run opens the session with Discord until exit
|
||||||
func (app *Bot) Run() error {
|
func (app *Bot) Run() error {
|
||||||
|
|
||||||
|
// Intialize submodules
|
||||||
|
app.prepareChat()
|
||||||
|
|
||||||
return app.Session.Run()
|
return app.Session.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +170,51 @@ func (app *Bot) onEventComplete(d *discord.Discord, event common.Event) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBot creates a new bot instance
|
func (app *Bot) onMessageRecieved(d *discord.Discord, channelID string, user common.User, message string) {
|
||||||
func NewBot() *Bot {
|
chats, ok := app.channelChats[channelID]
|
||||||
return &Bot{}
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chat := range chats {
|
||||||
|
chat.RecieveMessage(user, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Bot) prepareChat() {
|
||||||
|
|
||||||
|
// Associate channels with chat modules
|
||||||
|
for channelID, chatHandelerIDs := range app.chatLinks {
|
||||||
|
if _, ok := app.channelChats[channelID]; !ok {
|
||||||
|
app.channelChats[channelID] = []common.ExternalChatModule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chatHandlerID := range chatHandelerIDs {
|
||||||
|
if handler, ok := app.chatHandlers[chatHandlerID]; ok {
|
||||||
|
app.channelChats[channelID] = append(app.channelChats[channelID], handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize chat modules
|
||||||
|
for channelID, chats := range app.channelChats {
|
||||||
|
channel := app.Session.NewChannelFromID(channelID)
|
||||||
|
for _, chat := range chats {
|
||||||
|
app.InitalizeExternalChat(channel, chat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register listener if needed
|
||||||
|
if len(app.channelChats) > 0 {
|
||||||
|
app.Session.OnMessageRecieved(app.onMessageRecieved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBot creates a new bot instance
|
||||||
|
func NewBot(db persistence.Database) *Bot {
|
||||||
|
return &Bot{
|
||||||
|
Database: db,
|
||||||
|
channelChats: make(map[string][]common.ExternalChatModule),
|
||||||
|
chatHandlers: make(map[string]common.ExternalChatModule),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ComponentLoader struct {
|
type ComponentLoader struct {
|
||||||
bot *Bot
|
bot *Bot
|
||||||
|
configDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewComponentLoader(bot *Bot) *ComponentLoader {
|
func NewComponentLoader(bot *Bot, configDir string) *ComponentLoader {
|
||||||
return &ComponentLoader{
|
return &ComponentLoader{
|
||||||
bot: bot,
|
bot: bot,
|
||||||
|
configDir: configDir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +52,9 @@ func (loader *ComponentLoader) OnEventComplete(handler func(common.Event) error)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (loader *ComponentLoader) RegisterChatSyncModule(ID string, plugin common.ChatSyncModule) error {
|
func (loader *ComponentLoader) RegisterExternalChat(ID string, chat common.ExternalChatModule) error {
|
||||||
return fmt.Errorf("unimplemented")
|
loader.bot.chatHandlers[ID] = chat
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (loader *ComponentLoader) CreateEvent(event common.Event) error {
|
func (loader *ComponentLoader) CreateEvent(event common.Event) error {
|
||||||
@ -62,3 +65,11 @@ func (loader *ComponentLoader) Notify(message string) error {
|
|||||||
loader.bot.Notify(message)
|
loader.bot.Notify(message)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (loader *ComponentLoader) RegisterCommand(name string, config common.ChatCommandConfiguration, handler func(common.User, map[string]any) string) {
|
||||||
|
loader.bot.Session.RegisterCommand(name, config, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (loader *ComponentLoader) GetConfigPath(fileName string) string {
|
||||||
|
return filepath.Join(loader.configDir, fileName)
|
||||||
|
}
|
||||||
|
26
app/external_chat_manager.go
Normal file
26
app/external_chat_manager.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
|
"github.com/yeslayla/birdbot/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalChatManager struct {
|
||||||
|
chat common.ExternalChatModule
|
||||||
|
channel *core.Channel
|
||||||
|
bot *Bot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *ExternalChatManager) SendMessage(user string, message string) {
|
||||||
|
manager.bot.Session.WebhookSendMessage(manager.channel, user, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Bot) InitalizeExternalChat(channel *core.Channel, chat common.ExternalChatModule) {
|
||||||
|
manager := &ExternalChatManager{
|
||||||
|
channel: channel,
|
||||||
|
chat: chat,
|
||||||
|
bot: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.chat.Initialize(manager)
|
||||||
|
}
|
@ -3,9 +3,10 @@ package app
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"plugin"
|
"plugin"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadPlugin loads a plugin and returns its component if successful
|
// LoadPlugin loads a plugin and returns its component if successful
|
||||||
@ -49,7 +50,7 @@ func LoadPlugins(directory string) []common.Module {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if comp := LoadPlugin(path.Name()); comp != nil {
|
if comp := LoadPlugin(filepath.Join(directory, path.Name())); comp != nil {
|
||||||
components = append(components, comp)
|
components = append(components, comp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
type ChatSyncModule interface {
|
|
||||||
SendMessage(user string, message string)
|
|
||||||
RecieveMessage(user User, message string)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event represents a calendar event
|
|
||||||
type Event struct {
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
Location string
|
|
||||||
Completed bool
|
|
||||||
DateTime time.Time
|
|
||||||
CompleteDateTime time.Time
|
|
||||||
Description string
|
|
||||||
ImageURL string
|
|
||||||
|
|
||||||
Organizer User
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
type Module interface {
|
|
||||||
Initialize(birdbot ModuleManager) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModuleManager is the primary way for a module to interact with BirdBot
|
|
||||||
// by listening to events and committing actions
|
|
||||||
type ModuleManager 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
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
CreateEvent(event Event) error
|
|
||||||
Notify(message string) error
|
|
||||||
|
|
||||||
RegisterChatSyncModule(ID string, plugin ChatSyncModule) error
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// User represents a user within BirdBot
|
|
||||||
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)
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
@ -22,6 +22,10 @@ func GenerateEventChannelName(eventName string, location string, dateTime time.T
|
|||||||
city := GetCityFromLocation(location)
|
city := GetCityFromLocation(location)
|
||||||
year := dateTime.Year()
|
year := dateTime.Year()
|
||||||
|
|
||||||
|
// Remove special characters
|
||||||
|
eventName = regexp.MustCompile(`[^a-zA-Z0-9 ]+`).ReplaceAllString(eventName, "")
|
||||||
|
eventName = strings.Trim(eventName, " ")
|
||||||
|
|
||||||
channel := fmt.Sprint(month, "-", day, city, "-", eventName, "-", year)
|
channel := fmt.Sprint(month, "-", day, city, "-", eventName, "-", year)
|
||||||
channel = strings.ReplaceAll(channel, " ", "-")
|
channel = strings.ReplaceAll(channel, " ", "-")
|
||||||
channel = strings.ToLower(channel)
|
channel = strings.ToLower(channel)
|
||||||
|
32
core/channel_test.go
Normal file
32
core/channel_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateEventChannelName(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// Test Valid Address
|
||||||
|
channelName := GenerateEventChannelName("Hello World", "1234 Place Rd, Ann Arbor, MI 00000", time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC))
|
||||||
|
assert.Equal("jan-5-ann-arbor-hello-world-2022", channelName)
|
||||||
|
|
||||||
|
// Test Unparsable
|
||||||
|
// lmanley: Note it'd be nice to expand support for this
|
||||||
|
channelName = GenerateEventChannelName("Hello World", "Michigan Theater, Ann Arbor", time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC))
|
||||||
|
assert.Equal("jan-5-hello-world-2022", channelName)
|
||||||
|
|
||||||
|
// Test Short Location
|
||||||
|
channelName = GenerateEventChannelName("Hello World", "Monroe, MI", time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC))
|
||||||
|
assert.Equal("jan-5-monroe-hello-world-2022", channelName)
|
||||||
|
|
||||||
|
// Test Remote Event
|
||||||
|
channelName = GenerateEventChannelName("Hello World", RemoteLocation, time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC))
|
||||||
|
assert.Equal("jan-5-online-hello-world-2022", channelName)
|
||||||
|
|
||||||
|
channelName = GenerateEventChannelName("Hangout :)", "Quickly Livonia", time.Date(2022, time.January, 5, 0, 0, 0, 0, time.UTC))
|
||||||
|
assert.Equal("jan-5-quickly-livonia-hangout-2022", channelName)
|
||||||
|
}
|
@ -6,12 +6,15 @@ import "strings"
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Discord DiscordConfig `yaml:"discord"`
|
Discord DiscordConfig `yaml:"discord"`
|
||||||
Mastodon MastodonConfig `yaml:"mastodon"`
|
Mastodon MastodonConfig `yaml:"mastodon"`
|
||||||
|
Feedback Feedback `yaml:"feedback"`
|
||||||
|
StatusPortal StatusPortal `yaml:"status_portal"`
|
||||||
Features Features `yaml:"features"`
|
Features Features `yaml:"features"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscordConfig contains discord specific configuration
|
// DiscordConfig contains discord specific configuration
|
||||||
type DiscordConfig struct {
|
type DiscordConfig struct {
|
||||||
Token string `yaml:"token" env:"DISCORD_TOKEN"`
|
Token string `yaml:"token" env:"DISCORD_TOKEN"`
|
||||||
|
ApplicationID string `yaml:"application_id" env:"DISCORD_APPLICATION_ID"`
|
||||||
GuildID string `yaml:"guild_id" env:"DISCORD_GUILD_ID"`
|
GuildID string `yaml:"guild_id" env:"DISCORD_GUILD_ID"`
|
||||||
|
|
||||||
EventCategory string `yaml:"event_category" env:"DISCORD_EVENT_CATEGORY"`
|
EventCategory string `yaml:"event_category" env:"DISCORD_EVENT_CATEGORY"`
|
||||||
@ -19,6 +22,20 @@ type DiscordConfig struct {
|
|||||||
NotificationChannel string `yaml:"notification_channel" env:"DISCORD_NOTIFICATION_CHANNEL"`
|
NotificationChannel string `yaml:"notification_channel" env:"DISCORD_NOTIFICATION_CHANNEL"`
|
||||||
|
|
||||||
RoleSelections []RoleSelectionConfig `yaml:"role_selection"`
|
RoleSelections []RoleSelectionConfig `yaml:"role_selection"`
|
||||||
|
|
||||||
|
ChatLinks map[string][]string `yaml:"chat_links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Feedback struct {
|
||||||
|
WebhookURL string `yaml:"url" env:"BIRD_FEEDBACK_URL"`
|
||||||
|
PayloadType string `yaml:"type" env:"BIRD_FEEDBACK_TYPE"`
|
||||||
|
|
||||||
|
SuccessMessage string `yaml:"success_message"`
|
||||||
|
FailureMessage string `yaml:"failure_message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusPortal struct {
|
||||||
|
URL string `yaml:"url" env:"BIRD_STATUS_PORTAL_URL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoleSelectionConfig struct {
|
type RoleSelectionConfig struct {
|
||||||
@ -47,9 +64,11 @@ type MastodonConfig struct {
|
|||||||
type Features struct {
|
type Features struct {
|
||||||
ManageEventChannels Feature `yaml:"manage_event_channels" env:"BIRD_EVENT_CHANNELS"`
|
ManageEventChannels Feature `yaml:"manage_event_channels" env:"BIRD_EVENT_CHANNELS"`
|
||||||
AnnounceEvents Feature `yaml:"announce_events" env:"BIRD_ANNOUNCE_EVENTS"`
|
AnnounceEvents Feature `yaml:"announce_events" env:"BIRD_ANNOUNCE_EVENTS"`
|
||||||
ReccurringEvents Feature `yaml:"recurring_events" env:"BIRD_RECURRING_EVENTS"`
|
RecurringEvents Feature `yaml:"recurring_events" env:"BIRD_RECURRING_EVENTS"`
|
||||||
RoleSelection Feature `yaml:"role_selection" env:"BIRD_ROLE_SELECTION"`
|
RoleSelection Feature `yaml:"role_selection" env:"BIRD_ROLE_SELECTION"`
|
||||||
|
Feedback Feature `yaml:"feedback" env:"BIRD_FEEDBACK"`
|
||||||
LoadGamePlugins Feature `yaml:"load_game_plugins" env:"BIRD_LOAD_GAME_PLUGINS"`
|
LoadGamePlugins Feature `yaml:"load_game_plugins" env:"BIRD_LOAD_GAME_PLUGINS"`
|
||||||
|
StatusPortal Feature `yaml:"status_portal" env:"BIRD_STATUS_PORTAL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feature is a boolean string used to toggle functionality
|
// Feature is a boolean string used to toggle functionality
|
||||||
|
99
discord/command.go
Normal file
99
discord/command.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterCommand creates an new command that can be used to interact with bird bot
|
||||||
|
func (discord *Discord) RegisterCommand(name string, config common.ChatCommandConfiguration, handler func(common.User, map[string]any) string) {
|
||||||
|
command := &discordgo.ApplicationCommand{
|
||||||
|
Name: name,
|
||||||
|
Description: config.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert options to discordgo objects
|
||||||
|
command.Options = make([]*discordgo.ApplicationCommandOption, len(config.Options))
|
||||||
|
index := 0
|
||||||
|
for name, option := range config.Options {
|
||||||
|
command.Options[index] = &discordgo.ApplicationCommandOption{
|
||||||
|
Name: name,
|
||||||
|
Description: option.Description,
|
||||||
|
Required: option.Required,
|
||||||
|
Type: discordgo.ApplicationCommandOptionType(option.Type),
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register handler
|
||||||
|
discord.commandHandlers[name] = func(session *discordgo.Session, r *discordgo.InteractionCreate) {
|
||||||
|
if r.Interaction.Type != discordgo.InteractionApplicationCommand {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdOptions := r.ApplicationCommandData().Options
|
||||||
|
|
||||||
|
// Parse option types
|
||||||
|
optionsMap := make(map[string]any, len(cmdOptions))
|
||||||
|
for _, opt := range cmdOptions {
|
||||||
|
switch config.Options[opt.Name].Type {
|
||||||
|
case common.CommandTypeString:
|
||||||
|
optionsMap[opt.Name] = opt.StringValue()
|
||||||
|
case common.CommandTypeInt:
|
||||||
|
optionsMap[opt.Name] = opt.IntValue()
|
||||||
|
case common.CommandTypeBool:
|
||||||
|
optionsMap[opt.Name] = opt.BoolValue()
|
||||||
|
case common.CommandTypeFloat:
|
||||||
|
optionsMap[opt.Name] = opt.FloatValue()
|
||||||
|
default:
|
||||||
|
optionsMap[opt.Name] = opt.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.InteractionRespond(r.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
result := handler(NewUser(r.Member.User), optionsMap)
|
||||||
|
|
||||||
|
if result != "" {
|
||||||
|
// Handle response
|
||||||
|
responseData := &discordgo.WebhookParams{
|
||||||
|
Content: result,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.EphemeralResponse {
|
||||||
|
responseData.Flags = discordgo.MessageFlagsEphemeral
|
||||||
|
}
|
||||||
|
|
||||||
|
session.FollowupMessageCreate(r.Interaction, false, responseData)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
session.FollowupMessageCreate(r.Interaction, false, &discordgo.WebhookParams{
|
||||||
|
Content: "Command did not return a response!",
|
||||||
|
})
|
||||||
|
log.Printf("Command '%s' did not return a response: %v", name, optionsMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := discord.session.ApplicationCommandCreate(discord.applicationID, discord.guildID, command)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot create command '%s': %v", name, err)
|
||||||
|
}
|
||||||
|
discord.commands[name] = cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCommands deregisters all commands from the discord API
|
||||||
|
func (discord *Discord) ClearCommands() {
|
||||||
|
for _, v := range discord.commands {
|
||||||
|
err := discord.session.ApplicationCommandDelete(discord.session.State.User.ID, discord.guildID, v.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot delete command '%s': %v", v.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ package discord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Button struct {
|
type Button struct {
|
||||||
@ -24,6 +24,10 @@ func (discord *Discord) NewButton(id string, label string) *Button {
|
|||||||
// OnClick registers an event when the button is clicked
|
// OnClick registers an event when the button is clicked
|
||||||
func (button *Button) OnClick(action func(user common.User)) {
|
func (button *Button) OnClick(action func(user common.User)) {
|
||||||
button.discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.InteractionCreate) {
|
button.discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.InteractionCreate) {
|
||||||
|
if r.Interaction.Type != discordgo.InteractionMessageComponent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if r.MessageComponentData().CustomID == button.ID {
|
if r.MessageComponentData().CustomID == button.ID {
|
||||||
|
|
||||||
action(NewUser(r.Member.User))
|
action(NewUser(r.Member.User))
|
||||||
|
@ -8,31 +8,43 @@ import (
|
|||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
|
"github.com/yeslayla/birdbot/persistence"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Discord struct {
|
type Discord struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
|
|
||||||
guildID string
|
guildID string
|
||||||
|
applicationID string
|
||||||
session *discordgo.Session
|
session *discordgo.Session
|
||||||
|
|
||||||
|
commands map[string]*discordgo.ApplicationCommand
|
||||||
|
commandHandlers map[string]func(session *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
|
|
||||||
|
db persistence.Database
|
||||||
|
|
||||||
// Signal for shutdown
|
// Signal for shutdown
|
||||||
stop chan os.Signal
|
stop chan os.Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Discord session
|
// New creates a new Discord session
|
||||||
func New(guildID string, token string) *Discord {
|
func New(applicationID string, guildID string, token string, db persistence.Database) *Discord {
|
||||||
|
|
||||||
// Create Discord Session
|
// Create Discord Session
|
||||||
session, err := discordgo.New(fmt.Sprint("Bot ", token))
|
session, err := discordgo.New(fmt.Sprint("Bot ", token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create Discord session: %v", err)
|
log.Fatalf("Failed to create Discord session: %v", err)
|
||||||
}
|
}
|
||||||
|
session.ShouldReconnectOnError = true
|
||||||
return &Discord{
|
return &Discord{
|
||||||
|
db: db,
|
||||||
|
|
||||||
session: session,
|
session: session,
|
||||||
|
applicationID: applicationID,
|
||||||
guildID: guildID,
|
guildID: guildID,
|
||||||
|
commands: make(map[string]*discordgo.ApplicationCommand),
|
||||||
|
commandHandlers: make(map[string]func(*discordgo.Session, *discordgo.InteractionCreate)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,10 +56,27 @@ func (discord *Discord) Run() error {
|
|||||||
}
|
}
|
||||||
defer discord.session.Close()
|
defer discord.session.Close()
|
||||||
|
|
||||||
|
// Register command handler
|
||||||
|
discord.session.AddHandler(func(session *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if i.GuildID != discord.guildID || i.Interaction.Type != discordgo.InteractionApplicationCommand {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler, ok := discord.commandHandlers[i.ApplicationCommandData().Name]; ok {
|
||||||
|
handler(session, i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Validate state
|
||||||
|
discord.RefreshWebhookState()
|
||||||
|
|
||||||
// Keep alive
|
// Keep alive
|
||||||
discord.stop = make(chan os.Signal, 1)
|
discord.stop = make(chan os.Signal, 1)
|
||||||
signal.Notify(discord.stop, os.Interrupt)
|
signal.Notify(discord.stop, os.Interrupt)
|
||||||
<-discord.stop
|
<-discord.stop
|
||||||
|
|
||||||
|
discord.ClearCommands()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +125,17 @@ func (discord *Discord) OnEventUpdate(handler func(*Discord, common.Event)) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnMessageRecieved registers a handler when a message is recieved
|
||||||
|
func (discord *Discord) OnMessageRecieved(handler func(*Discord, string, common.User, string)) {
|
||||||
|
discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.MessageCreate) {
|
||||||
|
if r.GuildID != discord.guildID || r.Author.Bot {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(discord, r.ChannelID, NewUser(r.Author), r.Content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (discord *Discord) SetStatus(status string) {
|
func (discord *Discord) SetStatus(status string) {
|
||||||
if err := discord.session.UpdateGameStatus(0, status); err != nil {
|
if err := discord.session.UpdateGameStatus(0, status); err != nil {
|
||||||
log.Fatal("Failed to update status: ", err)
|
log.Fatal("Failed to update status: ", err)
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
"github.com/yeslayla/birdbot/core"
|
"github.com/yeslayla/birdbot/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
76
discord/message.go
Normal file
76
discord/message.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/yeslayla/birdbot/core"
|
||||||
|
"github.com/yeslayla/birdbot/persistence"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WebhookName = "BirdBot"
|
||||||
|
|
||||||
|
// RefreshWebhookState refreshes the state of all webhooks
|
||||||
|
func (discord *Discord) RefreshWebhookState() {
|
||||||
|
channels, err := discord.session.GuildChannels(discord.guildID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting channels: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
webhookData, err := discord.db.GetDiscordWebhook(channel.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting webhook from DB: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if webhookData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = discord.session.WebhookEdit(webhookData.ID, WebhookName, discord.GetAvatarBase64(NewUser(discord.session.State.User)), channel.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error updating webhook: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookSendMessage sends a message to a channel using a webhook
|
||||||
|
func (discord *Discord) WebhookSendMessage(channel *core.Channel, displayName string, message string) {
|
||||||
|
|
||||||
|
webhookData, err := discord.db.GetDiscordWebhook(channel.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting webhook from DB: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if webhookData == nil {
|
||||||
|
webhookAvatar := discord.GetAvatarBase64(NewUser(discord.session.State.User))
|
||||||
|
|
||||||
|
webhook, err := discord.session.WebhookCreate(channel.ID, WebhookName, webhookAvatar)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error creating webhook: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookData = &persistence.DBDiscordWebhook{
|
||||||
|
ID: webhook.ID,
|
||||||
|
Token: webhook.Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := discord.db.SetDiscordWebhook(channel.ID, webhookData); err != nil {
|
||||||
|
log.Fatalf("Error failed to store webhook in DB: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = discord.session.WebhookExecute(webhookData.ID, webhookData.Token, false, &discordgo.WebhookParams{
|
||||||
|
Content: message,
|
||||||
|
Username: displayName,
|
||||||
|
}); err != nil {
|
||||||
|
log.Printf("Failed to send message over webhook: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,15 @@
|
|||||||
package discord
|
package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewUser creates a new user object from a discordgo.User object
|
// NewUser creates a new user object from a discordgo.User object
|
||||||
@ -18,10 +23,41 @@ func NewUser(user *discordgo.User) common.User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return common.User{
|
return common.User{
|
||||||
|
DisplayName: user.Username,
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAvatar returns the users Avatar as a image.Image
|
||||||
|
func (discord *Discord) GetAvatar(user common.User) image.Image {
|
||||||
|
discordUser, err := discord.session.User(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error getting user: ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
avatar, err := discord.session.UserAvatarDecode(discordUser)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error decoding avatar: ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatar
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAvatarBase64 returns the base64 encoded avatar of a user
|
||||||
|
func (discord *Discord) GetAvatarBase64(user common.User) string {
|
||||||
|
avatar := discord.GetAvatar(user)
|
||||||
|
|
||||||
|
fmtAvatar := &bytes.Buffer{}
|
||||||
|
if err := jpeg.Encode(fmtAvatar, avatar, nil); err != nil {
|
||||||
|
log.Println("Error encoding avatar: ", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(fmtAvatar.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
// AssignRole adds a role to a user
|
// AssignRole adds a role to a user
|
||||||
func (discord *Discord) AssignRole(user common.User, role *Role) error {
|
func (discord *Discord) AssignRole(user common.User, role *Role) error {
|
||||||
return discord.session.GuildMemberRoleAdd(discord.guildID, user.ID, role.ID)
|
return discord.session.GuildMemberRoleAdd(discord.guildID, user.ID, role.ID)
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
|
||||||
"github.com/yeslayla/birdbot/mastodon"
|
|
||||||
)
|
|
||||||
|
|
||||||
type announceEventsComponent struct {
|
|
||||||
bot common.ModuleManager
|
|
||||||
mastodon *mastodon.Mastodon
|
|
||||||
guildID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAnnounceEventsComponent creates a new component
|
|
||||||
func NewAnnounceEventsComponent(mastodon *mastodon.Mastodon, guildID string) common.Module {
|
|
||||||
return &announceEventsComponent{
|
|
||||||
mastodon: mastodon,
|
|
||||||
guildID: guildID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize registers event listeners
|
|
||||||
func (c *announceEventsComponent) Initialize(birdbot common.ModuleManager) error {
|
|
||||||
c.bot = birdbot
|
|
||||||
|
|
||||||
_ = birdbot.OnEventCreate(c.OnEventCreate)
|
|
||||||
_ = birdbot.OnEventDelete(c.OnEventDelete)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
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
|
|
||||||
archiveCategoryID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewManageEventChannelsComponent creates a new component
|
|
||||||
func NewManageEventChannelsComponent(categoryID string, archiveCategoryID string, session *discord.Discord) common.Module {
|
|
||||||
return &manageEventChannelsComponent{
|
|
||||||
session: session,
|
|
||||||
categoryID: categoryID,
|
|
||||||
archiveCategoryID: archiveCategoryID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize registers event listeners
|
|
||||||
func (c *manageEventChannelsComponent) Initialize(birdbot common.ModuleManager) error {
|
|
||||||
_ = birdbot.OnEventCreate(c.OnEventCreate)
|
|
||||||
_ = birdbot.OnEventComplete(c.OnEventComplete)
|
|
||||||
_ = birdbot.OnEventDelete(c.OnEventDelete)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 != "" {
|
|
||||||
|
|
||||||
if err := c.session.MoveChannelToCategory(channel, c.archiveCategoryID); 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
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
|
||||||
"github.com/yeslayla/birdbot/discord"
|
|
||||||
)
|
|
||||||
|
|
||||||
type recurringEventsComponent struct {
|
|
||||||
session *discord.Discord
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRecurringEventsComponent creates a new component instance
|
|
||||||
func NewRecurringEventsComponent() common.Module {
|
|
||||||
return &recurringEventsComponent{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize registers event listeners
|
|
||||||
func (c *recurringEventsComponent) Initialize(birdbot common.ModuleManager) error {
|
|
||||||
_ = birdbot.OnEventComplete(c.OnEventComplete)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
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
|
|
||||||
}
|
|
31
go.mod
31
go.mod
@ -1,28 +1,33 @@
|
|||||||
module github.com/yeslayla/birdbot
|
module github.com/yeslayla/birdbot
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bwmarrin/discordgo v0.26.1
|
github.com/bwmarrin/discordgo v0.27.2-0.20240104191117-afc57886f91a
|
||||||
github.com/ilyakaznacheev/cleanenv v1.4.0
|
github.com/ilyakaznacheev/cleanenv v1.5.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/mattn/go-mastodon v0.0.6
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.19
|
||||||
|
github.com/rubenv/sql-migrate v1.6.0
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/yeslayla/birdbot-common v0.1.0
|
||||||
|
|
||||||
|
golang.org/x/crypto v0.20.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
github.com/joho/godotenv v1.4.0 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/magefile/mage v1.14.0 // indirect
|
github.com/magefile/mage v1.14.0 // indirect
|
||||||
github.com/mattn/go-mastodon v0.0.5 // indirect
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rubenv/sql-migrate v1.4.0 // indirect
|
github.com/stretchr/objx v0.5.1 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
||||||
golang.org/x/crypto v0.5.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
||||||
)
|
)
|
||||||
|
55
go.sum
55
go.sum
@ -41,6 +41,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
@ -63,6 +65,10 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
|||||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||||
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE=
|
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE=
|
||||||
github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
|
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
|
||||||
|
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
|
github.com/bwmarrin/discordgo v0.27.2-0.20240104191117-afc57886f91a h1:I1j/9FoqDN+W0ZXiSU91lJXwKCvnKBLgJKlBLYAbim4=
|
||||||
|
github.com/bwmarrin/discordgo v0.27.2-0.20240104191117-afc57886f91a/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
@ -192,6 +198,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
|||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
@ -226,11 +234,17 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ilyakaznacheev/cleanenv v1.4.0 h1:Gvwxt6wAPUo9OOxyp5Xz9eqhLsAey4AtbCF5zevDnvs=
|
github.com/ilyakaznacheev/cleanenv v1.4.0 h1:Gvwxt6wAPUo9OOxyp5Xz9eqhLsAey4AtbCF5zevDnvs=
|
||||||
github.com/ilyakaznacheev/cleanenv v1.4.0/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
|
github.com/ilyakaznacheev/cleanenv v1.4.0/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@ -255,6 +269,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
|
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||||
|
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
|
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
|
||||||
@ -273,11 +289,17 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-mastodon v0.0.5 h1:P0e1/R2v3ho6kM7BUW0noQm8gAqHE0p8Gq1TMapIVAc=
|
github.com/mattn/go-mastodon v0.0.5 h1:P0e1/R2v3ho6kM7BUW0noQm8gAqHE0p8Gq1TMapIVAc=
|
||||||
github.com/mattn/go-mastodon v0.0.5/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
|
github.com/mattn/go-mastodon v0.0.5/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
|
||||||
|
github.com/mattn/go-mastodon v0.0.6 h1:lqU1sOeeIapaDsDUL6udDZIzMb2Wqapo347VZlaOzf0=
|
||||||
|
github.com/mattn/go-mastodon v0.0.6/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
|
||||||
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
|
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
@ -333,6 +355,8 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
|||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rubenv/sql-migrate v1.4.0 h1:y4ndB3hq5tmjvQ8jcuqhLgeEqoxIjEidN5RaCkKOAAE=
|
github.com/rubenv/sql-migrate v1.4.0 h1:y4ndB3hq5tmjvQ8jcuqhLgeEqoxIjEidN5RaCkKOAAE=
|
||||||
github.com/rubenv/sql-migrate v1.4.0/go.mod h1:lRxHt4vTgRJtpGbulUUYHA9dzfbBJXRt+PwUF/jeNYo=
|
github.com/rubenv/sql-migrate v1.4.0/go.mod h1:lRxHt4vTgRJtpGbulUUYHA9dzfbBJXRt+PwUF/jeNYo=
|
||||||
|
github.com/rubenv/sql-migrate v1.6.0 h1:IZpcTlAx/VKXphWEpwWJ7BaMq05tYtE80zYz+8a5Il8=
|
||||||
|
github.com/rubenv/sql-migrate v1.6.0/go.mod h1:m3ilnKP7sNb4eYkLsp6cGdPOl4OBcXM6rcbzU+Oqc5k=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
@ -363,6 +387,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||||
|
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@ -373,6 +399,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
||||||
@ -380,6 +409,12 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFy
|
|||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
github.com/yeslayla/birdbot-common v0.0.0-20230619031144-7238d1b18f76 h1:lfZb/m0Ckc6FiAiCSAlJ/6coyx3CkEnnHZXhPLWqQa4=
|
||||||
|
github.com/yeslayla/birdbot-common v0.0.0-20230619031144-7238d1b18f76/go.mod h1:zyvQi9lqnd8tKudJW75FRyBONnAa2JwYk43vLECJqO0=
|
||||||
|
github.com/yeslayla/birdbot-common v0.0.0-20230619053929-5d690affa770 h1:tKCGApCuP7j8VrkZkq8XnN2nFFN/o/IuiRojTCaqpRo=
|
||||||
|
github.com/yeslayla/birdbot-common v0.0.0-20230619053929-5d690affa770/go.mod h1:zyvQi9lqnd8tKudJW75FRyBONnAa2JwYk43vLECJqO0=
|
||||||
|
github.com/yeslayla/birdbot-common v0.1.0 h1:Ozj9E8CP50iBqMviEpHxpKwdyCr7jkBMQonncHRvnqM=
|
||||||
|
github.com/yeslayla/birdbot-common v0.1.0/go.mod h1:zyvQi9lqnd8tKudJW75FRyBONnAa2JwYk43vLECJqO0=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@ -421,6 +456,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||||
|
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||||
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||||
|
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -500,6 +543,12 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -583,6 +632,12 @@ golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
|
28
main.go
28
main.go
@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/ilyakaznacheev/cleanenv"
|
"github.com/ilyakaznacheev/cleanenv"
|
||||||
"github.com/yeslayla/birdbot/app"
|
"github.com/yeslayla/birdbot/app"
|
||||||
@ -15,7 +16,7 @@ import (
|
|||||||
"github.com/yeslayla/birdbot/persistence"
|
"github.com/yeslayla/birdbot/persistence"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginsDirectory = "./plugins"
|
const PluginsDirectory = "/home/layla/.config/birdbot/plugins"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
@ -28,10 +29,13 @@ func main() {
|
|||||||
var db_file string
|
var db_file string
|
||||||
var version bool
|
var version bool
|
||||||
flag.StringVar(&config_file, "c", defaultConfigPath, "Path to config file")
|
flag.StringVar(&config_file, "c", defaultConfigPath, "Path to config file")
|
||||||
flag.StringVar(&db_file, "db", defaultDBPath, "Path to store persistant data")
|
flag.StringVar(&db_file, "db", defaultDBPath, "Path to store persistent data")
|
||||||
flag.BoolVar(&version, "v", false, "List version")
|
flag.BoolVar(&version, "v", false, "List version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Use given config dir
|
||||||
|
configDir = filepath.Dir(config_file)
|
||||||
|
|
||||||
if version {
|
if version {
|
||||||
fmt.Printf("BirdBot %s (%s)\n", app.Version, app.Build)
|
fmt.Printf("BirdBot %s (%s)\n", app.Version, app.Build)
|
||||||
return
|
return
|
||||||
@ -59,13 +63,13 @@ func main() {
|
|||||||
log.Fatal("Failed to migrate db: ", err)
|
log.Fatal("Failed to migrate db: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bot := app.NewBot()
|
bot := app.NewBot(db)
|
||||||
|
|
||||||
if err := bot.Initialize(cfg); err != nil {
|
if err := bot.Initialize(cfg); err != nil {
|
||||||
log.Fatal("Failed to initialize: ", err)
|
log.Fatal("Failed to initialize: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
loader := app.NewComponentLoader(bot)
|
loader := app.NewComponentLoader(bot, configDir)
|
||||||
|
|
||||||
if cfg.Features.AnnounceEvents.IsEnabledByDefault() {
|
if cfg.Features.AnnounceEvents.IsEnabledByDefault() {
|
||||||
loader.LoadComponent(modules.NewAnnounceEventsComponent(bot.Mastodon, cfg.Discord.NotificationChannel))
|
loader.LoadComponent(modules.NewAnnounceEventsComponent(bot.Mastodon, cfg.Discord.NotificationChannel))
|
||||||
@ -73,8 +77,11 @@ func main() {
|
|||||||
if cfg.Features.ManageEventChannels.IsEnabledByDefault() {
|
if cfg.Features.ManageEventChannels.IsEnabledByDefault() {
|
||||||
loader.LoadComponent(modules.NewManageEventChannelsComponent(cfg.Discord.EventCategory, cfg.Discord.ArchiveCategory, bot.Session))
|
loader.LoadComponent(modules.NewManageEventChannelsComponent(cfg.Discord.EventCategory, cfg.Discord.ArchiveCategory, bot.Session))
|
||||||
}
|
}
|
||||||
if cfg.Features.ReccurringEvents.IsEnabledByDefault() {
|
if cfg.Features.RecurringEvents.IsEnabled() {
|
||||||
loader.LoadComponent(modules.NewRecurringEventsComponent())
|
loader.LoadComponent(modules.NewRecurringEventsComponent(bot.Session))
|
||||||
|
}
|
||||||
|
if cfg.Features.StatusPortal.IsEnabled() {
|
||||||
|
loader.LoadComponent(modules.NewStatusComponent(cfg.StatusPortal.URL))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Features.RoleSelection.IsEnabledByDefault() {
|
if cfg.Features.RoleSelection.IsEnabledByDefault() {
|
||||||
@ -83,6 +90,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Features.Feedback.IsEnabled() {
|
||||||
|
loader.LoadComponent(modules.NewFeedbackWebhookComponent(cfg.Feedback.WebhookURL, modules.FeedbackWebhookConfiguration{
|
||||||
|
PayloadType: cfg.Feedback.PayloadType,
|
||||||
|
|
||||||
|
SuccessMessage: cfg.Feedback.SuccessMessage,
|
||||||
|
FailureMessage: cfg.Feedback.FailureMessage,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(PluginsDirectory); !os.IsNotExist(err) {
|
if _, err := os.Stat(PluginsDirectory); !os.IsNotExist(err) {
|
||||||
components := app.LoadPlugins(PluginsDirectory)
|
components := app.LoadPlugins(PluginsDirectory)
|
||||||
for _, comp := range components {
|
for _, comp := range components {
|
||||||
|
@ -3,7 +3,7 @@ package modules
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
"github.com/yeslayla/birdbot/mastodon"
|
"github.com/yeslayla/birdbot/mastodon"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
122
modules/feedback_webhook.go
Normal file
122
modules/feedback_webhook.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type feedbackWebhookModule struct {
|
||||||
|
webhookURL string
|
||||||
|
payloadType string
|
||||||
|
successMessage string
|
||||||
|
failureMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeedbackWebhookConfiguration struct {
|
||||||
|
SuccessMessage string
|
||||||
|
FailureMessage string
|
||||||
|
PayloadType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFeedbackWebhookComponent creates a new component
|
||||||
|
func NewFeedbackWebhookComponent(webhookURL string, config FeedbackWebhookConfiguration) common.Module {
|
||||||
|
m := &feedbackWebhookModule{
|
||||||
|
webhookURL: webhookURL,
|
||||||
|
payloadType: "default",
|
||||||
|
successMessage: "Feedback received!",
|
||||||
|
failureMessage: "Failed to send feedback!",
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SuccessMessage != "" {
|
||||||
|
m.successMessage = config.SuccessMessage
|
||||||
|
}
|
||||||
|
if config.FailureMessage != "" {
|
||||||
|
m.failureMessage = config.FailureMessage
|
||||||
|
}
|
||||||
|
if config.PayloadType != "" {
|
||||||
|
m.payloadType = config.PayloadType
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *feedbackWebhookModule) Initialize(birdbot common.ModuleManager) error {
|
||||||
|
birdbot.RegisterCommand("feedback", common.ChatCommandConfiguration{
|
||||||
|
Description: "Sends a feedback message",
|
||||||
|
EphemeralResponse: true,
|
||||||
|
Options: map[string]common.ChatCommandOption{
|
||||||
|
"message": {
|
||||||
|
Description: "Content of what you'd like to communicate in your feedback.",
|
||||||
|
Type: common.CommandTypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, func(user common.User, args map[string]any) string {
|
||||||
|
|
||||||
|
message, ok := args["message"]
|
||||||
|
if !ok {
|
||||||
|
return "Missing content in command"
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
// Supported payload types
|
||||||
|
switch c.payloadType {
|
||||||
|
case "discord":
|
||||||
|
data, _ = json.Marshal(map[string]any{
|
||||||
|
"content": fmt.Sprintf("%s: %s", user.DisplayName, message),
|
||||||
|
})
|
||||||
|
case "slack":
|
||||||
|
data, _ = json.Marshal(map[string]any{
|
||||||
|
"text": fmt.Sprintf("%s: %s", user.DisplayName, message),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
data, _ = json.Marshal(map[string]any{
|
||||||
|
"message": message,
|
||||||
|
"username": user.DisplayName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
body := bytes.NewBuffer(data)
|
||||||
|
|
||||||
|
// Send HTTP request
|
||||||
|
resp, err := http.Post(c.webhookURL, "application/json", body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to post feedback to url '%s': %s", c.webhookURL, err)
|
||||||
|
return c.failureMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate response
|
||||||
|
if resp.Status[0] != '2' {
|
||||||
|
log.Printf("Webhook returned %v: %s", resp.Status, message)
|
||||||
|
return c.failureMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read body for any special response
|
||||||
|
response := map[any]any{}
|
||||||
|
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return c.successMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(responseBody, &response); err != nil {
|
||||||
|
return c.successMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
if message, ok := response["message"]; ok {
|
||||||
|
v := fmt.Sprint(message)
|
||||||
|
if len(v) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.successMessage
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
@ -3,7 +3,7 @@ package modules
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
"github.com/yeslayla/birdbot/core"
|
"github.com/yeslayla/birdbot/core"
|
||||||
"github.com/yeslayla/birdbot/discord"
|
"github.com/yeslayla/birdbot/discord"
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
"github.com/yeslayla/birdbot/discord"
|
"github.com/yeslayla/birdbot/discord"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,8 +13,13 @@ type recurringEventsModule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRecurringEventsComponent creates a new component instance
|
// NewRecurringEventsComponent creates a new component instance
|
||||||
func NewRecurringEventsComponent() common.Module {
|
//
|
||||||
return &recurringEventsModule{}
|
// Deprecated: This recurring events are now a native feature in Discord,
|
||||||
|
// so this will be removed in the next version.
|
||||||
|
func NewRecurringEventsComponent(session *discord.Discord) common.Module {
|
||||||
|
return &recurringEventsModule{
|
||||||
|
session: session,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize registers event listeners
|
// Initialize registers event listeners
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/yeslayla/birdbot/common"
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
"github.com/yeslayla/birdbot/core"
|
"github.com/yeslayla/birdbot/core"
|
||||||
"github.com/yeslayla/birdbot/discord"
|
"github.com/yeslayla/birdbot/discord"
|
||||||
"github.com/yeslayla/birdbot/persistence"
|
"github.com/yeslayla/birdbot/persistence"
|
||||||
@ -15,7 +15,7 @@ type roleSelectionModule struct {
|
|||||||
db persistence.Database
|
db persistence.Database
|
||||||
|
|
||||||
cfg core.RoleSelectionConfig
|
cfg core.RoleSelectionConfig
|
||||||
exlusive bool
|
exclusive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRoleSelectionComponent creates a new component
|
// NewRoleSelectionComponent creates a new component
|
||||||
@ -24,7 +24,7 @@ func NewRoleSelectionComponent(discord *discord.Discord, db persistence.Database
|
|||||||
session: discord,
|
session: discord,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
db: db,
|
db: db,
|
||||||
exlusive: true,
|
exclusive: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +49,10 @@ func (c *roleSelectionModule) Initialize(birdbot common.ModuleManager) error {
|
|||||||
btn := c.session.NewButton(fmt.Sprint(c.cfg.Title, role.Name), role.Name)
|
btn := c.session.NewButton(fmt.Sprint(c.cfg.Title, role.Name), role.Name)
|
||||||
btn.OnClick(func(user common.User) {
|
btn.OnClick(func(user common.User) {
|
||||||
|
|
||||||
|
// Assign the roles asynchronously to avoid Discord's response timeout
|
||||||
|
go func() {
|
||||||
// Remove other roles if exclusive
|
// Remove other roles if exclusive
|
||||||
if c.exlusive {
|
if c.exclusive {
|
||||||
for _, r := range roles {
|
for _, r := range roles {
|
||||||
if r.ID == role.ID {
|
if r.ID == role.ID {
|
||||||
continue
|
continue
|
||||||
@ -70,6 +72,7 @@ func (c *roleSelectionModule) Initialize(birdbot common.ModuleManager) error {
|
|||||||
} else if err := c.session.AssignRole(user, role); err != nil {
|
} else if err := c.session.AssignRole(user, role); err != nil {
|
||||||
log.Printf("Failed to assign role: %s", err)
|
log.Printf("Failed to assign role: %s", err)
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
31
modules/status.go
Normal file
31
modules/status.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yeslayla/birdbot-common/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statusModule struct {
|
||||||
|
portalURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatusComponent creates a new component
|
||||||
|
func NewStatusComponent(portalURL string) common.Module {
|
||||||
|
m := &statusModule{
|
||||||
|
portalURL: portalURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *statusModule) Initialize(birdbot common.ModuleManager) error {
|
||||||
|
birdbot.RegisterCommand("status", common.ChatCommandConfiguration{
|
||||||
|
Description: "Gets the current status of the bot",
|
||||||
|
EphemeralResponse: false,
|
||||||
|
}, func(user common.User, args map[string]any) string {
|
||||||
|
|
||||||
|
return fmt.Sprintf("The bot is currently OK.\nSee Status Portal for more information: %s", c.portalURL)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,4 +4,12 @@ package persistence
|
|||||||
type Database interface {
|
type Database interface {
|
||||||
GetDiscordMessage(id string) (string, error)
|
GetDiscordMessage(id string) (string, error)
|
||||||
SetDiscordMessage(id string, messageID string) error
|
SetDiscordMessage(id string, messageID string) error
|
||||||
|
|
||||||
|
GetDiscordWebhook(id string) (*DBDiscordWebhook, error)
|
||||||
|
SetDiscordWebhook(id string, data *DBDiscordWebhook) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBDiscordWebhook struct {
|
||||||
|
ID string
|
||||||
|
Token string
|
||||||
}
|
}
|
||||||
|
9
persistence/sql/20230617-webhooks.sql
Normal file
9
persistence/sql/20230617-webhooks.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE IF NOT EXISTS discord_webhooks (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
webhook_id TEXT NOT NULL,
|
||||||
|
webhook_token TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE discord_webhooks;
|
@ -120,3 +120,49 @@ func (db *Sqlite3Database) SetDiscordMessage(id string, messageID string) error
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDiscordWebhook finds a discord webhook based on a given local id
|
||||||
|
func (db *Sqlite3Database) GetDiscordWebhook(id string) (*DBDiscordWebhook, error) {
|
||||||
|
|
||||||
|
var data DBDiscordWebhook = DBDiscordWebhook{}
|
||||||
|
row := db.db.QueryRow("SELECT webhook_id, webhook_token FROM discord_webhooks WHERE id = $1", id)
|
||||||
|
|
||||||
|
if err := row.Scan(&data.ID, &data.Token); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get discord webhook from sqlite3: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDiscordWebhook stores a discord webhook based on a given local id
|
||||||
|
func (db *Sqlite3Database) SetDiscordWebhook(id string, data *DBDiscordWebhook) error {
|
||||||
|
|
||||||
|
statement, err := db.db.Prepare("INSERT OR IGNORE INTO discord_webhooks (id, webhook_id, webhook_token) VALUES (?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := statement.Exec(id, data.ID, data.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _ := result.RowsAffected()
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
statement, err := db.db.Prepare("UPDATE discord_webhooks SET webhook_id = (?), webhook_token = (?) WHERE id = (?)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := statement.Exec(data.ID, data.Token, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -21,7 +21,6 @@ discord:
|
|||||||
# - name: Blue
|
# - name: Blue
|
||||||
# color: "#1a88ff"
|
# color: "#1a88ff"
|
||||||
|
|
||||||
|
|
||||||
# mastodon:
|
# mastodon:
|
||||||
# server: https://mastodon.social
|
# server: https://mastodon.social
|
||||||
# username: my_user
|
# username: my_user
|
||||||
@ -29,6 +28,9 @@ discord:
|
|||||||
# client_id: 1234
|
# client_id: 1234
|
||||||
# client_secret: secret2
|
# client_secret: secret2
|
||||||
|
|
||||||
|
# status_portal:
|
||||||
|
# url: https://status.example.com
|
||||||
|
|
||||||
# # Feature flags can be used to
|
# # Feature flags can be used to
|
||||||
# # disable specific features
|
# # disable specific features
|
||||||
# features:
|
# features:
|
||||||
@ -36,3 +38,4 @@ discord:
|
|||||||
# announce_events: true
|
# announce_events: true
|
||||||
# recurring_events: true
|
# recurring_events: true
|
||||||
# role_selection: true
|
# role_selection: true
|
||||||
|
# status_portal: false
|
Reference in New Issue
Block a user