change message with cool embed

This commit is contained in:
Keisuke Hirata 2024-10-20 03:42:47 +09:00
parent 780bd5f957
commit 161083c58f
8 changed files with 368 additions and 139 deletions

18
config.json Normal file
View File

@ -0,0 +1,18 @@
[
{
"server": "1119476423309131817",
"output_hook": "https://discord.com/api/webhooks/1297208829553020999/GpWxqlPVzGMDzBLWb5E6xc9jpd2revn5VbdOr9jgb-Cj8cwmEh6wD5943wZTCQ9jF2i8",
"output_channel": "1282388288002330654",
"channel_type": "exclude",
"channels": [
"1120639520426172447",
"1120646044594733107"
],
"emoji_type": "exclude",
"emojis": [
"👀",
"👍"
],
"count": 1
}
]

View File

@ -1,9 +1,8 @@
{
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
"run": "tsx src/index.ts",
"register": "tsx src/index.ts --register"
},
"type": "module",
"dependencies": {
@ -15,4 +14,4 @@
"tsx": "^4.19.0",
"typescript": "^5.5.4"
}
}
}

24
src/award_message.ts Normal file
View File

@ -0,0 +1,24 @@
import { EmbedBuilder, MessageReaction, PartialMessageReaction } from 'discord.js';
export default function awardEmbed(reaction: MessageReaction | PartialMessageReaction) {
function formatTime(time: Date) {
const yyyy = time.getFullYear();
const mm = time.getMonth().toString().padStart(2, '0');
const dd = time.getDate().toString().padStart(2, '0');
const hh = time.getHours().toString().padStart(2, '0');
const MM = time.getMinutes().toString().padStart(2, '0');
return `${yyyy}-${mm}-${dd} ${hh}:${MM}`;
}
return new EmbedBuilder()
.setColor(0x777777)
.setAuthor({
name: reaction.message.author?.displayName || "unknown",
iconURL: reaction.message.author?.avatarURL() || undefined,
})
.setDescription(reaction.message.content || null)
.setThumbnail(reaction.message.attachments.first()?.url || null)
.addFields({
name: `${reaction.emoji.name} x${reaction.count} `,
value: `in <#${reaction.message.channelId}> • ${formatTime(reaction.message.createdAt)}\n-# ${reaction.message.id}`,
})
}

83
src/commands.ts Normal file
View File

@ -0,0 +1,83 @@
import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from '@discordjs/builders';
const output = new SlashCommandSubcommandBuilder()
.setName('output')
.setDescription('Set the output channel')
.addChannelOption(option =>
option.setName('channel')
.setDescription('The channel to send messages to')
.setRequired(true));
const channels = new SlashCommandSubcommandBuilder()
.setName('channels')
.setDescription('Configure channel list')
.addStringOption(option =>
option.setName('add_remove')
.setDescription('add_remove')
.setRequired(true)
.addChoices(
{ name: 'Add', value: 'add' },
{ name: 'Remove', value: 'remove' }
))
.addChannelOption(option =>
option.setName('channel')
.setDescription('The channel to list')
.setRequired(true));
const channels_type = new SlashCommandSubcommandBuilder()
.setName('channels_type')
.setDescription('Set the channel list type')
.addStringOption(option =>
option.setName('channels_type')
.setDescription('include or exclude')
.setRequired(true)
.addChoices(
{ name: 'Include', value: 'include' },
{ name: 'Exclude', value: 'exclude' }
));
const emojis = new SlashCommandSubcommandBuilder()
.setName('emojis')
.setDescription('Configure emoji list')
.addStringOption(option =>
option.setName('add_remove')
.setDescription('add_remove')
.setRequired(true)
.addChoices(
{ name: 'Add', value: 'add' },
{ name: 'Remove', value: 'remove' }
))
.addStringOption(option =>
option.setName('emoji')
.setDescription('The emoji to list')
.setRequired(true));
const emojis_type = new SlashCommandSubcommandBuilder()
.setName('emojis_type')
.setDescription('Set the emoji list type')
.addStringOption(option =>
option.setName('emojis_type')
.setDescription('include or exclude')
.setRequired(true)
.addChoices(
{ name: 'Include', value: 'include' },
{ name: 'Exclude', value: 'exclude' }
));
const count = new SlashCommandSubcommandBuilder()
.setName('count')
.setDescription('Set the number of reactions to trigger')
.addIntegerOption(option =>
option.setName('count')
.setDescription('The number of reactions to trigger')
.setRequired(true));
const config = new SlashCommandBuilder()
.setName('config')
.setDescription('Configure the bot')
.addSubcommand(output)
.addSubcommand(channels)
.addSubcommand(channels_type)
.addSubcommand(emojis)
.addSubcommand(emojis_type)
.addSubcommand(count);
const commands = [config];
export default commands;

