feat: add welcome message functionality
- Introduced a new welcome service and repository for managing welcome messages, channels, and GIFs. - Added commands to set welcome channel, message, DM, and GIF through the bot's configuration. - Implemented event handling for new guild members to send welcome messages and DMs based on configuration. - Updated main application to include the new welcome service in the bot's services.
This commit is contained in:
@@ -30,9 +30,11 @@ func NewBot(token, appID, guildID, lavalinkHost, lavalinkPass string, cmds []*di
|
|||||||
// Intents needed for:
|
// Intents needed for:
|
||||||
// - InteractionCreate (slash commands): Guilds
|
// - InteractionCreate (slash commands): Guilds
|
||||||
// - MessageCreate (leveling): GuildMessages
|
// - MessageCreate (leveling): GuildMessages
|
||||||
|
// - GuildMemberAdd (welcome messages): GuildMembers
|
||||||
// - VoiceStateUpdate (meeting lobby): GuildVoiceStates
|
// - VoiceStateUpdate (meeting lobby): GuildVoiceStates
|
||||||
// - MessageReactionAdd (scheduling via reactions): GuildMessageReactions + DirectMessageReactions
|
// - MessageReactionAdd (scheduling via reactions): GuildMessageReactions + DirectMessageReactions
|
||||||
session.Identify.Intents = discordgo.IntentsGuilds |
|
session.Identify.Intents = discordgo.IntentsGuilds |
|
||||||
|
discordgo.IntentsGuildMembers |
|
||||||
discordgo.IntentsGuildMessages |
|
discordgo.IntentsGuildMessages |
|
||||||
discordgo.IntentsGuildVoiceStates |
|
discordgo.IntentsGuildVoiceStates |
|
||||||
discordgo.IntentsGuildMessageReactions |
|
discordgo.IntentsGuildMessageReactions |
|
||||||
@@ -73,6 +75,10 @@ func (b *Bot) Start() error {
|
|||||||
events.HandleMessageCreate(s, m, b.Services)
|
events.HandleMessageCreate(s, m, b.Services)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
b.Session.AddHandler(func(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
|
||||||
|
events.HandleGuildMemberAdd(s, m, b.Services)
|
||||||
|
})
|
||||||
|
|
||||||
b.Session.AddHandler(func(s *discordgo.Session, vs *discordgo.VoiceStateUpdate) {
|
b.Session.AddHandler(func(s *discordgo.Session, vs *discordgo.VoiceStateUpdate) {
|
||||||
events.HandleVoiceStateUpdate(s, vs, b.Services)
|
events.HandleVoiceStateUpdate(s, vs, b.Services)
|
||||||
music.OnVoiceStateUpdate(vs)
|
music.OnVoiceStateUpdate(vs)
|
||||||
|
|||||||
@@ -55,6 +55,59 @@ var Config = &discordgo.ApplicationCommand{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "setwelcomechannel",
|
||||||
|
Description: "Set the channel for welcome messages",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionChannel,
|
||||||
|
Name: "channel",
|
||||||
|
Description: "Text channel for welcomes",
|
||||||
|
Required: true,
|
||||||
|
ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "setwelcomemessage",
|
||||||
|
Description: "Set the welcome message template shown in the server",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "message",
|
||||||
|
Description: "Use {user} where the new member mention should appear",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "setwelcomedm",
|
||||||
|
Description: "Set the welcome DM template sent to new members",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "message",
|
||||||
|
Description: "Use {user} where the new member mention should appear",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "setwelcomegif",
|
||||||
|
Description: "Set the GIF/image URL used in welcome messages",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "url",
|
||||||
|
Description: "Direct image or GIF URL",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +120,8 @@ func ConfigHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
respondEphemeral(s, i, "You need the **Manage Server** permission to use this.")
|
respondEphemeral(s, i, "You need the **Manage Server** permission to use this.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if services.Global == nil || services.Global.Twitch == nil {
|
if services.Global == nil {
|
||||||
respondEphemeral(s, i, "Twitch configuration is not available.")
|
respondEphemeral(s, i, "Configuration services are not available.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +141,18 @@ func ConfigHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
case "settwitchnotificationchannel":
|
case "settwitchnotificationchannel":
|
||||||
log.Printf("twitch: /config settwitchnotificationchannel invoked by %s in guild %s", i.Member.User.ID, i.GuildID)
|
log.Printf("twitch: /config settwitchnotificationchannel invoked by %s in guild %s", i.Member.User.ID, i.GuildID)
|
||||||
handleSetChannel(s, i)
|
handleSetChannel(s, i)
|
||||||
|
case "setwelcomechannel":
|
||||||
|
log.Printf("welcome: /config setwelcomechannel invoked by %s in guild %s", i.Member.User.ID, i.GuildID)
|
||||||
|
handleSetWelcomeChannel(s, i)
|
||||||
|
case "setwelcomemessage":
|
||||||
|
log.Printf("welcome: /config setwelcomemessage invoked by %s in guild %s", i.Member.User.ID, i.GuildID)
|
||||||
|
handleSetWelcomeMessage(s, i)
|
||||||
|
case "setwelcomedm":
|
||||||
|
log.Printf("welcome: /config setwelcomedm invoked by %s in guild %s", i.Member.User.ID, i.GuildID)
|
||||||
|
handleSetWelcomeDM(s, i)
|
||||||
|
case "setwelcomegif":
|
||||||
|
log.Printf("welcome: /config setwelcomegif invoked by %s in guild %s", i.Member.User.ID, i.GuildID)
|
||||||
|
handleSetWelcomeGIF(s, i)
|
||||||
default:
|
default:
|
||||||
respondEphemeral(s, i, "Unknown subcommand.")
|
respondEphemeral(s, i, "Unknown subcommand.")
|
||||||
}
|
}
|
||||||
@@ -193,6 +258,147 @@ func handleSetChannel(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
respondEphemeral(s, i, "Twitch notifications will be sent to "+ch.Mention()+".")
|
respondEphemeral(s, i, "Twitch notifications will be sent to "+ch.Mention()+".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleSetWelcomeChannel(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if services.Global.Welcome == nil {
|
||||||
|
respondEphemeral(s, i, "Welcome configuration is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Invalid guild ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var chOpt *discordgo.ApplicationCommandInteractionDataOption
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
if o.Name == "channel" {
|
||||||
|
chOpt = o
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chOpt == nil {
|
||||||
|
respondEphemeral(s, i, "Missing channel option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := chOpt.ChannelValue(s)
|
||||||
|
if ch == nil {
|
||||||
|
respondEphemeral(s, i, "Invalid channel.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chID, err := strconv.ParseInt(ch.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Invalid channel ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.Welcome.SetChannel(context.Background(), guildID, chID); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to set welcome channel.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondEphemeral(s, i, "Welcome messages will be sent to "+ch.Mention()+".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetWelcomeMessage(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if services.Global.Welcome == nil {
|
||||||
|
respondEphemeral(s, i, "Welcome configuration is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Invalid guild ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var msg string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
if o.Name == "message" {
|
||||||
|
msg = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msg == "" {
|
||||||
|
respondEphemeral(s, i, "Message cannot be empty.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.Welcome.SetMessage(context.Background(), guildID, msg); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to set welcome message.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondEphemeral(s, i, "Welcome message updated.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetWelcomeDM(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if services.Global.Welcome == nil {
|
||||||
|
respondEphemeral(s, i, "Welcome configuration is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Invalid guild ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var msg string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
if o.Name == "message" {
|
||||||
|
msg = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msg == "" {
|
||||||
|
respondEphemeral(s, i, "DM message cannot be empty.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.Welcome.SetDM(context.Background(), guildID, msg); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to set welcome DM.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondEphemeral(s, i, "Welcome DM updated.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetWelcomeGIF(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if services.Global.Welcome == nil {
|
||||||
|
respondEphemeral(s, i, "Welcome configuration is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Invalid guild ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var url string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
if o.Name == "url" {
|
||||||
|
url = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if url == "" {
|
||||||
|
respondEphemeral(s, i, "URL cannot be empty.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.Welcome.SetGIF(context.Background(), guildID, url); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to set welcome GIF.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondEphemeral(s, i, "Welcome GIF updated.")
|
||||||
|
}
|
||||||
|
|
||||||
func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) {
|
func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) {
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package welcomerepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
GuildID int64
|
||||||
|
ChannelID int64
|
||||||
|
WelcomeMessage string
|
||||||
|
WelcomeDM string
|
||||||
|
WelcomeGIFURL string
|
||||||
|
HasConfiguration bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepo(db *sql.DB) *Repo {
|
||||||
|
return &Repo{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) GetSettings(ctx context.Context, guildID int64) (*Settings, error) {
|
||||||
|
const q = `
|
||||||
|
SELECT welcome_channel_id, welcome_message, welcome_dm, welcome_gif_url
|
||||||
|
FROM welcome
|
||||||
|
WHERE guild_id = $1
|
||||||
|
`
|
||||||
|
row := r.db.QueryRowContext(ctx, q, guildID)
|
||||||
|
var ch sql.NullInt64
|
||||||
|
var msg, dm, gif sql.NullString
|
||||||
|
|
||||||
|
switch err := row.Scan(&ch, &msg, &dm, &gif); err {
|
||||||
|
case nil:
|
||||||
|
s := &Settings{
|
||||||
|
GuildID: guildID,
|
||||||
|
ChannelID: 0,
|
||||||
|
WelcomeMessage: "",
|
||||||
|
WelcomeDM: "",
|
||||||
|
WelcomeGIFURL: "",
|
||||||
|
HasConfiguration: true,
|
||||||
|
}
|
||||||
|
if ch.Valid {
|
||||||
|
s.ChannelID = ch.Int64
|
||||||
|
}
|
||||||
|
if msg.Valid {
|
||||||
|
s.WelcomeMessage = msg.String
|
||||||
|
}
|
||||||
|
if dm.Valid {
|
||||||
|
s.WelcomeDM = dm.String
|
||||||
|
}
|
||||||
|
if gif.Valid {
|
||||||
|
s.WelcomeGIFURL = gif.String
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
case sql.ErrNoRows:
|
||||||
|
return &Settings{
|
||||||
|
GuildID: guildID,
|
||||||
|
HasConfiguration: false,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) UpsertChannel(ctx context.Context, guildID, channelID int64) error {
|
||||||
|
const q = `
|
||||||
|
INSERT INTO welcome (guild_id, welcome_channel_id)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (guild_id)
|
||||||
|
DO UPDATE SET welcome_channel_id = EXCLUDED.welcome_channel_id
|
||||||
|
`
|
||||||
|
_, err := r.db.ExecContext(ctx, q, guildID, channelID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) UpsertMessage(ctx context.Context, guildID int64, message string) error {
|
||||||
|
const q = `
|
||||||
|
INSERT INTO welcome (guild_id, welcome_message)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (guild_id)
|
||||||
|
DO UPDATE SET welcome_message = EXCLUDED.welcome_message
|
||||||
|
`
|
||||||
|
_, err := r.db.ExecContext(ctx, q, guildID, message)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) UpsertDM(ctx context.Context, guildID int64, dm string) error {
|
||||||
|
const q = `
|
||||||
|
INSERT INTO welcome (guild_id, welcome_dm)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (guild_id)
|
||||||
|
DO UPDATE SET welcome_dm = EXCLUDED.welcome_dm
|
||||||
|
`
|
||||||
|
_, err := r.db.ExecContext(ctx, q, guildID, dm)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) UpsertGIF(ctx context.Context, guildID int64, gifURL string) error {
|
||||||
|
const q = `
|
||||||
|
INSERT INTO welcome (guild_id, welcome_gif_url)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (guild_id)
|
||||||
|
DO UPDATE SET welcome_gif_url = EXCLUDED.welcome_gif_url
|
||||||
|
`
|
||||||
|
_, err := r.db.ExecContext(ctx, q, guildID, gifURL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"velox-bot/internal/db/services/schedule"
|
"velox-bot/internal/db/services/schedule"
|
||||||
"velox-bot/internal/db/services/twitch"
|
"velox-bot/internal/db/services/twitch"
|
||||||
"velox-bot/internal/db/services/usersettings"
|
"velox-bot/internal/db/services/usersettings"
|
||||||
|
"velox-bot/internal/db/services/welcome"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
@@ -20,11 +21,12 @@ type Services struct {
|
|||||||
Projects *projects.Service
|
Projects *projects.Service
|
||||||
RPS *rps.Service
|
RPS *rps.Service
|
||||||
Twitch *twitch.Service
|
Twitch *twitch.Service
|
||||||
|
Welcome *welcome.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
var Global *Services
|
var Global *Services
|
||||||
|
|
||||||
func NewServices(level *level.Service, levelSettings *levelsettings.Service, meeting *meeting.Service, schedule *schedule.Service, userSettings *usersettings.Service, projects *projects.Service, rps *rps.Service, twitchSvc *twitch.Service) *Services {
|
func NewServices(level *level.Service, levelSettings *levelsettings.Service, meeting *meeting.Service, schedule *schedule.Service, userSettings *usersettings.Service, projects *projects.Service, rps *rps.Service, twitchSvc *twitch.Service, welcomeSvc *welcome.Service) *Services {
|
||||||
s := &Services{
|
s := &Services{
|
||||||
Level: level,
|
Level: level,
|
||||||
LevelSettings: levelSettings,
|
LevelSettings: levelSettings,
|
||||||
@@ -34,6 +36,7 @@ func NewServices(level *level.Service, levelSettings *levelsettings.Service, mee
|
|||||||
Projects: projects,
|
Projects: projects,
|
||||||
RPS: rps,
|
RPS: rps,
|
||||||
Twitch: twitchSvc,
|
Twitch: twitchSvc,
|
||||||
|
Welcome: welcomeSvc,
|
||||||
}
|
}
|
||||||
Global = s
|
Global = s
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package welcome
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"velox-bot/internal/db/repos/welcomerepo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo *welcomerepo.Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(repo *welcomerepo.Repo) *Service {
|
||||||
|
return &Service{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSettings(ctx context.Context, guildID int64) (*welcomerepo.Settings, error) {
|
||||||
|
return s.repo.GetSettings(ctx, guildID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SetChannel(ctx context.Context, guildID, channelID int64) error {
|
||||||
|
return s.repo.UpsertChannel(ctx, guildID, channelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SetMessage(ctx context.Context, guildID int64, message string) error {
|
||||||
|
return s.repo.UpsertMessage(ctx, guildID, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SetDM(ctx context.Context, guildID int64, dm string) error {
|
||||||
|
return s.repo.UpsertDM(ctx, guildID, dm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SetGIF(ctx context.Context, guildID int64, gifURL string) error {
|
||||||
|
return s.repo.UpsertGIF(ctx, guildID, gifURL)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleGuildMemberAdd sends a configurable welcome message in a guild channel
|
||||||
|
// and an optional configurable DM to the joining user.
|
||||||
|
func HandleGuildMemberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd, svc *services.Services) {
|
||||||
|
if s == nil || m == nil || svc == nil || svc.Welcome == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.User == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
guildID := mustParseInt64(m.GuildID)
|
||||||
|
|
||||||
|
settings, err := svc.Welcome.GetSettings(ctx, guildID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("welcome: failed to load settings for guild %s: %v", m.GuildID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if settings == nil || settings.ChannelID == 0 {
|
||||||
|
// No welcome channel configured; nothing to do.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memberMention := m.User.Mention()
|
||||||
|
|
||||||
|
// Resolve guild/server name and user avatar for placeholders / visuals.
|
||||||
|
guildName := ""
|
||||||
|
userAvatarURL := ""
|
||||||
|
if g, err := s.State.Guild(m.GuildID); err == nil && g != nil {
|
||||||
|
guildName = g.Name
|
||||||
|
} else if g, err := s.Guild(m.GuildID); err == nil && g != nil {
|
||||||
|
guildName = g.Name
|
||||||
|
}
|
||||||
|
if m.User.Avatar != "" {
|
||||||
|
userAvatarURL = discordgo.EndpointUserAvatar(m.User.ID, m.User.Avatar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build guild welcome message.
|
||||||
|
channelID := strconv.FormatInt(settings.ChannelID, 10)
|
||||||
|
welcomeMsg := strings.TrimSpace(settings.WelcomeMessage)
|
||||||
|
if welcomeMsg == "" {
|
||||||
|
// Default message when none configured.
|
||||||
|
// "<user>! Welcome to <server>! Have fun!"
|
||||||
|
welcomeMsg = "<user>! Welcome to <server>! Have fun!"
|
||||||
|
}
|
||||||
|
welcomeMsg = strings.ReplaceAll(welcomeMsg, "{user}", memberMention)
|
||||||
|
if guildName != "" {
|
||||||
|
welcomeMsg = strings.ReplaceAll(welcomeMsg, "{server}", guildName)
|
||||||
|
welcomeMsg = strings.ReplaceAll(welcomeMsg, "<server>", guildName)
|
||||||
|
}
|
||||||
|
welcomeMsg = strings.ReplaceAll(welcomeMsg, "<user>", memberMention)
|
||||||
|
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: "👋 Welcome!",
|
||||||
|
Description: welcomeMsg,
|
||||||
|
Color: 0xFFA500,
|
||||||
|
Footer: &discordgo.MessageEmbedFooter{
|
||||||
|
Text: "ID: " + m.User.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if userAvatarURL != "" {
|
||||||
|
embed.Author = &discordgo.MessageEmbedAuthor{
|
||||||
|
Name: m.User.Username,
|
||||||
|
IconURL: userAvatarURL,
|
||||||
|
}
|
||||||
|
embed.Thumbnail = &discordgo.MessageEmbedThumbnail{
|
||||||
|
URL: userAvatarURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gif := strings.TrimSpace(settings.WelcomeGIFURL)
|
||||||
|
if gif == "" {
|
||||||
|
// Default GIF when none configured.
|
||||||
|
gif = "https://images-ext-1.discordapp.net/external/uJ6XfdK2WwDnei3RmNWUqSiOVboC4mK9r78TtgVE_9g/https/media.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif"
|
||||||
|
}
|
||||||
|
embed.Image = &discordgo.MessageEmbedImage{URL: gif}
|
||||||
|
|
||||||
|
if _, err := s.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{
|
||||||
|
Content: memberMention,
|
||||||
|
Embed: embed,
|
||||||
|
}); err != nil {
|
||||||
|
log.Printf("welcome: failed to send channel welcome in guild %s: %v", m.GuildID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional DM.
|
||||||
|
dmText := strings.TrimSpace(settings.WelcomeDM)
|
||||||
|
if dmText == "" {
|
||||||
|
// Default DM when none configured.
|
||||||
|
// "Welcome to <server>! Have fun!"
|
||||||
|
dmText = "Welcome to <server>! Have fun!"
|
||||||
|
}
|
||||||
|
dmText = strings.ReplaceAll(dmText, "{user}", memberMention)
|
||||||
|
if guildName != "" {
|
||||||
|
dmText = strings.ReplaceAll(dmText, "{server}", guildName)
|
||||||
|
dmText = strings.ReplaceAll(dmText, "<server>", guildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmCh, err := s.UserChannelCreate(m.User.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("welcome: failed to create DM channel for user %s: %v", m.User.ID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := s.ChannelMessageSend(dmCh.ID, dmText); err != nil {
|
||||||
|
log.Printf("welcome: failed to send DM welcome to user %s: %v", m.User.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"velox-bot/internal/db/repos/rpsrepo"
|
"velox-bot/internal/db/repos/rpsrepo"
|
||||||
"velox-bot/internal/db/repos/schedulerepo"
|
"velox-bot/internal/db/repos/schedulerepo"
|
||||||
"velox-bot/internal/db/repos/settingsrepo"
|
"velox-bot/internal/db/repos/settingsrepo"
|
||||||
|
"velox-bot/internal/db/repos/welcomerepo"
|
||||||
"velox-bot/internal/db/repos/twitchrepo"
|
"velox-bot/internal/db/repos/twitchrepo"
|
||||||
"velox-bot/internal/db/repos/usersettingsrepo"
|
"velox-bot/internal/db/repos/usersettingsrepo"
|
||||||
"velox-bot/internal/db/services"
|
"velox-bot/internal/db/services"
|
||||||
@@ -26,6 +27,7 @@ import (
|
|||||||
"velox-bot/internal/db/services/schedule"
|
"velox-bot/internal/db/services/schedule"
|
||||||
"velox-bot/internal/db/services/twitch"
|
"velox-bot/internal/db/services/twitch"
|
||||||
"velox-bot/internal/db/services/usersettings"
|
"velox-bot/internal/db/services/usersettings"
|
||||||
|
"velox-bot/internal/db/services/welcome"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -48,6 +50,7 @@ func main() {
|
|||||||
projectsRepo := projectsrepo.NewRepo(db)
|
projectsRepo := projectsrepo.NewRepo(db)
|
||||||
scheduleRepo := schedulerepo.NewRepo(db)
|
scheduleRepo := schedulerepo.NewRepo(db)
|
||||||
userSettingsRepo := usersettingsrepo.NewRepo(db)
|
userSettingsRepo := usersettingsrepo.NewRepo(db)
|
||||||
|
welcomeRepo := welcomerepo.NewRepo(db)
|
||||||
twitchRepo := twitchrepo.NewRepo(db)
|
twitchRepo := twitchrepo.NewRepo(db)
|
||||||
levelService := level.New(levelRepo, settingsRepo)
|
levelService := level.New(levelRepo, settingsRepo)
|
||||||
levelSettingsService := levelsettings.New(settingsRepo)
|
levelSettingsService := levelsettings.New(settingsRepo)
|
||||||
@@ -57,7 +60,8 @@ func main() {
|
|||||||
projectsService := projects.New(projectsRepo)
|
projectsService := projects.New(projectsRepo)
|
||||||
rpsService := rps.New(rpsRepo)
|
rpsService := rps.New(rpsRepo)
|
||||||
twitchService := twitch.New(twitchRepo, config.TwitchClientID)
|
twitchService := twitch.New(twitchRepo, config.TwitchClientID)
|
||||||
services := services.NewServices(levelService, levelSettingsService, meetingService, scheduleService, userSettingsService, projectsService, rpsService, twitchService)
|
welcomeService := welcome.New(welcomeRepo)
|
||||||
|
services := services.NewServices(levelService, levelSettingsService, meetingService, scheduleService, userSettingsService, projectsService, rpsService, twitchService, welcomeService)
|
||||||
|
|
||||||
bot, err := bot.NewBot(config.BotToken, config.AppID, config.GuildID, config.LavalinkHost, config.LavalinkPass, commands.AllCommands, services)
|
bot, err := bot.NewBot(config.BotToken, config.AppID, config.GuildID, config.LavalinkHost, config.LavalinkPass, commands.AllCommands, services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ CREATE TABLE IF NOT EXISTS twitch (
|
|||||||
CREATE TABLE IF NOT EXISTS levelsettings (
|
CREATE TABLE IF NOT EXISTS levelsettings (
|
||||||
guild_id BIGINT PRIMARY KEY,
|
guild_id BIGINT PRIMARY KEY,
|
||||||
levelsys BOOLEAN NOT NULL DEFAULT FALSE,
|
levelsys BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
role BIGINT,
|
|
||||||
levelreq INT,
|
|
||||||
message TEXT
|
message TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user