Add command

This commit is contained in:
Daniel Mason 2025-06-23 21:14:01 +12:00
parent 5dccf9580f
commit cd248dcaef
Signed by: idanoo
GPG key ID: 387387CDBC02F132
7 changed files with 166 additions and 28 deletions

View file

@ -15,3 +15,4 @@ Discord bot to measure emoji usage
/show-top-emojis /show-top-emojis
# Shows top 5 emojis and their 3 biggest users # Shows top 5 emojis and their 3 biggest users
``` ```

View file

@ -3,13 +3,13 @@ module github.com/idanoo/GoDiscMoji
go 1.23.4 go 1.23.4
require ( require (
github.com/bwmarrin/discordgo v0.28.1 github.com/bwmarrin/discordgo v0.29.0
github.com/golang-migrate/migrate v3.5.4+incompatible github.com/golang-migrate/migrate v3.5.4+incompatible
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.28
) )
require ( require (
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.33.0 // indirect
) )

View file

@ -1,5 +1,7 @@
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
@ -8,6 +10,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
@ -16,6 +20,8 @@ golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -25,6 +31,8 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View file

@ -11,7 +11,6 @@ import (
var ( var (
integerOptionMinValue = 1.0 integerOptionMinValue = 1.0
amountKey = "amount"
defaultRunCommandPermissions int64 = discordgo.PermissionKickMembers defaultRunCommandPermissions int64 = discordgo.PermissionKickMembers
@ -23,7 +22,7 @@ var (
Options: []*discordgo.ApplicationCommandOption{ Options: []*discordgo.ApplicationCommandOption{
{ {
Type: discordgo.ApplicationCommandOptionInteger, Type: discordgo.ApplicationCommandOptionInteger,
Name: amountKey, Name: "amount",
Description: "Amount to show", Description: "Amount to show",
MinValue: &integerOptionMinValue, MinValue: &integerOptionMinValue,
MaxValue: 20, MaxValue: 20,
@ -38,7 +37,7 @@ var (
Options: []*discordgo.ApplicationCommandOption{ Options: []*discordgo.ApplicationCommandOption{
{ {
Type: discordgo.ApplicationCommandOptionInteger, Type: discordgo.ApplicationCommandOptionInteger,
Name: amountKey, Name: "amount",
Description: "Amount to show", Description: "Amount to show",
MinValue: &integerOptionMinValue, MinValue: &integerOptionMinValue,
MaxValue: 20, MaxValue: 20,
@ -46,11 +45,31 @@ var (
}, },
}, },
}, },
{
Name: "purge-recent-emojis",
Description: "Purges recent emojis",
DefaultMemberPermissions: &defaultRunCommandPermissions,
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "user",
Description: "Select user",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "hours",
Description: "Hours to purge",
Required: true,
},
},
},
} }
commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
"show-top-emojis": showTopEmojis, "show-top-emojis": showTopEmojis,
"show-top-users": showTopUsers, "show-top-users": showTopUsers,
"purge-recent-emojis": purgeRecentEmojis,
} }
) )
@ -87,7 +106,7 @@ func showTopEmojis(s *discordgo.Session, i *discordgo.InteractionCreate) {
} }
amount := int64(5) amount := int64(5)
if opt, ok := optionMap[amountKey]; ok { if opt, ok := optionMap["amount"]; ok {
amount = opt.IntValue() amount = opt.IntValue()
} }
@ -118,7 +137,7 @@ func showTopEmojis(s *discordgo.Session, i *discordgo.InteractionCreate) {
sort.Ints(subkeys) sort.Ints(subkeys)
users := []string{} users := []string{}
msg += fmt.Sprintf("%s: %d", top[v].EmojiID, top[v].Count) msg += fmt.Sprintf("<:%s:%s> %d", top[v].EmojiName, top[v].EmojiID, top[v].Count)
for _, sv := range subkeys { for _, sv := range subkeys {
users = append(users, fmt.Sprintf("<@%s>: %d", topUsers[sv].EmojiID, topUsers[sv].Count)) users = append(users, fmt.Sprintf("<@%s>: %d", topUsers[sv].EmojiID, topUsers[sv].Count))
} }
@ -144,7 +163,7 @@ func showTopUsers(s *discordgo.Session, i *discordgo.InteractionCreate) {
} }
amount := int64(5) amount := int64(5)
if opt, ok := optionMap[amountKey]; ok { if opt, ok := optionMap["amount"]; ok {
amount = opt.IntValue() amount = opt.IntValue()
} }
@ -178,7 +197,7 @@ func showTopUsers(s *discordgo.Session, i *discordgo.InteractionCreate) {
users := []string{} users := []string{}
msg += fmt.Sprintf("<@%s>: %d", top[v].EmojiID, top[v].Count) msg += fmt.Sprintf("<@%s>: %d", top[v].EmojiID, top[v].Count)
for _, sv := range subkeys { for _, sv := range subkeys {
users = append(users, fmt.Sprintf("%s: %d", topUsers[sv].EmojiID, topUsers[sv].Count)) users = append(users, fmt.Sprintf("<:%s:%s> %d", topUsers[sv].EmojiName, topUsers[sv].EmojiID, topUsers[sv].Count))
} }
msg += " (" + strings.Join(users, ", ") + ")\n" msg += " (" + strings.Join(users, ", ") + ")\n"
} }
@ -191,3 +210,73 @@ func showTopUsers(s *discordgo.Session, i *discordgo.InteractionCreate) {
}, },
}) })
} }
// purgeRecentEmojis - Purges recent emojis for a user
func purgeRecentEmojis(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Access options in the order provided by the user.
options := i.ApplicationCommandData().Options
optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
for _, opt := range options {
optionMap[opt.Name] = opt
}
user := &discordgo.User{}
if opt, ok := optionMap["user"]; ok {
user = opt.UserValue(s)
} else {
slog.Error("Invalid user option provided")
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "No user specified",
AllowedMentions: &discordgo.MessageAllowedMentions{},
},
})
return
}
hours := int64(24)
if opt, ok := optionMap["hours"]; ok {
hours = opt.IntValue()
} else {
slog.Error("Invalid hours option provided")
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "No hours specified",
AllowedMentions: &discordgo.MessageAllowedMentions{},
},
})
return
}
emojis, err := b.Db.GetRecentEmojisForUser(i.GuildID, user.ID, hours)
if err != nil {
slog.Error("Error getting recent emojis for user", "err", err)
return
}
x := 0
for _, emoji := range emojis {
err := s.MessageReactionRemove(emoji.ChannelID, emoji.MessageID, emoji.EmojiID, emoji.UserID)
if err != nil {
slog.Error("Error removing emoji reaction", "err", err, "emoji", emoji.EmojiID, "user", user.ID)
continue
}
err = b.Db.DeleteEmojiUsage(i.GuildID, emoji.ChannelID, emoji.MessageID, emoji.UserID, emoji.EmojiID)
if err != nil {
slog.Error("Error deleting emoji usage", "err", err)
continue
}
x++
}
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: fmt.Sprintf("Purged %d emojis for user %s", x, user.Username),
AllowedMentions: &discordgo.MessageAllowedMentions{},
},
})
}

