feat: added option to make joel shut the fuck up
This commit is contained in:
47
src/commands/definitions/blacklist-mention.ts
Normal file
47
src/commands/definitions/blacklist-mention.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Blacklist mention command
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||||
|
import type { Command } from "../types";
|
||||||
|
import { userRepository } from "../../database";
|
||||||
|
|
||||||
|
const command: Command = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("blacklist-mention")
|
||||||
|
.setDescription("Blacklist a user (or yourself) from being randomly mentioned by Joel")
|
||||||
|
.setDMPermission(false)
|
||||||
|
.addUserOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("user")
|
||||||
|
.setDescription("The user to blacklist (requires Manage Guild permission if not yourself)")
|
||||||
|
.setRequired(false)
|
||||||
|
),
|
||||||
|
category: "utility",
|
||||||
|
execute: async (interaction) => {
|
||||||
|
const targetUser = interaction.options.getUser("user") || interaction.user;
|
||||||
|
|
||||||
|
// If trying to blacklist someone else, check permissions
|
||||||
|
if (targetUser.id !== interaction.user.id) {
|
||||||
|
const member = await interaction.guild?.members.fetch(interaction.user.id);
|
||||||
|
if (!member?.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: "You need the Manage Guild permission to blacklist other users.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await userRepository.setOptOut(targetUser.id, true);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
content: targetUser.id === interaction.user.id
|
||||||
|
? "You have been successfully blacklisted from random mentions."
|
||||||
|
: `Successfully blacklisted <@${targetUser.id}> from random mentions.`,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default command;
|
||||||
47
src/commands/definitions/unblacklist-mention.ts
Normal file
47
src/commands/definitions/unblacklist-mention.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Unblacklist mention command
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||||
|
import type { Command } from "../types";
|
||||||
|
import { userRepository } from "../../database";
|
||||||
|
|
||||||
|
const command: Command = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("unblacklist-mention")
|
||||||
|
.setDescription("Remove a user (or yourself) from the random mention blacklist")
|
||||||
|
.setDMPermission(false)
|
||||||
|
.addUserOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("user")
|
||||||
|
.setDescription("The user to unblacklist (requires Manage Guild permission if not yourself)")
|
||||||
|
.setRequired(false)
|
||||||
|
),
|
||||||
|
category: "utility",
|
||||||
|
execute: async (interaction) => {
|
||||||
|
const targetUser = interaction.options.getUser("user") || interaction.user;
|
||||||
|
|
||||||
|
// If trying to unblacklist someone else, check permissions
|
||||||
|
if (targetUser.id !== interaction.user.id) {
|
||||||
|
const member = await interaction.guild?.members.fetch(interaction.user.id);
|
||||||
|
if (!member?.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: "You need the Manage Guild permission to unblacklist other users.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await userRepository.setOptOut(targetUser.id, false);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
content: targetUser.id === interaction.user.id
|
||||||
|
? "You have been successfully removed from the random mention blacklist."
|
||||||
|
: `Successfully removed <@${targetUser.id}> from the random mention blacklist.`,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default command;
|
||||||
@@ -131,8 +131,8 @@ export const config: BotConfig = {
|
|||||||
mentionCooldown: 24 * 60 * 60 * 1000, // 24 hours
|
mentionCooldown: 24 * 60 * 60 * 1000, // 24 hours
|
||||||
mentionProbability: 0.001,
|
mentionProbability: 0.001,
|
||||||
spontaneousSchedulerEnabled: getBooleanEnvOrDefault("BOT_SPONTANEOUS_SCHEDULER_ENABLED", true),
|
spontaneousSchedulerEnabled: getBooleanEnvOrDefault("BOT_SPONTANEOUS_SCHEDULER_ENABLED", true),
|
||||||
spontaneousSchedulerMinIntervalMs: parseInt(getEnvOrDefault("BOT_SPONTANEOUS_MIN_INTERVAL_MS", String(45 * 60 * 1000))),
|
spontaneousSchedulerMinIntervalMs: parseInt(getEnvOrDefault("BOT_SPONTANEOUS_MIN_INTERVAL_MS", String(2 * 24 * 60 * 60 * 1000))), // 2 days
|
||||||
spontaneousSchedulerMaxIntervalMs: parseInt(getEnvOrDefault("BOT_SPONTANEOUS_MAX_INTERVAL_MS", String(180 * 60 * 1000))),
|
spontaneousSchedulerMaxIntervalMs: parseInt(getEnvOrDefault("BOT_SPONTANEOUS_MAX_INTERVAL_MS", String(7 * 24 * 60 * 60 * 1000))), // 7 days
|
||||||
},
|
},
|
||||||
web: {
|
web: {
|
||||||
port: parseInt(getEnvOrDefault("WEB_PORT", "3000")),
|
port: parseInt(getEnvOrDefault("WEB_PORT", "3000")),
|
||||||
|
|||||||
1
src/database/drizzle/0007_peaceful_juggernaut.sql
Normal file
1
src/database/drizzle/0007_peaceful_juggernaut.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SELECT 1;
|
||||||
@@ -39,6 +39,11 @@ export const userRepository = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getOptedOutUserIds(): Promise<string[]> {
|
||||||
|
const results = await db.select({ id: users.id }).from(users).where(eq(users.opt_out, 1));
|
||||||
|
return results.map((r) => r.id);
|
||||||
|
},
|
||||||
|
|
||||||
async addMembership(userId: string, guildId: string): Promise<void> {
|
async addMembership(userId: string, guildId: string): Promise<void> {
|
||||||
await db
|
await db
|
||||||
.insert(membership)
|
.insert(membership)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import type { Message } from "discord.js";
|
import type { Message } from "discord.js";
|
||||||
import { config } from "../../core/config";
|
import { config } from "../../core/config";
|
||||||
import { createLogger } from "../../core/logger";
|
import { createLogger } from "../../core/logger";
|
||||||
|
import { userRepository } from "../../database";
|
||||||
|
|
||||||
const logger = createLogger("Features:Mentions");
|
const logger = createLogger("Features:Mentions");
|
||||||
|
|
||||||
@@ -22,8 +23,9 @@ export async function getRandomMemberMention(
|
|||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const members = await message.guild.members.fetch({ limit: 100 });
|
const members = await message.guild.members.fetch({ limit: 100 });
|
||||||
|
const optedOutUsers = await userRepository.getOptedOutUserIds();
|
||||||
|
|
||||||
const excludedSet = new Set(excludedUserIds);
|
const excludedSet = new Set([...excludedUserIds, ...optedOutUsers]);
|
||||||
const validMembers = members.filter(
|
const validMembers = members.filter(
|
||||||
(member) => !member.user.bot && !excludedSet.has(member.id)
|
(member) => !member.user.bot && !excludedSet.has(member.id)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,16 +7,18 @@ import { eq } from "drizzle-orm";
|
|||||||
import type { BotClient } from "../../core/client";
|
import type { BotClient } from "../../core/client";
|
||||||
import { config } from "../../core/config";
|
import { config } from "../../core/config";
|
||||||
import { createLogger } from "../../core/logger";
|
import { createLogger } from "../../core/logger";
|
||||||
import { db } from "../../database";
|
import { db, userRepository } from "../../database";
|
||||||
import { botOptions } from "../../database/schema";
|
import { botOptions } from "../../database/schema";
|
||||||
import { getAiService } from "../../services/ai";
|
import { getAiService } from "../../services/ai";
|
||||||
|
|
||||||
const logger = createLogger("Features:Joel:SpontaneousCron");
|
const logger = createLogger("Features:Joel:SpontaneousCron");
|
||||||
|
|
||||||
const SPONTANEOUS_TOPICS = [
|
const SPONTANEOUS_TOPICS = [
|
||||||
"a weird thought",
|
"an unhinged shower thought",
|
||||||
"a funny joke",
|
"a completely unsolicited aggressive life advice",
|
||||||
"a chaotic world update",
|
"a bizarre threat that makes no sense",
|
||||||
|
"a weirdly specific and insulting observation",
|
||||||
|
"a chaotic question that demands an answer",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
@@ -151,7 +153,10 @@ function isWritableTextChannel(channel: unknown, client: BotClient): channel is
|
|||||||
async function getRandomGuildMemberMention(guild: Guild): Promise<string | null> {
|
async function getRandomGuildMemberMention(guild: Guild): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const members = await guild.members.fetch({ limit: 200 });
|
const members = await guild.members.fetch({ limit: 200 });
|
||||||
const eligible = members.filter((member) => !member.user.bot);
|
const optedOutUsers = await userRepository.getOptedOutUserIds();
|
||||||
|
const excludedSet = new Set(optedOutUsers);
|
||||||
|
|
||||||
|
const eligible = members.filter((member) => !member.user.bot && !excludedSet.has(member.id));
|
||||||
|
|
||||||
const randomMember = eligible.random();
|
const randomMember = eligible.random();
|
||||||
if (!randomMember) {
|
if (!randomMember) {
|
||||||
@@ -172,12 +177,14 @@ async function generateNonsense(): Promise<string | null> {
|
|||||||
|
|
||||||
const response = await ai.generateResponse(
|
const response = await ai.generateResponse(
|
||||||
`Write one short Discord message containing ${topic}.`,
|
`Write one short Discord message containing ${topic}.`,
|
||||||
`You are Joel posting an unsolicited random message.
|
`You are Joel, an unhinged, aggressive, and chaotic Discord bot. You are randomly pinging a user to say something completely unsolicited.
|
||||||
Rules:
|
Rules:
|
||||||
- Mention nothing about being an AI
|
- The message should make the user think "wtf did he just say?"
|
||||||
- 1-2 sentences, max 220 characters
|
- Be aggressive, insulting, or bizarrely threatening, but in a humorous, absurd way (not extremely fucked up or genuinely disturbing).
|
||||||
- Funny or absurd tone
|
- Use your signature unhinged personality (swearing is fine, but keep it funny).
|
||||||
- No lists, no hashtags`
|
- Mention nothing about being an AI.
|
||||||
|
- 1-2 sentences, max 220 characters.
|
||||||
|
- No lists, no hashtags, no greetings.`
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = response.text.trim().replace(/\s+/g, " ");
|
const text = response.text.trim().replace(/\s+/g, " ");
|
||||||
|
|||||||
Reference in New Issue
Block a user