Update to add most functionality
This commit is contained in:
parent
5af394fbde
commit
1a1d4ccb6b
5 changed files with 242 additions and 94 deletions
75
README.md
75
README.md
|
@ -1,75 +0,0 @@
|
|||
[![GitHub](https://img.shields.io/github/license/TheShadowEevee/Konpeki-Discord-Bot)](https://github.com/TheShadowEevee/Konpeki-Discord-Bot/blob/master/LICENSE) [![GitHub repo size](https://img.shields.io/github/repo-size/TheShadowEevee/Konpeki-Discord-Bot)](https://github.com/TheShadowEevee/Konpeki-Discord-Bot) [![GitHub package.json dependency version (prod)](https://img.shields.io/github/package-json/dependency-version/TheShadowEevee/Konpeki-Discord-Bot/discord.js)](https://discord.js.org/) [![CodeFactor](https://www.codefactor.io/repository/github/theshadoweevee/konpeki-discord-bot/badge)](https://www.codefactor.io/repository/github/theshadoweevee/konpeki-discord-bot) [![Discord](https://img.shields.io/discord/1052844258173931540?label=discord)](https://discord.gg/Zt8zruXexJ)
|
||||
# Konpeki Discord Bot
|
||||
|
||||
Konpeki is a Discord Bot with varied functions. Written with discord.js, the Konpeki bot is designed to be easy to use and setup, as well as easy to change to work how you want it to. The name Konpeki comes from the Japenese word for Azure; there's no other reason for the name.
|
||||
|
||||
For general support, or to try out a live, public version of the bot, feel free to join the bot's [Discord Server](https://discord.gg/Zt8zruXexJ)!
|
||||
|
||||
The live version of the bot is `Konpeki Shiho#2603`, run by TheShadowEevee.
|
||||
|
||||
|
||||
## Adding to your server
|
||||
|
||||
You can add the public bot, Konpeki Shiho, to your server [here](https://discord.com/oauth2/authorize?client_id=812862868721631272&permissions=275146345472&scope=bot%20applications.commands)!
|
||||
- Please note that currently Konpeki Shiho is *not* a verified bot due to lack of server membership. You can help get the bot verified by adding it to your server! Konpeki Shiho runs and updates directly off of the code from this repository, while development is done primarily against the private bot Little Shiho.
|
||||
|
||||
If you are running your own version of the Konpeki bot, change the Client ID in the link to your bot's Client ID found in the Discord Developer portal. See [Running Locally](#Running-Locally) for more.
|
||||
## Running Locally
|
||||
|
||||
These instructions will get you setup with a basic setup for development.
|
||||
If you are running the bot as-is, it is recommended to use the [pm2](https://pm2.io/) process manager or a similar tool to keep the bot running unattended.
|
||||
|
||||
Before you begin, install these prerequisites:
|
||||
- [Node.js](https://nodejs.org/en/download/)
|
||||
- [Git](https://git-scm.com/downloads/)
|
||||
|
||||
#### ***Creating the bot on Discord***
|
||||
The first step to setup any Discord bot is creating one in the [Discord Developer Portal](https://discord.com/developers/).
|
||||
|
||||
- Go to the [Discord Developer Portal](https://discord.com/developers/)
|
||||
- Click New Application, name it, and press Create.
|
||||
- The Application ID on the page you are brought to is your Client ID. You will need this later.
|
||||
- Click the Bot tab on the left, then New Bot.
|
||||
- If you get an error saying too many users have this username, rename your application to something more unique.
|
||||
- Copy your bot's token somewhere *safe*.
|
||||
- Do not share this! This will give anyone access to your bot. Reset it immediatly if it becomes public.
|
||||
- If you lose the token, you will have to reset it and get a new one.
|
||||
|
||||
#### ***Configuring the bot***
|
||||
Once you have created a bot, you need to configure it.
|
||||
|
||||
- Clone the repositiory and move into the folder.
|
||||
```bash
|
||||
git clone https://github.com/TheShadowEevee/Konpeki-Discord-Bot
|
||||
cd Konpeki-Discord-Bot
|
||||
```
|
||||
- Copy `config.json.template` and name the new file `config.json`.
|
||||
- Fill in the `config.json` file with your bot's token, client id, and your username.
|
||||
|
||||
#### ***Running the bot***
|
||||
Once your bot is created, run the below commands to clone and run the bot.
|
||||
|
||||
```bash
|
||||
# Install NPM dependencies
|
||||
npm install
|
||||
|
||||
# Setup slash commands
|
||||
node deploy-commands.js
|
||||
|
||||
# Run the bot
|
||||
node index.js
|
||||
```
|
||||
|
||||
#### ***Updating the bot***
|
||||
Update the bot periodically to ensure your up-to-date.
|
||||
|
||||
If you're updating from Github:
|
||||
- Run `git pull` from the bot's folder
|
||||
- This will get the lastest updates from Github
|
||||
- Run `node delete-commands.js && node deploy-commands.js`
|
||||
- This will recreate all your slash commands to ensure they are up-to-date
|
||||
|
||||
If you're updating based on changes you made:
|
||||
- You only need to do this if you have created or deleted a file from the `commands` folder
|
||||
- Run `node delete-commands.js && node deploy-commands.js`
|
||||
- This will recreate all your slash commands to ensure they are up-to-date
|
16
TODO.md
16
TODO.md
|
@ -1,17 +1,17 @@
|
|||
TODO List for feature completeness
|
||||
|
||||
P0:
|
||||
- [ ] Check for Media in a message
|
||||
- [ ] Regex Parse Git Commits
|
||||
- [ ] Github top priority
|
||||
- [ ] Gitea/Forgejo second
|
||||
- [x] Check for Media in a message
|
||||
- [x] Regex Parse Git Commits
|
||||
- [x] Github top priority
|
||||
- [x] Gitea/Forgejo second
|
||||
- [ ] Any others third
|
||||
- [ ] Check for reactions on a message
|
||||
- [ ] Start / End Dates
|
||||
- [x] Check for reactions on a message
|
||||
- [x] Start / End Dates
|
||||
|
||||
P1:
|
||||
- [ ] Check if in a thread
|
||||
- [ ] Allow reaction to be chosen in the command rather than hard coded
|
||||
- [x] Check if in a thread
|
||||
- [x] Allow reaction to be chosen in the command rather than hard coded
|
||||
|
||||
P2:
|
||||
- [ ] Calculate Days, Highest Streak
|
|
@ -4,24 +4,201 @@
|
|||
*/
|
||||
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||
const { convertSnowflakeToDate, convertDateToSnowflake } = require('../utils/convert.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('commit-count')
|
||||
.setDescription('Counts Commits'),
|
||||
.setDescription('Counts Commits')
|
||||
// .addStringOption(option =>
|
||||
// option.setName('emoji')
|
||||
// .setDescription('Optionally choose an emoji for verified messages'))
|
||||
// .addMentionableOption(option =>
|
||||
// option.setName('role')
|
||||
// .setDescription('Choose a role for those that can react to verify'))
|
||||
.addStringOption(option =>
|
||||
option.setName('start')
|
||||
.setDescription('Optionally specify a start date (MM/DD/YYYY preferred)'))
|
||||
.addStringOption(option =>
|
||||
option.setName('end')
|
||||
.setDescription('Optionally specify a end date (MM/DD/YYYY preferred)')),
|
||||
async execute(interaction) {
|
||||
if (interaction.inGuild() && interaction.channel.isThread()) {
|
||||
const threadStarterMessage = await interaction.channel.fetchStarterMessage();
|
||||
const emojiCheck = interaction.options.getString('emoji') ?? 'gh_green_square';
|
||||
const roleCheck = interaction.options.getString('role') ?? '1232803664150659133';
|
||||
const startDate = new Date(interaction.options.getString('start-date') ?? threadStarterMessage.createdTimestamp);
|
||||
const endDate = new Date(interaction.options.getString('end-date') ?? interaction.createdTimestamp);
|
||||
|
||||
const exampleEmbed = new EmbedBuilder()
|
||||
.setColor(interaction.member.displayHexColor)
|
||||
.setTitle('Commits')
|
||||
.setDescription('By Day (Method)')
|
||||
.addFields(
|
||||
{ name: 'Valid Commits', value: 'December 20th (Media)\nDecember 21st (Commit URL)\nDecember 23rd (Reaction)' },
|
||||
await interaction.deferReply();
|
||||
|
||||
{ name: 'Missed Days', value: 'December 22nd' },
|
||||
{ name: 'Organizers:', value: 'Please react with :gh_green_square: to verify missing days!' },
|
||||
// Fetch Messages
|
||||
const sum_messages = [];
|
||||
let first_id = convertDateToSnowflake(startDate);
|
||||
const messageMap = new Map();
|
||||
let continueFetching = true;
|
||||
|
||||
while (continueFetching) {
|
||||
const options = { limit: 100 };
|
||||
options.after = first_id;
|
||||
|
||||
try {
|
||||
const messages = await interaction.channel.messages.fetch(options);
|
||||
sum_messages.push(...messages.values());
|
||||
|
||||
for (const [snowflake, message] of messages) {
|
||||
if (message.author.id === threadStarterMessage.member.id) {
|
||||
const messageDate = convertSnowflakeToDate(snowflake);
|
||||
let monthName = '';
|
||||
|
||||
// Assuming only Dec, Jan for now, can be expanded later
|
||||
if (messageDate.getMonth() === 11) {
|
||||
monthName = 'Dec.';
|
||||
}
|
||||
else if (messageDate.getMonth() === 0) {
|
||||
monthName = 'Jan.';
|
||||
}
|
||||
|
||||
const mapKey = monthName + ' ' + messageDate.getDate().toString();
|
||||
|
||||
if (messageMap.has(mapKey)) {
|
||||
const mapValue = messageMap.get(mapKey);
|
||||
mapValue.push(snowflake);
|
||||
messageMap.set(mapKey, mapValue);
|
||||
}
|
||||
else {
|
||||
const mapValue = [snowflake];
|
||||
messageMap.set(mapKey, mapValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
first_id = messages.first().id;
|
||||
|
||||
if (messages.size < 100) {
|
||||
continueFetching = false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error fetching messages: ', error);
|
||||
continueFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start Commit Checking
|
||||
let validString = '';
|
||||
let invalidString = '';
|
||||
const validDays = [];
|
||||
|
||||
for (const [date] of messageMap) {
|
||||
let dateVerified = false;
|
||||
let verifyReason = '';
|
||||
|
||||
const messageList = messageMap.get(date);
|
||||
for (const messageId of messageList) {
|
||||
if (dateVerified != true) {
|
||||
await interaction.channel.messages.fetch(messageId)
|
||||
.then(async message => {
|
||||
if (/http.:\/\/.*\/(commit|compare)\/.*/g.test(message.content)) {
|
||||
dateVerified = true;
|
||||
verifyReason = 'Commit URL';
|
||||
}
|
||||
else if (message.attachments.size > 0) {
|
||||
dateVerified = true;
|
||||
verifyReason = 'Attachment';
|
||||
}
|
||||
else if (message.reactions.cache.size > 0) {
|
||||
for (const reaction of message.reactions.cache.values()) {
|
||||
const emojiName = reaction._emoji.name;
|
||||
const reactionUsers = await reaction.users.fetch();
|
||||
|
||||
if (emojiName === emojiCheck) {
|
||||
for (const [userId] of reactionUsers) {
|
||||
const member = await interaction.guild.members.fetch(userId);
|
||||
if (member.roles.cache.has(roleCheck)) {
|
||||
dateVerified = true;
|
||||
verifyReason = 'Reaction';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
if (dateVerified) {
|
||||
validString += `${date}: ${verifyReason}\n`;
|
||||
validDays.push(date);
|
||||
}
|
||||
}
|
||||
|
||||
const resultsEmbed = {
|
||||
title: 'Commits',
|
||||
fields: [
|
||||
{
|
||||
name: 'Valid Commits',
|
||||
value: validString,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const allDays = [];
|
||||
|
||||
let currentDate = startDate;
|
||||
while (currentDate <= endDate) {
|
||||
allDays.push(new Date(currentDate));
|
||||
currentDate = currentDate.setDate(currentDate.getDate + 1);
|
||||
}
|
||||
|
||||
for (const day of allDays) {
|
||||
let monthName = '';
|
||||
// Assuming only Dec, Jan for ease at the moment. Can be changed later
|
||||
if (day.getMonth() == 11) {
|
||||
monthName = 'Dec.';
|
||||
}
|
||||
else if (day.getMonth() == 0) {
|
||||
monthName = 'Jan.';
|
||||
}
|
||||
|
||||
const formattedDay = monthName + ' ' + day.getDate().toString();
|
||||
|
||||
if (!validDays.includes(formattedDay)) {
|
||||
invalidString += `${formattedDay}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidString != '') {
|
||||
resultsEmbed.fields.push(
|
||||
{
|
||||
name: 'Missed Days',
|
||||
value: invalidString,
|
||||
},
|
||||
);
|
||||
|
||||
resultsEmbed.footer =
|
||||
{
|
||||
text: `Organizers: Please check the above dates and react with ${emojiCheck} on any missed commits!`,
|
||||
};
|
||||
}
|
||||
|
||||
resultsEmbed.fields.push(
|
||||
{
|
||||
name: 'Total Commit Count',
|
||||
value: validDays.length,
|
||||
},
|
||||
);
|
||||
|
||||
await interaction.reply({ embeds: [exampleEmbed], ephemeral: true });
|
||||
await interaction.editReply({ embeds: [resultsEmbed], ephemeral: false });
|
||||
}
|
||||
else {
|
||||
const notInThread = new EmbedBuilder()
|
||||
.setColor(interaction.member.displayHexColor)
|
||||
.setTitle('Not in a Thread')
|
||||
.setDescription('You must run this command in a Thread!');
|
||||
await interaction.reply({ embeds: [notInThread], ephemeral: true });
|
||||
}
|
||||
},
|
||||
};
|
2
index.js
2
index.js
|
@ -22,7 +22,7 @@ const { token, botOwner } = require('./config.json');
|
|||
const { activity, status } = require('./presence.json');
|
||||
|
||||
// Create a new client instance
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.MessageContent] });
|
||||
|
||||
// Setup the commands collection
|
||||
client.commands = new Collection();
|
||||
|
|
46
utils/convert.js
Normal file
46
utils/convert.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
// From https://github.com/vegeta897/snow-stamp/blob/main/src/convert.js
|
||||
// MIT LICENSE
|
||||
// Modified from source
|
||||
|
||||
export const DISCORD_EPOCH = 1420070400000
|
||||
|
||||
// Converts a snowflake ID string into a JS Date object using the provided epoch (in ms), or Discord's epoch if not provided
|
||||
export function convertSnowflakeToDate(snowflake, epoch = DISCORD_EPOCH) {
|
||||
// Convert snowflake to BigInt to extract timestamp bits
|
||||
// https://discord.com/developers/docs/reference#snowflakes
|
||||
const milliseconds = BigInt(snowflake) >> 22n
|
||||
return new Date(Number(milliseconds) + epoch)
|
||||
}
|
||||
|
||||
|
||||
export function convertDateToSnowflake(date, epoch = DISCORD_EPOCH) {
|
||||
const milliseconds = BigInt(date.valueOf()) - BigInt(epoch)
|
||||
const snowflake = (milliseconds << BigInt(22)).toString();
|
||||
|
||||
return snowflake;
|
||||
}
|
||||
|
||||
// Validates a snowflake ID string and returns a JS Date object if valid
|
||||
export function validateSnowflake(snowflake, epoch) {
|
||||
if (!Number.isInteger(+snowflake)) {
|
||||
throw new Error(
|
||||
"That doesn't look like a snowflake. Snowflakes contain only numbers."
|
||||
)
|
||||
}
|
||||
|
||||
if (snowflake < 4194304) {
|
||||
throw new Error(
|
||||
"That doesn't look like a snowflake. Snowflakes are much larger numbers."
|
||||
)
|
||||
}
|
||||
|
||||
const timestamp = convertSnowflakeToDate(snowflake, epoch)
|
||||
|
||||
if (Number.isNaN(timestamp.getTime())) {
|
||||
throw new Error(
|
||||
"That doesn't look like a snowflake. Snowflakes have fewer digits."
|
||||
)
|
||||
}
|
||||
|
||||
return timestamp
|
||||
}
|
Loading…
Reference in a new issue