Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

13 starboard #96

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a5cbad9
set up setReactboard command
Plyb Apr 11, 2023
7bb0c8b
add code for discriminating reaction types
Plyb Apr 11, 2023
c1e2636
save reactboard settings in db
Plyb Apr 11, 2023
c5958cf
factor out reaction duplication code
Plyb Apr 11, 2023
a1aaf23
add updateReactboard handler with reactboard fetching
Plyb Apr 11, 2023
6a17ecd
partial reactboard message finding
Plyb Apr 13, 2023
9f5aaf1
Merge branch 'main' into 13-starboard
Plyb Apr 13, 2023
46e04bc
add updating of existing posts
Plyb Apr 13, 2023
408288c
add posting of new messages
Plyb Apr 13, 2023
bf1f0c3
factor out channel fetching
Plyb Apr 13, 2023
4e37de0
factor out adding of new posts
Plyb Apr 13, 2023
0bc8307
save posts in db so we don't get duplicate posts
Plyb Apr 13, 2023
6885661
enable built in emoji support
Plyb Apr 14, 2023
dd12c17
add ability to detect removals
Plyb Apr 14, 2023
442aed8
build embed
Plyb Apr 14, 2023
776de18
prevent illegal starrings
Plyb Apr 14, 2023
cb423d8
add tests for setReactboard command
Plyb Apr 14, 2023
42ac27a
add tests for updating reactboard
Plyb Apr 15, 2023
2a4d926
update documentation
Plyb Apr 15, 2023
da728e0
restrict setreactboard to admins
Plyb Apr 15, 2023
fd667c7
Merge branch '13-starboard' of https://github.com/BYU-CS-Discord/CSBo…
Plyb Apr 15, 2023
a4740f9
Merge branch 'main' into 13-starboard
Plyb Apr 15, 2023
b0801e4
fix changelog format
Plyb Apr 15, 2023
ff41fec
Apply suggestions from code review
Plyb Apr 19, 2023
d17156a
use upper camel case for prisma schema
Plyb Apr 19, 2023
6c28b32
include originalMessageId
Plyb Apr 19, 2023
c1dc725
only use emojis from the guild
Plyb Apr 19, 2023
2226bd1
fix tests
Plyb Apr 19, 2023
97bfd6d
add db migrations to the setup script.
Plyb Apr 22, 2023
6e155c7
Merge branch '13-starboard' of https://github.com/BYU-CS-Discord/CSBo…
Plyb Apr 22, 2023
582b2fe
Just discord minvalue option
Plyb Apr 25, 2023
05bf145
Merge branch 'main' into 13-starboard
Plyb Jul 8, 2023
052a256
13 update changelog
Plyb Jul 8, 2023
44c980e
13 repair tests
Plyb Jul 8, 2023
48503f2
Revert "13 repair tests"
Plyb Jul 8, 2023
3e0cc4a
Update src/reactionHandlers/updateReactboard.ts
Plyb Jul 14, 2023
3c24c99
Merge branch 'main' into 13-starboard
JstnMcBrd Jan 19, 2024
1551f8b
Migrate reactboard tests to vitest
JstnMcBrd Jan 19, 2024
d8f8075
Cleanup merge
JstnMcBrd Jan 19, 2024
ecb1ffe
Lockfile v3
JstnMcBrd Jan 19, 2024
6d15f39
Merge branch 'main' into 13-starboard
JstnMcBrd Feb 5, 2024
783e0eb
Fix lint errors
JstnMcBrd Feb 5, 2024
237114f
Fix lint errors
JstnMcBrd Feb 5, 2024
3549be7
Mock logger
JstnMcBrd Feb 5, 2024
def14bb
Merge branch 'main' into 13-starboard
JstnMcBrd Feb 7, 2024
b1a661f
Cleanup from merge
JstnMcBrd Feb 7, 2024
33a457f
Merge branch 'main' into 13-starboard
JstnMcBrd Feb 7, 2024
c417473
Merge branch 'main' into 13-starboard
AverageHelper Apr 8, 2024
3bfaee1
Merge branch 'main' into 13-starboard
JstnMcBrd Aug 24, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `/iscasdown` slash command to check if BYU's CAS is working
- Starboard, and associated commands

### Changed

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ Retrieves the profile picture of the given user.

Not complete. For now, this command simply auto-completes the tag the user types, but it does not send the tag.

### /setreactboard

Creates a new reactboard or updates an existing one. A reactboard is a channel where the bot will repost messages that recieve a specified number of a specified reaction. The primary use is for a starboard where messages that receive the right number of stars will be added, along with how many stars they received.

### /stats ( track / update / list / leaderboard / untrack )