View file

@ -88,7 +88,7 @@ func (bot *Bot) HandleAddReaction(discord *discordgo.Session, reaction *discordg
return return
} }
err := bot.Db.LogEmojiUsage(reaction.GuildID, reaction.ChannelID, reaction.MessageID, reaction.UserID, reaction.Emoji.Name) err := bot.Db.LogEmojiUsage(reaction.GuildID, reaction.ChannelID, reaction.MessageID, reaction.UserID, reaction.Emoji.ID, reaction.Emoji.Name)
if err != nil { if err != nil {
slog.Error("Failed to log emoji usage", "err", err) slog.Error("Failed to log emoji usage", "err", err)
} }
@ -101,7 +101,7 @@ func (bot *Bot) HandleRemoveReaction(discord *discordgo.Session, reaction *disco
return return
} }
err := bot.Db.DeleteEmojiUsage(reaction.GuildID, reaction.ChannelID, reaction.MessageID, reaction.UserID, reaction.Emoji.Name) err := bot.Db.DeleteEmojiUsage(reaction.GuildID, reaction.ChannelID, reaction.MessageID, reaction.UserID, reaction.Emoji.ID)
if err != nil { if err != nil {
slog.Error("Failed to delete single emoji usage", "err", err) slog.Error("Failed to delete single emoji usage", "err", err)
} }

View file

