diff --git a/bot/bot.go b/bot/bot.go
index 58c80e5f..803ca471 100644
--- a/bot/bot.go
+++ b/bot/bot.go
@@ -34,7 +34,6 @@ import (
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/sabafly/gobot/database"
- "github.com/sabafly/gobot/ent/migrate"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
@@ -150,10 +149,7 @@ func run() error {
// return fmt.Errorf("cacheを開けません: %w", err)
// }
- if err := db.Schema.Create(ctx,
- migrate.WithForeignKeys(!config.DisableForeignKeys)); err != nil {
- return fmt.Errorf("スキーマを定義できません: %w", err)
- }
+ // ent schema migration is removed as we use GORM migration in database.NewDB
if _, err := translate.LoadDir(config.TranslateDir); err != nil {
return fmt.Errorf("翻訳ファイルが読み込めません path=%s: %w", config.TranslateDir, err)
@@ -168,11 +164,11 @@ func run() error {
}
emoji.SetDefaultRegistry(reg)
- component := components.New(ctx, db, *config, gormDB)
+ component := components.New(ctx, *config, gormDB)
component.Version = version
component.AddCommands(
- debug.Command(component),
+ debug.Command(component, db),
ping.Command(component),
message.Command(component),
role.Command(component),
diff --git a/bot/commands/debug/debug.go b/bot/commands/debug/debug.go
index 6de6be2a..6e36f0f8 100644
--- a/bot/commands/debug/debug.go
+++ b/bot/commands/debug/debug.go
@@ -21,33 +21,23 @@
package debug
import (
- "fmt"
"iter"
"log/slog"
"slices"
- "strings"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
- "github.com/disgoorg/json/v2"
"github.com/disgoorg/omit"
"github.com/disgoorg/snowflake/v2"
- "github.com/google/uuid"
- "github.com/redis/go-redis/v9"
- "github.com/sabafly/gobot/bot/commands/debug/db"
- "github.com/sabafly/gobot/bot/commands/role"
"github.com/sabafly/gobot/bot/components"
"github.com/sabafly/gobot/bot/components/generic"
"github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/rolepanelplaced"
- "github.com/sabafly/gobot/ent/schema"
- "github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/errors"
"github.com/sabafly/gobot/internal/i18n"
"github.com/sabafly/gobot/internal/translate"
)
-func Command(c *components.Components) *generic.Command {
+func Command(c *components.Components, entClient *ent.Client) *generic.Command {
return (&generic.Command{
Namespace: "debug",
Private: true,
@@ -86,28 +76,6 @@ func Command(c *components.Components) *generic.Command {
},
},
},
- discord.ApplicationCommandOptionSubCommandGroup{
- Name: "redis",
- Description: "redis",
- Options: []discord.ApplicationCommandOptionSubCommand{
- {
- Name: "import",
- Description: "import",
- Options: []discord.ApplicationCommandOption{
- discord.ApplicationCommandOptionString{
- Name: "addr",
- Description: "address",
- Required: true,
- },
- discord.ApplicationCommandOptionInt{
- Name: "db",
- Description: "db",
- Required: true,
- },
- },
- },
- },
- },
discord.ApplicationCommandOptionSubCommandGroup{
Name: "guild",
Description: "guild",
@@ -129,6 +97,16 @@ func Command(c *components.Components) *generic.Command {
},
},
},
+ discord.ApplicationCommandOptionSubCommandGroup{
+ Name: "migration",
+ Description: "database migration",
+ Options: []discord.ApplicationCommandOptionSubCommand{
+ {
+ Name: "ent_to_gorm",
+ Description: "migrate from ent to gorm",
+ },
+ },
+ },
},
},
},
@@ -222,122 +200,8 @@ func Command(c *components.Components) *generic.Command {
}
return nil
}),
- "/debug/redis/import": generic.CommandHandler(func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- client := redis.NewClient(&redis.Options{
- Addr: event.SlashCommandInteractionData().String("addr"),
- DB: event.SlashCommandInteractionData().Int("db"),
- })
- // GuildData
- guildCmd := client.HGetAll(event, "guild-data")
- if err := guildCmd.Err(); err != nil {
- return errors.NewError(err)
- }
-
- rpv2Cmd := client.HGetAll(event, "role-panel-v2")
- if err := rpv2Cmd.Err(); err != nil {
- return errors.NewError(err)
- }
-
- rpv2List := map[uuid.UUID]db.RolePanelV2{}
-
- for _, v := range rpv2Cmd.Val() {
- var rpv2 db.RolePanelV2
- if err := json.Unmarshal([]byte(v), &rpv2); err != nil {
- slog.Error("unmarshalに失敗", "err", err)
- continue
- }
- rpv2List[rpv2.ID] = rpv2
- }
-
- for _, v := range guildCmd.Val() {
- var guildData db.GuildData
- if err := json.Unmarshal([]byte(v), &guildData); err != nil {
- slog.Error("unmarshalに失敗", "err", err)
- continue
- }
- if guildData.DataVersion == nil || *guildData.DataVersion != 11 {
- continue
- }
-
- g, err := c.GuildCreateID(event, guildData.ID)
- if err != nil {
- slog.Error("guild取得に失敗", slog.Any("err", err))
- continue
- }
-
- var createRolePanelBulk []*ent.RolePanelCreate
-
- for u := range guildData.RolePanelV2 {
- rpv2, ok := rpv2List[u]
- if !ok {
- continue
- }
-
- roles := make([]schema.Role, len(rpv2.Roles))
-
- for i, r := range rpv2.Roles {
- roles[i] = schema.Role{
- ID: r.RoleID,
- Name: r.RoleName,
- Emoji: r.Emoji,
- }
- }
-
- createRolePanelBulk = append(createRolePanelBulk,
- c.DB().RolePanel.Create().
- SetID(rpv2.ID).
- SetRoles(roles).
- SetName(rpv2.Name).
- SetGuild(g).
- SetDescription(rpv2.Description),
- )
- }
-
- rolePanels, err := c.DB().RolePanel.CreateBulk(createRolePanelBulk...).Save(event)
- if err != nil {
- return errors.NewError(err)
- }
-
- placedIDMap := map[[2]snowflake.ID]uuid.UUID{}
- for k, u := range guildData.RolePanelV2Placed {
- ks := strings.Split(k, "/")
- channelID, messageID := snowflake.MustParse(ks[0]), snowflake.MustParse(ks[1])
- placedIDMap[[2]snowflake.ID{channelID, messageID}] = u
- }
-
- for k, u := range placedIDMap {
- index := slices.IndexFunc(rolePanels, func(rp *ent.RolePanel) bool { return rp.ID == u })
- if index == -1 {
- continue
- }
-
- keyString := fmt.Sprintf("%d/%d", k[0], k[1])
-
- placed := c.DB().RolePanelPlaced.Create().
- SetChannelID(k[0]).
- SetMessageID(k[1]).
- SetType(rolepanelplaced.Type(guildData.RolePanelV2PlacedConfig[keyString].PanelType)).
- SetButtonType(builtin.Or(guildData.RolePanelV2PlacedConfig[keyString].ButtonStyle != 0, guildData.RolePanelV2PlacedConfig[keyString].ButtonStyle, 1)).
- SetFoldingSelectMenu(guildData.RolePanelV2PlacedConfig[keyString].SimpleSelectMenu).
- SetUseDisplayName(guildData.RolePanelV2PlacedConfig[keyString].UseDisplayName).
- SetShowName(guildData.RolePanelV2PlacedConfig[keyString].ButtonShowName).
- SetHideNotice(guildData.RolePanelV2PlacedConfig[keyString].HideNotice).
- SetName(rolePanels[index].Name).
- SetDescription(rolePanels[index].Description).
- SetRoles(rolePanels[index].Roles).
- SetRolePanel(rolePanels[index]).
- SetGuild(g).
- SaveX(event)
-
- role.UpdateRolePanel(event, placed, event.Locale(), event.Client())
- }
-
- }
-
- if err := event.RespondMessage(discord.NewMessageBuilder().SetContent("OK")); err != nil {
- return errors.NewError(err)
- }
- return nil
+ "/debug/migration/ent_to_gorm": generic.CommandHandler(func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ return migrateEntToGormHandler(c, entClient, event)
}),
},
}).SetComponent(c)
diff --git a/bot/commands/debug/migration.go b/bot/commands/debug/migration.go
new file mode 100644
index 00000000..65fb2d3b
--- /dev/null
+++ b/bot/commands/debug/migration.go
@@ -0,0 +1,273 @@
+package debug
+
+import (
+ "encoding/json"
+ "log/slog"
+
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/disgo/events"
+ "github.com/sabafly/gobot/bot/components"
+ gormModels "github.com/sabafly/gobot/database/models"
+ "github.com/sabafly/gobot/ent"
+ "github.com/sabafly/gobot/internal/errors"
+ "gorm.io/gorm"
+)
+
+func migrateEntToGormHandler(c *components.Components, entClient *ent.Client, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ if err := event.DeferCreateMessage(false); err != nil {
+ return errors.NewError(err)
+ }
+
+ ctx := c.Ctx()
+ gormDB := c.GormDB()
+
+ if err := gormDB.Transaction(func(tx *gorm.DB) error {
+ slog.Info("Starting migration...")
+
+ // 1. Users
+ slog.Info("Migrating Users...")
+ users, err := entClient.User.Query().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, u := range users {
+ gu := gormModels.User{
+ ID: u.ID,
+ Name: u.Name,
+ CreatedAt: u.CreatedAt,
+ Locale: u.Locale,
+ XP: u.Xp,
+ }
+ if err := tx.Save(&gu).Error; err != nil {
+ return err
+ }
+ }
+
+ // 2. Guilds
+ slog.Info("Migrating Guilds...")
+ guilds, err := entClient.Guild.Query().WithOwner().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, g := range guilds {
+ gg := gormModels.Guild{
+ ID: g.ID,
+ Name: g.Name,
+ Locale: g.Locale,
+ LevelUpMessage: g.LevelUpMessage,
+ LevelUpChannel: g.LevelUpChannel,
+ LevelUpExcludeChannel: g.LevelUpExcludeChannel,
+ LevelMee6Imported: g.LevelMee6Imported,
+ LevelRole: g.LevelRole,
+ Permissions: g.Permissions,
+ RemindCount: g.RemindCount,
+ RolePanelEditTimes: g.RolePanelEditTimes,
+ BumpEnabled: g.BumpEnabled,
+ BumpMessageTitle: g.BumpMessageTitle,
+ BumpMessage: g.BumpMessage,
+ BumpRemindMessageTitle: g.BumpRemindMessageTitle,
+ BumpRemindMessage: g.BumpRemindMessage,
+ UpEnabled: g.UpEnabled,
+ UpMessageTitle: g.UpMessageTitle,
+ UpMessage: g.UpMessage,
+ UpRemindMessageTitle: g.UpRemindMessageTitle,
+ UpRemindMessage: g.UpRemindMessage,
+ BumpMention: g.BumpMention,
+ UpMention: g.UpMention,
+ LevelingDisabled: g.LevelingDisabled,
+ OwnerID: &g.Edges.Owner.ID,
+ }
+ if err := tx.Save(&gg).Error; err != nil {
+ return err
+ }
+ }
+
+ // 3. WordSuffix
+ slog.Info("Migrating WordSuffix...")
+ wordSuffixes, err := entClient.WordSuffix.Query().WithGuild().WithOwner().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, ws := range wordSuffixes {
+ gws := gormModels.WordSuffix{
+ ID: ws.ID,
+ Suffix: ws.Suffix,
+ Expired: ws.Expired,
+ OwnerID: ws.Edges.Owner.ID,
+ Rule: string(ws.Rule),
+ }
+ if ws.Edges.Guild != nil {
+ gws.GuildID = &ws.Edges.Guild.ID
+ }
+ if err := tx.Save(&gws).Error; err != nil {
+ return err
+ }
+ }
+
+ // 4. RolePanel Family
+ slog.Info("Migrating RolePanel...")
+ rolePanels, err := entClient.RolePanel.Query().WithGuild().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, rp := range rolePanels {
+ grp := gormModels.RolePanel{
+ ID: rp.ID,
+ Name: rp.Name,
+ Description: rp.Description,
+ UpdatedAt: rp.UpdatedAt,
+ AppliedAt: rp.AppliedAt,
+ GuildID: rp.Edges.Guild.ID,
+ }
+ grp.Roles = make([]gormModels.Role, len(rp.Roles))
+ for i, r := range rp.Roles {
+ grp.Roles[i] = gormModels.Role{ID: r.ID, Name: r.Name, Emoji: r.Emoji}
+ }
+ if err := tx.Save(&grp).Error; err != nil {
+ return err
+ }
+ }
+
+ slog.Info("Migrating RolePanelEdit...")
+ rpEdits, err := entClient.RolePanelEdit.Query().WithGuild().WithParent().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, rpe := range rpEdits {
+ grpe := gormModels.RolePanelEdit{
+ ID: rpe.ID,
+ ChannelID: rpe.ChannelID,
+ EmojiAuthor: rpe.EmojiAuthor,
+ Token: rpe.Token,
+ SelectedRole: rpe.SelectedRole,
+ Modified: rpe.Modified,
+ Name: rpe.Name,
+ Description: rpe.Description,
+ GuildID: rpe.Edges.Guild.ID,
+ ParentID: rpe.Edges.Parent.ID,
+ }
+ grpe.Roles = make([]gormModels.Role, len(rpe.Roles))
+ for i, r := range rpe.Roles {
+ grpe.Roles[i] = gormModels.Role{ID: r.ID, Name: r.Name, Emoji: r.Emoji}
+ }
+ if err := tx.Save(&grpe).Error; err != nil {
+ return err
+ }
+ }
+
+ slog.Info("Migrating RolePanelPlaced...")
+ rpPlaced, err := entClient.RolePanelPlaced.Query().WithGuild().WithRolePanel().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, rpp := range rpPlaced {
+ grpp := gormModels.RolePanelPlaced{
+ ID: rpp.ID,
+ MessageID: rpp.MessageID,
+ ChannelID: rpp.ChannelID,
+ Type: string(rpp.Type),
+ ButtonType: rpp.ButtonType,
+ ShowName: rpp.ShowName,
+ FoldingSelectMenu: rpp.FoldingSelectMenu,
+ HideNotice: rpp.HideNotice,
+ UseDisplayName: rpp.UseDisplayName,
+ CreatedAt: rpp.CreatedAt,
+ Uses: rpp.Uses,
+ Name: rpp.Name,
+ Description: rpp.Description,
+ UpdatedAt: rpp.UpdatedAt,
+ GuildID: rpp.Edges.Guild.ID,
+ RolePanelID: rpp.Edges.RolePanel.ID,
+ }
+ grpp.Roles = make([]gormModels.Role, len(rpp.Roles))
+ for i, r := range rpp.Roles {
+ grpp.Roles[i] = gormModels.Role{ID: r.ID, Name: r.Name, Emoji: r.Emoji}
+ }
+ if err := tx.Save(&grpp).Error; err != nil {
+ return err
+ }
+ }
+
+ // 5. MessagePin
+ slog.Info("Migrating MessagePin...")
+ pins, err := entClient.MessagePin.Query().WithGuild().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, p := range pins {
+ gmp := gormModels.MessagePin{
+ ID: p.ID,
+ GuildID: p.Edges.Guild.ID,
+ ChannelID: p.ChannelID,
+ Content: p.Content,
+ Embeds: p.Embeds,
+ BeforeID: p.BeforeID,
+ }
+ if rlData, err := json.Marshal(p.RateLimit); err == nil {
+ var gormRL gormModels.RateLimit
+ if err := json.Unmarshal(rlData, &gormRL); err == nil {
+ gmp.RateLimit = gormRL
+ }
+ }
+ if err := tx.Save(&gmp).Error; err != nil {
+ return err
+ }
+ }
+
+ // 6. MessageRemind
+ slog.Info("Migrating MessageRemind...")
+ reminds, err := entClient.MessageRemind.Query().WithGuild().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, r := range reminds {
+ gmr := gormModels.MessageRemind{
+ ID: r.ID,
+ GuildID: r.Edges.Guild.ID,
+ ChannelID: r.ChannelID,
+ AuthorID: r.AuthorID,
+ Time: r.Time,
+ Content: r.Content,
+ Name: r.Name,
+ }
+ if err := tx.Save(&gmr).Error; err != nil {
+ return err
+ }
+ }
+
+ // 7. Members
+ slog.Info("Migrating Members...")
+ members, err := entClient.Member.Query().WithGuild().WithUser().All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, m := range members {
+ gm := gormModels.Member{
+ GuildID: m.Edges.Guild.ID,
+ UserID: m.Edges.User.ID,
+ Permission: m.Permission,
+ XP: m.Xp,
+ LastXP: m.LastXp,
+ MessageCount: m.MessageCount,
+ LastNotifiedLevel: m.LastNotifiedLevel,
+ LastMessageHashes: m.LastMessageHashes,
+ }
+ if err := tx.Save(&gm).Error; err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }); err != nil {
+ slog.Error("Migration failed", slog.Any("err", err))
+ return errors.NewError(err)
+ }
+
+ if err := event.RespondMessage(
+ discord.NewMessageBuilder().
+ SetContent("Migration complete!"),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
diff --git a/bot/commands/level/level.go b/bot/commands/level/level.go
index 9c04cf7a..37211684 100644
--- a/bot/commands/level/level.go
+++ b/bot/commands/level/level.go
@@ -21,37 +21,20 @@
package level
import (
- "cmp"
"context"
- "crypto/sha1"
- "encoding/hex"
- "encoding/json"
- "fmt"
"log/slog"
- "math/rand/v2"
- "net/http"
- "slices"
"strconv"
"strings"
"time"
- "entgo.io/ent/dialect/sql"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
- "github.com/disgoorg/disgo/events"
"github.com/disgoorg/snowflake/v2"
- "github.com/sabafly/gobot/bot/commands/gopoint"
"github.com/sabafly/gobot/bot/components"
"github.com/sabafly/gobot/bot/components/generic"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/member"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
- "github.com/sabafly/gobot/internal/discordutil"
- "github.com/sabafly/gobot/internal/embeds"
- "github.com/sabafly/gobot/internal/errors"
- "github.com/sabafly/gobot/internal/smap"
- "github.com/sabafly/gobot/internal/translate"
- "github.com/sabafly/gobot/internal/xppoint"
+ "gorm.io/gorm"
)
func Command(c *components.Components) components.Command {
@@ -252,809 +235,153 @@ func Command(c *components.Components) components.Command {
Permission: []generic.Permission{
generic.PermissionDefaultString("level.required-point"),
},
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- mem, err := c.MemberCreate(event, event.User(), *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- level := uint64(0)
- l, ok := event.SlashCommandInteractionData().OptInt("level")
- level = uint64(l)
- if !ok {
- level = mem.Xp.Level() + 1
- }
- builder := discord.NewMessageBuilder()
- builder.SetEmbeds(
- embeds.SetEmbedProperties(discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.required-point.embed.title", translate.WithTemplate(map[string]any{"Level": level}))).
- SetDescriptionf("# `%d`xp\n%s\n%s",
- xppoint.TotalPoint(level),
- translate.Message(event.Locale(), "components.level.required-point.embed.description", translate.WithTemplate(map[string]any{"User": event.Member().EffectiveName(), "Xp": mem.Xp})),
- translate.Message(event.Locale(), "components.level.required-point.embed.description.diff", translate.WithTemplate(map[string]any{"Xp": builtin.Or(xppoint.TotalPoint(level) > uint64(mem.Xp), xppoint.TotalPoint(level)-uint64(mem.Xp), 0)})),
- ).
- Build()),
- )
- if err := event.CreateMessage(builder.BuildCreate()); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ CommandHandler: requiredPointHandler,
},
"/level/rank": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionDefaultString("level.rank"),
},
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- target, ok := event.SlashCommandInteractionData().OptMember("target")
- if !ok {
- target = *event.Member()
- }
- if target.User.Bot || target.User.System {
- return errors.NewError(errors.ErrorMessage("errors.invalid.bot.target", event))
- }
- m, err := c.MemberCreate(event, target.User, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- gl, err := c.GuildRequest(event.Client(), g.ID)
- if err != nil {
- return errors.NewError(err)
- }
- members, err := g.QueryMembers().Order(
- member.ByXp(
- sql.OrderDesc(),
- ),
- ).All(event)
- if err != nil {
- return errors.NewError(err)
- }
- ids := make([]int, len(members))
- for i, m := range slices.All(members) {
- ids[i] = m.ID
- }
- index := slices.Index(ids, m.ID)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- levelMessage(g, gl, m, index, target.Member, event),
- ),
- ).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ CommandHandler: rankHandler,
},
"/level/leaderboard": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionDefaultString("level.leaderboard"),
},
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- const pageCount = 25
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- gl, err := c.GuildRequest(event.Client(), g.ID)
- if err != nil {
- return errors.NewError(err)
- }
- page := event.SlashCommandInteractionData().Int("page")
- page = builtin.Or(page > 0, page, 1)
- count := g.QueryMembers().CountX(event)
- if page > count/pageCount+1 {
- return errors.NewError(errors.ErrorMessage("errors.invalid.page", event))
- }
- members := g.QueryMembers().
- Order(member.ByXp(sql.OrderDesc())).
- Offset((page - 1) * pageCount).
- Limit(pageCount).
- AllX(event)
- var leaderboard string
- for i, m := range members {
- leaderboard += fmt.Sprintf("**#%d | %s XP: `%d` Level: `%d`**\n",
- i+1+((page-1)*pageCount),
- discord.UserMention(m.UserID),
- m.Xp, m.Xp.Level(),
- )
- }
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetEmbedAuthor(
- &discord.EmbedAuthor{
- Name: g.Name,
- IconURL: builtin.NonNil(gl.IconURL()),
- },
- ).
- SetTitlef("🏆%s(%d/%d)",
- translate.Message(event.Locale(), "components.level.leaderboard.title"),
- page,
- count/pageCount+1,
- ).
- SetDescription(leaderboard).
- Build(),
- ),
- ).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ CommandHandler: leaderboardHandler,
},
"/level/transfer": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.transfer"),
},
- DiscordPerm: discord.PermissionManageGuild.Add(discord.PermissionModerateMembers),
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- gl, err := c.GuildRequest(event.Client(), g.ID)
- if err != nil {
- return errors.NewError(err)
- }
-
- to := event.SlashCommandInteractionData().Member("to")
- from, ok := event.SlashCommandInteractionData().OptMember("from")
- if !ok {
- from = *event.Member()
- }
- if from.User.Bot || from.User.System || to.User.Bot || to.User.System {
- return errors.NewError(errors.ErrorMessage("errors.invalid.bot.target", event))
- }
- if to.User.ID == from.User.ID {
- return errors.NewError(errors.ErrorMessage("errors.invalid.self.target", event))
- }
-
- fromUser, err := c.MemberCreate(event, from.User, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- toUser, err := c.MemberCreate(event, to.User, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- movedXp := uint64(fromUser.Xp)
- fromUser.Xp = xppoint.XP(0)
- fromUser = fromUser.Update().SetXp(fromUser.Xp).ClearLastNotifiedLevel().SaveX(event)
- if toUser, err = addXp(event, toUser.Update(), movedXp, event.Client(), toUser, g, event.Channel().ID(), to.EffectiveName(), true); err != nil {
- return errors.NewError(err)
- }
- ids := g.QueryMembers().Order(
- member.ByXp(
- sql.OrderDesc(),
- ),
- ).IDsX(event)
- fromIndex := slices.Index(ids, fromUser.ID)
- toIndex := slices.Index(ids, toUser.ID)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedsProperties(
- []discord.Embed{
- levelMessage(g, gl, fromUser, fromIndex, from.Member, event),
- levelMessage(g, gl, toUser, toIndex, to.Member, event),
- discord.NewEmbedBuilder().
- SetTitlef("`%d`xp 移動しました", movedXp).
- Build(),
- },
- )...,
- ).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild.Add(discord.PermissionModerateMembers),
+ CommandHandler: transferHandler,
},
"/level/up/message": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.up.message"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- if err := event.Modal(
- discord.NewModalCreateBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.up.message.modal.title")).
- SetCustomID("level:up_message_modal").
- SetComponents(
- discord.NewLabel(translate.Message(event.Locale(), "components.level.up.message.modal.input.message"),
- discord.TextInputComponent{
- CustomID: "message",
- Style: discord.TextInputStyleParagraph,
- MinLength: builtin.Ptr(1),
- MaxLength: 140,
- Required: true,
- Placeholder: translate.Message(event.Locale(), "components.level.up.message.modal.input.message.placeholder"),
- Value: g.LevelUpMessage,
- },
- ),
- ).
- Build(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: upMessageHandler,
},
"/level/up/message-channel": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.message-channel"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- if channel, ok := event.SlashCommandInteractionData().OptChannel("channel"); ok {
- g = g.Update().
- SetLevelUpChannel(channel.ID).
- SaveX(event)
- } else {
- g = g.Update().
- ClearLevelUpChannel().
- SaveX(event)
- }
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetContent(translate.Message(event.Locale(), "components.level.up.message-channel.message",
- translate.WithTemplate(map[string]any{
- "Channel": builtin.Or(g.LevelUpChannel != nil,
- discord.ChannelMention(builtin.NonNil(g.LevelUpChannel)),
- translate.Message(event.Locale(), "components.level.up.message-channel.default"),
- ),
- }),
- )).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: upMessageChannelHandler,
},
"/level/exclude-channel/add": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.exclude-channel.add"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- channel := event.SlashCommandInteractionData().Channel("channel")
- if slices.Contains(g.LevelUpExcludeChannel, channel.ID) {
- return errors.NewError(errors.ErrorMessage("errors.already_exist", event))
- }
- g.LevelUpExcludeChannel = append(g.LevelUpExcludeChannel, channel.ID)
- g.Update().
- SetLevelUpExcludeChannel(g.LevelUpExcludeChannel).
- ExecX(event)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetContent(translate.Message(event.Locale(), "components.level.exclude-channel.add.message",
- translate.WithTemplate(map[string]any{"Channel": discord.ChannelMention(channel.ID)}),
- )).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: excludeChannelAddHandler,
},
"/level/exclude-channel/remove": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.exclude-channel.remove"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- channel := event.SlashCommandInteractionData().Channel("channel")
- index := slices.Index(g.LevelUpExcludeChannel, channel.ID)
- if index == -1 {
- return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
- }
- g.LevelUpExcludeChannel = slices.Delete(g.LevelUpExcludeChannel, index, index+1)
- g.Update().
- SetLevelUpExcludeChannel(g.LevelUpExcludeChannel).
- ExecX(event)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetContent(translate.Message(event.Locale(), "components.level.exclude-channel.remove.message",
- translate.WithTemplate(map[string]any{"Channel": discord.ChannelMention(channel.ID)}),
- )).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: excludeChannelRemoveHandler,
},
"/level/exclude-channel/clear": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.exclude-channel.clear"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- g.Update().
- ClearLevelUpExcludeChannel().
- ExecX(event)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetContent(translate.Message(event.Locale(), "components.level.exclude-channel.clear.message")).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: excludeChannelClearHandler,
},
"/level/import-mee6": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.import-mee6"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
-
- if g.LevelMee6Imported {
- return errors.NewError(errors.ErrorMessage("components.level.import-mee6.message.already", event))
- }
-
- var members []discord.Member
- memberCount := 1000
- afterID := snowflake.ID(0)
- for memberCount == 1000 {
- m, err := event.Client().Rest.GetMembers(*event.GuildID(), memberCount, afterID)
- if err != nil {
- return errors.NewError(err)
- }
- memberCount = len(m)
- members = append(members, m...)
- afterID = m[len(m)-1].User.ID
- }
-
- slog.Info("mee6インポート", slog.Any("gid", event.GuildID()), slog.Int("member_count", len(members)))
-
- memberCount = 0
- url := fmt.Sprintf("https://mee6.xyz/api/plugins/levels/leaderboard/%s", event.GuildID().String())
- for page := 0; true; page++ {
- response, err := http.Get(fmt.Sprintf("%s?page=%d", url, page))
- if err != nil || response.StatusCode != http.StatusOK {
- switch response.StatusCode {
- case http.StatusUnauthorized:
- if err := event.RespondMessage(
- discord.NewMessageBuilder().
- SetContent(
- fmt.Sprintf("# FAILED\n```| STATUS CODE | %d\n| RESPONSE | %v```%s",
- response.StatusCode,
- err,
- translate.Message(event.Locale(), "components.level.import-mee6.message.unauthorized",
- translate.WithTemplate(map[string]any{"GuildID": *event.GuildID()}),
- ),
- ),
- ),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- default:
- if err := event.RespondMessage(
- discord.NewMessageBuilder().
- SetContent(fmt.Sprintf("# FAILED\n```| STATUS CODE | %d\n| RESPONSE | %v```", response.StatusCode, err)),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- }
- }
- var leaderboard mee6LeaderBoard
- if err := json.NewDecoder(response.Body).Decode(&leaderboard); err != nil {
- return errors.NewError(err)
- }
- _ = response.Body.Close()
- if len(leaderboard.Players) < 1 {
- break
- }
- for _, player := range leaderboard.Players {
- index := slices.IndexFunc(members, func(m discord.Member) bool { return m.User.ID == player.ID })
- if index == -1 {
- continue
- }
- slog.Info("mee6メンバーインポート", slog.Any("gid", event.GuildID()), slog.Any("member_id", player.ID))
- m, err := c.MemberCreate(event, members[index].User, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- m.Update().SetXp(xppoint.XP(player.Xp)).ExecX(event)
- memberCount++
- }
- }
-
- g.Update().SetLevelMee6Imported(true).ExecX(event)
-
- if err := event.RespondMessage(
- discord.NewMessageBuilder().
- SetContent(fmt.Sprintf("# SUCCEED\n```| IMPORTED MEMBER COUNT | %d```", memberCount)),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: importMee6Handler,
},
"/level/reset": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.reset"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- target := event.SlashCommandInteractionData().Member("target")
- m, err := c.MemberCreate(event, target.User, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- m.Update().SetXp(xppoint.XP(0)).ExecX(event)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetContent(translate.Message(event.Locale(), "components.level.reset.message",
- translate.WithTemplate(map[string]any{"User": discord.UserMention(target.User.ID)}),
- )).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: resetHandler,
},
"/level/exclude-channel/list": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.exclude-channel.list"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- var listStr string
- for i, id := range g.LevelUpExcludeChannel {
- listStr += fmt.Sprintf("%d. %s\n", i+1, discord.ChannelMention(id))
- }
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.exclude-channel.list.message")).
- SetDescription(
- builtin.Or(listStr != "",
- listStr,
- "- "+translate.Message(event.Locale(), "components.level.exclude-channel.list.message.none"),
- ),
- ).
- Build(),
- ),
- ).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: excludeChannelListHandler,
},
"/level/role/set": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.role.set"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- if len(g.LevelRole) >= 20 {
- return errors.NewError(errors.ErrorMessage("errors.create.reach_max", event))
- }
- level := event.SlashCommandInteractionData().Int("level")
- role := event.SlashCommandInteractionData().Role("role")
- g.LevelRole = builtin.NonNilMap(g.LevelRole)
- g.LevelRole[level] = role.ID
- self, valid := event.Client().Caches.SelfMember(*event.GuildID())
- if !valid {
- return errors.NewError(errors.ErrorMessage("errors.invalid.self", event))
- }
- var roles []discord.Role
- for _, id := range self.RoleIDs {
- role, ok := event.Client().Caches.Role(*event.GuildID(), id)
- if !ok {
- continue
- }
- roles = append(roles, role)
- }
- highestRole := discordutil.GetHighestRole(roles)
- if highestRole == nil {
- return errors.NewError(errors.ErrorMessage("errors.invalid.self", event))
- }
-
- if role.Managed || role.Compare(*highestRole) != -1 || role.ID == *event.GuildID() {
- return errors.NewError(errors.ErrorMessage("errors.invalid.role", event))
- }
-
- g.Update().
- SetLevelRole(g.LevelRole).
- ExecX(event)
-
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.role.set.message.embed.title")).
- SetDescription(
- translate.Message(event.Locale(), "components.level.role.set.message.embed.description",
- translate.WithTemplate(map[string]any{
- "Level": strconv.Itoa(level),
- "Role": discord.RoleMention(role.ID),
- }),
- ),
- ).
- Build(),
- ),
- ).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: roleSetHandler,
},
"/level/role/list": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.role.list"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- g.LevelRole = builtin.NonNilMap(g.LevelRole)
- var listStr string
- for k, v := range smap.MakeSortMap(g.LevelRole).Iter(cmp.Compare[int]) {
- listStr += "- " + translate.Message(event.Locale(), "components.level.role.list.message",
- translate.WithTemplate(map[string]any{
- "Level": strconv.Itoa(k),
- "Role": discord.RoleMention(v),
- }),
- ) + "\n"
- }
- if err := event.RespondMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.role.list.message.embed.title")).
- SetDescription(
- builtin.Or(listStr != "",
- listStr,
- translate.Message(event.Locale(), "components.level.role.list.message.none"),
- ),
- ).
- Build(),
- ),
- ),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: roleListHandler,
},
"/level/role/remove": generic.PCommandHandler{
Permission: []generic.Permission{
generic.PermissionString("level.role.remove"),
},
- DiscordPerm: discord.PermissionManageGuild,
- CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- g.LevelRole = builtin.NonNilMap(g.LevelRole)
- level := event.SlashCommandInteractionData().Int("level")
- r, ok := g.LevelRole[level]
- if !ok {
- return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
- }
- delete(g.LevelRole, level)
-
- g.Update().
- SetLevelRole(g.LevelRole).
- ExecX(event)
-
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.role.remove.message.embed.title")).
- SetDescription(
- translate.Message(event.Locale(), "components.level.role.remove.message.embed.description",
- translate.WithTemplate(map[string]any{
- "Level": strconv.Itoa(level),
- "Role": discord.RoleMention(r),
- }),
- ),
- ).
- Build(),
- ),
- ).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
- }
- return nil
- },
+ DiscordPerm: discord.PermissionManageGuild,
+ CommandHandler: roleRemoveHandler,
},
},
ModalHandlers: map[string]generic.ModalHandler{
- "level:up_message_modal": func(c *components.Components, event *events.ModalSubmitInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- g = g.Update().
- SetLevelUpMessage(event.ModalSubmitInteraction.Data.Text("message")).
- SaveX(event)
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.level.up.message.message")).
- SetDescription(g.LevelUpMessage).
- Build(),
- ),
- ).
- BuildCreate(),
- ); err != nil {
- return nil
- }
- return nil
- },
- },
- EventHandler: func(c *components.Components, event bot.Event) errors.Error {
- switch event := event.(type) {
- case *events.GuildMessageCreate:
- if event.Message.Author.Bot || event.Message.Author.System || event.Message.Type.System() {
- return nil
- }
- if event.Message.Type != discord.MessageTypeDefault && event.Message.Type != discord.MessageTypeReply {
- return nil
- }
- g, err := c.GuildCreateID(event, event.GuildID)
- if err != nil {
- return errors.NewError(err)
- }
- if g.LevelingDisabled {
- return nil
- }
- if slices.Contains(g.LevelUpExcludeChannel, event.ChannelID) {
- return nil
- }
- var channel discord.GuildChannel
- channel, ok := event.Channel()
- if !ok {
- c, err := event.Client().Rest.GetChannel(event.ChannelID)
- if err != nil {
- return errors.NewError(err)
- }
- channel, _ = c.(discord.GuildChannel)
- }
- if channel.ParentID() != nil && slices.Contains(g.LevelUpExcludeChannel, *channel.ParentID()) {
- return nil
- }
- m, err := c.MemberCreate(event, event.Message.Author, event.GuildID)
- if err != nil {
- return errors.NewError(err)
- }
- hash := sha1.Sum([]byte(event.Message.Content))
- hashStr := hex.EncodeToString(hash[:])
- if slices.Contains(m.LastMessageHashes, hashStr) {
- return nil
- }
- if len(m.LastMessageHashes) >= 10 {
- m.LastMessageHashes = slices.Delete(m.LastMessageHashes, 0, 1)
- }
- m.LastMessageHashes = append(m.LastMessageHashes, hashStr)
- m.Update().
- SetLastMessageHashes(m.LastMessageHashes).
- SaveX(event)
-
- if _, err = addXp(event, m.Update(), rand.N[uint64](16)+15, event.Client(), m, g, event.ChannelID, event.Message.Author.EffectiveName(), false); err != nil {
- return errors.NewError(err)
- }
-
- if err := gopoint.AddPoint(c, m.UserID, g.ID, rand.Int64N(2)*50); err != nil {
- slog.Error("ポイント追加に失敗", slog.Any("err", err), slog.Any("user_id", m.UserID), slog.Any("guild_id", g.ID))
- }
-
- }
- return nil
+ "level:up_message_modal": upMessageModalHandler,
},
+ EventHandler: eventHandler,
}).SetComponent(c)
}
-func addXp(ctx context.Context, memberUpdate *ent.MemberUpdateOne, xp uint64, client *bot.Client, m *ent.Member, g *ent.Guild, channelID snowflake.ID, username string, ignoreCooldown bool) (*ent.Member, error) {
- before := builtin.NonNilOrDefault(m.LastNotifiedLevel, m.Xp.Level())
- if ignoreCooldown || time.Now().After(m.LastXp.Add(time.Minute*3)) {
- m.Xp.Add(xp)
- memberUpdate.
- SetXp(m.Xp).
- SetLastXp(time.Now())
+func addXp(ctx context.Context, xp uint64, client *bot.Client, m *models.Member, g *models.Guild, channelID snowflake.ID, username string, ignoreCooldown bool, db *gorm.DB) (*models.Member, error) {
+ before := builtin.NonNilOrDefault(m.LastNotifiedLevel, m.XP.Level())
+ if ignoreCooldown || time.Now().After(m.LastXP.Add(time.Minute*3)) {
+ m.XP.Add(xp)
+ m.LastXP = time.Now()
+ }
+ after := m.XP.Level()
+ m.LastNotifiedLevel = &after
+ m.MessageCount++
+
+ if err := db.Save(m).Error; err != nil {
+ return m, err
}
- after := m.Xp.Level()
- m = memberUpdate.
- SetLastNotifiedLevel(after).
- SetMessageCount(m.MessageCount + 1).
- SaveX(ctx)
+
if before < after {
- for i := range after - before {
- if err := levelUp(g, before+i+1, client, g.ID, m); err != nil {
- return m, err
- }
+ for i := uint64(0); i < after-before; i++ {
+ _ = levelUp(g, before+i+1, client, g.ID, m)
}
- // レベルアップ通知
content := g.LevelUpMessage
content = strings.ReplaceAll(content, "{user}", discord.UserMention(m.UserID))
content = strings.ReplaceAll(content, "{username}", username)
content = strings.ReplaceAll(content, "{before_level}", strconv.FormatUint(before, 10))
content = strings.ReplaceAll(content, "{after_level}", strconv.FormatUint(after, 10))
- content = strings.ReplaceAll(content, "{xp}", strconv.FormatUint(uint64(m.Xp), 10))
- if _, err := client.Rest.
- CreateMessage(
- builtin.Or(builtin.NonNil(g.LevelUpChannel) != 0, builtin.NonNil(g.LevelUpChannel), channelID),
- discord.NewMessageBuilder().
- SetContent(content).
- BuildCreate(),
- ); err != nil {
+ content = strings.ReplaceAll(content, "{xp}", strconv.FormatUint(uint64(m.XP), 10))
+
+ targetChannel := channelID
+ if g.LevelUpChannel != nil && *g.LevelUpChannel != 0 {
+ targetChannel = *g.LevelUpChannel
+ }
+
+ if _, err := client.Rest.CreateMessage(targetChannel, discord.NewMessageBuilder().SetContent(content).BuildCreate()); err != nil {
return m, err
}
}
return m, nil
}
-func levelUp(g *ent.Guild, after uint64, client *bot.Client, guildID snowflake.ID, m *ent.Member) error {
- // レベルロール
- r, ok := g.LevelRole[int(after)]
+func levelUp(g *models.Guild, after uint64, client *bot.Client, guildID snowflake.ID, m *models.Member) error {
+ rID, ok := g.LevelRole[int(after)]
if ok {
- if err := client.Rest.AddMemberRole(guildID, m.UserID, r); err != nil {
+ if err := client.Rest.AddMemberRole(guildID, m.UserID, rID); err != nil {
slog.Error("レベルロール付与に失敗", slog.Any("err", err))
}
}
diff --git a/bot/commands/level/level_components.go b/bot/commands/level/level_components.go
new file mode 100644
index 00000000..375c8a78
--- /dev/null
+++ b/bot/commands/level/level_components.go
@@ -0,0 +1,116 @@
+/*
+ * gobot -- a useful discord bot
+ *
+ * Copyright (C) 2024 Sabafly Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package level
+
+import (
+ "crypto/sha1"
+ "encoding/hex"
+ "log/slog"
+ "math/rand/v2"
+ "slices"
+
+ "github.com/disgoorg/disgo/bot"
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/disgo/events"
+ "github.com/sabafly/gobot/bot/commands/gopoint"
+ "github.com/sabafly/gobot/bot/components"
+ "github.com/sabafly/gobot/internal/embeds"
+ "github.com/sabafly/gobot/internal/errors"
+ "github.com/sabafly/gobot/internal/translate"
+)
+
+func upMessageModalHandler(c *components.Components, event *events.ModalSubmitInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ g.LevelUpMessage = event.Data.Text("message")
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.up.message.message")).
+ SetDescription(g.LevelUpMessage).
+ Build()
+
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetEmbeds(embeds.SetEmbedProperties(embed)).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func eventHandler(c *components.Components, event bot.Event) errors.Error {
+ switch event := event.(type) {
+ case *events.GuildMessageCreate:
+ if event.Message.Author.Bot || event.Message.Author.System || event.Message.Type.System() {
+ return nil
+ }
+ if event.Message.Type != discord.MessageTypeDefault && event.Message.Type != discord.MessageTypeReply {
+ return nil
+ }
+ g, err := c.GuildCreateID(event, event.GuildID)
+ if err != nil || g.LevelingDisabled || slices.Contains(g.LevelUpExcludeChannel, event.ChannelID) {
+ return nil
+ }
+ var channel discord.GuildChannel
+ ch, ok := event.Channel()
+ if !ok {
+ ch2, _ := event.Client().Rest.GetChannel(event.ChannelID)
+ channel, _ = ch2.(discord.GuildChannel)
+ } else {
+ channel = ch
+ }
+ if channel != nil && channel.ParentID() != nil && slices.Contains(g.LevelUpExcludeChannel, *channel.ParentID()) {
+ return nil
+ }
+ m, err := c.MemberCreate(event, event.Message.Author, event.GuildID)
+ if err != nil {
+ return errors.NewError(err)
+ }
+ hash := sha1.Sum([]byte(event.Message.Content))
+ hashStr := hex.EncodeToString(hash[:])
+ if slices.Contains(m.LastMessageHashes, hashStr) {
+ return nil
+ }
+ if len(m.LastMessageHashes) >= 10 {
+ m.LastMessageHashes = slices.Delete(m.LastMessageHashes, 0, 1)
+ }
+ m.LastMessageHashes = append(m.LastMessageHashes, hashStr)
+ if err := c.GormDB().Save(m).Error; err != nil {
+ return errors.NewError(err)
+ }
+
+ if _, err = addXp(event, rand.N[uint64](16)+15, event.Client(), m, g, event.ChannelID, event.Message.Author.EffectiveName(), false, c.GormDB()); err != nil {
+ return errors.NewError(err)
+ }
+
+ if err := gopoint.AddPoint(c, m.UserID, g.ID, rand.Int64N(2)*50); err != nil {
+ slog.Error("ポイント追加に失敗", slog.Any("err", err), slog.Any("user_id", m.UserID), slog.Any("guild_id", g.ID))
+ }
+
+ }
+ return nil
+}
diff --git a/bot/commands/level/level_handlers.go b/bot/commands/level/level_handlers.go
new file mode 100644
index 00000000..bc92ba02
--- /dev/null
+++ b/bot/commands/level/level_handlers.go
@@ -0,0 +1,600 @@
+/*
+ * gobot -- a useful discord bot
+ *
+ * Copyright (C) 2024 Sabafly Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package level
+
+import (
+ "cmp"
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "slices"
+ "strconv"
+ "time"
+
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/disgo/events"
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/sabafly/gobot/bot/components"
+ "github.com/sabafly/gobot/database/models"
+ "github.com/sabafly/gobot/internal/builtin"
+ "github.com/sabafly/gobot/internal/discordutil"
+ "github.com/sabafly/gobot/internal/embeds"
+ "github.com/sabafly/gobot/internal/errors"
+ "github.com/sabafly/gobot/internal/smap"
+ "github.com/sabafly/gobot/internal/translate"
+ "github.com/sabafly/gobot/internal/xppoint"
+ "gorm.io/gorm"
+)
+
+func requiredPointHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ mem, err := c.MemberCreate(event, event.User(), *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ levelNum := uint64(0)
+ if l, ok := event.SlashCommandInteractionData().OptInt("level"); ok {
+ levelNum = uint64(l)
+ } else {
+ levelNum = mem.XP.Level() + 1
+ }
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.required-point.embed.title", translate.WithTemplate(map[string]any{"Level": levelNum}))).
+ SetDescriptionf("# `%d`xp\n%s\n%s",
+ xppoint.TotalPoint(levelNum),
+ translate.Message(event.Locale(), "components.level.required-point.embed.description", translate.WithTemplate(map[string]any{"User": event.Member().EffectiveName(), "Xp": mem.XP})),
+ translate.Message(event.Locale(), "components.level.required-point.embed.description.diff", translate.WithTemplate(map[string]any{"Xp": builtin.Or(xppoint.TotalPoint(levelNum) > uint64(mem.XP), xppoint.TotalPoint(levelNum)-uint64(mem.XP), 0)})),
+ ).
+ Build()
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed)).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func rankHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ target, ok := event.SlashCommandInteractionData().OptMember("target")
+ if !ok {
+ target = *event.Member()
+ }
+ if target.User.Bot || target.User.System {
+ return errors.NewError(errors.ErrorMessage("errors.invalid.bot.target", event))
+ }
+ m, err := c.MemberCreate(event, target.User, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ gl, err := c.GuildRequest(event.Client(), g.ID)
+ if err != nil {
+ return errors.NewError(err)
+ }
+
+ var members []models.Member
+ if err := c.GormDB().Where("guild_id = ?", g.ID).Order("xp desc").Find(&members).Error; err != nil {
+ return errors.NewError(err)
+ }
+
+ ids := make([]int, len(members))
+ for i, mem := range members {
+ ids[i] = mem.ID
+ }
+ index := slices.Index(ids, m.ID)
+
+ embed := levelMessage(g, gl, m, index, target.Member, event)
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed)).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func leaderboardHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ const pageCount = 25
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ gl, err := c.GuildRequest(event.Client(), g.ID)
+ if err != nil {
+ return errors.NewError(err)
+ }
+ page := event.SlashCommandInteractionData().Int("page")
+ if page < 1 {
+ page = 1
+ }
+
+ var count int64
+ c.GormDB().Model(&models.Member{}).Where("guild_id = ?", g.ID).Count(&count)
+
+ if int64(page) > (count+pageCount-1)/pageCount {
+ return errors.NewError(errors.ErrorMessage("errors.invalid.page", event))
+ }
+
+ var members []models.Member
+ c.GormDB().Where("guild_id = ?", g.ID).Order("xp desc").Offset((page - 1) * pageCount).Limit(pageCount).Find(&members)
+
+ var leaderboard string
+ for i, m := range members {
+ leaderboard += fmt.Sprintf("**#%d | %s XP: `%d` Level: `%d`**\n",
+ i+1+((page-1)*pageCount),
+ discord.UserMention(m.UserID),
+ m.XP, m.XP.Level(),
+ )
+ }
+
+ embed := discord.NewEmbedBuilder().
+ SetEmbedAuthor(
+ &discord.EmbedAuthor{
+ Name: g.Name,
+ IconURL: builtin.NonNil(gl.IconURL()),
+ },
+ ).
+ SetTitlef("🏆%s(%d/%d)",
+ translate.Message(event.Locale(), "components.level.leaderboard.title"),
+ page,
+ (count+pageCount-1)/pageCount,
+ ).
+ SetDescription(leaderboard).
+ Build()
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed)).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func transferHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ gl, err := c.GuildRequest(event.Client(), g.ID)
+ if err != nil {
+ return errors.NewError(err)
+ }
+
+ to := event.SlashCommandInteractionData().Member("to")
+ from, ok := event.SlashCommandInteractionData().OptMember("from")
+ if !ok {
+ from = *event.Member()
+ }
+ if from.User.Bot || from.User.System || to.User.Bot || to.User.System {
+ return errors.NewError(errors.ErrorMessage("errors.invalid.bot.target", event))
+ }
+ if to.User.ID == from.User.ID {
+ return errors.NewError(errors.ErrorMessage("errors.invalid.self.target", event))
+ }
+
+ fromUser, err := c.MemberCreate(event, from.User, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ toUser, err := c.MemberCreate(event, to.User, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ movedXp := uint64(fromUser.XP)
+ fromUser.XP = xppoint.XP(0)
+ fromUser.LastNotifiedLevel = nil
+
+ if err := c.GormDB().Transaction(func(tx *gorm.DB) error {
+ if err := tx.Save(fromUser).Error; err != nil {
+ return err
+ }
+
+ var err error
+ if toUser, err = addXp(event, movedXp, event.Client(), toUser, g, event.Channel().ID(), to.EffectiveName(), true, tx); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return errors.NewError(err)
+ }
+
+ var ids []int
+ c.GormDB().Model(&models.Member{}).Where("guild_id = ?", g.ID).Order("xp desc").Pluck("id", &ids)
+
+ fromIndex, toIndex := slices.Index(ids, fromUser.ID), slices.Index(ids, toUser.ID)
+
+ embedsList := []discord.Embed{
+ levelMessage(g, gl, fromUser, fromIndex, from.Member, event),
+ levelMessage(g, gl, toUser, toIndex, to.Member, event),
+ discord.NewEmbedBuilder().SetTitlef("`%d`xp 移動しました", movedXp).Build(),
+ }
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedsProperties(embedsList)...).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func upMessageHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+
+ actionRow := discord.NewLabel(
+ translate.Message(event.Locale(), "components.level.up.message.modal.input.message"),
+ discord.TextInputComponent{
+ CustomID: "message",
+ Style: discord.TextInputStyleParagraph,
+ MinLength: builtin.Ptr(1),
+ MaxLength: 140,
+ Required: true,
+ Placeholder: translate.Message(event.Locale(), "components.level.up.message.modal.input.message.placeholder"),
+ Value: g.LevelUpMessage,
+ },
+ )
+
+ if err := event.Modal(
+ discord.NewModalCreateBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.up.message.modal.title")).
+ SetCustomID("level:up_message_modal").
+ SetComponents(actionRow).
+ Build(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func upMessageChannelHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ if channel, ok := event.SlashCommandInteractionData().OptChannel("channel"); ok {
+ g.LevelUpChannel = &channel.ID
+ } else {
+ g.LevelUpChannel = nil
+ }
+ c.GormDB().Save(g)
+
+ var channelText string
+ if g.LevelUpChannel != nil {
+ channelText = discord.ChannelMention(builtin.NonNil(g.LevelUpChannel))
+ } else {
+ channelText = translate.Message(event.Locale(), "components.level.up.message-channel.default")
+ }
+
+ content := translate.Message(event.Locale(), "components.level.up.message-channel.message",
+ translate.WithTemplate(map[string]any{
+ "Channel": channelText,
+ }),
+ )
+
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetContent(content).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func excludeChannelAddHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ channel := event.SlashCommandInteractionData().Channel("channel")
+ if slices.Contains(g.LevelUpExcludeChannel, channel.ID) {
+ return errors.NewError(errors.ErrorMessage("errors.already_exist", event))
+ }
+ g.LevelUpExcludeChannel = append(g.LevelUpExcludeChannel, channel.ID)
+ c.GormDB().Save(g)
+
+ content := translate.Message(event.Locale(), "components.level.exclude-channel.add.message",
+ translate.WithTemplate(map[string]any{"Channel": discord.ChannelMention(channel.ID)}),
+ )
+
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetContent(content).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func excludeChannelRemoveHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ channel := event.SlashCommandInteractionData().Channel("channel")
+ index := slices.Index(g.LevelUpExcludeChannel, channel.ID)
+ if index == -1 {
+ return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
+ }
+ g.LevelUpExcludeChannel = slices.Delete(g.LevelUpExcludeChannel, index, index+1)
+ c.GormDB().Save(g)
+
+ content := translate.Message(event.Locale(), "components.level.exclude-channel.remove.message",
+ translate.WithTemplate(map[string]any{"Channel": discord.ChannelMention(channel.ID)}),
+ )
+
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetContent(content).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func excludeChannelClearHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ g.LevelUpExcludeChannel = []snowflake.ID{}
+ c.GormDB().Save(g)
+
+ content := translate.Message(event.Locale(), "components.level.exclude-channel.clear.message")
+
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetContent(content).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func excludeChannelListHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ var listStr string
+ for i, id := range g.LevelUpExcludeChannel {
+ listStr += fmt.Sprintf("%d. %s\n", i+1, discord.ChannelMention(id))
+ }
+
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.exclude-channel.list.message")).
+ SetDescription(builtin.Or(listStr != "", listStr, "- "+translate.Message(event.Locale(), "components.level.exclude-channel.list.message.none"))).
+ Build()
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed)).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func importMee6Handler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+
+ if g.LevelMee6Imported {
+ return errors.NewError(errors.ErrorMessage("components.level.import-mee6.message.already", event))
+ }
+
+ var discordMembers []discord.Member
+ afterID := snowflake.ID(0)
+ for {
+ m, err := event.Client().Rest.GetMembers(*event.GuildID(), 1000, afterID)
+ if err != nil {
+ return errors.NewError(err)
+ }
+ discordMembers = append(discordMembers, m...)
+ if len(m) < 1000 {
+ break
+ }
+ afterID = m[len(m)-1].User.ID
+ }
+
+ slog.Info("mee6インポート", slog.Any("gid", event.GuildID()), slog.Int("member_count", len(discordMembers)))
+
+ importedCount := 0
+ url := fmt.Sprintf("https://mee6.xyz/api/plugins/levels/leaderboard/%s", event.GuildID().String())
+ client := &http.Client{Timeout: 10 * time.Second}
+ for page := 0; true; page++ {
+ response, err := client.Get(fmt.Sprintf("%s?page=%d", url, page))
+ if err != nil || response.StatusCode != http.StatusOK {
+ sc := 0
+ if response != nil {
+ sc = response.StatusCode
+ _ = response.Body.Close()
+ }
+ if sc == http.StatusUnauthorized {
+ return errors.NewError(errors.ErrorMessage("components.level.import-mee6.message.unauthorized", event))
+ }
+ if importedCount > 0 {
+ break
+ }
+ if err != nil {
+ return errors.NewError(err)
+ }
+ return errors.NewError(fmt.Errorf("mee6 API error: %d", sc))
+ }
+ var leaderboard struct {
+ Players []struct {
+ ID string `json:"id"`
+ Xp int64 `json:"xp"`
+ } `json:"players"`
+ }
+ if err := json.NewDecoder(response.Body).Decode(&leaderboard); err != nil {
+ _ = response.Body.Close()
+ return errors.NewError(err)
+ }
+ _ = response.Body.Close()
+ if len(leaderboard.Players) < 1 {
+ break
+ }
+ for _, player := range leaderboard.Players {
+ pID, _ := snowflake.Parse(player.ID)
+ idx := slices.IndexFunc(discordMembers, func(m discord.Member) bool { return m.User.ID == pID })
+ if idx != -1 {
+ m, _ := c.MemberCreate(event, discordMembers[idx].User, *event.GuildID())
+ m.XP = xppoint.XP(player.Xp)
+ c.GormDB().Save(m)
+ importedCount++
+ }
+ }
+ }
+
+ g.LevelMee6Imported = true
+ c.GormDB().Save(g)
+
+ if err := event.RespondMessage(discord.NewMessageBuilder().SetContent(fmt.Sprintf("# SUCCEED\n```| IMPORTED MEMBER COUNT | %d```", importedCount))); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func resetHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ target := event.SlashCommandInteractionData().Member("target")
+ m, err := c.MemberCreate(event, target.User, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ m.XP = xppoint.XP(0)
+ c.GormDB().Save(m)
+
+ content := translate.Message(event.Locale(), "components.level.reset.message",
+ translate.WithTemplate(map[string]any{"User": discord.UserMention(target.User.ID)}),
+ )
+
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetContent(content).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func roleSetHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ if len(g.LevelRole) >= 20 {
+ return errors.NewError(errors.ErrorMessage("errors.create.reach_max", event))
+ }
+ levelNum := event.SlashCommandInteractionData().Int("level")
+ role := event.SlashCommandInteractionData().Role("role")
+ g.LevelRole = builtin.NonNilMap(g.LevelRole)
+ g.LevelRole[levelNum] = role.ID
+ self, valid := event.Client().Caches.SelfMember(*event.GuildID())
+ if !valid {
+ return errors.NewError(errors.ErrorMessage("errors.invalid.self", event))
+ }
+ var roles []discord.Role
+ for _, id := range self.RoleIDs {
+ if r, ok := event.Client().Caches.Role(*event.GuildID(), id); ok {
+ roles = append(roles, r)
+ }
+ }
+ highestRole := discordutil.GetHighestRole(roles)
+ if highestRole == nil || role.Managed || role.Compare(*highestRole) != -1 || role.ID == *event.GuildID() {
+ return errors.NewError(errors.ErrorMessage("errors.invalid.role", event))
+ }
+
+ c.GormDB().Save(g)
+
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.role.set.message.embed.title")).
+ SetDescription(
+ translate.Message(event.Locale(), "components.level.role.set.message.embed.description",
+ translate.WithTemplate(map[string]any{
+ "Level": strconv.Itoa(levelNum),
+ "Role": discord.RoleMention(role.ID),
+ }),
+ ),
+ ).
+ Build()
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed)).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func roleRemoveHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ g.LevelRole = builtin.NonNilMap(g.LevelRole)
+ levelNum := event.SlashCommandInteractionData().Int("level")
+ rID, ok := g.LevelRole[levelNum]
+ if !ok {
+ return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
+ }
+ delete(g.LevelRole, levelNum)
+
+ c.GormDB().Save(g)
+
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.role.remove.message.embed.title")).
+ SetDescription(
+ translate.Message(event.Locale(), "components.level.role.remove.message.embed.description",
+ translate.WithTemplate(map[string]any{
+ "Level": strconv.Itoa(levelNum),
+ "Role": discord.RoleMention(rID),
+ }),
+ ),
+ ).
+ Build()
+
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed)).BuildCreate()); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
+
+func roleListHandler(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
+ g, err := c.GuildCreateID(event, *event.GuildID())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ g.LevelRole = builtin.NonNilMap(g.LevelRole)
+ var listStr string
+ for k, v := range smap.MakeSortMap(g.LevelRole).Iter(cmp.Compare[int]) {
+ listStr += "- " + translate.Message(event.Locale(), "components.level.role.list.message", translate.WithTemplate(map[string]any{"Level": strconv.Itoa(k), "Role": discord.RoleMention(v)})) + "\n"
+ }
+
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.level.role.list.message.embed.title")).
+ SetDescription(builtin.Or(listStr != "", listStr, translate.Message(event.Locale(), "components.level.role.list.message.none"))).
+ Build()
+
+ if err := event.RespondMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed))); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
+}
diff --git a/bot/commands/level/level_message.go b/bot/commands/level/level_message.go
index c4614765..34345145 100644
--- a/bot/commands/level/level_message.go
+++ b/bot/commands/level/level_message.go
@@ -24,16 +24,16 @@ import (
"fmt"
"github.com/disgoorg/disgo/discord"
- "github.com/sabafly/gobot/ent"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/translate"
"github.com/sabafly/gobot/internal/xppoint"
)
func levelMessage(
- g *ent.Guild,
+ g *models.Guild,
gl *discord.Guild,
- m *ent.Member,
+ m *models.Member,
index int,
member discord.Member,
event interface {
@@ -60,8 +60,8 @@ func levelMessage(
).
SetDescription("## "+translate.Message(event.Locale(), "components.level.rank.embed.description",
translate.WithTemplate(map[string]any{
- "Level": m.Xp.Level(),
- "Xp": m.Xp,
+ "Level": m.XP.Level(),
+ "Xp": m.XP,
}),
)).
SetFields(
@@ -72,11 +72,11 @@ func levelMessage(
},
discord.EmbedField{
Name: translate.Message(event.Locale(), "components.level.rank.embed.fields.next_level",
- translate.WithTemplate(map[string]any{"NextLevel": m.Xp.Level() + 1}),
+ translate.WithTemplate(map[string]any{"NextLevel": m.XP.Level() + 1}),
),
Value: fmt.Sprintf("`%d`xp / `%d`xp",
- xppoint.RequiredPoint(m.Xp.Level())-(xppoint.TotalPoint(m.Xp.Level()+1)-uint64(m.Xp)),
- xppoint.RequiredPoint(m.Xp.Level()),
+ xppoint.RequiredPoint(m.XP.Level())-(xppoint.TotalPoint(m.XP.Level()+1)-uint64(m.XP)),
+ xppoint.RequiredPoint(m.XP.Level()),
),
Inline: builtin.Ptr(true),
},
diff --git a/bot/commands/message/message.go b/bot/commands/message/message.go
index 804e728e..33cc0a4a 100644
--- a/bot/commands/message/message.go
+++ b/bot/commands/message/message.go
@@ -28,24 +28,23 @@ import (
"strconv"
"strings"
"time"
+ "unicode"
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/snowflake/v2"
+ "gorm.io/gorm"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/sabafly/gobot/bot/components"
"github.com/sabafly/gobot/bot/components/generic"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/guild"
- "github.com/sabafly/gobot/ent/messagepin"
- "github.com/sabafly/gobot/ent/messageremind"
- "github.com/sabafly/gobot/ent/wordsuffix"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/errors"
"github.com/sabafly/gobot/internal/parse"
"github.com/sabafly/gobot/internal/translate"
+ "github.com/sabafly/gobot/internal/uuidv7"
)
const (
@@ -58,6 +57,12 @@ const (
PinArgumentTypeDuration1w
)
+const (
+ WordSuffixRuleWebhook = "webhook"
+ WordSuffixRuleWarn = "warn"
+ WordSuffixRuleDelete = "delete"
+)
+
func Command(c *components.Components) *generic.Command {
return (&generic.Command{
Namespace: "message",
@@ -103,17 +108,17 @@ func Command(c *components.Components) *generic.Command {
{
Name: "webhook",
NameLocalizations: translate.MessageMap("components.message.suffix.set.command.options.rule.webhook", false),
- Value: wordsuffix.RuleWebhook.String(),
+ Value: WordSuffixRuleWebhook,
},
{
Name: "warn",
NameLocalizations: translate.MessageMap("components.message.suffix.set.command.options.rule.warn", false),
- Value: wordsuffix.RuleWarn.String(),
+ Value: WordSuffixRuleWarn,
},
{
Name: "delete",
NameLocalizations: translate.MessageMap("components.message.suffix.set.command.options.rule.delete", false),
- Value: wordsuffix.RuleDelete.String(),
+ Value: WordSuffixRuleDelete,
},
},
},
@@ -281,25 +286,36 @@ func Command(c *components.Components) *generic.Command {
}
expired = builtin.Or(d != 0, builtin.Ptr(time.Now().Add(d)), nil)
}
- var w *ent.WordSuffix
- if u.QueryWordSuffix().Where(wordsuffix.GuildID(g.ID)).ExistX(event) {
- w = u.QueryWordSuffix().Where(wordsuffix.GuildID(g.ID)).OnlyX(event)
- w = w.Update().
- SetSuffix(event.SlashCommandInteractionData().String("suffix")).
- SetOwner(u).
- SetRule(wordsuffix.Rule(event.SlashCommandInteractionData().String("rule"))).
- SetNillableExpired(expired).
- SaveX(event)
+
+ var w models.WordSuffix
+ err = c.GormDB().Where("guild_id = ? AND owner_id = ?", g.ID, u.ID).First(&w).Error
+ if err == nil {
+ // Update
+ w.Suffix = event.SlashCommandInteractionData().String("suffix")
+ w.Rule = event.SlashCommandInteractionData().String("rule")
+ w.Expired = expired
+ if err := c.GormDB().Save(&w).Error; err != nil {
+ return errors.NewError(err)
+ }
} else {
- w = c.DB().WordSuffix.
- Create().
- SetGuild(g).
- SetSuffix(event.SlashCommandInteractionData().String("suffix")).
- SetOwner(u).
- SetRule(wordsuffix.Rule(event.SlashCommandInteractionData().String("rule"))).
- SetNillableExpired(expired).
- SaveX(event)
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return errors.NewError(err)
+ }
+
+ // Create
+ w = models.WordSuffix{
+ ID: uuidv7.New(),
+ GuildID: &g.ID,
+ Suffix: event.SlashCommandInteractionData().String("suffix"),
+ OwnerID: u.ID,
+ Rule: event.SlashCommandInteractionData().String("rule"),
+ Expired: expired,
+ }
+ if err := c.GormDB().Create(&w).Error; err != nil {
+ return errors.NewError(err)
+ }
}
+
var durationString string
if expired != nil {
durationString = discord.FormattedTimestampMention(expired.Unix(), discord.TimestampStyleRelative)
@@ -312,13 +328,11 @@ func Command(c *components.Components) *generic.Command {
translate.Message(
event.Locale(),
"components.message.suffix.set.message",
- translate.WithTemplate(map[string]any{"User": discord.UserMention(u.ID), "Suffix": w.Suffix}),
- ),
+ translate.WithTemplate(map[string]any{"User": discord.UserMention(u.ID), "Suffix": w.Suffix})),
translate.Message(
event.Locale(),
"components.message.suffix.duration.message",
- translate.WithTemplate(map[string]any{"Duration": durationString}),
- ),
+ translate.WithTemplate(map[string]any{"Duration": durationString})),
).
BuildCreate(),
); err != nil {
@@ -349,20 +363,28 @@ func Command(c *components.Components) *generic.Command {
return errors.NewError(err)
}
- if !u.QueryWordSuffix().Where(wordsuffix.GuildID(g.ID)).ExistX(event) {
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetContent(translate.Message(event.Locale(), "components.message.suffix.remove.message.no_suffix", translate.WithTemplate(map[string]any{"User": discord.UserMention(u.ID)}))).
- SetAllowedMentions(&discord.AllowedMentions{}).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
- return errors.NewError(err)
+ var w models.WordSuffix
+ err = c.GormDB().Where("guild_id = ? AND owner_id = ?", g.ID, u.ID).First(&w).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ if err := event.CreateMessage(
+ discord.NewMessageBuilder().
+ SetContent(translate.Message(event.Locale(), "components.message.suffix.remove.message.no_suffix", translate.WithTemplate(map[string]any{"User": discord.UserMention(u.ID)}))).
+ SetAllowedMentions(&discord.AllowedMentions{}).
+ SetFlags(discord.MessageFlagEphemeral).
+ BuildCreate(),
+ ); err != nil {
+ return errors.NewError(err)
+ }
+ return nil
}
- return nil
+ return errors.NewError(err)
+ }
+
+ if err := c.GormDB().Delete(&w).Error; err != nil {
+ return errors.NewError(err)
}
- c.DB().WordSuffix.DeleteOneID(u.QueryWordSuffix().Where(wordsuffix.GuildID(g.ID)).FirstIDX(event)).ExecX(event)
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.message.suffix.remove.message", translate.WithTemplate(map[string]any{"User": discord.UserMention(u.ID)}))).
@@ -402,12 +424,10 @@ func Command(c *components.Components) *generic.Command {
messageStr := translate.Message(event.Locale(), "components.message.suffix.check.message.none",
translate.WithTemplate(map[string]any{"User": discord.UserMention(u.ID)}),
)
- if u.QueryWordSuffix().Where(
- wordsuffix.GuildID(g.ID),
- ).ExistX(event) {
- w := u.QueryWordSuffix().Where(
- wordsuffix.GuildID(g.ID),
- ).FirstX(event)
+
+ var w models.WordSuffix
+ err = c.GormDB().Where("guild_id = ? AND owner_id = ?", g.ID, u.ID).First(&w).Error
+ if err == nil {
messageStr = translate.Message(event.Locale(), "components.message.suffix.check.message",
translate.WithTemplate(
map[string]any{
@@ -417,11 +437,12 @@ func Command(c *components.Components) *generic.Command {
),
"User": discord.UserMention(u.ID),
"Suffix": w.Suffix,
- "Rule": translate.Message(event.Locale(), "components.message.suffix.set.command.options.rule."+w.Rule.String()),
+ "Rule": translate.Message(event.Locale(), "components.message.suffix.set.command.options.rule."+w.Rule),
},
),
)
}
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(messageStr).
@@ -466,19 +487,27 @@ func Command(c *components.Components) *generic.Command {
},
DiscordPerm: discord.PermissionManageMessages,
CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- g, err := c.GuildCreateID(event, *event.GuildID())
+ _, err := c.GuildCreateID(event, *event.GuildID())
if err != nil {
return errors.NewError(err)
}
- if !g.QueryMessagePins().Where(messagepin.ChannelID(event.Channel().ID())).ExistX(event) {
- return errors.NewError(errors.ErrorMessage("errors.unavailable.message.pin", event))
+ var m models.MessagePin
+ err = c.GormDB().Where("channel_id = ?", event.Channel().ID()).First(&m).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return errors.NewError(errors.ErrorMessage("errors.unavailable.message.pin", event))
+ }
+ return errors.NewError(err)
}
- if beforeID := g.QueryMessagePins().Where(messagepin.ChannelID(event.Channel().ID())).FirstX(event).BeforeID; beforeID != nil {
- _ = event.Client().Rest.DeleteMessage(event.Channel().ID(), *beforeID)
+
+ if m.BeforeID != nil {
+ _ = event.Client().Rest.DeleteMessage(event.Channel().ID(), *m.BeforeID)
}
- c.DB().MessagePin.Delete().Where(messagepin.ChannelID(event.Channel().ID())).ExecX(event)
+ if err := c.GormDB().Delete(&m).Error; err != nil {
+ return errors.NewError(err)
+ }
if err := event.CreateMessage(
discord.NewMessageBuilder().
@@ -555,15 +584,17 @@ func Command(c *components.Components) *generic.Command {
},
DiscordPerm: discord.PermissionManageMessages,
CommandHandler: func(c *components.Components, event *events.ApplicationCommandInteractionCreate) errors.Error {
- count := c.DB().MessageRemind.Delete().Where(
- messageremind.HasGuildWith(guild.ID(*event.GuildID())),
- messageremind.NameContains(event.SlashCommandInteractionData().String("remind")),
- ).ExecX(event)
+ res := c.GormDB().Where("guild_id = ? AND name LIKE ?", *event.GuildID(), "%"+event.SlashCommandInteractionData().String("remind")+"%").Delete(&models.MessageRemind{})
+ if res.Error != nil {
+ return errors.NewError(res.Error)
+ }
+ count := res.RowsAffected
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.message.remind.cancel.message",
translate.WithTemplate(map[string]any{
- "Count": strconv.Itoa(count),
+ "Count": strconv.FormatInt(count, 10),
}),
)).
BuildCreate(),
@@ -582,12 +613,8 @@ func Command(c *components.Components) *generic.Command {
},
DiscordPerm: discord.PermissionManageMessages,
AutocompleteHandler: func(c *components.Components, event *events.AutocompleteInteractionCreate) errors.Error {
- reminds := c.DB().MessageRemind.Query().Where(
- messageremind.HasGuildWith(guild.ID(*event.GuildID())),
- messageremind.NameContains(event.Data.String("remind")),
- ).
- Limit(25).
- AllX(event)
+ var reminds []models.MessageRemind
+ c.GormDB().Where("guild_id = ? AND name LIKE ?", *event.GuildID(), "%"+event.Data.String("remind")+"%").Limit(25).Find(&reminds)
choices := make([]discord.AutocompleteChoice, len(reminds))
for i, mr := range reminds {
@@ -612,19 +639,23 @@ func Command(c *components.Components) *generic.Command {
}
// もし既にあったら抹消する
- if g.QueryMessagePins().Where(messagepin.ChannelID(event.Channel().ID())).ExistX(event) {
- if beforeID := g.QueryMessagePins().Where(messagepin.ChannelID(event.Channel().ID())).FirstX(event).BeforeID; beforeID != nil {
- _ = event.Client().Rest.DeleteMessage(event.Channel().ID(), *beforeID)
+ var oldPin models.MessagePin
+ if err := component.GormDB().Where("channel_id = ?", event.Channel().ID()).First(&oldPin).Error; err == nil {
+ if oldPin.BeforeID != nil {
+ _ = event.Client().Rest.DeleteMessage(event.Channel().ID(), *oldPin.BeforeID)
}
+ component.GormDB().Delete(&oldPin)
+ }
- component.DB().MessagePin.Delete().Where(messagepin.ChannelID(event.Channel().ID())).ExecX(event)
+ m := models.MessagePin{
+ ChannelID: event.Channel().ID(),
+ Content: event.Data.Text("content"),
+ GuildID: g.ID,
+ }
+ if err := component.GormDB().Create(&m).Error; err != nil {
+ return errors.NewError(err)
}
- m := component.DB().MessagePin.Create().
- SetChannelID(event.Channel().ID()).
- SetContent(event.Data.Text("content")).
- SetGuild(g).
- SaveX(event)
channel, err := event.Client().Rest.GetChannel(m.ChannelID)
if err != nil {
return errors.NewError(err)
@@ -646,7 +677,10 @@ func Command(c *components.Components) *generic.Command {
return errors.NewError(err)
}
- m.Update().SetBeforeID(message.ID).SaveX(event)
+ m.BeforeID = &message.ID
+ if err := component.GormDB().Save(&m).Error; err != nil {
+ return errors.NewError(err)
+ }
if err := event.CreateMessage(
discord.NewMessageBuilder().
@@ -668,15 +702,21 @@ func Command(c *components.Components) *generic.Command {
if time.Now().After(tm) {
return errors.NewError(errors.ErrorMessage("errors.invalid.time.before", event))
}
- c.DB().MessageRemind.Create().
- SetGuild(g).
- SetTime(tm).
- SetContent(event.Data.Text("content")).
- SetChannelID(event.Channel().ID()).
- SetAuthorID(event.Member().User.ID).
- SetName(event.Data.Text("name")).
- ExecX(event)
- g.Update().AddRemindCount(1).ExecX(event)
+
+ remind := models.MessageRemind{
+ GuildID: g.ID,
+ Time: tm,
+ Content: event.Data.Text("content"),
+ ChannelID: event.Channel().ID(),
+ AuthorID: event.Member().User.ID,
+ Name: event.Data.Text("name"),
+ }
+ if err := c.GormDB().Create(&remind).Error; err != nil {
+ return errors.NewError(err)
+ }
+
+ g.RemindCount++
+ c.GormDB().Save(g)
if err := event.CreateMessage(
discord.NewMessageBuilder().
@@ -697,11 +737,9 @@ func Command(c *components.Components) *generic.Command {
{
Duration: time.Minute,
Worker: func(c *components.Components, client *bot.Client) error {
- reminds := c.DB().MessageRemind.Query().
- Where(
- messageremind.TimeLT(time.Now()),
- ).
- AllX(c.Ctx())
+ var reminds []models.MessageRemind
+ c.GormDB().Where("time < ?", time.Now()).Find(&reminds)
+
for _, remind := range reminds {
if _, err := client.Rest.CreateMessage(remind.ChannelID,
discord.NewMessageBuilder().
@@ -712,11 +750,7 @@ func Command(c *components.Components) *generic.Command {
}
}
- c.DB().MessageRemind.Delete().
- Where(
- messageremind.TimeLT(time.Now()),
- ).
- ExecX(c.Ctx())
+ c.GormDB().Where("time < ?", time.Now()).Delete(&models.MessageRemind{})
return nil
},
},
@@ -738,8 +772,8 @@ func Command(c *components.Components) *generic.Command {
// 変数が初期化されていないことが潜在的なバグの原因になりかねない
// 語尾の処理
- var w *ent.WordSuffix
- var u *ent.User
+ var w models.WordSuffix
+ var u *models.User
if e.Message.Type.System() || e.Message.Author.System || e.Message.Author.Bot {
goto messagePin
@@ -753,26 +787,29 @@ func Command(c *components.Components) *generic.Command {
return errors.NewError(err)
}
- if u.QueryWordSuffix().Where(wordsuffix.GuildID(e.GuildID)).ExistX(e) {
- // Guild
- w = u.QueryWordSuffix().Where(wordsuffix.GuildID(e.GuildID)).FirstX(e)
+ // Guild
+ if err := c.GormDB().Where("owner_id = ? AND guild_id = ?", u.ID, e.GuildID).First(&w).Error; err == nil {
+ // Found
} else {
// Global
- if !u.QueryWordSuffix().Where(wordsuffix.GuildIDIsNil()).ExistX(e) {
+ if err := c.GormDB().Where("owner_id = ? AND guild_id IS NULL", u.ID).First(&w).Error; err != nil {
+ if err != gorm.ErrRecordNotFound {
+ slog.Error("語尾取得エラー", "err", err)
+ }
+ // Not found
slog.Debug("語尾が存在しません")
goto messagePin
}
- w = u.QueryWordSuffix().Where(wordsuffix.GuildIDIsNil()).FirstX(e)
}
{
webhookFlag := false
- if w.Rule == wordsuffix.RuleWebhook {
+ if w.Rule == WordSuffixRuleWebhook {
c.GetLock("message_pin").Mutex(e.ChannelID).Lock()
webhookFlag = true
}
- err = messageSuffixMessageCreateHandler(w, u, e, c)
+ err = messageSuffixMessageCreateHandler(&w, u, e, c)
if webhookFlag {
c.GetLock("message_pin").Mutex(e.ChannelID).Unlock()
@@ -800,12 +837,20 @@ func Command(c *components.Components) *generic.Command {
if err != nil {
return errors.NewError(err)
}
- if !g.QueryMessagePins().Where(messagepin.ChannelID(event.ChannelID)).ExistX(event) {
- return nil
+
+ var m models.MessagePin
+ if err := c.GormDB().Where("guild_id = ? AND channel_id = ?", g.ID, event.ChannelID).First(&m).Error; err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil
+ }
+ return errors.NewError(err)
}
+
c.GetLock("message_pin").Mutex(e.ChannelID).Lock()
defer c.GetLock("message_pin").Mutex(e.ChannelID).Unlock()
- m := g.QueryMessagePins().Where(messagepin.ChannelID(event.ChannelID)).FirstX(event)
+
+ // Re-fetch to be safe under lock? Or is it overkill?
+ // The original code fetched it here.
webhook, err := event.Client().WebhookManager.GetMessenger(channel)
if err != nil {
@@ -852,10 +897,11 @@ func Command(c *components.Components) *generic.Command {
return errors.NewError(err)
}
- m.Update().SetBeforeID(message.ID).SetRateLimit(m.RateLimit).ExecX(event)
+ m.BeforeID = &message.ID
+ c.GormDB().Save(&m)
slog.Info("ピン留め更新", "cid", event.ChannelID, "mid", event.MessageID)
} else {
- m.Update().SetRateLimit(m.RateLimit).ExecX(event)
+ c.GormDB().Save(&m)
}
return nil
}(e, c); err != nil {
@@ -874,14 +920,15 @@ func Command(c *components.Components) *generic.Command {
if err != nil {
return errors.NewError(err)
}
- if !g.QueryMessagePins().Where(messagepin.ChannelID(e.ChannelID)).ExistX(e) {
+
+ var m models.MessagePin
+ if err := c.GormDB().Where("guild_id = ? AND channel_id = ?", g.ID, e.ChannelID).First(&m).Error; err != nil {
return nil
}
- m := g.QueryMessagePins().Where(messagepin.ChannelID(e.ChannelID)).FirstX(e)
if m.BeforeID != nil && *m.BeforeID == e.MessageID {
slog.Info("ピン留め削除", "cid", e.ChannelID, "mid", e.MessageID)
- c.DB().MessagePin.DeleteOneID(m.ID).ExecX(e)
+ c.GormDB().Delete(&m)
}
}
return nil
@@ -889,18 +936,18 @@ func Command(c *components.Components) *generic.Command {
}).SetComponent(c)
}
-func messageSuffixMessageCreateHandler(w *ent.WordSuffix, u *ent.User, e *events.GuildMessageCreate, c *components.Components) errors.Error {
+func messageSuffixMessageCreateHandler(w *models.WordSuffix, u *models.User, e *events.GuildMessageCreate, c *components.Components) errors.Error {
slog.Debug("メッセージ作成")
if e.Message.Content == "" {
return nil
}
if w.Expired != nil && time.Now().Compare(*w.Expired) == 1 {
- c.DB().WordSuffix.DeleteOneID(w.ID).ExecX(e)
+ c.GormDB().Delete(w)
return nil
}
switch w.Rule {
- case wordsuffix.RuleDelete:
+ case WordSuffixRuleDelete:
if strings.HasSuffix(e.Message.Content, w.Suffix) {
return nil
}
@@ -908,7 +955,7 @@ func messageSuffixMessageCreateHandler(w *ent.WordSuffix, u *ent.User, e *events
slog.Error("メッセージを削除できません", "err", err)
return errors.NewError(err)
}
- case wordsuffix.RuleWarn:
+ case WordSuffixRuleWarn:
if strings.HasSuffix(e.Message.Content, w.Suffix) {
return nil
}
@@ -924,11 +971,39 @@ func messageSuffixMessageCreateHandler(w *ent.WordSuffix, u *ent.User, e *events
slog.Error("メッセージを作成できません", "err", err)
return errors.NewError(err)
}
- case wordsuffix.RuleWebhook:
- content := e.Message.Content
- if !strings.HasSuffix(e.Message.Content, w.Suffix) {
- content += w.Suffix
+ case WordSuffixRuleWebhook:
+ var content string
+ for s := range strings.SplitSeq(e.Message.Content, "\n") {
+ if s == "" {
+ content += "\n"
+ continue
+ }
+ // すでに語尾がある場合はそのまま通す
+ if strings.HasSuffix(s, w.Suffix) {
+ content += s + "\n"
+ continue
+ }
+ // 末尾に文字列以外の文字がある場合(アルファベット、かな漢字以外のすべての文字)それらの前に語尾をなければ追加する
+ // 例: "こんにちは→→"の場合、"こんにちは"の後ろに語尾を追加し、"→→"の後ろには追加しない
+ runes := []rune(s)
+ i := len(runes) - 1
+ for i >= 0 {
+ if (runes[i] < 'A' || runes[i] > 'Z') && (runes[i] < 'a' || runes[i] > 'z') && (runes[i] < '0' || runes[i] > '9') && !unicode.In(runes[i], unicode.Hiragana, unicode.Katakana, unicode.Han) {
+ i--
+ } else {
+ break
+ }
+ }
+ if i < 0 {
+ // すべて文字列以外の文字の場合、そのまま通す
+ content += s + "\n"
+ continue
+ }
+ content += string(runes[:i+1]) + w.Suffix + string(runes[i+1:]) + "\n"
}
+ content = content[:len(content)-1] // 最後の改行を削除
+
+ // メッセージを削除
if err := e.Client().Rest.DeleteMessage(e.ChannelID, e.MessageID); err != nil {
return errors.NewError(err)
}
diff --git a/bot/commands/permission/permission.go b/bot/commands/permission/permission.go
index 5a73de31..6dc266ab 100644
--- a/bot/commands/permission/permission.go
+++ b/bot/commands/permission/permission.go
@@ -137,9 +137,9 @@ func Command(c *components.Components) components.Command {
return errors.NewError(err)
}
t.Permission.Set(perm, value)
- t = t.Update().
- SetPermission(t.Permission).
- SaveX(event)
+ if err := c.GormDB().Save(t).Error; err != nil {
+ return errors.NewError(err)
+ }
mention = discord.UserMention(t.UserID)
} else {
role := event.SlashCommandInteractionData().Role("target")
@@ -150,7 +150,7 @@ func Command(c *components.Components) components.Command {
p := g.Permissions[role.ID]
p.Set(perm, value)
g.Permissions[role.ID] = p
- g.Update().SetPermissions(g.Permissions).ExecX(event)
+ c.GormDB().Save(g)
mention = discord.RoleMention(role.ID)
}
if err := event.CreateMessage(
@@ -189,9 +189,7 @@ func Command(c *components.Components) components.Command {
return errors.NewError(err)
}
t.Permission.UnSet(perm)
- t = t.Update().
- SetPermission(t.Permission).
- SaveX(event)
+ c.GormDB().Save(t)
mention = discord.UserMention(t.UserID)
} else {
role := event.SlashCommandInteractionData().Role("target")
@@ -202,7 +200,7 @@ func Command(c *components.Components) components.Command {
p := g.Permissions[role.ID]
p.UnSet(perm)
g.Permissions[role.ID] = p
- g.Update().SetPermissions(g.Permissions).ExecX(event)
+ c.GormDB().Save(g)
mention = discord.RoleMention(role.ID)
}
if err := event.CreateMessage(
diff --git a/bot/commands/role/import.go b/bot/commands/role/import.go
index 7889f04f..629971c6 100644
--- a/bot/commands/role/import.go
+++ b/bot/commands/role/import.go
@@ -30,7 +30,7 @@ import (
"github.com/disgoorg/snowflake/v2"
"github.com/sabafly/gobot/bot/components"
"github.com/sabafly/gobot/bot/components/generic"
- "github.com/sabafly/gobot/ent/schema"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/discordutil"
"github.com/sabafly/gobot/internal/emoji"
@@ -62,7 +62,7 @@ func ImportCommand(c *components.Components) components.Command {
}
message := event.MessageCommandInteractionData().TargetMessage()
lines := strings.Split(message.Embeds[0].Description, "\n")
- var roles []schema.Role
+ var roles []models.Role
roleCount := 0
for _, v := range lines {
if !roleRegexp.MatchString(v) {
@@ -91,7 +91,7 @@ func ImportCommand(c *components.Components) components.Command {
role = *rolePtr
}
roleCount++
- roles = append(roles, schema.Role{
+ roles = append(roles, models.Role{
ID: role.ID,
Name: role.Name,
Emoji: &componentEmoji,
@@ -106,25 +106,31 @@ func ImportCommand(c *components.Components) components.Command {
return errors.NewError(err)
}
- panel := c.DB().RolePanel.Create().
- SetName(builtin.Or(message.Embeds[0].Title != "", message.Embeds[0].Title, translate.Message(event.Locale(), "components.role.panel.default_name"))).
- SetDescription("").
- SetRoles(roles).
- SetGuild(g).
- SaveX(event)
+ panel := models.RolePanel{
+ Name: builtin.Or(message.Embeds[0].Title != "", message.Embeds[0].Title, translate.Message(event.Locale(), "components.role.panel.default_name")),
+ Description: "",
+ Roles: roles,
+ GuildID: g.ID,
+ }
+ if err := c.GormDB().Create(&panel).Error; err != nil {
+ return errors.NewError(err)
+ }
- place := c.DB().RolePanelPlaced.Create().
- SetGuild(g).
- SetChannelID(event.Channel().ID()).
- SetRolePanel(panel).
- SetName(panel.Name).
- SetDescription(panel.Description).
- SetRoles(panel.Roles).
- SetUpdatedAt(time.Now()).
- SaveX(event)
+ place := models.RolePanelPlaced{
+ GuildID: g.ID,
+ ChannelID: event.Channel().ID(),
+ RolePanelID: panel.ID,
+ Name: panel.Name,
+ Description: panel.Description,
+ Roles: panel.Roles,
+ UpdatedAt: time.Now(),
+ }
+ if err := c.GormDB().Create(&place).Error; err != nil {
+ return errors.NewError(err)
+ }
if err := event.CreateMessage(
- rpPlaceBaseMenu(place, event.Locale()).
+ rpPlaceBaseMenu(&place, event.Locale()).
SetFlags(discord.MessageFlagEphemeral).
BuildCreate(),
); err != nil {
diff --git a/bot/commands/role/panel.go b/bot/commands/role/panel.go
index 7d0e498d..eccc57e1 100644
--- a/bot/commands/role/panel.go
+++ b/bot/commands/role/panel.go
@@ -27,24 +27,27 @@ import (
"github.com/disgoorg/snowflake/v2"
"github.com/google/uuid"
"github.com/sabafly/gobot/bot/components"
- "github.com/sabafly/gobot/ent/guild"
- "github.com/sabafly/gobot/ent/rolepanel"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/errors"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/rolepanelplaced"
"github.com/sabafly/gobot/internal/discordutil"
)
-func rolePanelPlace(ctx context.Context, place *ent.RolePanelPlaced, locale discord.Locale, client *bot.Client, react bool) error {
+const (
+ RolePanelPlacedTypeReaction = "reaction"
+ RolePanelPlacedTypeButton = "button"
+ RolePanelPlacedTypeSelectMenu = "select_menu"
+)
+
+func rolePanelPlace(ctx context.Context, place *models.RolePanelPlaced, locale discord.Locale, client *bot.Client, react bool, c *components.Components) error {
builder := rpPlacedMessage(place, locale)
if place.MessageID != nil {
if _, err := client.Rest.UpdateMessage(place.ChannelID, *place.MessageID, builder.BuildUpdate()); err != nil {
return err
}
- if place.Type == rolepanelplaced.TypeReaction && react {
+ if place.Type == RolePanelPlacedTypeReaction && react {
if err := client.Rest.RemoveAllReactions(place.ChannelID, *place.MessageID); err != nil {
return err
}
@@ -54,10 +57,13 @@ func rolePanelPlace(ctx context.Context, place *ent.RolePanelPlaced, locale disc
if err != nil {
return err
}
- *place = *place.Update().SetMessageID(m.ID).SaveX(ctx)
+ place.MessageID = &m.ID
+ if err := c.GormDB().Save(place).Error; err != nil {
+ return err
+ }
}
- if place.Type == rolepanelplaced.TypeReaction && react {
+ if place.Type == RolePanelPlacedTypeReaction && react {
for i, r := range place.Roles {
if r.Emoji == nil {
r.Emoji = &discord.ComponentEmoji{
@@ -72,23 +78,29 @@ func rolePanelPlace(ctx context.Context, place *ent.RolePanelPlaced, locale disc
return nil
}
-func createPanelPlace(ctx context.Context, c *components.Components, panelID uuid.UUID, channelID snowflake.ID, g *ent.Guild) (*ent.RolePanelPlaced, error) {
+func createPanelPlace(ctx context.Context, c *components.Components, panelID uuid.UUID, channelID snowflake.ID, g *models.Guild) (*models.RolePanelPlaced, error) {
- c.DB().RolePanelPlaced.Delete().Where(rolepanelplaced.And(rolepanelplaced.Or(rolepanelplaced.MessageIDIsNil(), rolepanelplaced.TypeIsNil()), rolepanelplaced.HasGuildWith(guild.ID(g.ID)))).ExecX(ctx)
+ // Clean up incomplete placements
+ c.GormDB().Where("(message_id IS NULL OR type = '') AND guild_id = ?", g.ID).Delete(&models.RolePanelPlaced{})
- if !g.QueryRolePanels().Where(rolepanel.ID(panelID)).ExistX(ctx) {
+ var panel models.RolePanel
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", panelID, g.ID).First(&panel).Error; err != nil {
return nil, errors.New("rolepanel not found")
}
- panel := g.QueryRolePanels().Where(rolepanel.ID(panelID)).FirstX(ctx)
+ placed := models.RolePanelPlaced{
+ GuildID: g.ID,
+ ChannelID: channelID,
+ RolePanelID: panel.ID,
+ Name: panel.Name,
+ Description: panel.Description,
+ Roles: panel.Roles,
+ UpdatedAt: time.Now(),
+ }
+
+ if err := c.GormDB().Create(&placed).Error; err != nil {
+ return nil, err
+ }
- return c.DB().RolePanelPlaced.Create().
- SetGuild(g).
- SetChannelID(channelID).
- SetRolePanel(panel).
- SetName(panel.Name).
- SetDescription(panel.Description).
- SetRoles(panel.Roles).
- SetUpdatedAt(time.Now()).
- SaveX(ctx), nil
+ return &placed, nil
}
diff --git a/bot/commands/role/panel_autocomplete.go b/bot/commands/role/panel_autocomplete.go
index 170e7df5..360ca76c 100644
--- a/bot/commands/role/panel_autocomplete.go
+++ b/bot/commands/role/panel_autocomplete.go
@@ -27,8 +27,7 @@ import (
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/sabafly/gobot/bot/components"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/rolepanel"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/errors"
)
@@ -38,11 +37,16 @@ func panelAutocomplete(c *components.Components, event *events.AutocompleteInter
if err != nil {
return errors.NewError(err)
}
- panels := g.QueryRolePanels().Where(rolepanel.NameContains(event.Data.String("panel"))).AllX(event)
+
+ var panels []models.RolePanel
+ if err := c.GormDB().Where("guild_id = ? AND name LIKE ?", g.ID, "%"+event.Data.String("panel")+"%").Find(&panels).Error; err != nil {
+ return errors.NewError(err)
+ }
+
choices := make([]discord.AutocompleteChoice, len(panels))
for i, p := range panels {
choices[i] = discord.AutocompleteChoiceString{
- Name: builtin.Or(slices.ContainsFunc(panels, func(rp *ent.RolePanel) bool { return rp.ID != p.ID && rp.Name == p.Name }), fmt.Sprintf("%s (%s)", p.Name, p.ID), p.Name),
+ Name: builtin.Or(slices.ContainsFunc(panels, func(rp models.RolePanel) bool { return rp.ID != p.ID && rp.Name == p.Name }), fmt.Sprintf("%s (%s)", p.Name, p.ID), p.Name),
Value: p.ID.String(),
}
}
diff --git a/bot/commands/role/panel_message.go b/bot/commands/role/panel_message.go
index 094ac34d..42dfeb1b 100644
--- a/bot/commands/role/panel_message.go
+++ b/bot/commands/role/panel_message.go
@@ -21,14 +21,12 @@
package role
import (
- "context"
"fmt"
"slices"
"github.com/disgoorg/disgo/discord"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/rolepanelplaced"
- "github.com/sabafly/gobot/ent/schema"
+ "github.com/sabafly/gobot/bot/components"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/discordutil"
"github.com/sabafly/gobot/internal/embeds"
@@ -36,7 +34,7 @@ import (
"github.com/sabafly/gobot/internal/translate"
)
-func initialize(edit *ent.RolePanelEdit, panel *ent.RolePanel) {
+func initialize(edit *models.RolePanelEdit, panel *models.RolePanel) {
if edit.Roles == nil {
edit.Roles = panel.Roles
}
@@ -48,48 +46,49 @@ func initialize(edit *ent.RolePanelEdit, panel *ent.RolePanel) {
}
}
-func rpEditBaseMessage(ctx context.Context, panel *ent.RolePanel, edit *ent.RolePanelEdit, locale discord.Locale) discord.MessageBuilder {
+func rpEditBaseMessage(c *components.Components, panel *models.RolePanel, edit *models.RolePanelEdit, locale discord.Locale) (discord.MessageBuilder, error) {
initialize(edit, panel)
builder := discord.NewMessageBuilder()
var roleField string
for i, r := range edit.Roles {
- if r.Emoji == nil {
- r.Emoji = &discord.ComponentEmoji{
+ emojiVal := r.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
Name: discordutil.Index2Emoji(i),
}
}
- roleField += fmt.Sprintf("%s: %s: %s\n", discordutil.FormatComponentEmoji(*r.Emoji), r.Name, discord.RoleMention(r.ID))
+ roleField += fmt.Sprintf("%s: %s: %s\n", discordutil.FormatComponentEmoji(*emojiVal), r.Name, discord.RoleMention(r.ID))
}
- builder.SetEmbeds(
- embeds.SetEmbedsProperties(
- []discord.Embed{
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(locale, "components.role.panel.edit.menu.base.title")).
- SetFields(
- discord.EmbedField{
- Name: translate.Message(locale, "components.role.panel.edit.menu.base.field.name"),
- Value: builtin.NonNil(edit.Name),
- Inline: builtin.Ptr(true),
- },
- discord.EmbedField{
- Name: translate.Message(locale, "components.role.panel.edit.menu.base.field.description"),
- Value: builtin.Or(builtin.NonNil(edit.Description) != "", builtin.NonNil(edit.Description), fmt.Sprintf("`%s`", translate.Message(locale, "components.role.panel.edit.menu.base.field.value.empty"))),
- Inline: builtin.Ptr(true),
- },
- discord.EmbedField{
- Name: translate.Message(locale, "components.role.panel.edit.menu.base.field.roles"),
- Value: builtin.Or(roleField != "", roleField, fmt.Sprintf("`%s`", translate.Message(locale, "components.role.panel.edit.menu.base.field.value.empty"))),
- },
- ).
- SetFooterTextf("id: %s", panel.ID).
- Build(),
- },
- )...,
- )
- placeCount := panel.QueryPlacements().CountX(ctx)
+ embedList := []discord.Embed{
+ discord.NewEmbedBuilder().
+ SetTitle(translate.Message(locale, "components.role.panel.edit.menu.base.title")).
+ SetFields(
+ discord.EmbedField{
+ Name: translate.Message(locale, "components.role.panel.edit.menu.base.field.name"),
+ Value: builtin.NonNil(edit.Name),
+ Inline: builtin.Ptr(true),
+ },
+ discord.EmbedField{
+ Name: translate.Message(locale, "components.role.panel.edit.menu.base.field.description"),
+ Value: builtin.Or(builtin.NonNil(edit.Description) != "", builtin.NonNil(edit.Description), fmt.Sprintf("`%s`", translate.Message(locale, "components.role.panel.edit.menu.base.field.value.empty"))),
+ Inline: builtin.Ptr(true),
+ },
+ discord.EmbedField{
+ Name: translate.Message(locale, "components.role.panel.edit.menu.base.field.roles"),
+ Value: builtin.Or(roleField != "", roleField, fmt.Sprintf("`%s`", translate.Message(locale, "components.role.panel.edit.menu.base.field.value.empty"))),
+ }).
+ SetFooterTextf("id: %s", panel.ID).
+ Build(),
+ }
+ builder.SetEmbeds(embeds.SetEmbedsProperties(embedList)...)
+
+ var placeCount int64
+ if err := c.GormDB().Model(&models.RolePanelPlaced{}).Where("role_panel_id = ?", panel.ID).Count(&placeCount).Error; err != nil {
+ return builder, err
+ }
- disabled := len(edit.Roles) < 1 || edit.SelectedRole == nil || !slices.ContainsFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })
+ disabled := len(edit.Roles) < 1 || edit.SelectedRole == nil || !slices.ContainsFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
builder.SetComponents(
discord.NewActionRow(
discord.ButtonComponent{
@@ -130,34 +129,32 @@ func rpEditBaseMessage(ctx context.Context, panel *ent.RolePanel, edit *ent.Role
),
discord.NewActionRow(
func() discord.StringSelectMenuComponent {
- menu := discord.StringSelectMenuComponent{
+ options := make([]discord.StringSelectMenuOption, len(edit.Roles))
+ for i, r := range edit.Roles {
+ emojiVal := r.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
+ Name: discordutil.Index2Emoji(i),
+ }
+ }
+ options[i] = discord.StringSelectMenuOption{
+ Label: r.Name,
+ Value: r.ID.String(),
+ Emoji: emojiVal,
+ Default: edit.SelectedRole != nil && *edit.SelectedRole == r.ID,
+ }
+ }
+ if len(edit.Roles) < 1 {
+ options = append(options, discord.NewStringSelectMenuOption("nil", "nil"))
+ }
+ return discord.StringSelectMenuComponent{
CustomID: fmt.Sprintf("role:panel_edit_component:select_role:%s", edit.ID),
Placeholder: translate.Message(locale, "components.role.panel.edit.menu.base.components.select_role"),
MinValues: builtin.Ptr(0),
MaxValues: 1,
Disabled: len(edit.Roles) < 1,
- Options: func() []discord.StringSelectMenuOption {
- options := make([]discord.StringSelectMenuOption, len(edit.Roles))
- for i, r := range edit.Roles {
- if r.Emoji == nil {
- r.Emoji = &discord.ComponentEmoji{
- Name: discordutil.Index2Emoji(i),
- }
- }
- options[i] = discord.StringSelectMenuOption{
- Label: r.Name,
- Value: r.ID.String(),
- Emoji: r.Emoji,
- Default: edit.SelectedRole != nil && *edit.SelectedRole == r.ID,
- }
- }
- if len(edit.Roles) < 1 {
- options = append(options, discord.NewStringSelectMenuOption("nil", "nil"))
- }
- return options
- }(),
+ Options: options,
}
- return menu
}(),
),
discord.NewActionRow(
@@ -165,7 +162,7 @@ func rpEditBaseMessage(ctx context.Context, panel *ent.RolePanel, edit *ent.Role
Style: discord.ButtonStylePrimary,
Label: "↑",
CustomID: fmt.Sprintf("role:panel_edit_component:move_up:%s", edit.ID),
- Disabled: disabled || slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole }) == 0,
+ Disabled: disabled || slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole }) == 0,
},
discord.ButtonComponent{
Style: discord.ButtonStyleDanger,
@@ -177,7 +174,7 @@ func rpEditBaseMessage(ctx context.Context, panel *ent.RolePanel, edit *ent.Role
Style: discord.ButtonStylePrimary,
Label: "↓",
CustomID: fmt.Sprintf("role:panel_edit_component:move_down:%s", edit.ID),
- Disabled: disabled || slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole }) == len(edit.Roles)-1,
+ Disabled: disabled || slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole }) == len(edit.Roles)-1,
},
),
discord.NewActionRow(
@@ -196,35 +193,33 @@ func rpEditBaseMessage(ctx context.Context, panel *ent.RolePanel, edit *ent.Role
),
)
- return builder
+ return builder, nil
}
-func rpEditModifyRolesMessage(edit *ent.RolePanelEdit, locale discord.Locale) discord.MessageBuilder {
+func rpEditModifyRolesMessage(edit *models.RolePanelEdit, locale discord.Locale) discord.MessageBuilder {
builder := discord.NewMessageBuilder()
var roleField string
for i, r := range edit.Roles {
- if r.Emoji == nil {
- r.Emoji = &discord.ComponentEmoji{
+ emojiVal := r.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
Name: discordutil.Index2Emoji(i),
}
}
- roleField += fmt.Sprintf("%s: %s: %s\n", discordutil.FormatComponentEmoji(*r.Emoji), r.Name, discord.RoleMention(r.ID))
+ roleField += fmt.Sprintf("%s: %s: %s\n", discordutil.FormatComponentEmoji(*emojiVal), r.Name, discord.RoleMention(r.ID))
}
- builder.SetEmbeds(
- embeds.SetEmbedsProperties(
- []discord.Embed{
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(locale, "components.role.panel.edit.menu.modify_roles.title")).
- SetFields(
- discord.EmbedField{
- Name: translate.Message(locale, "components.role.panel.edit.menu.modify_roles.field.roles"),
- Value: roleField,
- },
- ).
- Build(),
- },
- )...,
- )
+ embedList := []discord.Embed{
+ discord.NewEmbedBuilder().
+ SetTitle(translate.Message(locale, "components.role.panel.edit.menu.modify_roles.title")).
+ SetFields(
+ discord.EmbedField{
+ Name: translate.Message(locale, "components.role.panel.edit.menu.modify_roles.field.roles"),
+ Value: roleField,
+ },
+ ).
+ Build(),
+ }
+ builder.SetEmbeds(embeds.SetEmbedsProperties(embedList)...)
builder.SetComponents(
discord.NewActionRow(
@@ -252,16 +247,14 @@ func rpEditModifyRolesMessage(edit *ent.RolePanelEdit, locale discord.Locale) di
return builder
}
-func rpEditSetEmojiMessage(edit *ent.RolePanelEdit, locale discord.Locale) discord.MessageBuilder {
+func rpEditSetEmojiMessage(edit *models.RolePanelEdit, locale discord.Locale) discord.MessageBuilder {
builder := discord.NewMessageBuilder()
- builder.SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(locale, "components.role.panel.edit.menu.set_emoji.title")).
- SetDescription(translate.Message(locale, "components.role.panel.edit.menu.set_emoji.description")).
- Build(),
- ),
- )
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(locale, "components.role.panel.edit.menu.set_emoji.title")).
+ SetDescription(translate.Message(locale, "components.role.panel.edit.menu.set_emoji.description")).
+ Build()
+
+ builder.SetEmbeds(embeds.SetEmbedProperties(embed))
builder.SetComponents(
discord.NewActionRow(
@@ -280,36 +273,34 @@ func rpEditSetEmojiMessage(edit *ent.RolePanelEdit, locale discord.Locale) disco
return builder
}
-func rpPlaceBaseMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.MessageBuilder {
+func rpPlaceBaseMenu(place *models.RolePanelPlaced, locale discord.Locale) discord.MessageBuilder {
builder := discord.NewMessageBuilder()
var roleField string
for i, r := range place.Roles {
- if r.Emoji == nil {
- r.Emoji = &discord.ComponentEmoji{
+ emojiVal := r.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
Name: discordutil.Index2Emoji(i),
}
}
- roleField += fmt.Sprintf("%s| %s\n", discordutil.FormatComponentEmoji(*r.Emoji), builtin.Or(place.UseDisplayName, r.Name, discord.RoleMention(r.ID)))
+ roleField += fmt.Sprintf("%s| %s\n", discordutil.FormatComponentEmoji(*emojiVal), builtin.Or(place.UseDisplayName, r.Name, discord.RoleMention(r.ID)))
}
- builder.SetEmbeds(
- embeds.SetEmbedsProperties(
- []discord.Embed{
- discord.NewEmbedBuilder().
- SetAuthorName(translate.Message(locale, "components.role.panel.place.menu.author.text")).
- Build(),
- discord.NewEmbedBuilder().
- SetTitle(place.Name).
- SetDescription(place.Description).
- SetFields(
- discord.EmbedField{
- Name: translate.Message(locale, "components.role.panel.embed.field.role"),
- Value: roleField,
- },
- ).
- Build(),
- },
- )...,
- )
+ embedList := []discord.Embed{
+ discord.NewEmbedBuilder().
+ SetAuthorName(translate.Message(locale, "components.role.panel.place.menu.author.text")).
+ Build(),
+ discord.NewEmbedBuilder().
+ SetTitle(place.Name).
+ SetDescription(place.Description).
+ SetFields(
+ discord.EmbedField{
+ Name: translate.Message(locale, "components.role.panel.embed.field.role"),
+ Value: roleField,
+ },
+ ).
+ Build(),
+ }
+ builder.SetEmbeds(embeds.SetEmbedsProperties(embedList)...)
builder.AddComponents(
discord.NewActionRow(
@@ -321,24 +312,24 @@ func rpPlaceBaseMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.
Options: []discord.StringSelectMenuOption{
{
Label: translate.Message(locale, "components.role.panel.type.reaction"),
- Value: rolepanelplaced.TypeReaction.String(),
+ Value: RolePanelPlacedTypeReaction,
Description: translate.Message(locale, "components.role.panel.type.reaction.description"),
Emoji: emoji.Reaction,
- Default: place.Type == rolepanelplaced.TypeReaction,
+ Default: place.Type == RolePanelPlacedTypeReaction,
},
{
Label: translate.Message(locale, "components.role.panel.type.select_menu"),
- Value: rolepanelplaced.TypeSelectMenu.String(),
+ Value: RolePanelPlacedTypeSelectMenu,
Description: translate.Message(locale, "components.role.panel.type.select_menu.description"),
Emoji: emoji.SelectMenu,
- Default: place.Type == rolepanelplaced.TypeSelectMenu,
+ Default: place.Type == RolePanelPlacedTypeSelectMenu,
},
{
Label: translate.Message(locale, "components.role.panel.type.button"),
- Value: rolepanelplaced.TypeButton.String(),
+ Value: RolePanelPlacedTypeButton,
Description: translate.Message(locale, "components.role.panel.type.button.description"),
Emoji: emoji.Button,
- Default: place.Type == rolepanelplaced.TypeButton,
+ Default: place.Type == RolePanelPlacedTypeButton,
},
},
},
@@ -346,7 +337,7 @@ func rpPlaceBaseMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.
)
switch place.Type {
- case rolepanelplaced.TypeButton:
+ case RolePanelPlacedTypeButton:
builder.AddComponents(
discord.NewActionRow(
discord.StringSelectMenuComponent{
@@ -390,7 +381,7 @@ func rpPlaceBaseMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.
},
),
)
- case rolepanelplaced.TypeSelectMenu:
+ case RolePanelPlacedTypeSelectMenu:
builder.AddComponents(
discord.NewActionRow(
discord.ButtonComponent{
@@ -401,7 +392,7 @@ func rpPlaceBaseMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.
},
),
)
- case rolepanelplaced.TypeReaction:
+ case RolePanelPlacedTypeReaction:
builder.AddComponents(
discord.NewActionRow(
discord.ButtonComponent{
@@ -437,49 +428,49 @@ func rpPlaceBaseMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.
return builder
}
-func rpPlacedMessage(place *ent.RolePanelPlaced, locale discord.Locale) discord.MessageBuilder {
+func rpPlacedMessage(place *models.RolePanelPlaced, locale discord.Locale) discord.MessageBuilder {
builder := discord.NewMessageBuilder()
var roleField string
for i, r := range place.Roles {
- if r.Emoji == nil {
- r.Emoji = &discord.ComponentEmoji{
+ emojiVal := r.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
Name: discordutil.Index2Emoji(i),
}
}
- roleField += fmt.Sprintf("%s| %s\n", discordutil.FormatComponentEmoji(*r.Emoji), builtin.Or(place.UseDisplayName, r.Name, discord.RoleMention(r.ID)))
+ roleField += fmt.Sprintf("%s| %s\n", discordutil.FormatComponentEmoji(*emojiVal), builtin.Or(place.UseDisplayName, r.Name, discord.RoleMention(r.ID)))
}
- builder.SetEmbeds(
- embeds.SetEmbedsProperties(
- []discord.Embed{
- discord.NewEmbedBuilder().
- SetTitle(place.Name).
- SetDescription(place.Description).
- SetFields(
- discord.EmbedField{
- Name: translate.Message(locale, "components.role.panel.embed.field.role"),
- Value: roleField,
- },
- ).
- Build(),
- },
- )...,
- )
+ embedList := []discord.Embed{
+ discord.NewEmbedBuilder().
+ SetTitle(place.Name).
+ SetDescription(place.Description).
+ SetFields(
+ discord.EmbedField{
+ Name: translate.Message(locale, "components.role.panel.embed.field.role"),
+ Value: roleField,
+ },
+ ).
+ Build(),
+ }
+ builder.SetEmbeds(embeds.SetEmbedsProperties(embedList)...)
+
switch place.Type {
- case rolepanelplaced.TypeButton:
+ case RolePanelPlacedTypeButton:
buttons := make([]discord.InteractiveComponent, len(place.Roles))
for i, role := range place.Roles {
var label string
if place.ShowName {
label = role.Name
}
- if role.Emoji == nil {
- role.Emoji = &discord.ComponentEmoji{
+ emojiVal := role.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
Name: discordutil.Index2Emoji(i),
}
}
buttons[i] = discord.ButtonComponent{
Style: place.ButtonType,
- Emoji: role.Emoji,
+ Emoji: emojiVal,
Label: label,
CustomID: fmt.Sprintf("role:panel_use:button:%s:%s", place.ID, role.ID),
}
@@ -496,7 +487,7 @@ func rpPlacedMessage(place *ent.RolePanelPlaced, locale discord.Locale) discord.
builder.AddComponents(
components...,
)
- case rolepanelplaced.TypeSelectMenu:
+ case RolePanelPlacedTypeSelectMenu:
if place.FoldingSelectMenu {
builder.AddComponents(
discord.NewActionRow(
@@ -514,18 +505,19 @@ func rpPlacedMessage(place *ent.RolePanelPlaced, locale discord.Locale) discord.
return builder
}
-func rpPlacedSelectMenu(place *ent.RolePanelPlaced, locale discord.Locale) discord.ActionRowComponent {
+func rpPlacedSelectMenu(place *models.RolePanelPlaced, locale discord.Locale) discord.ActionRowComponent {
options := make([]discord.StringSelectMenuOption, len(place.Roles))
for i, role := range place.Roles {
- if role.Emoji == nil {
- role.Emoji = &discord.ComponentEmoji{
+ emojiVal := role.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{
Name: discordutil.Index2Emoji(i),
}
}
options[i] = discord.StringSelectMenuOption{
Label: role.Name,
Value: role.ID.String(),
- Emoji: role.Emoji,
+ Emoji: emojiVal,
}
}
actionRow := discord.NewActionRow(
diff --git a/bot/commands/role/role.go b/bot/commands/role/role.go
index ec4ba05d..b4117387 100644
--- a/bot/commands/role/role.go
+++ b/bot/commands/role/role.go
@@ -34,14 +34,11 @@ import (
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/snowflake/v2"
"github.com/google/uuid"
+ "gorm.io/gorm"
+
"github.com/sabafly/gobot/bot/components"
"github.com/sabafly/gobot/bot/components/generic"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/guild"
- "github.com/sabafly/gobot/ent/rolepanel"
- "github.com/sabafly/gobot/ent/rolepaneledit"
- "github.com/sabafly/gobot/ent/rolepanelplaced"
- "github.com/sabafly/gobot/ent/schema"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/discordutil"
"github.com/sabafly/gobot/internal/embeds"
@@ -141,8 +138,7 @@ func Command(c *components.Components) components.Command {
MaxLength: 32,
Required: true,
Value: translate.Message(event.Locale(), "components.role.panel.default_name"),
- },
- ),
+ }),
discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.create.modal.input.2.label"),
discord.TextInputComponent{
CustomID: "description",
@@ -155,7 +151,6 @@ func Command(c *components.Components) components.Command {
); err != nil {
return errors.NewError(err)
}
-
return nil
},
},
@@ -169,59 +164,49 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
-
panelID, err := uuid.Parse(event.SlashCommandInteractionData().String("panel"))
if err != nil {
return errors.NewError(err)
}
-
- if !g.QueryRolePanels().Where(rolepanel.ID(panelID)).ExistX(event) {
+ var rolePanel models.RolePanel
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", panelID, g.ID).First(&rolePanel).Error; err != nil {
return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
}
-
- rolePanel := g.QueryRolePanels().WithEdit().Where(rolepanel.ID(panelID)).FirstX(event)
-
- if rolePanel.QueryEdit().ExistX(event) {
- c.DB().RolePanelEdit.DeleteOneID(rolePanel.QueryEdit().FirstIDX(event)).ExecX(event)
+ var oldEdit models.RolePanelEdit
+ if err := c.GormDB().Where("parent_id = ?", rolePanel.ID).First(&oldEdit).Error; err == nil {
+ c.GormDB().Delete(&oldEdit)
}
-
var removeRoles []snowflake.ID
- var roles []discord.Role
+ var discordRoles []discord.Role
for _, r := range rolePanel.Roles {
- if roles == nil {
- roles, err = event.Client().Rest.GetRoles(*event.GuildID())
+ if discordRoles == nil {
+ discordRoles, err = event.Client().Rest.GetRoles(*event.GuildID())
if err != nil {
return errors.NewError(err)
}
}
- if slices.ContainsFunc(roles, func(role discord.Role) bool { return role.ID == r.ID }) {
- continue
+ if !slices.ContainsFunc(discordRoles, func(role discord.Role) bool { return role.ID == r.ID }) {
+ removeRoles = append(removeRoles, r.ID)
}
- removeRoles = append(removeRoles, r.ID)
}
for _, id := range removeRoles {
- rolePanel.Roles = slices.DeleteFunc(rolePanel.Roles, func(r schema.Role) bool { return r.ID == id })
+ rolePanel.Roles = slices.DeleteFunc(rolePanel.Roles, func(r models.Role) bool { return r.ID == id })
}
- rolePanel =
- rolePanel.Update().
- SetUpdatedAt(time.Now()).
- SetRoles(rolePanel.Roles).
- SaveX(event)
-
- edit := c.DB().RolePanelEdit.Create().
- SetGuild(g).
- SetParent(rolePanel).
- SetChannelID(event.Channel().ID()).
- SaveX(event)
-
- if err := event.CreateMessage(
- rpEditBaseMessage(event, rolePanel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
+ rolePanel.UpdatedAt = time.Now()
+ c.GormDB().Save(&rolePanel)
+ edit := models.RolePanelEdit{ID: uuid.New(), GuildID: g.ID, ParentID: rolePanel.ID, ChannelID: event.Channel().ID()}
+ if err := c.GormDB().Create(&edit).Error; err != nil {
+ return errors.NewError(err)
+ }
+ edit.Parent = rolePanel
+ builder, err := rpEditBaseMessage(c, &rolePanel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.CreateMessage(builder.BuildCreate()); err != nil {
return errors.NewError(err)
}
-
return nil
},
},
@@ -235,22 +220,17 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
-
panelID, err := uuid.Parse(event.SlashCommandInteractionData().String("panel"))
if err != nil {
return errors.NewError(err)
}
-
place, err := createPanelPlace(event, c, panelID, event.Channel().ID(), g)
if err != nil {
return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
}
-
- if err := event.CreateMessage(
- rpPlaceBaseMenu(place, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
+ builder := rpPlaceBaseMenu(place, event.Locale())
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.CreateMessage(builder.BuildCreate()); err != nil {
return errors.NewError(err)
}
return nil
@@ -270,42 +250,49 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
-
- if !g.QueryRolePanels().Where(rolepanel.ID(panelID)).ExistX(event) {
+ var panel models.RolePanel
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", panelID, g.ID).First(&panel).Error; err != nil {
return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
}
- panel := g.QueryRolePanels().Where(rolepanel.ID(panelID)).FirstX(event)
-
- places := panel.QueryPlacements().AllX(event)
- for _, place := range places {
- if place.MessageID == nil {
- continue
+ if err := c.GormDB().Transaction(func(tx *gorm.DB) error {
+ var places []models.RolePanelPlaced
+ if err := tx.Where("role_panel_id = ?", panel.ID).Find(&places).Error; err != nil {
+ return err
}
- _ = event.Client().Rest.DeleteMessage(place.ChannelID, *place.MessageID)
- }
- c.DB().RolePanelPlaced.Delete().
- Where(rolepanelplaced.HasRolePanelWith(rolepanel.ID(panel.ID))).
- ExecX(event)
- c.DB().RolePanelEdit.Delete().
- Where(rolepaneledit.HasParentWith(rolepanel.ID(panel.ID))).
- ExecX(event)
+ for _, place := range places {
+ if place.MessageID != nil {
+ if err := event.Client().Rest.DeleteMessage(place.ChannelID, *place.MessageID); err != nil {
+ slog.Error("Failed to delete message", "error", err, "panel_id", panel.ID, "channel_id", place.ChannelID, "message_id", *place.MessageID)
+ return err
+ }
+ }
+ }
- c.DB().RolePanel.DeleteOne(panel).ExecX(event)
+ if err := tx.Where("role_panel_id = ?", panel.ID).Delete(&models.RolePanelPlaced{}).Error; err != nil {
+ return err
+ }
+ if err := tx.Where("parent_id = ?", panel.ID).Delete(&models.RolePanelEdit{}).Error; err != nil {
+ return err
+ }
+ if err := tx.Delete(&panel).Error; err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return errors.NewError(err)
+ }
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.role.panel.delete.message.embed.title")).
- SetDescription(translate.Message(event.Locale(), "components.role.panel.delete.message.embed.description", translate.WithTemplate(map[string]any{"RolePanel": panel.Name}))).
- Build(),
- ),
- ).
- BuildCreate(),
- ); err != nil {
+ builder := discord.NewMessageBuilder()
+ builder.SetEmbeds(
+ embeds.SetEmbedProperties(
+ discord.NewEmbedBuilder().
+ SetTitle(translate.Message(event.Locale(), "components.role.panel.delete.message.embed.title")).
+ SetDescription(translate.Message(event.Locale(), "components.role.panel.delete.message.embed.description", translate.WithTemplate(map[string]any{"RolePanel": panel.Name}))).
+ Build(),
+ ))
+ if err := event.CreateMessage(builder.BuildCreate()); err != nil {
return errors.NewError(err)
}
return nil
@@ -313,27 +300,9 @@ func Command(c *components.Components) components.Command {
},
},
AutocompleteHandlers: map[string]generic.PermissionAutocompleteHandler{
- "/role/panel/place:panel": generic.PAutocompleteHandler{
- Permission: []generic.Permission{
- generic.PermissionString("role.panel.place"),
- },
- DiscordPerm: discord.PermissionManageRoles,
- AutocompleteHandler: panelAutocomplete,
- },
- "/role/panel/edit:panel": generic.PAutocompleteHandler{
- Permission: []generic.Permission{
- generic.PermissionString("role.panel.edit"),
- },
- DiscordPerm: discord.PermissionManageRoles,
- AutocompleteHandler: panelAutocomplete,
- },
- "/role/panel/delete:panel": generic.PAutocompleteHandler{
- Permission: []generic.Permission{
- generic.PermissionString("role.panel.delete"),
- },
- DiscordPerm: discord.PermissionManageRoles,
- AutocompleteHandler: panelAutocomplete,
- },
+ "/role/panel/place:panel": generic.PAutocompleteHandler{Permission: []generic.Permission{generic.PermissionString("role.panel.place")}, DiscordPerm: discord.PermissionManageRoles, AutocompleteHandler: panelAutocomplete},
+ "/role/panel/edit:panel": generic.PAutocompleteHandler{Permission: []generic.Permission{generic.PermissionString("role.panel.edit")}, DiscordPerm: discord.PermissionManageRoles, AutocompleteHandler: panelAutocomplete},
+ "/role/panel/delete:panel": generic.PAutocompleteHandler{Permission: []generic.Permission{generic.PermissionString("role.panel.delete")}, DiscordPerm: discord.PermissionManageRoles, AutocompleteHandler: panelAutocomplete},
},
ModalHandlers: map[string]generic.ModalHandler{
"role:panel_create_modal": func(c *components.Components, event *events.ModalSubmitInteractionCreate) errors.Error {
@@ -341,29 +310,24 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
-
- rolePanel := c.DB().RolePanel.Create().
- SetName(event.Data.Text("name")).
- SetDescription(event.Data.Text("description")).
- SetGuild(g).
- SaveX(event)
-
- edit := c.DB().RolePanelEdit.Create().
- SetGuild(g).
- SetParent(rolePanel).
- SetChannelID(event.Channel().ID()).
- SaveX(event)
-
- initialize(edit, rolePanel)
-
- if err := event.CreateMessage(
- rpEditBaseMessage(event, rolePanel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
+ rolePanel := models.RolePanel{ID: uuid.New(), Name: event.Data.Text("name"), Description: event.Data.Text("description"), GuildID: g.ID}
+ if err := c.GormDB().Create(&rolePanel).Error; err != nil {
+ return errors.NewError(err)
+ }
+ edit := models.RolePanelEdit{ID: uuid.New(), GuildID: g.ID, ParentID: rolePanel.ID, ChannelID: event.Channel().ID()}
+ if err := c.GormDB().Create(&edit).Error; err != nil {
+ return errors.NewError(err)
+ }
+ edit.Parent = rolePanel
+ initialize(&edit, &rolePanel)
+ builder, err := rpEditBaseMessage(c, &rolePanel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.CreateMessage(builder.BuildCreate()); err != nil {
return errors.NewError(err)
}
-
return nil
},
"role:panel_edit_modal": func(c *components.Components, event *events.ModalSubmitInteractionCreate) errors.Error {
@@ -371,64 +335,43 @@ func Command(c *components.Components) components.Command {
action := args[2]
editID, err := uuid.Parse(args[3])
if err != nil {
- return errors.NewError(err)
+ return errors.NewError(errors.ErrorMessage("errors.timeout", event))
}
g, err := c.GuildCreateID(event, *event.GuildID())
if err != nil {
return errors.NewError(err)
}
- if !g.QueryRolePanelEdits().Where(rolepaneledit.ID(editID)).ExistX(event) {
+ var edit models.RolePanelEdit
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", editID, g.ID).First(&edit).Error; err != nil {
return errors.NewError(errors.ErrorMessage("errors.timeout", event))
}
-
- edit := g.QueryRolePanelEdits().Where(rolepaneledit.ID(editID)).FirstX(event)
- panel := edit.QueryParent().OnlyX(event)
-
- initialize(edit, panel)
-
+ var panel models.RolePanel
+ c.GormDB().Where("id = ?", edit.ParentID).First(&panel)
+ initialize(&edit, &panel)
switch action {
case "change_name":
- edit = edit.Update().
- SetModified(true).
- SetName(event.Data.Text("name")).
- SaveX(event)
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
- return errors.NewError(err)
- }
+ name := event.Data.Text("name")
+ edit.Modified, edit.Name = true, &name
case "change_description":
- edit = edit.Update().
- SetModified(true).
- SetDescription(event.Data.Text("description")).
- SaveX(event)
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
- return errors.NewError(err)
- }
+ desc := event.Data.Text("description")
+ edit.Modified, edit.Description = true, &desc
case "set_display_name":
if edit.SelectedRole != nil {
- edit.Roles[slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })].Name = event.Data.Text("display_name")
- edit = edit.Update().
- SetModified(true).
- SetRoles(edit.Roles).
- SaveX(event)
- }
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
- return errors.NewError(err)
+ idx := slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
+ if idx != -1 {
+ edit.Roles[idx].Name, edit.Modified = event.Data.Text("display_name"), true
+ }
}
}
-
+ c.GormDB().Save(&edit)
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
+ return errors.NewError(err)
+ }
return nil
},
},
@@ -443,66 +386,42 @@ func Command(c *components.Components) components.Command {
action := args[2]
editID, err := uuid.Parse(args[3])
if err != nil {
- return errors.NewError(err)
+ return errors.NewError(errors.ErrorMessage("errors.invalid_argument", event))
}
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- if !g.QueryRolePanelEdits().Where(rolepaneledit.ID(editID)).ExistX(event) {
+ g, _ := c.GuildCreateID(event, *event.GuildID())
+ var edit models.RolePanelEdit
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", editID, g.ID).First(&edit).Error; err != nil {
return errors.NewError(errors.ErrorMessage("errors.timeout", event))
}
- edit := g.QueryRolePanelEdits().Where(rolepaneledit.ID(editID)).FirstX(event)
- panel := edit.QueryParent().OnlyX(event)
-
- initialize(edit, panel)
-
+ var panel models.RolePanel
+ c.GormDB().Where("id = ?", edit.ParentID).First(&panel)
+ initialize(&edit, &panel)
switch action {
case "change_name", "change_description":
- if err := event.Modal(
- discord.NewModalCreateBuilder().
- SetTitle(translate.Message(event.Locale(), fmt.Sprintf("components.role.panel.edit.action.%s.title", action))).
- SetCustomID(fmt.Sprintf("role:panel_edit_modal:%s:%s", action, edit.ID)).
- SetComponents(
- builtin.Or(action == "change_name",
- discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.edit.change_name.modal.input.name.label"),
- discord.TextInputComponent{
- CustomID: "name",
- Style: discord.TextInputStyleShort,
- MinLength: builtin.Ptr(1),
- MaxLength: 32,
- Required: true,
- Value: *edit.Name,
- },
- ),
- discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.edit.change_description.modal.input.description.label"),
- discord.TextInputComponent{
- CustomID: "description",
- Style: discord.TextInputStyleParagraph,
- MaxLength: 140,
- Value: *edit.Description,
- },
- ),
- ),
- ).
- Build(),
- ); err != nil {
+ nameVal, descVal := "", ""
+ if edit.Name != nil {
+ nameVal = *edit.Name
+ }
+ if edit.Description != nil {
+ descVal = *edit.Description
+ }
+ modal := discord.NewModalCreateBuilder().SetTitle(translate.Message(event.Locale(), fmt.Sprintf("components.role.panel.edit.action.%s.title", action))).SetCustomID(fmt.Sprintf("role:panel_edit_modal:%s:%s", action, edit.ID)).SetComponents(builtin.Or(action == "change_name", discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.edit.change_name.modal.input.name.label"), discord.TextInputComponent{CustomID: "name", Style: discord.TextInputStyleShort, MinLength: builtin.Ptr(1), MaxLength: 32, Required: true, Value: nameVal}), discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.edit.change_description.modal.input.description.label"), discord.TextInputComponent{CustomID: "description", Style: discord.TextInputStyleParagraph, MaxLength: 140, Value: descVal}))).Build()
+ if err := event.Modal(modal); err != nil {
return errors.NewError(err)
}
case "modify_roles":
- if err := event.UpdateMessage(
- rpEditModifyRolesMessage(edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ builder := rpEditModifyRolesMessage(&edit, event.Locale())
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "base_menu":
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "add_role":
@@ -512,24 +431,18 @@ func Command(c *components.Components) components.Command {
return errors.NewError(errors.ErrorMessage("errors.invalid.self", event))
}
var roles []discord.Role
- roleMap := map[snowflake.ID]discord.Role{}
for _, id := range self.RoleIDs {
- role, err := event.Client().Rest.GetRole(*event.GuildID(), id)
- if err != nil {
- slog.Error("API ERROR GetRole", "error", err, "guild_id", *event.GuildID(), "id", id)
- continue
+ if r, err := event.Client().Rest.GetRole(*event.GuildID(), id); err == nil {
+ roles = append(roles, *r)
}
- roleMap[id] = *role
- roles = append(roles, *role)
}
highestRole := discordutil.GetHighestRole(roles)
if highestRole == nil {
return errors.NewError(errors.ErrorMessage("errors.invalid.self", event))
}
var deletedRole []snowflake.ID
-
for i, r := range selectedRoles {
- if slices.ContainsFunc(edit.Roles, func(r1 schema.Role) bool { return r1.ID == r.ID }) {
+ if slices.ContainsFunc(edit.Roles, func(r1 models.Role) bool { return r1.ID == r.ID }) {
continue
}
if r.Managed || r.Compare(*highestRole) != -1 {
@@ -537,46 +450,31 @@ func Command(c *components.Components) components.Command {
deletedRole = append(deletedRole, i)
continue
}
- edit.Roles = append(edit.Roles, schema.Role{
- ID: r.ID,
- Name: r.Name,
- })
+ edit.Roles = append(edit.Roles, models.Role{ID: r.ID, Name: r.Name})
}
-
if len(deletedRole) > 0 {
- var deletedRoleString string
+ var s string
for _, id := range deletedRole {
- deletedRoleString += fmt.Sprintf("- %s\r", discord.RoleMention(id))
+ s += fmt.Sprintf("- %s\r", discord.RoleMention(id))
}
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.role.panel.edit.add_role.deleted_role.embed.title")).
- SetDescriptionf("%s\n"+deletedRoleString, translate.Message(event.Locale(), "components.role.panel.edit.add_role.deleted_role.embed.description")).
- Build(),
- ),
- ).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
+ embed := discord.NewEmbedBuilder().SetTitle(translate.Message(event.Locale(), "components.role.panel.edit.add_role.deleted_role.embed.title")).SetDescriptionf("%s\n"+s, translate.Message(event.Locale(), "components.role.panel.edit.add_role.deleted_role.embed.description")).Build()
+ if err := event.CreateMessage(discord.NewMessageBuilder().
+ SetEmbeds(embeds.SetEmbedProperties(embed)).
+ SetFlags(discord.MessageFlagEphemeral).
+ BuildCreate()); err != nil {
return errors.NewError(err)
}
return nil
}
- edit.Roles = slices.DeleteFunc(edit.Roles, func(r schema.Role) bool { _, ok := selectedRoles[r.ID]; return !ok })
-
- edit = edit.Update().
- SetModified(true).
- SetRoles(edit.Roles).
- SaveX(event)
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ edit.Roles = slices.DeleteFunc(edit.Roles, func(r models.Role) bool { _, ok := selectedRoles[r.ID]; return !ok })
+ edit.Modified = true
+ c.GormDB().Save(&edit)
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "select_role":
@@ -584,193 +482,144 @@ func Command(c *components.Components) components.Command {
if values := event.StringSelectMenuInteractionData().Values; len(values) > 0 {
id = builtin.Ptr(snowflake.MustParse(values[0]))
}
- if id == nil {
- edit = edit.Update().
- ClearSelectedRole().
- SaveX(event)
- } else {
- edit = edit.Update().
- SetNillableSelectedRole(id).
- SaveX(event)
+ edit.SelectedRole = id
+ c.GormDB().Save(&edit)
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
}
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "delete":
if edit.SelectedRole != nil {
- edit.Roles = slices.DeleteFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })
- edit = edit.Update().
- SetModified(true).
- SetRoles(edit.Roles).
- SaveX(event)
+ edit.Roles = slices.DeleteFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
+ edit.Modified = true
+ c.GormDB().Save(&edit)
}
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "move_up", "move_down":
if edit.SelectedRole != nil {
- index := slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })
+ idx := slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
mv := builtin.Or(action == "move_up", -1, 1)
- edit.Roles[index+mv], edit.Roles[index] = edit.Roles[index], edit.Roles[index+mv]
-
- edit = edit.Update().
- SetModified(true).
- SetRoles(edit.Roles).
- SaveX(event)
+ if idx+mv >= 0 && idx+mv < len(edit.Roles) {
+ edit.Roles[idx+mv], edit.Roles[idx], edit.Modified = edit.Roles[idx], edit.Roles[idx+mv], true
+ c.GormDB().Save(&edit)
+ }
}
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "set_display_name":
if edit.SelectedRole != nil {
- if err := event.Modal(
- discord.NewModalCreateBuilder().
- SetTitle(translate.Message(event.Locale(), "components.role.panel.edit.set_display.name.modal.title")).
- SetCustomID(fmt.Sprintf("role:panel_edit_modal:set_display_name:%s", edit.ID)).
- SetComponents(
- discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.edit.set_display.name.modal.input.display_name.label"),
- discord.TextInputComponent{
- CustomID: "display_name",
- Style: discord.TextInputStyleShort,
- MinLength: builtin.Ptr(1),
- MaxLength: 100,
- Required: true,
- Value: edit.Roles[slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })].Name,
- },
- ),
- ).
- Build(),
- ); err != nil {
- return errors.NewError(err)
+ idx := slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
+ if idx != -1 {
+ modal := discord.NewModalCreateBuilder().SetTitle(translate.Message(event.Locale(), "components.role.panel.edit.set_display.name.modal.title")).SetCustomID(fmt.Sprintf("role:panel_edit_modal:set_display_name:%s", edit.ID)).SetComponents(discord.NewLabel(translate.Message(event.Locale(), "components.role.panel.edit.set_display.name.modal.input.display_name.label"), discord.TextInputComponent{CustomID: "display_name", Style: discord.TextInputStyleShort, MinLength: builtin.Ptr(1), MaxLength: 100, Required: true, Value: edit.Roles[idx].Name})).Build()
+ if err := event.Modal(modal); err != nil {
+ return errors.NewError(err)
+ }
}
}
case "set_emoji":
if edit.SelectedRole != nil {
- edit = edit.Update().
- SetEmojiAuthor(event.User().ID).
- SetToken(event.Token()).
- SaveX(event)
- }
-
- if err := event.UpdateMessage(
- rpEditSetEmojiMessage(edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ token := event.Token()
+ edit.EmojiAuthor = builtin.Ptr(event.User().ID)
+ edit.Token = &token
+ c.GormDB().Save(&edit)
+ }
+ builder := rpEditSetEmojiMessage(&edit, event.Locale())
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "cancel_emoji", "reset_emoji":
- edit = edit.Update().
- ClearEmojiAuthor().
- ClearToken().
- SaveX(event)
-
- if action == "reset_emoji" {
- edit.Roles[slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })].Emoji = nil
- edit = edit.Update().
- SetModified(true).
- SetRoles(edit.Roles).
- SaveX(event)
+ edit.EmojiAuthor, edit.Token = nil, nil
+ if action == "reset_emoji" && edit.SelectedRole != nil {
+ idx := slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
+ if idx != -1 {
+ edit.Roles[idx].Emoji, edit.Modified = nil, true
+ }
}
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ if err := c.GormDB().Save(&edit).Error; err != nil {
+ return errors.NewError(err)
+ }
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "save_change":
-
- edit = edit.Update().
- SetModified(false).
- SaveX(event)
-
- update := panel.Update().
- SetUpdatedAt(time.Now()).
- SetNillableName(edit.Name).
- SetNillableDescription(edit.Description)
+ edit.Modified = false
+ if err := c.GormDB().Save(&edit).Error; err != nil {
+ return errors.NewError(err)
+ }
+ if edit.Name != nil {
+ panel.Name = *edit.Name
+ }
+ if edit.Description != nil {
+ panel.Description = *edit.Description
+ }
+ panel.UpdatedAt = time.Now()
if edit.Roles != nil {
- update.SetRoles(edit.Roles)
+ panel.Roles = edit.Roles
}
- panel = update.SaveX(event)
-
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ if err := c.GormDB().Save(&panel).Error; err != nil {
+ return errors.NewError(err)
+ }
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
+ return errors.NewError(err)
+ }
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
case "apply_change":
var ok bool
- g.RolePanelEditTimes, ok = ratelimit.CheckLimit(g.RolePanelEditTimes, []ratelimit.Rule{
- {
- Limit: 3,
- Unit: time.Minute * 10,
- },
- {
- Limit: 5,
- Unit: time.Minute * 30,
- },
- })
- g.Update().
- SetRolePanelEditTimes(g.RolePanelEditTimes).
- SaveX(event)
+ g.RolePanelEditTimes, ok = ratelimit.CheckLimit(g.RolePanelEditTimes, []ratelimit.Rule{{Limit: 3, Unit: time.Minute * 10}, {Limit: 5, Unit: time.Minute * 30}})
+ c.GormDB().Save(g)
if !ok || len(panel.Roles) < 1 {
return errors.NewError(errors.ErrorMessage("errors.ratelimited", event))
}
-
- panel = panel.Update().
- SetAppliedAt(time.Now()).
- SaveX(event)
-
- c.DB().RolePanelPlaced.Delete().Where(rolepanelplaced.And(rolepanelplaced.Or(rolepanelplaced.MessageIDIsNil(), rolepanelplaced.TypeIsNil()), rolepanelplaced.HasGuildWith(guild.ID(g.ID)))).ExecX(event)
- go updateRolePanel(event, panel, event.Locale(), event.Client(), true)
- if err := event.UpdateMessage(
- rpEditBaseMessage(event, panel, edit, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ panel.AppliedAt = time.Now()
+ c.GormDB().Save(&panel)
+ c.GormDB().Where("(message_id IS NULL OR type = '') AND guild_id = ?", g.ID).Delete(&models.RolePanelPlaced{})
+ go updateRolePanel(context.Background(), &panel, event.Locale(), event.Client(), true, c)
+ builder, err := rpEditBaseMessage(c, &panel, &edit, event.Locale())
+ if err != nil {
return errors.NewError(err)
}
- case "place":
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
-
+ case "place":
place, err := createPanelPlace(event, c, panel.ID, event.Channel().ID(), g)
if err != nil {
return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
}
-
- if err := event.UpdateMessage(
- rpPlaceBaseMenu(place, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ builder := rpPlaceBaseMenu(place, event.Locale())
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
- default:
- slog.Warn("不明なcustom_id", "id", event.Data.CustomID())
}
-
return nil
},
},
@@ -781,26 +630,17 @@ func Command(c *components.Components) components.Command {
DiscordPerm: discord.PermissionManageRoles,
ComponentHandler: func(c *components.Components, event *events.ComponentInteractionCreate) errors.Error {
args := strings.Split(event.Data.CustomID(), ":")
- action := args[2]
- placeID, err := uuid.Parse(args[3])
- if err != nil {
- return errors.NewError(err)
- }
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- if !g.QueryRolePanelPlacements().Where(rolepanelplaced.ID(placeID)).ExistX(event) {
+ action, placeID := args[2], uuid.MustParse(args[3])
+ g, _ := c.GuildCreateID(event, *event.GuildID())
+ var place models.RolePanelPlaced
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", placeID, g.ID).First(&place).Error; err != nil {
return errors.NewError(errors.ErrorMessage("errors.timeout", event))
}
- place := g.QueryRolePanelPlacements().Where(rolepanelplaced.ID(placeID)).FirstX(event)
- panel := place.QueryRolePanel().OnlyX(event)
-
+ var panel models.RolePanel
+ c.GormDB().Where("id = ?", place.RolePanelID).First(&panel)
switch action {
case "type":
- place = place.Update().
- SetType(rolepanelplaced.Type(event.StringSelectMenuInteractionData().Values[0])).
- SaveX(event)
+ place.Type = event.StringSelectMenuInteractionData().Values[0]
case "button_type":
var t = discord.ButtonStylePrimary
switch event.StringSelectMenuInteractionData().Values[0] {
@@ -813,56 +653,34 @@ func Command(c *components.Components) components.Command {
case "gray":
t = discord.ButtonStyleSecondary
}
- place = place.Update().
- SetButtonType(t).
- SaveX(event)
+ place.ButtonType = t
case "show_name":
- place = place.Update().
- SetShowName(!place.ShowName).
- SaveX(event)
+ place.ShowName = !place.ShowName
case "folding_select_menu":
- place = place.Update().
- SetFoldingSelectMenu(!place.FoldingSelectMenu).
- SaveX(event)
+ place.FoldingSelectMenu = !place.FoldingSelectMenu
case "hide_notice":
- place = place.Update().
- SetHideNotice(!place.HideNotice).
- SaveX(event)
+ place.HideNotice = !place.HideNotice
case "use_display_name":
- place = place.Update().
- SetUseDisplayName(!place.UseDisplayName).
- SaveX(event)
+ place.UseDisplayName = !place.UseDisplayName
case "create":
if len(panel.Roles) < 1 {
return errors.NewError(errors.ErrorMessage("errors.not_exist", event))
}
- if err := rolePanelPlace(event, place, event.Locale(), event.Client(), true); err != nil {
+ if err := rolePanelPlace(event, &place, event.Locale(), event.Client(), true, c); err != nil {
return errors.NewError(err)
}
-
- updateMessage := discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.role.panel.create.message")).
- SetDescription(translate.Message(event.Locale(), "components.role.panel.create.description")).
- Build(),
- ),
- ).
- BuildUpdate()
- updateMessage.Components = &[]discord.LayoutComponent{}
- if err := event.UpdateMessage(
- updateMessage,
- ); err != nil {
+ embed := discord.NewEmbedBuilder().SetTitle(translate.Message(event.Locale(), "components.role.panel.create.message")).SetDescription(translate.Message(event.Locale(), "components.role.panel.create.description")).Build()
+ if err := event.UpdateMessage(discord.NewMessageBuilder().
+ SetEmbeds(embeds.SetEmbedProperties(embed)).
+ BuildUpdate()); err != nil {
return errors.NewError(err)
}
return nil
}
- if err := event.UpdateMessage(
- rpPlaceBaseMenu(place, event.Locale()).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
+ c.GormDB().Save(&place)
+ builder := rpPlaceBaseMenu(&place, event.Locale())
+ builder.SetFlags(discord.MessageFlagEphemeral)
+ if err := event.UpdateMessage(builder.BuildUpdate()); err != nil {
return errors.NewError(err)
}
return nil
@@ -870,99 +688,54 @@ func Command(c *components.Components) components.Command {
},
"role:panel_use": generic.ComponentHandler(func(c *components.Components, event *events.ComponentInteractionCreate) errors.Error {
args := strings.Split(event.Data.CustomID(), ":")
- action := args[2]
- placeID, err := uuid.Parse(args[3])
- if err != nil {
- return errors.NewError(err)
- }
- g, err := c.GuildCreateID(event, *event.GuildID())
- if err != nil {
- return errors.NewError(err)
- }
- if !g.QueryRolePanelPlacements().Where(rolepanelplaced.ID(placeID)).ExistX(event) {
- if err := event.Client().Rest.DeleteMessage(event.Channel().ID(), event.Message.ID); err != nil {
- return errors.NewError(err)
- }
+ action, placeID := args[2], uuid.MustParse(args[3])
+ g, _ := c.GuildCreateID(event, *event.GuildID())
+ var place models.RolePanelPlaced
+ if err := c.GormDB().Where("id = ? AND guild_id = ?", placeID, g.ID).First(&place).Error; err != nil {
+ _ = event.Client().Rest.DeleteMessage(event.Channel().ID(), event.Message.ID)
return errors.NewError(errors.ErrorMessage("errors.deleted", event))
}
- place := g.QueryRolePanelPlacements().Where(rolepanelplaced.ID(placeID)).FirstX(event)
-
switch action {
case "button":
roleID := snowflake.MustParse(args[4])
- if !slices.ContainsFunc(place.Roles, func(r schema.Role) bool { return r.ID == roleID }) {
- if err := event.UpdateMessage(
- rpPlacedMessage(place, event.Locale()).
- BuildUpdate(),
- ); err != nil {
+ if !slices.ContainsFunc(place.Roles, func(r models.Role) bool { return r.ID == roleID }) {
+ if err := event.UpdateMessage(rpPlacedMessage(&place, event.Locale()).BuildUpdate()); err != nil {
return errors.NewError(err)
}
return nil
}
-
- _, ok := event.Client().Caches.Role(*event.GuildID(), roleID)
- if !ok {
- if err := event.DeferUpdateMessage(); err != nil {
- return errors.NewError(err)
- }
+ if _, ok := event.Client().Caches.Role(*event.GuildID(), roleID); !ok {
+ _ = event.DeferUpdateMessage()
return nil
}
-
contain := slices.Contains(event.Member().RoleIDs, roleID)
+ reason := rest.WithReason(fmt.Sprintf("Role Panel \"%s\" (%s)", place.Name, place.ID))
if contain {
- if err := event.Client().Rest.RemoveMemberRole(g.ID, event.User().ID, roleID, rest.WithReason(fmt.Sprintf("Role Panel \"%s\" (%s)", place.Name, place.ID))); err != nil {
+ if err := event.Client().Rest.RemoveMemberRole(g.ID, event.User().ID, roleID, reason); err != nil {
return errors.NewError(errors.ErrorMessage("errors.fail.role.panel", event))
}
} else {
- if err := event.Client().Rest.AddMemberRole(g.ID, event.User().ID, roleID, rest.WithReason(fmt.Sprintf("Role Panel \"%s\" (%s)", place.Name, place.ID))); err != nil {
+ if err := event.Client().Rest.AddMemberRole(g.ID, event.User().ID, roleID, reason); err != nil {
return errors.NewError(errors.ErrorMessage("errors.fail.role.panel", event))
}
}
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.role.panel.use."+builtin.Or(!contain, "added", "removed"))).
- SetDescription(translate.Message(event.Locale(), "components.role.panel.use."+builtin.Or(!contain, "added", "removed")+".description", translate.WithTemplate(map[string]any{"Role": discord.RoleMention(roleID)}))).
- Build(),
- ),
- ).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
+ embed := discord.NewEmbedBuilder().SetTitle(translate.Message(event.Locale(), "components.role.panel.use."+builtin.Or(!contain, "added", "removed"))).SetDescription(translate.Message(event.Locale(), "components.role.panel.use."+builtin.Or(!contain, "added", "removed")+`.description`, translate.WithTemplate(map[string]any{"Role": discord.RoleMention(roleID)}))).Build()
+ if err := event.CreateMessage(discord.NewMessageBuilder().
+ SetEmbeds(embeds.SetEmbedProperties(embed)).
+ SetFlags(discord.MessageFlagEphemeral).
+ BuildCreate()); err != nil {
return errors.NewError(err)
}
case "select_menu_fold":
options := make([]discord.StringSelectMenuOption, len(place.Roles))
for i, role := range place.Roles {
- if role.Emoji == nil {
- role.Emoji = &discord.ComponentEmoji{
- Name: discordutil.Index2Emoji(i),
- }
- }
- options[i] = discord.StringSelectMenuOption{
- Label: role.Name,
- Value: role.ID.String(),
- Emoji: role.Emoji,
- Default: slices.Contains(event.Member().RoleIDs, role.ID),
+ emojiVal := role.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{Name: discordutil.Index2Emoji(i)}
}
+ options[i] = discord.StringSelectMenuOption{Label: role.Name, Value: role.ID.String(), Emoji: emojiVal, Default: slices.Contains(event.Member().RoleIDs, role.ID)}
}
- actionRow := discord.NewActionRow(
- discord.StringSelectMenuComponent{
- CustomID: fmt.Sprintf("role:panel_use:select_menu:%s", place.ID.String()),
- Placeholder: translate.Message(event.Locale(), "components.role.panel.components.select_menu.placeholder"),
- MinValues: builtin.Ptr(0),
- MaxValues: len(place.Roles),
- Options: options,
- },
- )
- if err := event.CreateMessage(
- discord.NewMessageBuilder().
- SetComponents(actionRow).
- SetFlags(discord.MessageFlagEphemeral).
- BuildCreate(),
- ); err != nil {
+ if err := event.CreateMessage(discord.NewMessageBuilder().SetComponents(discord.NewActionRow(discord.StringSelectMenuComponent{CustomID: fmt.Sprintf("role:panel_use:select_menu:%s", place.ID.String()), Placeholder: translate.Message(event.Locale(), "components.role.panel.components.select_menu.placeholder"), MinValues: builtin.Ptr(0), MaxValues: len(place.Roles), Options: options})).SetFlags(discord.MessageFlagEphemeral).BuildCreate()); err != nil {
return errors.NewError(err)
}
case "select_menu":
@@ -970,88 +743,47 @@ func Command(c *components.Components) components.Command {
for _, v := range event.StringSelectMenuInteractionData().Values {
selectedRoles = append(selectedRoles, snowflake.MustParse(v))
}
-
- var addRoles []snowflake.ID
- var removedRoles []snowflake.ID
- var unchangedRole []snowflake.ID
+ var addRoles, removedRoles, unchangedRole []snowflake.ID
for _, role := range place.Roles {
if slices.Contains(selectedRoles, role.ID) {
- // 選ばれたとき
if slices.Index(event.Member().RoleIDs, role.ID) != -1 {
- // 持ってたなら
unchangedRole = append(unchangedRole, role.ID)
- continue
} else {
- // 持ってないなら
- _, ok := event.Client().Caches.Role(*event.GuildID(), role.ID)
- if !ok {
- continue
- }
- addRoles = append(addRoles, role.ID)
-
- if err := event.Client().Rest.AddMemberRole(*event.GuildID(), event.User().ID, role.ID); err != nil {
- return errors.NewError(errors.ErrorMessage("errors.fail.role.panel", event))
+ if _, ok := event.Client().Caches.Role(*event.GuildID(), role.ID); ok {
+ addRoles = append(addRoles, role.ID)
+ _ = event.Client().Rest.AddMemberRole(*event.GuildID(), event.User().ID, role.ID)
}
}
} else {
- // 選ばれてないとき
if slices.Index(event.Member().RoleIDs, role.ID) != -1 {
- // 持ってたなら
removedRoles = append(removedRoles, role.ID)
-
- if err := event.Client().Rest.RemoveMemberRole(*event.GuildID(), event.User().ID, role.ID); err != nil {
- return errors.NewError(errors.ErrorMessage("errors.fail.role.panel", event))
- }
- } else {
- // 持ってないなら
- continue
+ _ = event.Client().Rest.RemoveMemberRole(*event.GuildID(), event.User().ID, role.ID)
}
}
}
-
- embed := discord.NewEmbedBuilder().
- SetTitle(translate.Message(event.Locale(), "components.role.panel.use.changed"))
+ embed := discord.NewEmbedBuilder().SetTitle(translate.Message(event.Locale(), "components.role.panel.use.changed"))
if len(addRoles) > 0 {
- var addRolesString string
+ var s string
for _, id := range addRoles {
- addRolesString += fmt.Sprintf("%s\n", discord.RoleMention(id))
+ s += fmt.Sprintf("%s\n", discord.RoleMention(id))
}
- embed.AddFields(
- discord.EmbedField{
- Name: translate.Message(event.Locale(), "components.role.panel.use.changed.add"),
- Value: addRolesString,
- },
- )
+ embed.AddFields(discord.EmbedField{Name: translate.Message(event.Locale(), "components.role.panel.use.changed.add"), Value: s})
}
if len(unchangedRole) > 0 {
- var unchangedRoleString string
+ var s string
for _, id := range unchangedRole {
- unchangedRoleString += fmt.Sprintf("%s\n", discord.RoleMention(id))
+ s += fmt.Sprintf("%s\n", discord.RoleMention(id))
}
- embed.AddFields(
- discord.EmbedField{
- Name: translate.Message(event.Locale(), "components.role.panel.use.changed.unchanged"),
- Value: unchangedRoleString,
- },
- )
+ embed.AddFields(discord.EmbedField{Name: translate.Message(event.Locale(), "components.role.panel.use.changed.unchanged"), Value: s})
}
if len(removedRoles) > 0 {
- var removedRolesString string
+ var s string
for _, id := range removedRoles {
- removedRolesString += fmt.Sprintf("%s\n", discord.RoleMention(id))
+ s += fmt.Sprintf("%s\n", discord.RoleMention(id))
}
- embed.AddFields(
- discord.EmbedField{
- Name: translate.Message(event.Locale(), "components.role.panel.use.changed.remove"),
- Value: removedRolesString,
- },
- )
+ embed.AddFields(discord.EmbedField{Name: translate.Message(event.Locale(), "components.role.panel.use.changed.remove"), Value: s})
}
- if err := event.RespondMessage(
- discord.NewMessageBuilder().
- SetEmbeds(embeds.SetEmbedProperties(embed.Build())).
- SetFlags(discord.MessageFlagEphemeral),
- ); err != nil {
+ if err := event.RespondMessage(discord.NewMessageBuilder().SetEmbeds(embeds.SetEmbedProperties(embed.Build())).SetFlags(discord.MessageFlagEphemeral)); err != nil {
return errors.NewError(err)
}
}
@@ -1064,177 +796,129 @@ func Command(c *components.Components) components.Command {
if event.Message.Author.Bot || event.Message.Author.System {
return nil
}
- g, err := c.GuildCreateID(event, event.GuildID)
- if err != nil {
- return errors.NewError(err)
- }
u, err := c.UserCreate(event, event.Message.Author)
if err != nil {
return errors.NewError(err)
}
-
- edits := g.QueryRolePanelEdits().Where(rolepaneledit.ChannelID(event.ChannelID)).AllX(event)
+ var edits []models.RolePanelEdit
+ c.GormDB().Where("channel_id = ?", event.ChannelID).Find(&edits)
for _, edit := range edits {
if edit.EmojiAuthor == nil || *edit.EmojiAuthor != event.Message.Author.ID || edit.Token == nil {
continue
}
- token := *edit.Token
- emojis := emoji.FindAllString(event.Message.Content)
+ token, emojis := *edit.Token, emoji.FindAllString(event.Message.Content)
if len(emojis) < 1 {
continue
}
componentEmoji := discordutil.ParseComponentEmoji(emojis[0])
- panel := edit.QueryParent().OnlyX(event)
-
- initialize(edit, panel)
-
- edit.Roles[slices.IndexFunc(edit.Roles, func(r schema.Role) bool { return r.ID == *edit.SelectedRole })].Emoji = &componentEmoji
- edit = edit.Update().
- ClearEmojiAuthor().
- ClearToken().
- SetRoles(edit.Roles).
- SaveX(event)
-
- if err := event.Client().Rest.AddReaction(event.ChannelID, event.MessageID, "✅"); err != nil {
- return errors.NewError(err)
+ var panel models.RolePanel
+ if err := c.GormDB().Where("id = ?", edit.ParentID).First(&panel).Error; err != nil {
+ continue
}
-
- if _, err := event.Client().Rest.UpdateInteractionResponse(event.Client().ApplicationID, token,
- rpEditBaseMessage(event, panel, edit, u.Locale).
- SetFlags(discord.MessageFlagEphemeral).
- BuildUpdate(),
- ); err != nil {
- return errors.NewError(err)
+ initialize(&edit, &panel)
+ if edit.SelectedRole != nil {
+ idx := slices.IndexFunc(edit.Roles, func(r models.Role) bool { return r.ID == *edit.SelectedRole })
+ if idx != -1 {
+ edit.Roles[idx].Emoji, edit.EmojiAuthor, edit.Token = &componentEmoji, nil, nil
+ c.GormDB().Save(&edit)
+ }
+ }
+ _ = event.Client().Rest.AddReaction(event.ChannelID, event.MessageID, "✅")
+ builder, err := rpEditBaseMessage(c, &panel, &edit, u.Locale)
+ if err == nil {
+ _, _ = event.Client().Rest.UpdateInteractionResponse(event.Client().ApplicationID, token, builder.SetFlags(discord.MessageFlagEphemeral).BuildUpdate())
}
}
case *events.GuildMessageDelete:
- g, err := c.GuildCreateID(event, event.GuildID)
- if err != nil {
- return errors.NewError(err)
- }
-
- c.DB().RolePanelPlaced.Delete().
- Where(
- rolepanelplaced.And(
- rolepanelplaced.HasGuildWith(guild.ID(g.ID)),
- rolepanelplaced.ChannelID(event.ChannelID),
- rolepanelplaced.MessageID(event.MessageID),
- ),
- ).
- ExecX(event)
+ g, _ := c.GuildCreateID(event, event.GuildID)
+ c.GormDB().Where("guild_id = ? AND channel_id = ? AND message_id = ?", g.ID, event.ChannelID, event.MessageID).Delete(&models.RolePanelPlaced{})
case *events.GuildMessageReactionAdd:
if event.Member.User.Bot || event.Member.User.System {
return nil
}
- g, err := c.GuildCreateID(event, event.GuildID)
- if err != nil {
- return errors.NewError(err)
- }
u, err := c.UserCreate(event, event.Member.User)
if err != nil {
return errors.NewError(err)
}
-
- if !g.QueryRolePanelPlacements().Where(rolepanelplaced.ChannelID(event.ChannelID), rolepanelplaced.MessageID(event.MessageID)).ExistX(event) {
+ var place models.RolePanelPlaced
+ if err := c.GormDB().Where("channel_id = ? AND message_id = ?", event.ChannelID, event.MessageID).First(&place).Error; err != nil {
return nil
}
- place := g.QueryRolePanelPlacements().Where(rolepanelplaced.ChannelID(event.ChannelID), rolepanelplaced.MessageID(event.MessageID)).FirstX(event)
- panel := place.QueryRolePanel().OnlyX(event)
-
- if err := event.Client().Rest.RemoveUserReaction(event.ChannelID, event.MessageID, event.Emoji.Reaction(), event.UserID); err != nil {
- return errors.NewError(err)
+ var panel models.RolePanel
+ if err := c.GormDB().Where("id = ?", place.RolePanelID).First(&panel).Error; err != nil {
+ return nil
}
-
+ _ = event.Client().Rest.RemoveUserReaction(event.ChannelID, event.MessageID, event.Emoji.Reaction(), event.UserID)
for i, role := range panel.Roles {
- if role.Emoji == nil {
- role.Emoji = &discord.ComponentEmoji{
- Name: discordutil.Index2Emoji(i),
- }
+ emojiVal := role.Emoji
+ if emojiVal == nil {
+ emojiVal = &discord.ComponentEmoji{Name: discordutil.Index2Emoji(i)}
}
- if event.Emoji.Reaction() != discordutil.ReactionComponentEmoji(*role.Emoji) {
+ if event.Emoji.Reaction() != discordutil.ReactionComponentEmoji(*emojiVal) {
continue
}
- _, ok := event.Client().Caches.Role(event.GuildID, role.ID)
- if !ok {
+ if _, ok := event.Client().Caches.Role(event.GuildID, role.ID); !ok {
return nil
}
contains := slices.Contains(event.Member.RoleIDs, role.ID)
+ var err error
if contains {
err = event.Client().Rest.RemoveMemberRole(event.GuildID, event.UserID, role.ID)
} else {
err = event.Client().Rest.AddMemberRole(event.GuildID, event.UserID, role.ID)
}
if err != nil {
- m, err := event.Client().Rest.CreateMessage(event.ChannelID,
- discord.NewMessageBuilder().
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitlef("❗ %s", translate.Message(u.Locale, "errors.fail.role.panel")).
- SetDescription(translate.Message(u.Locale, "errors.fail.role.panel.description")).
- SetColor(0xff2121).
- Build(),
- ),
- ).
- SetFlags(discord.MessageFlagEphemeral).BuildCreate(),
- )
- if err != nil {
- return errors.NewError(err)
+ embed := discord.NewEmbedBuilder().SetTitlef("❗ %s", translate.Message(u.Locale, "errors.fail.role.panel")).SetDescription(translate.Message(u.Locale, "errors.fail.role.panel.description")).SetColor(0xff2121).Build()
+ m, err := event.Client().Rest.CreateMessage(event.ChannelID, discord.NewMessageBuilder().
+ SetEmbeds(embeds.SetEmbedProperties(embed)).
+ SetFlags(discord.MessageFlagEphemeral).
+ BuildCreate())
+ if err == nil {
+ go func() {
+ if err := discordutil.DeleteMessageAfter(event.Client(), event.ChannelID, m.ID, time.Second*10); err != nil {
+ slog.Error("Failed to delete message", "err", err)
+ }
+ }()
}
- go func() {
- if err := discordutil.DeleteMessageAfter(event.Client(), event.ChannelID, m.ID, time.Second*10); err != nil {
- slog.Error("削除に失敗", "err", err, "channel_id", event.ChannelID, "message_id", m.ID)
- }
- }()
- return nil
- }
- if place.HideNotice {
return nil
}
- m, err := event.Client().Rest.CreateMessage(event.ChannelID,
- discord.NewMessageBuilder().
+ if !place.HideNotice {
+ embed := discord.NewEmbedBuilder().
+ SetTitle(translate.Message(u.Locale, "components.role.panel.use."+builtin.Or(!contains, "added", "removed"))).
+ SetDescription(translate.Message(u.Locale, "components.role.panel.use."+builtin.Or(!contains, "added", "removed")+`.description`, translate.WithTemplate(map[string]any{"Role": discord.RoleMention(role.ID)}))).
+ Build()
+ m, err := event.Client().Rest.CreateMessage(event.ChannelID, discord.NewMessageBuilder().
SetContent(discord.UserMention(event.UserID)).
- SetEmbeds(
- embeds.SetEmbedProperties(
- discord.NewEmbedBuilder().
- SetTitle(translate.Message(u.Locale, "components.role.panel.use."+builtin.Or(!contains, "added", "removed"))).
- SetDescription(translate.Message(u.Locale, "components.role.panel.use."+builtin.Or(!contains, "added", "removed")+".description", translate.WithTemplate(map[string]any{"Role": discord.RoleMention(role.ID)}))).
- Build(),
- ),
- ).
- SetFlags(discord.MessageFlagEphemeral).BuildCreate(),
- )
- if err != nil {
- return errors.NewError(err)
- }
- go func() {
- if err := discordutil.DeleteMessageAfter(event.Client(), event.ChannelID, m.ID, time.Second*10); err != nil {
- slog.Error("削除に失敗", "err", err, "channel_id", event.ChannelID, "message_id", m.ID)
+ SetEmbeds(embeds.SetEmbedProperties(embed)).
+ SetFlags(discord.MessageFlagEphemeral).
+ BuildCreate())
+ if err == nil {
+ go func() {
+ if err := discordutil.DeleteMessageAfter(event.Client(), event.ChannelID, m.ID, time.Second*10); err != nil {
+ slog.Error("Failed to delete message", "err", err)
+ }
+ }()
}
- }()
+ }
}
}
return nil
- },
- }).SetComponent(c)
+ }}).SetComponent(c)
}
-func UpdateRolePanel(ctx context.Context, place *ent.RolePanelPlaced, locale discord.Locale, client *bot.Client) {
- if err := rolePanelPlace(ctx, place, locale, client, true); err != nil {
+func UpdateRolePanel(ctx context.Context, place *models.RolePanelPlaced, locale discord.Locale, client *bot.Client, c *components.Components) {
+ if err := rolePanelPlace(ctx, place, locale, client, true, c); err != nil {
slog.Error("アップデートに失敗", "err", err)
}
}
-func updateRolePanel(ctx context.Context, panel *ent.RolePanel, locale discord.Locale, client *bot.Client, react bool) {
- places := panel.QueryPlacements().AllX(ctx)
+func updateRolePanel(ctx context.Context, panel *models.RolePanel, locale discord.Locale, client *bot.Client, react bool, c *components.Components) {
+ var places []models.RolePanelPlaced
+ c.GormDB().Where("role_panel_id = ?", panel.ID).Find(&places)
for _, place := range places {
- place = place.Update().
- SetName(panel.Name).
- SetDescription(panel.Description).
- SetRoles(panel.Roles).
- SetUpdatedAt(time.Now()).
- SaveX(ctx)
- if err := rolePanelPlace(ctx, place, locale, client, react); err != nil {
+ place.Name, place.Description, place.Roles, place.UpdatedAt = panel.Name, panel.Description, panel.Roles, time.Now()
+ c.GormDB().Save(&place)
+ if err := rolePanelPlace(ctx, &place, locale, client, react, c); err != nil {
slog.Error("アップデートに失敗", "err", err)
}
}
diff --git a/bot/commands/setting/setting.go b/bot/commands/setting/setting.go
index 9e05857c..a18d0409 100644
--- a/bot/commands/setting/setting.go
+++ b/bot/commands/setting/setting.go
@@ -31,7 +31,7 @@ import (
"github.com/disgoorg/snowflake/v2"
"github.com/sabafly/gobot/bot/components"
"github.com/sabafly/gobot/bot/components/generic"
- "github.com/sabafly/gobot/ent"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/builtin"
"github.com/sabafly/gobot/internal/embeds"
"github.com/sabafly/gobot/internal/errors"
@@ -125,29 +125,6 @@ func Command(c *components.Components) components.Command {
},
},
},
- // discord.ApplicationCommandOptionSubCommandGroup{
- // Name: "welcome",
- // Description: "welcome",
- // Options: []discord.ApplicationCommandOptionSubCommand{
- // {
- // Name: "set-message",
- // Description: "set message",
- // DescriptionLocalizations: translate.MessageMap("components.setting.welcome.set-message", false),
- // },
- // {
- // Name: "set-channel",
- // Description: "set channel",
- // DescriptionLocalizations: translate.MessageMap("components.setting.welcome.set-channel", false),
- // Options: []discord.ApplicationCommandOption{
- // discord.ApplicationCommandOptionChannel{
- // Name: "channel",
- // Description: "channel",
- // DescriptionLocalizations: translate.MessageMap("components.setting.welcome.channel", false),
- // },
- // },
- // },
- // },
- // },
},
},
},
@@ -200,9 +177,11 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- g = g.Update().
- SetBumpEnabled(!g.BumpEnabled).
- SaveX(event)
+ g.BumpEnabled = !g.BumpEnabled
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.setting.bump.toggle."+builtin.Or(g.BumpEnabled, "enabled", "disabled"))).
@@ -223,9 +202,11 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- g = g.Update().
- SetUpEnabled(!g.UpEnabled).
- SaveX(event)
+ g.UpEnabled = !g.UpEnabled
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.setting.up.toggle."+builtin.Or(g.UpEnabled, "enabled", "disabled"))).
@@ -246,13 +227,15 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- update := g.Update()
if r, ok := event.SlashCommandInteractionData().OptRole("target"); ok {
- update.SetBumpMention(r.ID)
+ g.BumpMention = &r.ID
} else {
- update.ClearBumpMention()
+ g.BumpMention = nil
}
- g = update.SaveX(event)
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.setting.bump.mention.used",
@@ -280,13 +263,15 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- update := g.Update()
if r, ok := event.SlashCommandInteractionData().OptRole("target"); ok {
- update.SetUpMention(r.ID)
+ g.UpMention = &r.ID
} else {
- update.ClearUpMention()
+ g.UpMention = nil
+ }
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
}
- g = update.SaveX(event)
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.setting.up.mention.used",
@@ -450,9 +435,11 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- g = g.Update().
- SetLevelingDisabled(!g.LevelingDisabled).
- SaveX(event)
+ g.LevelingDisabled = !g.LevelingDisabled
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
if err := event.CreateMessage(
discord.NewMessageBuilder().
SetContent(translate.Message(event.Locale(), "components.setting.leveling.enable."+builtin.Or(!g.LevelingDisabled, "enabled", "disabled"))).
@@ -470,12 +457,14 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- g.Update().
- SetBumpMessageTitle(event.ModalSubmitInteraction.Data.Text("message_title")).
- SetBumpMessage(event.ModalSubmitInteraction.Data.Text("message")).
- SetBumpRemindMessageTitle(event.ModalSubmitInteraction.Data.Text("remind.message_title")).
- SetBumpRemindMessage(event.ModalSubmitInteraction.Data.Text("remind.message")).
- ExecX(event)
+ g.BumpMessageTitle = event.ModalSubmitInteraction.Data.Text("message_title")
+ g.BumpMessage = event.ModalSubmitInteraction.Data.Text("message")
+ g.BumpRemindMessageTitle = event.ModalSubmitInteraction.Data.Text("remind.message_title")
+ g.BumpRemindMessage = event.ModalSubmitInteraction.Data.Text("remind.message")
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
if err := event.DeferUpdateMessage(); err != nil {
return errors.NewError(err)
}
@@ -486,12 +475,14 @@ func Command(c *components.Components) components.Command {
if err != nil {
return errors.NewError(err)
}
- g.Update().
- SetUpMessageTitle(event.ModalSubmitInteraction.Data.Text("message_title")).
- SetUpMessage(event.ModalSubmitInteraction.Data.Text("message")).
- SetUpRemindMessageTitle(event.ModalSubmitInteraction.Data.Text("remind.message_title")).
- SetUpRemindMessage(event.ModalSubmitInteraction.Data.Text("remind.message")).
- ExecX(event)
+ g.UpMessageTitle = event.ModalSubmitInteraction.Data.Text("message_title")
+ g.UpMessage = event.ModalSubmitInteraction.Data.Text("message")
+ g.UpRemindMessageTitle = event.ModalSubmitInteraction.Data.Text("remind.message_title")
+ g.UpRemindMessage = event.ModalSubmitInteraction.Data.Text("remind.message")
+ if err := c.GormDB().Save(g).Error; err != nil {
+ return errors.NewError(err)
+ }
+
if err := event.DeferUpdateMessage(); err != nil {
return errors.NewError(err)
}
@@ -589,7 +580,7 @@ type notice struct {
var bumpNotice = map[snowflake.ID]notice{}
var bumpLock sync.Mutex
-func bumpHandler(c *components.Components, g *ent.Guild, event *events.GuildMessageCreate) error {
+func bumpHandler(c *components.Components, g *models.Guild, event *events.GuildMessageCreate) error {
bumpLock.Lock()
defer bumpLock.Unlock()
if event.Message.Interaction == nil || event.Message.Interaction.Name != "bump" {
@@ -615,7 +606,7 @@ func bumpHandler(c *components.Components, g *ent.Guild, event *events.GuildMess
var upNotice = map[snowflake.ID]notice{}
var upLock sync.Mutex
-func upHandler(c *components.Components, g *ent.Guild, event *events.GuildMessageCreate) error {
+func upHandler(c *components.Components, g *models.Guild, event *events.GuildMessageCreate) error {
upLock.Lock()
defer upLock.Unlock()
if event.Message.Interaction == nil || event.Message.Interaction.Name != "dissoku up" {
diff --git a/bot/components/components.go b/bot/components/components.go
index 89a659a9..72c1999c 100644
--- a/bot/components/components.go
+++ b/bot/components/components.go
@@ -24,16 +24,14 @@ import (
"context"
"github.com/sabafly/gobot/database"
- "github.com/sabafly/gobot/ent"
"github.com/sabafly/gobot/internal/smap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
-func New(ctx context.Context, db *ent.Client, conf Config, gormDb *database.DB) *Components {
+func New(ctx context.Context, conf Config, gormDb *database.DB) *Components {
return &Components{
ctx: ctx,
- db: db,
commandsRegistry: make(map[string]Command),
config: conf,
gormDb: gormDb,
@@ -42,7 +40,6 @@ func New(ctx context.Context, db *ent.Client, conf Config, gormDb *database.DB)
type Components struct {
ctx context.Context
- db *ent.Client
gormDb *database.DB
config Config
@@ -56,8 +53,6 @@ type Components struct {
func (c *Components) Ctx() context.Context { return c.ctx }
-func (c *Components) DB() *ent.Client { return c.db }
-
// DO NOT REUSE RETURN VALUE, MUST CALL EACH TIME TO GET NEW SESSION
func (c *Components) GormDB() *gorm.DB {
return c.gormDb.DB.WithContext(c.ctx).
diff --git a/bot/components/generic/permission.go b/bot/components/generic/permission.go
index 79d631be..9b638cb5 100644
--- a/bot/components/generic/permission.go
+++ b/bot/components/generic/permission.go
@@ -31,10 +31,7 @@ import (
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/snowflake/v2"
"github.com/sabafly/gobot/bot/components"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/guild"
- "github.com/sabafly/gobot/ent/member"
- "github.com/sabafly/gobot/ent/user"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/translate"
)
@@ -62,30 +59,26 @@ func noPermissionMessage(event interface {
)
}
-func PermissionCheck(ctx context.Context, c *components.Components, g *ent.Guild, client *bot.Client, m discord.ResolvedMember, guildID snowflake.ID, perms []Permission) bool {
+func PermissionCheck(ctx context.Context, c *components.Components, g *models.Guild, client *bot.Client, m discord.ResolvedMember, guildID snowflake.ID, perms []Permission) bool {
if len(perms) == 0 {
return true
}
- if m := c.DB().Guild.Query().
- Where(guild.ID(guildID)).
- FirstX(ctx).
- QueryMembers().
- Where(member.HasUserWith(user.ID(m.User.ID))).
- FirstX(ctx); m != nil {
+ var member models.Member
+ if err := c.GormDB().Where("guild_id = ? AND user_id = ?", guildID, m.User.ID).First(&member).Error; err == nil {
for _, p := range perms {
var r bool
if p.Default() {
- if m.Permission.Disabled(p.PermString()) {
+ if member.Permission.Disabled(p.PermString()) {
return false
} else {
r = true
}
} else {
- if m.Permission.Enabled(p.PermString()) {
+ if member.Permission.Enabled(p.PermString()) {
r = true
- } else if m.Permission.Disabled(p.PermString()) {
+ } else if member.Permission.Disabled(p.PermString()) {
return false
}
}
@@ -100,7 +93,7 @@ func PermissionCheck(ctx context.Context, c *components.Components, g *ent.Guild
return RolePermissionCheck(g, guildID, client, m.RoleIDs, perms)
}
-func RolePermissionCheck(g *ent.Guild, guildID snowflake.ID, client *bot.Client, roleIds []snowflake.ID, perms []Permission) bool {
+func RolePermissionCheck(g *models.Guild, guildID snowflake.ID, client *bot.Client, roleIds []snowflake.ID, perms []Permission) bool {
if len(perms) == 0 {
return true
}
@@ -152,14 +145,10 @@ func permissionCheck(event interface {
}
if dPerm != 0 && event.Member().Permissions.Has(dPerm) {
- if m := c.DB().Guild.Query().
- Where(guild.ID(*event.GuildID())).
- FirstX(event).
- QueryMembers().
- Where(member.HasUserWith(user.ID(event.User().ID))).
- FirstX(event); m != nil {
+ var member models.Member
+ if err := c.GormDB().Where("guild_id = ? AND user_id = ?", *event.GuildID(), event.User().ID).First(&member).Error; err == nil {
for _, p := range perms {
- if m.Permission.Disabled(p.PermString()) {
+ if member.Permission.Disabled(p.PermString()) {
return false
}
}
diff --git a/bot/components/guild.go b/bot/components/guild.go
index b804d42a..5750da31 100644
--- a/bot/components/guild.go
+++ b/bot/components/guild.go
@@ -25,21 +25,12 @@ import (
"log/slog"
"github.com/sabafly/gobot/database/models"
- "github.com/sabafly/gobot/ent/member"
- "github.com/sabafly/gobot/ent/messagepin"
- "github.com/sabafly/gobot/ent/messageremind"
- "github.com/sabafly/gobot/ent/rolepanel"
- "github.com/sabafly/gobot/ent/rolepaneledit"
- "github.com/sabafly/gobot/ent/rolepanelplaced"
- "github.com/sabafly/gobot/ent/wordsuffix"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/snowflake/v2"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/guild"
- "github.com/sabafly/gobot/ent/user"
+ "gorm.io/gorm"
)
func (c *Components) OnGuildReady() func(event *events.GuildReady) {
@@ -66,8 +57,18 @@ func (c *Components) OnGuildReady() func(event *events.GuildReady) {
return
}
- u = c.db.User.Query().Where(user.ID(u.ID)).OnlyX(event)
- slog.Debug("ギルドオーナー情報", "id", u.ID, "name", u.Name, "own_guilds", u.QueryOwnGuilds().AllX(event), "guilds", u.QueryGuilds().AllX(event))
+ var ownedGuilds []models.Guild
+ c.GormDB().Where("owner_id = ?", u.ID).Find(&ownedGuilds)
+
+ // For joined guilds, need a join query via Members
+ var joinedMembers []models.Member
+ c.GormDB().Preload("Guild").Where("user_id = ?", u.ID).Find(&joinedMembers)
+ var joinedGuilds []models.Guild
+ for _, m := range joinedMembers {
+ joinedGuilds = append(joinedGuilds, m.Guild)
+ }
+
+ slog.Debug("ギルドオーナー情報", "id", u.ID, "name", u.Name, "own_guilds", ownedGuilds, "guilds", joinedGuilds)
}
}
@@ -95,48 +96,94 @@ func (c *Components) OnGuildJoin() func(event *events.GuildJoin) {
return
}
- u = c.db.User.Query().Where(user.ID(u.ID)).OnlyX(event)
- slog.Debug("ギルドオーナー情報", "id", u.ID, "name", u.Name, "own_guilds", u.QueryOwnGuilds().AllX(event), "guilds", u.QueryGuilds().AllX(event))
+ var ownedGuilds []models.Guild
+ c.GormDB().Where("owner_id = ?", u.ID).Find(&ownedGuilds)
+
+ var joinedMembers []models.Member
+ c.GormDB().Preload("Guild").Where("user_id = ?", u.ID).Find(&joinedMembers)
+ var joinedGuilds []models.Guild
+ for _, m := range joinedMembers {
+ joinedGuilds = append(joinedGuilds, m.Guild)
+ }
+
+ slog.Debug("ギルドオーナー情報", "id", u.ID, "name", u.Name, "own_guilds", ownedGuilds, "guilds", joinedGuilds)
}
}
func (c *Components) OnGuildLeave() func(event *events.GuildLeave) {
return func(event *events.GuildLeave) {
slog.Info("ギルド脱退", "id", event.Guild.ID, "name", event.Guild.Name)
- c.db.Member.Delete().Where(member.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.MessagePin.Delete().Where(messagepin.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.MessageRemind.Delete().Where(messageremind.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.RolePanelPlaced.Delete().Where(rolepanelplaced.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.RolePanelEdit.Delete().Where(rolepaneledit.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.RolePanel.Delete().Where(rolepanel.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.WordSuffix.Delete().Where(wordsuffix.HasGuildWith(guild.ID(event.Guild.ID))).ExecX(event)
- c.db.Guild.DeleteOneID(event.Guild.ID).ExecX(event)
+
+ // Use transaction for deletion
+ if err := c.GormDB().Transaction(func(tx *gorm.DB) error {
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.Member{}).Error; err != nil {
+ slog.Error("ギルド脱退 メンバー削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.MessagePin{}).Error; err != nil {
+ slog.Error("ギルド脱退 メッセージピン削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.MessageRemind{}).Error; err != nil {
+ slog.Error("ギルド脱退 メッセージリマインド削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.RolePanelPlaced{}).Error; err != nil {
+ slog.Error("ギルド脱退 ロールパネル配置削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.RolePanelEdit{}).Error; err != nil {
+ slog.Error("ギルド脱退 ロールパネル編集削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.RolePanel{}).Error; err != nil {
+ slog.Error("ギルド脱退 ロールパネル削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Where("guild_id = ?", event.Guild.ID).Delete(&models.WordSuffix{}).Error; err != nil {
+ slog.Error("ギルド脱退 ワードサフィックス削除に失敗", "err", err)
+ return err
+ }
+ if err := tx.Delete(&models.Guild{ID: event.Guild.ID}).Error; err != nil {
+ slog.Error("ギルド脱退 ギルド削除に失敗", "err", err)
+ return err
+ }
+ return nil
+ }); err != nil {
+ slog.Error("ギルド脱退 データベースからの削除に失敗", "err", err)
+ return
+ }
}
}
-func (c *Components) GuildCreate(ctx context.Context, ownerID snowflake.ID, g *discord.Guild) (*ent.Guild, error) {
- ok := c.db.Guild.
- Query().
- Where(guild.ID(g.ID)).ExistX(ctx)
- if ok {
- return c.db.Guild.
- Query().
- Where(guild.ID(g.ID)).
- Only(ctx)
+func (c *Components) GuildCreate(ctx context.Context, ownerID snowflake.ID, g *discord.Guild) (*models.Guild, error) {
+ var guild models.Guild
+ err := c.GormDB().Where("id = ?", g.ID).First(&guild).Error
+ if err == nil {
+ return &guild, nil
}
+ if err != gorm.ErrRecordNotFound {
+ return nil, err
+ }
+
slog.Debug("新規ギルド作成", "gid", g.ID)
- return c.db.Guild.Create().
- SetID(g.ID).
- SetName(g.Name).
- SetOwnerID(ownerID).
- Save(ctx)
+ guild = models.Guild{
+ ID: g.ID,
+ Name: g.Name,
+ OwnerID: &ownerID,
+ }
+ if err := c.GormDB().Create(&guild).Error; err != nil {
+ return nil, err
+ }
+ return &guild, nil
}
-func (c *Components) GuildCreateID(ctx context.Context, gid snowflake.ID) (*ent.Guild, error) {
- return c.db.Guild.
- Query().
- Where(guild.ID(gid)).
- Only(ctx)
+func (c *Components) GuildCreateID(ctx context.Context, gid snowflake.ID) (*models.Guild, error) {
+ var guild models.Guild
+ if err := c.GormDB().Where("id = ?", gid).First(&guild).Error; err != nil {
+ return nil, err
+ }
+ return &guild, nil
}
func (c *Components) GuildRequest(client *bot.Client, gid snowflake.ID) (*discord.Guild, error) {
@@ -152,7 +199,8 @@ func (c *Components) GuildRequest(client *bot.Client, gid snowflake.ID) (*discor
func (c *Components) InitializeGuild(ctx context.Context, guild discord.Guild) error {
g := models.Guild{
- ID: guild.ID,
+ ID: guild.ID,
+ OwnerID: &guild.OwnerID,
}
if err := c.GormDB().Where(g).FirstOrCreate(&g).Error; err != nil {
return err
diff --git a/bot/components/member.go b/bot/components/member.go
index 1a45d0ca..d61486c3 100644
--- a/bot/components/member.go
+++ b/bot/components/member.go
@@ -25,27 +25,32 @@ import (
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/snowflake/v2"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/guild"
- "github.com/sabafly/gobot/ent/member"
- "github.com/sabafly/gobot/ent/user"
+ "github.com/sabafly/gobot/database/models"
+ "gorm.io/gorm"
)
-func (c *Components) MemberCreate(ctx context.Context, u discord.User, gid snowflake.ID) (*ent.Member, error) {
- eu, err := c.UserCreate(ctx, u)
+func (c *Components) MemberCreate(ctx context.Context, u discord.User, gid snowflake.ID) (*models.Member, error) {
+ _, err := c.UserCreate(ctx, u)
if err != nil {
return nil, err
}
- ok := c.db.Member.
- Query().
- Where(member.HasUserWith(user.ID(u.ID)), member.HasGuildWith(guild.ID(gid))).ExistX(ctx)
- if ok {
- return c.db.Member.
- Query().
- Where(member.HasUserWith(user.ID(u.ID)), member.HasGuildWith(guild.ID(gid))).Only(ctx)
+
+ var member models.Member
+ err = c.GormDB().Where("guild_id = ? AND user_id = ?", gid, u.ID).First(&member).Error
+ if err == nil {
+ return &member, nil
+ }
+ if err != gorm.ErrRecordNotFound {
+ return nil, err
+ }
+
+ member = models.Member{
+ GuildID: gid,
+ UserID: u.ID,
+ }
+ if err := c.GormDB().Create(&member).Error; err != nil {
+ return nil, err
}
- return c.db.Member.Create().
- SetUser(eu).
- SetGuildID(gid).
- Save(ctx)
+
+ return &member, nil
}
diff --git a/bot/components/user.go b/bot/components/user.go
index 67dbc980..9f6adfc5 100644
--- a/bot/components/user.go
+++ b/bot/components/user.go
@@ -25,25 +25,28 @@ import (
"log/slog"
"github.com/disgoorg/disgo/discord"
- "github.com/sabafly/gobot/ent"
- "github.com/sabafly/gobot/ent/user"
+ "github.com/sabafly/gobot/database/models"
"github.com/sabafly/gobot/internal/errors"
)
-func (c *Components) UserCreate(ctx context.Context, u discord.User) (*ent.User, error) {
+func (c *Components) UserCreate(ctx context.Context, u discord.User) (*models.User, error) {
if u.Bot || u.System {
return nil, errors.New("bot cannot use to create user")
}
- if ok := c.db.User.
- Query().
- Where(user.ID(u.ID)).ExistX(ctx); ok {
- return c.db.User.
- Query().
- Where(user.ID(u.ID)).Only(ctx)
+
+ user := models.User{
+ ID: u.ID,
+ Name: u.EffectiveName(),
+ }
+
+ result := c.GormDB().FirstOrCreate(&user, models.User{ID: u.ID})
+ if result.Error != nil {
+ return nil, result.Error
}
- slog.Debug("新規ユーザー作成", "uid", u.ID, "uname", u.Username)
- return c.db.User.Create().
- SetID(u.ID).
- SetName(u.EffectiveName()).
- Save(ctx)
+
+ if result.RowsAffected > 0 {
+ slog.Debug("新規ユーザー作成", "uid", u.ID, "uname", u.Username)
+ }
+
+ return &user, nil
}
diff --git a/database/database.go b/database/database.go
index cf0d8281..e4011582 100644
--- a/database/database.go
+++ b/database/database.go
@@ -32,6 +32,13 @@ func NewDB(dsn string) (*DB, error) {
&models.BetOption{},
&models.Bet{},
&models.BetEntrant{},
+ &models.Member{},
+ &models.RolePanel{},
+ &models.RolePanelEdit{},
+ &models.RolePanelPlaced{},
+ &models.MessagePin{},
+ &models.MessageRemind{},
+ &models.WordSuffix{},
); err != nil {
return nil, err
}
diff --git a/database/models/bet.go b/database/models/bet.go
index c126a5e8..d7aa8d26 100644
--- a/database/models/bet.go
+++ b/database/models/bet.go
@@ -6,6 +6,7 @@ import (
"github.com/disgoorg/snowflake/v2"
"github.com/google/uuid"
+ "gorm.io/gorm"
)
type BetHost struct {
@@ -28,6 +29,13 @@ type BetHost struct {
Locale string `gorm:"type:varchar(10);not null;default:'en';"`
}
+func (b *BetHost) BeforeCreate(tx *gorm.DB) error {
+ if b.ID == uuid.Nil {
+ b.ID = uuid.New()
+ }
+ return nil
+}
+
type BetVoteType string
const (
diff --git a/database/models/guild.go b/database/models/guild.go
index 27b03e75..7df0f12f 100644
--- a/database/models/guild.go
+++ b/database/models/guild.go
@@ -1,7 +1,39 @@
package models
-import "github.com/disgoorg/snowflake/v2"
+import (
+ "time"
+
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/sabafly/gobot/internal/permissions"
+)
type Guild struct {
- ID snowflake.ID `gorm:"primary_key;column:id;type:bigint(20) unsigned;not null"`
+ ID snowflake.ID `gorm:"primary_key;column:id;type:bigint(20) unsigned;not null"`
+ Name string `gorm:"not null"`
+ Locale discord.Locale `gorm:"default:'ja'"`
+ LevelUpMessage string `gorm:"default:'{user}がレベルアップしたよ!🥳\n**{before_level} レベル → {after_level} レベル**'"`
+ LevelUpChannel *snowflake.ID `gorm:"type:bigint(20)"`
+ LevelUpExcludeChannel []snowflake.ID `gorm:"serializer:json"`
+ LevelMee6Imported bool `gorm:"default:false"`
+ LevelRole map[int]snowflake.ID `gorm:"serializer:json"`
+ Permissions map[snowflake.ID]permissions.Permission `gorm:"serializer:json"`
+ RemindCount int `gorm:"default:0"`
+ RolePanelEditTimes []time.Time `gorm:"serializer:json"`
+ BumpEnabled bool `gorm:"default:true"`
+ BumpMessageTitle string `gorm:"default:'Bumpを検知しました'"`
+ BumpMessage string `gorm:"default:'2時間後に通知します'"`
+ BumpRemindMessageTitle string `gorm:"default:'Bumpの時間です'"`
+ BumpRemindMessage string `gorm:"default:'でBumpしましょう'"`
+ UpEnabled bool `gorm:"default:true"`
+ UpMessageTitle string `gorm:"default:'UPを検知しました'"`
+ UpMessage string `gorm:"default:'1時間後に通知します'"`
+ UpRemindMessageTitle string `gorm:"default:'UPの時間です'"`
+ UpRemindMessage string `gorm:"default:'でUPしましょう'"`
+ BumpMention *snowflake.ID `gorm:"type:bigint(20)"`
+ UpMention *snowflake.ID `gorm:"type:bigint(20)"`
+ LevelingDisabled bool `gorm:"default:false"`
+
+ OwnerID *snowflake.ID `gorm:"type:bigint(20) unsigned;column:owner_id;index:idx_guild_owner"`
+ Owner *User `gorm:"foreignKey:OwnerID;constraint:OnDelete:SET NULL;"`
}
diff --git a/database/models/member.go b/database/models/member.go
new file mode 100644
index 00000000..9b153c24
--- /dev/null
+++ b/database/models/member.go
@@ -0,0 +1,23 @@
+package models
+
+import (
+ "time"
+
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/sabafly/gobot/internal/permissions"
+ "github.com/sabafly/gobot/internal/xppoint"
+)
+
+type Member struct {
+ ID int `gorm:"primary_key;auto_increment"`
+ GuildID snowflake.ID `gorm:"type:bigint(20);not null;index:idx_member_guild_user,unique"`
+ Guild Guild `gorm:"foreignKey:GuildID;constraint:OnDelete:CASCADE;"`
+ UserID snowflake.ID `gorm:"type:bigint(20);not null;index:idx_member_guild_user,unique"`
+ User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;"`
+ Permission permissions.Permission `gorm:"serializer:json"`
+ XP xppoint.XP `gorm:"default:0"`
+ LastXP time.Time
+ MessageCount uint64 `gorm:"default:0"`
+ LastNotifiedLevel *uint64
+ LastMessageHashes []string `gorm:"serializer:json"`
+}
diff --git a/database/models/messagepin.go b/database/models/messagepin.go
new file mode 100644
index 00000000..60c254a1
--- /dev/null
+++ b/database/models/messagepin.go
@@ -0,0 +1,50 @@
+package models
+
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/google/uuid"
+ "gorm.io/gorm"
+)
+
+type RateLimit struct {
+ Limit []time.Time
+}
+
+func (r RateLimit) MarshalJSON() ([]byte, error) {
+ return json.Marshal(r.Limit)
+}
+
+func (r *RateLimit) UnmarshalJSON(b []byte) error {
+ return json.Unmarshal(b, &r.Limit)
+}
+
+func (r *RateLimit) CheckLimit() bool {
+ if (len(r.Limit) >= 3 && time.Since(r.Limit[2]) < time.Second*5) || (len(r.Limit) >= 10 && time.Since(r.Limit[9]) < time.Second*30) {
+ return false
+ }
+ r.Limit = append([]time.Time{time.Now()}, r.Limit[0:min(10, len(r.Limit))]...)
+ ok := (len(r.Limit) < 3 || time.Since(r.Limit[2]) >= time.Second*5) && (len(r.Limit) < 10 || time.Since(r.Limit[9]) >= time.Second*30)
+ return ok
+}
+
+type MessagePin struct {
+ ID uuid.UUID `gorm:"type:uuid;primary_key;"`
+ GuildID snowflake.ID `gorm:"type:bigint(20);not null;index"`
+ Guild Guild `gorm:"foreignKey:GuildID;constraint:OnDelete:CASCADE;"`
+ ChannelID snowflake.ID `gorm:"type:bigint(20);uniqueIndex"`
+ Content string `gorm:"type:text"`
+ Embeds []discord.Embed `gorm:"serializer:json"`
+ BeforeID *snowflake.ID `gorm:"type:bigint(20)"`
+ RateLimit RateLimit `gorm:"serializer:json"`
+}
+
+func (m *MessagePin) BeforeCreate(tx *gorm.DB) error {
+ if m.ID == uuid.Nil {
+ m.ID = uuid.New()
+ }
+ return nil
+}
diff --git a/database/models/messageremind.go b/database/models/messageremind.go
new file mode 100644
index 00000000..829ac755
--- /dev/null
+++ b/database/models/messageremind.go
@@ -0,0 +1,27 @@
+package models
+
+import (
+ "time"
+
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/google/uuid"
+ "gorm.io/gorm"
+)
+
+type MessageRemind struct {
+ ID uuid.UUID `gorm:"type:uuid;primary_key;"`
+ GuildID snowflake.ID `gorm:"type:bigint(20);not null;index"`
+ Guild Guild `gorm:"foreignKey:GuildID;constraint:OnDelete:CASCADE;"`
+ ChannelID snowflake.ID `gorm:"type:bigint(20)"`
+ AuthorID snowflake.ID `gorm:"type:bigint(20)"`
+ Time time.Time
+ Content string `gorm:"type:text;not null"`
+ Name string `gorm:"not null"`
+}
+
+func (m *MessageRemind) BeforeCreate(tx *gorm.DB) error {
+ if m.ID == uuid.Nil {
+ m.ID = uuid.New()
+ }
+ return nil
+}
diff --git a/database/models/rolepanel.go b/database/models/rolepanel.go
new file mode 100644
index 00000000..40eca399
--- /dev/null
+++ b/database/models/rolepanel.go
@@ -0,0 +1,94 @@
+package models
+
+import (
+ "time"
+
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/google/uuid"
+ "gorm.io/gorm"
+)
+
+type Role struct {
+ ID snowflake.ID `json:"id"`
+ Name string `json:"name"`
+ Emoji *discord.ComponentEmoji `json:"emoji"`
+}
+
+type RolePanel struct {
+ ID uuid.UUID `gorm:"type:uuid;primary_key;"`
+ Name string `gorm:"not null"`
+ Description string `gorm:"type:text"`
+ Roles []Role `gorm:"serializer:json"`
+ UpdatedAt time.Time
+ AppliedAt time.Time
+
+ GuildID snowflake.ID `gorm:"type:bigint(20);not null;index"`
+ Guild Guild `gorm:"foreignKey:GuildID;constraint:OnDelete:CASCADE;"`
+
+ Placements []RolePanelPlaced `gorm:"foreignKey:RolePanelID"`
+ Edit *RolePanelEdit `gorm:"foreignKey:ParentID"`
+}
+
+func (r *RolePanel) BeforeCreate(tx *gorm.DB) error {
+ if r.ID == uuid.Nil {
+ r.ID = uuid.New()
+ }
+ return nil
+}
+
+type RolePanelEdit struct {
+ ID uuid.UUID `gorm:"type:uuid;primary_key;"`
+ ChannelID snowflake.ID `gorm:"type:bigint(20)"`
+ EmojiAuthor *snowflake.ID `gorm:"type:bigint(20)"`
+ Token *string
+ SelectedRole *snowflake.ID `gorm:"type:bigint(20)"`
+ Modified bool `gorm:"default:false"`
+ Name *string
+ Description *string
+ Roles []Role `gorm:"serializer:json"`
+
+ GuildID snowflake.ID `gorm:"type:bigint(20);not null;index"`
+ Guild Guild `gorm:"foreignKey:GuildID"`
+
+ ParentID uuid.UUID `gorm:"type:uuid;not null;uniqueIndex"`
+ Parent RolePanel `gorm:"foreignKey:ParentID"`
+}
+
+func (r *RolePanelEdit) BeforeCreate(tx *gorm.DB) error {
+ if r.ID == uuid.Nil {
+ r.ID = uuid.New()
+ }
+ return nil
+}
+
+type RolePanelPlaced struct {
+ ID uuid.UUID `gorm:"type:uuid;primary_key;"`
+ MessageID *snowflake.ID `gorm:"type:bigint(20)"`
+ ChannelID snowflake.ID `gorm:"type:bigint(20)"`
+ Type string `gorm:"type:varchar(20)"` // button, reaction, select_menu
+ ButtonType discord.ButtonStyle `gorm:"default:1"`
+ ShowName bool `gorm:"default:false"`
+ FoldingSelectMenu bool `gorm:"default:true"`
+ HideNotice bool `gorm:"default:false"`
+ UseDisplayName bool `gorm:"default:false"`
+ CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
+ Uses int `gorm:"default:0"`
+ Name string `gorm:"not null"`
+ Description string `gorm:"type:text"`
+ Roles []Role `gorm:"serializer:json"`
+ UpdatedAt time.Time
+
+ GuildID snowflake.ID `gorm:"type:bigint(20);not null;index"`
+ Guild Guild `gorm:"foreignKey:GuildID;constraint:OnDelete:CASCADE;"`
+
+ RolePanelID uuid.UUID `gorm:"type:uuid;not null;index"`
+ RolePanel RolePanel `gorm:"foreignKey:RolePanelID;constraint:OnDelete:CASCADE;"`
+}
+
+func (r *RolePanelPlaced) BeforeCreate(tx *gorm.DB) error {
+ if r.ID == uuid.Nil {
+ r.ID = uuid.New()
+ }
+ return nil
+}
diff --git a/database/models/user.go b/database/models/user.go
index b85e444e..f4baa07c 100644
--- a/database/models/user.go
+++ b/database/models/user.go
@@ -1,7 +1,17 @@
package models
-import "github.com/disgoorg/snowflake/v2"
+import (
+ "time"
+
+ "github.com/disgoorg/disgo/discord"
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/sabafly/gobot/internal/xppoint"
+)
type User struct {
- ID snowflake.ID `gorm:"primary_key;column:id;type:bigint(20) unsigned;not null"`
+ ID snowflake.ID `gorm:"primary_key;column:id;type:bigint(20) unsigned;not null"`
+ Name string `gorm:"not null"`
+ CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
+ Locale discord.Locale `gorm:"default:'ja'"`
+ XP xppoint.XP `gorm:"default:0"`
}
diff --git a/database/models/wordsuffix.go b/database/models/wordsuffix.go
new file mode 100644
index 00000000..ae3495d1
--- /dev/null
+++ b/database/models/wordsuffix.go
@@ -0,0 +1,31 @@
+package models
+
+import (
+ "time"
+
+ "github.com/disgoorg/snowflake/v2"
+ "github.com/google/uuid"
+ "github.com/sabafly/gobot/internal/uuidv7"
+ "gorm.io/gorm"
+)
+
+type WordSuffix struct {
+ ID uuid.UUID `gorm:"type:uuid;primary_key;"`
+ Suffix string `gorm:"not null"`
+ Expired *time.Time
+
+ GuildID *snowflake.ID `gorm:"type:bigint(20);index"`
+ Guild *Guild `gorm:"foreignKey:GuildID"`
+
+ OwnerID snowflake.ID `gorm:"type:bigint(20);not null;index"`
+ Owner User `gorm:"foreignKey:OwnerID"`
+
+ Rule string `gorm:"default:'webhook'"` // webhook, warn, delete
+}
+
+func (w *WordSuffix) BeforeCreate(tx *gorm.DB) error {
+ if w.ID == uuid.Nil {
+ w.ID = uuidv7.New()
+ }
+ return nil
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index d6106717..e6781e47 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,7 @@ services:
build:
context: .
dockerfile: Dockerfile
+ # image: ghcr.io/sabafly/gobot:latest
tty: true
env_file:
- ./mysql/.env