View File

@ -2,7 +2,7 @@ import fs from 'fs';
import { Entry } from './entry.ts';
export function writeConfig(entries: Entry[]) {
export async function writeConfig(entries: Entry[]) {
const data = JSON.stringify(entries, null, 2);
fs.writeFile('./config.json', data, (err) => {
if (err) {

View File

@ -1,12 +1,18 @@
export enum Type {
INCLUDE = "include",
EXCLUDE = "exclude"
}
export class Entry {
server: string;
channel: string;
emoji: string;
count: number;
constructor(server: string, channel: string, emoji: string, count: number) {
output_channel: string = "";
output_hook: string = "";
channel_type: Type = Type.EXCLUDE;
channels: string[] = [];
emoji_type: Type = Type.EXCLUDE;
emojis: string[] = [];
count: number = 3;
constructor(server: string) {
this.server = server;
this.channel = channel;
this.emoji = emoji;
this.count = count;
}
}

View File

@ -1,3 +1,24 @@
import 'dotenv/config'
const TOKEN = process.env.TOKEN;
const CLIENT_ID = process.env.CLIENT_ID;
if (!TOKEN || !CLIENT_ID) {
console.error(`No ${TOKEN ? "CLIENT_ID" : "TOKEN"} provided`);
process.exit();
}
import commands from "./commands.ts";
if (process.argv.includes("--register")) {
const rest = new REST({ version: "10" }).setToken(TOKEN);
try {
console.log("refreshing slash commands...");
await rest.put(Routes.applicationCommands(CLIENT_ID), { body: commands });
console.log("OK");
} catch (error) {
console.error(error);
}
process.exit();
}
import {
REST,
Routes,
@ -5,99 +26,170 @@ import {
Partials,
GatewayIntentBits,
Events,
TextChannel,
Webhook,
WebhookMessageCreateOptions,
WebhookClient,
} from "discord.js";
import { writeConfig, readConfig } from "./config.ts";
import 'dotenv/config'
const TOKEN = process.env.TOKEN;
const CLIENT_ID = process.env.CLIENT_ID;
if (!TOKEN) {
console.error("No token provided");
process.exit();
}
if (!CLIENT_ID) {
console.error("No client ID provided");
process.exit();
}
const rest = new REST({ version: "10" }).setToken(TOKEN);
try {
console.log("refreshing slash commands...");
await rest.put(Routes.applicationCommands(CLIENT_ID), {
body: [
{
name: "config",
description: "Replies with the channel ID",
},
],
});
console.log("OK");
} catch (error) {
console.error(error);
}
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.MessageContent,
],
partials: [Partials.Message, Partials.Channel, Partials.Reaction],
});
client.on("ready", () => {
if (!client.user) return;
if (!client.user) {
console.error("Failed to login");
process.exit();
}
console.log(`Logged in as ${client.user.tag}!`);
client.user.setActivity("for reactions");
});
import { Entry } from "./entry.ts";
import { Entry, Type } from "./entry.ts";
const entries: Entry[] = readConfig();
writeConfig(entries);
import modal from "./modal/configure.ts";
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.isChatInputCommand()) {
if (interaction.commandName === "config") {
const subcommand = interaction.options.getSubcommand(true);
if (subcommand === "output") {
const channel = interaction.options.getChannel("channel", true);
try {
await interaction.deferReply({ ephemeral: true, });
let entry = entries.find((entry) => entry.server === interaction.guildId);
if (!entry) {
entry = new Entry(interaction.guildId!);
entries.push(entry);
}
// entry.output_hook = (await registWebhook(channel as TextChannel));
entry.output_channel = channel.id;
writeConfig(entries);
} catch (error) {
console.error(error);
} finally {
await interaction.followUp({
content: `Set output to <#${channel.id}>`,
});
}
} else if (subcommand === "channels") {
const channel = interaction.options.getChannel("channel", true);
const mode = interaction.options.getString("add_remove", true);
try {
await interaction.deferReply({ ephemeral: true });
let entry = entries.find((entry) => entry.server === interaction.guildId);
if (!entry) {
entry = new Entry(interaction.guildId!);
entries.push(entry);
}
if (mode === "add") entry.channels.push(channel.id);
else entry.channels = entry.channels.filter((c) => c !== channel.id);
writeConfig(entries);
} catch (error) {
console.error(error);
} finally {
await interaction.followUp({
content: `Set to watch for reactions in <#${channel.id}>`,
});
}
} else if (subcommand === "channels_type") {
const channel_type = interaction.options.getString("channels_type", true);
try {
await interaction.deferReply({ ephemeral: true });
if (interaction.commandName === "config") {
await interaction.showModal(modal);
}
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isModalSubmit()) return;
if (interaction.customId === "config") {
const emoji = interaction.fields.getTextInputValue("emoji");
const count = parseInt(interaction.fields.getTextInputValue("count"));
const channelId = interaction.fields.getTextInputValue("channelId");
const serverId = interaction.guildId;
if (!emoji || !channelId) {
await interaction.reply({
content: "Please fill out all fields",
ephemeral: true,
});
return;
let entry = entries.find((entry) => entry.server === interaction.guildId);
if (!entry) {
entry = new Entry(interaction.guildId!);
entries.push(entry);
}
entry.channel_type = channel_type as Type;
writeConfig(entries);
} catch (error) {
console.error(error);
} finally {
await interaction.followUp({
content: `channel list type set to ${channel_type}`,
ephemeral: true,
});
}
} else if (subcommand === "emojis") {
const emoji = interaction.options.getString("emoji", true);
const mode = interaction.options.getString("add_remove", true);
try {
await interaction.deferReply({ ephemeral: true });
let entry = entries.find(
(entry) => entry.server === interaction.guildId
);
if (!entry) {
entry = new Entry(interaction.guildId!);
entries.push(entry);
}
if (mode === "add") entry.emojis.push(emoji);
else entry.emojis = entry.emojis.filter((e) => e !== emoji);
await writeConfig(entries);
} catch (error) {
console.error(error);
} finally {
await interaction.followUp({
content: `Set to watch for ${emoji}`,
ephemeral: true,
});
}
} else if (subcommand === "emojis_type") {
const emoji_type = interaction.options.getString("emojis_type", true);
try {
await interaction.deferReply({ ephemeral: true });
let entry = entries.find(
(entry) => entry.server === interaction.guildId
);
if (!entry) {
entry = new Entry(interaction.guildId!);
entries.push(entry);
}
entry.emoji_type = emoji_type as Type;
writeConfig(entries);
} catch (error) {
console.error(error);
} finally {
await interaction.followUp({
content: `emoji list type set to ${emoji_type}`,
ephemeral: true,
});
}
} else if (subcommand === "count") {
const count = interaction.options.getInteger("count", true);
try {
await interaction.deferReply({ ephemeral: true });
let entry = entries.find(
(entry) => entry.server === interaction.guildId
);
if (!entry) {
entry = new Entry(interaction.guildId!);
entries.push(entry);
}
entry.count = count;
await writeConfig(entries);
} catch (error) {
console.error(error);
} finally {
await interaction.followUp({
content: `The number needed to trigger is now ${count}`,
ephemeral: true,
});
}
}
}
if (serverId === null) {
return;
}
console.log(`Set entry: ${serverId} -> ${channelId} (${emoji})`);
const entry = entries.find((entry) => entry.server === serverId);
if (entry) {
entry.channel = channelId;
entry.emoji = emoji;
entry.count = count;
} else {
entries.push(new Entry(serverId, channelId, emoji, count));
}
writeConfig(entries);
await interaction.reply({
content: `Set to send messages to <#${channelId}>, watching for ${emoji}`,
ephemeral: true,
});
}
});
import awardEmbed from "./award_message.ts";
client.on(Events.MessageReactionAdd, async (reaction, user) => {
if (reaction.partial) {
try {
@ -109,25 +201,74 @@ client.on(Events.MessageReactionAdd, async (reaction, user) => {
}
if (!reaction.message.guild) return;
const entry = entries.find(
(entry) => entry.server === reaction.message.guildId
);
if (
!entry ||
reaction.emoji.name !== entry.emoji ||
reaction.count === null ||
reaction.count < entry.count
)
return;
const entry = entries.find((entry) => entry.server === reaction.message.guildId);
if (entry === undefined) return;
if ((entry.channel_type === Type.EXCLUDE) === entry.channels.includes(reaction.message.channelId)) return;
if ((entry.emoji_type === Type.EXCLUDE) === entry.emojis.includes(`${reaction.emoji.name}`)) return;
const channel = await client.channels.fetch(entry.channel);
if (channel === null || !channel.isTextBased() || channel.isDMBased()) return;
console.log(`Reaction added: ${reaction.emoji.name} in ${reaction.message.guild.channels.cache.get(reaction.message.channelId)?.name}`);
if (reaction.count === null || reaction.count < entry.count) return;
if (reaction.count === entry.count) {
await channel.send(
`🎉 ${reaction.count} people have reacted with ${entry.emoji}! 🎉`
);
const attachments = reaction.message.attachments.filter((attachment) => !(!attachment.height && !attachment.width))
const channel = reaction.message.guild.channels.cache.get(entry.output_channel) as TextChannel;
if (!channel) return;
channel.send({
embeds: [awardEmbed(reaction)]
});
// sendWebhookMessage(entry.output_hook, {
// username: `${reaction.message.author?.displayName}`,
// avatarURL: reaction.message.author?.avatarURL() || "",
// content: reaction.message.content?.replace(/<@!?(\d+)>/g, (match) => {
// const id = match.match(/\d+/)?.[0];
// if (!id) return match;
// const member = reaction.message.guild?.members.cache.get(id);
// return `**@ ${member?.displayName}**` || match;
// }) || "",
// files: attachments.map((attachment) => attachment.url),
// embeds: [awardEmbed(reaction)],
// });
} else if (reaction.count > entry.count) {
}
});
client.login(TOKEN);
// async function registWebhook(channel: TextChannel): Promise<string> {
// const existing = entries.find((entry) => entry.server === channel.guildId)?.output_hook;
// if (existing) {
// try {
// const webhook = new WebhookClient({ url: existing });
// webhook.edit({
// name: 'Reaction Award Bot',
// channel: channel.id,
// });
// return webhook.url;
// } catch (error) {
// console.error("failed to edit existing webhook");
// }
// } else {
// try {
// const webhook = await channel.createWebhook({
// name: 'Reaction Award Bot',
// })
// return webhook.url
// } catch (error) {
// console.error("failed to create webhook");
// }
// }
// return "";
// }
// async function sendWebhookMessage(url: string, options: WebhookMessageCreateOptions) {
// try {
// const webhook = new WebhookClient({ url });
// if (!webhook) return;
// await webhook.send(options);
// } catch (error) {
// console.error(error);
// }
// }

View File

@ -1,42 +0,0 @@
import {
ModalBuilder,
TextInputBuilder,
ActionRowBuilder,
TextInputStyle,
ModalActionRowComponentBuilder,
} from 'discord.js';
const modal = new ModalBuilder()
.setCustomId('config')
.setTitle('Configuration');
const emojiInput = new TextInputBuilder()
.setCustomId('emoji')
.setLabel('Emoji')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Enter the emoji')
.setValue('⭐')
.setRequired(true);
const countInput = new TextInputBuilder()
.setCustomId('count')
.setLabel('Count')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Enter the count')
.setValue('5')
.setRequired(true);
const channelIdInput = new TextInputBuilder()
.setCustomId('channelId')
.setLabel('Channel ID')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Enter the channel ID')
.setRequired(true);
const first = new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(emojiInput)
const second = new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(countInput)
const third = new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(channelIdInput)
modal.addComponents(first, second, third);
export default modal;