@ -44,6 +44,7 @@ func (db *Database) runMigrations() (*Database, error) {
"`message_id` TEXT, " + "`message_id` TEXT, " +
"`user_id` TEXT, " + "`user_id` TEXT, " +
"`emoji_id` TEXT, " + "`emoji_id` TEXT, " +
"`emoji_name` TEXT, " +
"`timestamp` DATETIME" + "`timestamp` DATETIME" +
")") ")")
if err != nil { if err != nil {

View file

@ -1,15 +1,27 @@
package db package db
import "fmt"
type EmojiMap struct { type EmojiMap struct {
EmojiID string EmojiID string
EmojiName string
Count int64 Count int64
} }
type EmojiUsage struct {
GuildID string
ChannelID string
MessageID string
UserID string
EmojiID string
Timestamp string
}
// LogEmojiUsage - Log usage // LogEmojiUsage - Log usage
func (db *Database) LogEmojiUsage(guildID, channelID, messageID, userID, emojiID string) error { func (db *Database) LogEmojiUsage(guildID, channelID, messageID, userID, emojiID, emojiName string) error {
_, err := db.db.Exec( _, err := db.db.Exec(
"INSERT INTO `emoji_usage` (`guild_id`, `channel_id`, `message_id`, `user_id`, `emoji_id`, `timestamp`) VALUES (?,?,?,?,?, datetime())", "INSERT INTO `emoji_usage` (`guild_id`, `channel_id`, `message_id`, `user_id`, `emoji_id`, `emoji_name`, `timestamp`) VALUES (?,?,?,?,?,?, datetime())",
guildID, channelID, messageID, userID, emojiID, guildID, channelID, messageID, userID, emojiID, emojiName,
) )
return err return err
@ -91,7 +103,7 @@ func (db *Database) GetTopUsersForGuildEmoji(guildID string, emojiID string, num
func (db *Database) GetTopEmojisForGuild(guildID string, num int64) (map[int]EmojiMap, error) { func (db *Database) GetTopEmojisForGuild(guildID string, num int64) (map[int]EmojiMap, error) {
data := make(map[int]EmojiMap) data := make(map[int]EmojiMap)
row, err := db.db.Query( row, err := db.db.Query(
"SELECT emoji_id, count(*) FROM `emoji_usage` WHERE `guild_id` = ? GROUP BY emoji_id ORDER BY count(*) DESC LIMIT ?", "SELECT emoji_name, emoji_id, count(*) FROM `emoji_usage` WHERE `guild_id` = ? GROUP BY emoji_id ORDER BY count(*) DESC LIMIT ?",
guildID, guildID,
num, num,
) )
@ -103,10 +115,11 @@ func (db *Database) GetTopEmojisForGuild(guildID string, num int64) (map[int]Emo
defer row.Close() defer row.Close()
i := 0 i := 0
for row.Next() { for row.Next() {
var emoji string var emojiName string
var emojiID string
var count int64 var count int64
row.Scan(&emoji, &count) row.Scan(&emojiName, &emojiID, &count)
data[i] = EmojiMap{EmojiID: emoji, Count: count} data[i] = EmojiMap{EmojiID: emojiID, EmojiName: emojiName, Count: count}
i++ i++
} }
@ -117,7 +130,7 @@ func (db *Database) GetTopEmojisForGuild(guildID string, num int64) (map[int]Emo
func (db *Database) GetTopEmojisForGuildUser(guildID string, userID string, num int) (map[int]EmojiMap, error) { func (db *Database) GetTopEmojisForGuildUser(guildID string, userID string, num int) (map[int]EmojiMap, error) {
data := make(map[int]EmojiMap) data := make(map[int]EmojiMap)
row, err := db.db.Query( row, err := db.db.Query(
"SELECT emoji_id, count(*) FROM `emoji_usage` WHERE `guild_id` = ? AND `user_id` = ? GROUP BY emoji_id ORDER BY count(*) DESC LIMIT ?", "SELECT emoji_name, emoji_id, count(*) FROM `emoji_usage` WHERE `guild_id` = ? AND `user_id` = ? GROUP BY emoji_name ORDER BY count(*) DESC LIMIT ?",
guildID, guildID,
userID, userID,
num, num,
@ -130,12 +143,38 @@ func (db *Database) GetTopEmojisForGuildUser(guildID string, userID string, num
defer row.Close() defer row.Close()
i := 0 i := 0
for row.Next() { for row.Next() {
var emoji string var emojiName string
var emojiID string
var count int64 var count int64
row.Scan(&emoji, &count) row.Scan(&emojiName, &emojiID, &count)
data[i] = EmojiMap{EmojiID: emoji, Count: count} data[i] = EmojiMap{EmojiID: emojiID, EmojiName: emojiName, Count: count}
i++ i++
} }
return data, nil return data, nil
} }
// GetRecentEmojisForUser - Get recent emojis used by user map[]
func (db *Database) GetRecentEmojisForUser(guildID string, userID string, hours int64) ([]EmojiUsage, error) {
var data []EmojiUsage
row, err := db.db.Query(
"SELECT guild_id, channel_id, message_id, user_id, emoji_id, timestamp "+
"FROM `emoji_usage` WHERE `guild_id` = ? AND `user_id` = ? AND timestamp >= datetime('now', '-"+fmt.Sprintf("%d", hours)+" hours') "+
"ORDER BY timestamp DESC",
guildID,
userID,
)
if err != nil {
return data, err
}
defer row.Close()
for row.Next() {
usage := EmojiUsage{}
row.Scan(&usage.GuildID, &usage.ChannelID, &usage.MessageID, &usage.UserID, &usage.EmojiID, &usage.Timestamp)
data = append(data, usage)
}
return data, nil
}