Skip to content

Commit 157ef9a

Browse files
Merge pull request #247 from Pdzly/feature/anti-starboard
Feature: Anti Starboard
2 parents ca078cc + 72404e9 commit 157ef9a

12 files changed

Lines changed: 862 additions & 254 deletions

src/Config.prod.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export const config: Config = {
1818
emojiId: "⭐",
1919
channel: "975786395211816980",
2020
threshold: 2,
21+
color: "Blue",
22+
blacklistChannelIds: [],
23+
},
24+
antiStarboard: {
25+
emojiId: "💀",
26+
channel: "1486328029293838458",
27+
threshold: 2,
28+
color: "DarkRed",
2129
blacklistChannelIds: [],
2230
},
2331
commands: {

src/Config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ const devConfig: Config = {
1818
emojiId: "⭐",
1919
channel: "1407366658552631296",
2020
threshold: 1,
21+
color: "Blue",
22+
blacklistChannelIds: ["904478147351806015"],
23+
},
24+
antiStarboard: {
25+
emojiId: "💀",
26+
channel: "1478460342550921237",
27+
threshold: 1,
28+
color: "DarkRed",
2129
blacklistChannelIds: ["904478147351806015"],
2230
},
2331
commands: {

src/config.type.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import type { Snowflake } from "discord.js";
1+
import type { ColorResolvable, Snowflake } from "discord.js";
22
import type { InformationMessage } from "./modules/information/information.js";
33
import type { BrandingConfig } from "./util/branding.js";
44

55
export interface AchievementsConfig {
6+
/** Where achievement notifications are sent by default */
7+
notificationMode?: "trigger" | "channel" | "dm";
68
/** Dedicated channel for notifications (required if mode is "channel") */
79
notificationChannel?: string;
810
/** Fallback channel if trigger location unavailable (for "trigger" mode) */
@@ -100,6 +102,14 @@ export interface Config {
100102
emojiId: string;
101103
channel: string;
102104
threshold: number;
105+
color: ColorResolvable;
106+
blacklistChannelIds?: Snowflake[];
107+
};
108+
antiStarboard?: {
109+
emojiId: string;
110+
channel: string;
111+
threshold: number;
112+
color: ColorResolvable;
103113
blacklistChannelIds?: Snowflake[];
104114
};
105115
suggest: {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { ColorResolvable, Message } from "discord.js";
2+
import { config } from "../../Config.js";
3+
import { getReactionCountForEmoji } from "./starboard.reaction-utils.js";
4+
5+
export interface StarboardContentRenderOptions {
6+
emojiId: string;
7+
threshold: number;
8+
color: ColorResolvable;
9+
}
10+
11+
const getAntiStarHintForMessage = (
12+
message: Pick<Message, "reactions">,
13+
renderOptions: StarboardContentRenderOptions,
14+
): string | null => {
15+
if (
16+
!config.antiStarboard ||
17+
renderOptions.emojiId !== config.starboard.emojiId
18+
) {
19+
return null;
20+
}
21+
22+
const antiStarCount = getReactionCountForEmoji(
23+
message,
24+
config.antiStarboard.emojiId,
25+
);
26+
if (antiStarCount < 1) {
27+
return null;
28+
}
29+
30+
return `(- ${config.antiStarboard.emojiId}: ${antiStarCount})`;
31+
};
32+
33+
export const buildStarboardMessageContent = (
34+
message: Pick<Message, "url" | "reactions">,
35+
stars: number,
36+
renderOptions: StarboardContentRenderOptions,
37+
): string => {
38+
const contentParts = [`${renderOptions.emojiId}: ${stars}`];
39+
const antiStarHint = getAntiStarHintForMessage(message, renderOptions);
40+
if (antiStarHint) {
41+
contentParts.push(antiStarHint);
42+
}
43+
contentParts.push("|", message.url);
44+
45+
return contentParts.join(" ");
46+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, expect, test } from "bun:test";
2+
import {
3+
getReactionCountForEmoji,
4+
getThresholdReactionCount,
5+
matchesConfiguredEmoji,
6+
} from "./starboard.reaction-utils.js";
7+
import { createMessageWithReactions } from "./starboard.test-utils.js";
8+
9+
describe("matchesConfiguredEmoji", () => {
10+
test("matches unicode emoji with and without variation selector", () => {
11+
expect(matchesConfiguredEmoji("⭐", null, "⭐️")).toBe(true);
12+
});
13+
14+
test("matches custom emoji by id when config uses Discord emoji syntax", () => {
15+
expect(
16+
matchesConfiguredEmoji("star", "1234567890", "<:star:1234567890>"),
17+
).toBe(true);
18+
});
19+
});
20+
21+
describe("getReactionCountForEmoji", () => {
22+
test("matches unicode emoji with and without variation selector", () => {
23+
const message = createMessageWithReactions([
24+
{
25+
emoji: {
26+
name: "⭐",
27+
id: null,
28+
},
29+
count: 3,
30+
},
31+
]);
32+
33+
expect(getReactionCountForEmoji(message, "⭐️")).toBe(3);
34+
});
35+
36+
test("matches custom emoji when configured as <:name:id>", () => {
37+
const message = createMessageWithReactions([
38+
{
39+
emoji: {
40+
name: "star",
41+
id: "1234567890",
42+
},
43+
count: 4,
44+
},
45+
]);
46+
47+
expect(getReactionCountForEmoji(message, "<:star:1234567890>")).toBe(4);
48+
});
49+
});
50+
51+
describe("getThresholdReactionCount", () => {
52+
test("uses message reaction count when event count is missing", () => {
53+
const message = createMessageWithReactions([
54+
{
55+
emoji: {
56+
name: "⭐",
57+
id: null,
58+
},
59+
count: 2,
60+
},
61+
]);
62+
63+
expect(getThresholdReactionCount(message, "⭐", null)).toBe(2);
64+
});
65+
66+
test("falls back to event reaction count when message cache has no match", () => {
67+
const message = createMessageWithReactions([]);
68+
69+
expect(getThresholdReactionCount(message, "⭐", 2)).toBe(2);
70+
});
71+
});

0 commit comments

Comments
 (0)