Tracks a statistic for the issuer. Use the `track` subcommand to begin tracking, `update` to add or subtract to it, `list` to show all the stats being tracked for the issuer, `leaderboard` to show the users with the highest scores for a stat, and `untrack` to stop tracking a stat for you.
Expand Down Expand Up @@ -182,7 +186,7 @@ ADMINISTRATORS=COMMA,SEPARATED,ID,LIST

### Invite your bot to your server

Go to https://discordapi.com/permissions.html#378091424832 and paste in your bot's client ID to get an invite link.
Go to https://discordapi.com/permissions.html#379165174848 and paste in your bot's client ID to get an invite link.
JstnMcBrd marked this conversation as resolved.
Show resolved Hide resolved

### Setting up Docker

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"lint:fix": "npm run lint -- --fix",
"release": "./node_modules/.bin/tsx ./scripts/release.ts",
"restart": "./node_modules/.bin/pm2 restart cs-bot",
"setup": "npm ci && npm run export-version && npm run build --production && npm run commands:deploy",
"setup": "npm ci && npm run export-version && npm run db:migrate && npm run build --production && npm run commands:deploy",
"start": "./node_modules/.bin/pm2 start ./dist/main.js --name cs-bot",
"stop": "./node_modules/.bin/pm2 delete cs-bot",
"test": "./node_modules/.bin/vitest",
Expand Down
11 changes: 11 additions & 0 deletions prisma/migrations/20230411151150_reactboard/migration.sql
AverageHelper marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- CreateTable
CREATE TABLE "reactboard" (
"id" SERIAL NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"react" TEXT NOT NULL,
"isCustomReact" BOOLEAN NOT NULL,
"threshold" INTEGER NOT NULL,

CONSTRAINT "reactboard_pkey" PRIMARY KEY ("id")
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- A unique constraint covering the columns `[guildId,channelId]` on the table `reactboard` will be added. If there are existing duplicate values, this will fail.

*/
-- CreateIndex
CREATE UNIQUE INDEX "reactboard_guildId_channelId_key" ON "reactboard"("guildId", "channelId");
12 changes: 12 additions & 0 deletions prisma/migrations/20230411182041_reactboard_posts/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE "reactboardPost" (
"id" SERIAL NOT NULL,
"reactboardId" INTEGER NOT NULL,
"originalMessageId" TEXT NOT NULL,
"reactboardMessageId" TEXT NOT NULL,

CONSTRAINT "reactboardPost_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "reactboardPost" ADD CONSTRAINT "reactboardPost_reactboardId_fkey" FOREIGN KEY ("reactboardId") REFERENCES "reactboard"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Warnings:

- You are about to drop the `reactboard` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `reactboardPost` table. If the table is not empty, all the data it contains will be lost.

*/
-- DropForeignKey
ALTER TABLE "reactboardPost" DROP CONSTRAINT "reactboardPost_reactboardId_fkey";

-- DropTable
DROP TABLE "reactboard";

-- DropTable
DROP TABLE "reactboardPost";

-- CreateTable
CREATE TABLE "Reactboard" (
"id" SERIAL NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"react" TEXT NOT NULL,
"isCustomReact" BOOLEAN NOT NULL,
"threshold" INTEGER NOT NULL,

CONSTRAINT "Reactboard_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "ReactboardPost" (
"id" SERIAL NOT NULL,
"reactboardId" INTEGER NOT NULL,
"originalMessageId" TEXT NOT NULL,
"reactboardMessageId" TEXT NOT NULL,

CONSTRAINT "ReactboardPost_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Reactboard_guildId_channelId_key" ON "Reactboard"("guildId", "channelId");

-- AddForeignKey
ALTER TABLE "ReactboardPost" ADD CONSTRAINT "ReactboardPost_reactboardId_fkey" FOREIGN KEY ("reactboardId") REFERENCES "Reactboard"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- Added the required column `originalChannelId` to the `ReactboardPost` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "ReactboardPost" ADD COLUMN "originalChannelId" TEXT NOT NULL;
52 changes: 22 additions & 30 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ datasource db {
url = env("DATABASE_URL")
}

model Reactboard {
id Int @id @default(autoincrement())
guildId String
channelId String
react String
isCustomReact Boolean
threshold Int

@@unique([guildId, channelId], name: "location")

reactboardPosts ReactboardPost[]
}

model ReactboardPost {
id Int @id @default(autoincrement())
reactboardId Int
reactboard Reactboard @relation(fields: [reactboardId], references: [id])
originalMessageId String
AverageHelper marked this conversation as resolved.
Show resolved Hide resolved
originalChannelId String
reactboardMessageId String
}

model Scoreboard {
id Int @id @default(autoincrement())
userId String
Expand All @@ -18,23 +40,6 @@ model Scoreboard {
score Float
}

// All our Models will be defined here
// Below is an example of what one would look like in our usecase
// NOTE NOTHING HERE IS FINAL, THIS IS JUST AN EXAMPLE
// Idk what I am doing

// model Emoteboard {
// id Int @id @default(autoincrement())
// createdAt DateTime @default(now())
// author String
// postId String @unique
// count Int
// emote Emote @default(STAR)
// channel String
// guild String
// userId String
// }

// model Tag {
// id Int @id @default(autoincrement())
// createdAt DateTime @default(now())
Expand All @@ -49,16 +54,3 @@ model Scoreboard {
// starredCount Int
// leaderboards Leaderboard[]
// }

// model Leaderboard {
// id Int @id @default(autoincrement())
// createdAt DateTime @default(now())
// name String
// users User[]
// userCounts Int[]
// }

// enum Emote {
// STAR
// BASED
// }
12 changes: 12 additions & 0 deletions src/@types/ReactionHandler.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { MessageReaction, PartialMessageReaction, PartialUser, User } from 'discord.js';

declare global {
interface ReactionHandler {
execute: (context: ReactionHandlerContext) => void | Promise<void>;
}

interface ReactionHandlerContext {
reaction: MessageReaction | PartialMessageReaction;
user: User | PartialUser;
}
}
121 changes: 121 additions & 0 deletions src/commands/setReactboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { DeepMockProxy } from 'vitest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';

import type { PrismaClient } from '@prisma/client';
import type { TextChannel } from 'discord.js';

import { setReactboard } from './setReactboard.js';
import { db } from '../database/index.js';
import { UserMessageError } from '../helpers/UserMessageError.js';

vi.mock('../database', () => ({
db: mockDeep<PrismaClient>(),
}));

describe('setReactboard', () => {
const dbMock = db as unknown as DeepMockProxy<PrismaClient>;
// eslint-disable-next-line @typescript-eslint/unbound-method
const mockUpsert = dbMock.reactboard.upsert;

const mockGuildId = 'test-guild-id';
const mockChannel = {
id: 'test-channel-id',
};
const mockThreshold = 5;
const mockReact = '⭐';

const mockReplyPrivately = vi.fn();
const mockGetString = vi.fn<[name: string], string | null>();
const mockGetInteger = vi.fn<[name: string], number>();
const mockGetChannel = vi.fn<[name: string], TextChannel | null>();
const mockGetEmoji = vi.fn();
let context: GuildedCommandContext;

beforeEach(() => {
context = {
replyPrivately: mockReplyPrivately,
options: {
getString: mockGetString,
getInteger: mockGetInteger,
getChannel: mockGetChannel,
},
guild: {
id: mockGuildId,
emojis: {
fetch: mockGetEmoji,
},
},
} as unknown as GuildedCommandContext;

mockGetChannel.mockReturnValue(mockChannel as unknown as TextChannel);
mockGetInteger.mockReturnValue(mockThreshold);
mockGetString.mockReturnValue(mockReact);

vi.clearAllMocks();
});

test('upserts a reactboard', async () => {
await expect(setReactboard.execute(context)).resolves.toBeUndefined();
expect(mockUpsert).toHaveBeenCalledTimes(1);
expect(mockUpsert).toHaveBeenCalledWith({
where: {
location: {
channelId: mockChannel.id,
guildId: mockGuildId,
},
},
update: {
threshold: mockThreshold,
react: mockReact,
isCustomReact: false,
},
create: {
channelId: mockChannel.id,
guildId: mockGuildId,
threshold: mockThreshold,
react: mockReact,
isCustomReact: false,
},
});
expect(mockReplyPrivately).toHaveBeenCalledTimes(1);
});

test('upserts a reactboard with a custom emoji', async () => {
const customEmojiId = '1234567890';
mockGetString.mockReturnValue(`<:abcdef:${customEmojiId}>`);
mockGetEmoji.mockReturnValue({
id: customEmojiId,
});

await expect(setReactboard.execute(context)).resolves.toBeUndefined();
expect(mockUpsert).toHaveBeenCalledTimes(1);
expect(mockUpsert).toHaveBeenCalledWith({
where: {
location: {
channelId: mockChannel.id,
guildId: mockGuildId,
},
},
update: {
threshold: mockThreshold,
react: customEmojiId,
isCustomReact: true,
},
create: {
channelId: mockChannel.id,
guildId: mockGuildId,
threshold: mockThreshold,
react: customEmojiId,
isCustomReact: true,
},
});
expect(mockReplyPrivately).toHaveBeenCalledTimes(1);
});

test('fails with UserMessageError when threshold is below 1', async () => {
mockGetInteger.mockReturnValue(0);

await expect(setReactboard.execute(context)).rejects.toThrow(UserMessageError);
});
});
Loading