Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ func main() {
log.Info().Msg("Nickname history enabled, adding listeners...")
s.AddHandler(history.OnGuildMemberUpdate)
}
s.AddHandler(history.OnGuildMemeberJoin)
s.AddHandler(history.OnGuildMemberLeave)

// Register available slash commands
log.Info().Msg("Adding commands...")
Expand Down
57 changes: 55 additions & 2 deletions internal/history/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"math280h/wisp/db"
"math280h/wisp/internal/shared"
"time"

"github.com/bwmarrin/discordgo"
"github.com/rs/zerolog/log"
Expand All @@ -20,8 +21,8 @@ func updateNickIfChanged(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {

newNick := m.Nick

if userObj.Nickname != newNick {
log.Debug().Msg("Nickname changed" + newNick)
if m.Nick != "" && userObj.Nickname != newNick {
log.Debug().Msg("Nickname changed: " + newNick)
if newNick == "" {
newNick = m.Member.User.Username
}
Expand Down Expand Up @@ -63,3 +64,55 @@ func updateNickIfChanged(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
func OnGuildMemberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
updateNickIfChanged(s, m)
}

func OnGuildMemeberJoin(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
_, err := shared.DBClient.User.UpsertOne(
db.User.UserID.Equals(m.Member.User.ID),
).Create(
db.User.UserID.Set(m.Member.User.ID),
db.User.Nickname.Set(m.Member.User.Username),
db.User.LastJoin.Set(time.Now().Format(time.RFC3339)),
).Update(
db.User.LastJoin.Set(time.Now().Format(time.RFC3339)),
db.User.Nickname.Set(m.Member.User.Username),
).Exec(context.Background())
if err != nil {
log.Error().Err(err).Msg("Failed to create user")
return
}

embed := discordgo.MessageEmbed{
Title: "Member Joined",
Description: m.Member.User.Mention() + " (" + m.Member.User.String() + ")",
Color: shared.Green,
}

_, channelErr := s.ChannelMessageSendEmbed(*shared.HistoryChannel, &embed)
if channelErr != nil {
log.Error().Err(channelErr).Msg("Failed to send message")
}
}

func OnGuildMemberLeave(s *discordgo.Session, m *discordgo.GuildMemberRemove) {
_, err := shared.DBClient.User.FindUnique(
db.User.UserID.Equals(m.Member.User.ID),
).Update(
db.User.LastLeave.Set(time.Now().Format(time.RFC3339)),
db.User.LeaveCount.Increment(1),
).Exec(context.Background())
if err != nil {
log.Error().Err(err).Msg("Failed to update user")
return
}

embed := discordgo.MessageEmbed{
Title: "Member Left",
Description: m.Member.User.Mention() + " (" + m.Member.User.String() + ")",
Color: shared.Red,
}

_, channelErr := s.ChannelMessageSendEmbed(*shared.HistoryChannel, &embed)
if channelErr != nil {
log.Error().Err(channelErr).Msg("Failed to send message")
}
}
31 changes: 31 additions & 0 deletions internal/moderation/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ func GenerateOverviewEmbed(user db.UserModel, userDiscordID string, reports int,
}
}

// Ensure last leave is not empty, if it is set to "Never"
usrLastLeave, lastLeaveOk := user.LastLeave()
if !lastLeaveOk {
usrLastLeave = "Never"
}

usrLastJoin, lastJoinOk := user.LastJoin()
if !lastJoinOk {
usrLastJoin = "Never"
}

embed := discordgo.MessageEmbed{
Color: shared.DarkBlue,
Fields: []*discordgo.MessageEmbedField{
Expand Down Expand Up @@ -103,6 +114,26 @@ func GenerateOverviewEmbed(user db.UserModel, userDiscordID string, reports int,
Value: strconv.Itoa(reports),
Inline: true,
},
{
Name: "__Leave History__",
Value: "",
Inline: false,
},
{
Name: "Last Join",
Value: shared.StringWithTzToDiscordTimestamp(usrLastJoin),
Inline: true,
},
{
Name: "Last Leave",
Value: shared.StringWithTzToDiscordTimestamp(usrLastLeave),
Inline: true,
},
{
Name: "Leave Count",
Value: strconv.Itoa(user.LeaveCount),
Inline: true,
},
{
Name: "__Most Recent Infraction__",
Value: "",
Expand Down
13 changes: 13 additions & 0 deletions internal/shared/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@ func StringTimeToDiscordTimestamp(t string) string {
unixTimestamp := parsedTime.Unix()
return "<t:" + strconv.FormatInt(unixTimestamp, 10) + ":R>"
}

func StringWithTzToDiscordTimestamp(t string) string {
if t == "Never" {
return "Never"
}

parsedTime, err := time.Parse("2006-01-02T15:04:05-07:00", t)
if err != nil {
log.Error().Err(err).Msg("Failed to parse time")
}
unixTimestamp := parsedTime.Unix()
return "<t:" + strconv.FormatInt(unixTimestamp, 10) + ":R>"
}
83 changes: 83 additions & 0 deletions migrations/20250106051446_add_leave_join_tracking/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_infractions" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" INTEGER NOT NULL,
"reason" TEXT NOT NULL,
"type" TEXT NOT NULL,
"points" INTEGER NOT NULL,
"moderator_id" TEXT NOT NULL,
"moderator_username" TEXT NOT NULL,
"created_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "infractions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_infractions" ("created_at", "id", "moderator_id", "moderator_username", "points", "reason", "type", "user_id") SELECT "created_at", "id", "moderator_id", "moderator_username", "points", "reason", "type", "user_id" FROM "infractions";
DROP TABLE "infractions";
ALTER TABLE "new_infractions" RENAME TO "infractions";
CREATE TABLE "new_messages" (
"id" TEXT NOT NULL PRIMARY KEY,
"content" TEXT NOT NULL,
"author_id" INTEGER NOT NULL,
"channel_id" TEXT NOT NULL,
"created_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "messages_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_messages" ("author_id", "channel_id", "content", "created_at", "id", "updated_at") SELECT "author_id", "channel_id", "content", "created_at", "id", "updated_at" FROM "messages";
DROP TABLE "messages";
ALTER TABLE "new_messages" RENAME TO "messages";
CREATE UNIQUE INDEX "messages_id_key" ON "messages"("id");
CREATE TABLE "new_reports" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"channel_id" TEXT NOT NULL,
"channel_name" TEXT NOT NULL,
"user_id" INTEGER NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"created_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "reports_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_reports" ("channel_id", "channel_name", "created_at", "id", "status", "updated_at", "user_id") SELECT "channel_id", "channel_name", "created_at", "id", "status", "updated_at", "user_id" FROM "reports";
DROP TABLE "reports";
ALTER TABLE "new_reports" RENAME TO "reports";
CREATE TABLE "new_suggestion_votes" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"suggestion_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"sentiment" TEXT NOT NULL DEFAULT 'vote_up',
"created_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "suggestion_votes_suggestion_id_fkey" FOREIGN KEY ("suggestion_id") REFERENCES "suggestions" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "suggestion_votes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_suggestion_votes" ("created_at", "id", "sentiment", "suggestion_id", "user_id") SELECT "created_at", "id", "sentiment", "suggestion_id", "user_id" FROM "suggestion_votes";
DROP TABLE "suggestion_votes";
ALTER TABLE "new_suggestion_votes" RENAME TO "suggestion_votes";
CREATE TABLE "new_suggestions" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"suggestion" TEXT NOT NULL,
"user_id" INTEGER NOT NULL,
"embed_id" TEXT,
"status" TEXT NOT NULL DEFAULT 'pending',
"created_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "suggestions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_suggestions" ("created_at", "embed_id", "id", "status", "suggestion", "user_id") SELECT "created_at", "embed_id", "id", "status", "suggestion", "user_id" FROM "suggestions";
DROP TABLE "suggestions";
ALTER TABLE "new_suggestions" RENAME TO "suggestions";
CREATE TABLE "new_users" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" TEXT NOT NULL,
"nickname" TEXT NOT NULL,
"points" INTEGER NOT NULL DEFAULT 0,
"last_join" TEXT,
"last_leave" TEXT,
"leave_count" INTEGER NOT NULL DEFAULT 0,
"created_at" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO "new_users" ("created_at", "id", "nickname", "points", "user_id") SELECT "created_at", "id", "nickname", "points", "user_id" FROM "users";
DROP TABLE "users";
ALTER TABLE "new_users" RENAME TO "users";
CREATE UNIQUE INDEX "users_user_id_key" ON "users"("user_id");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
4 changes: 4 additions & 0 deletions schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ model User {
nickname String
points Int @default(0)

lastJoin String? @map("last_join")
lastLeave String? @map("last_leave")
leaveCount Int @default(0) @map("leave_count")

reports Report[]
infractions Infraction[]
suggestions Suggestion[]
Expand Down
Loading