-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Akhil Pillai
committed
Nov 18, 2024
1 parent
0428ccf
commit b5118dd
Showing
32 changed files
with
5,018 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default { | ||
extends: ['@commitlint/config-conventional'] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.commitlintrc.ts | ||
.eslint* | ||
.git* | ||
.npm* | ||
.prettier* | ||
knip.ts | ||
README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
botfiles | ||
.devcontainer | ||
dist | ||
**/*.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
{ | ||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], | ||
"parser": "@typescript-eslint/parser", | ||
"parserOptions": { | ||
"ecmaVersion": "latest", | ||
"sourceType": "module" | ||
}, | ||
"plugins": ["@typescript-eslint"], | ||
"root": true, | ||
"rules": { | ||
"@typescript-eslint/no-unused-vars": "error", | ||
"camelcase": "error", | ||
"capitalized-comments": "off", | ||
"comma-dangle": "error", | ||
"eol-last": "error", | ||
"func-call-spacing": "error", | ||
"getter-return": "error", | ||
"indent": "off", | ||
"new-parens": "error", | ||
"no-alert": "error", | ||
"no-console": "error", | ||
"no-const-assign": "error", | ||
"no-debugger": "error", | ||
"no-delete-var": "error", | ||
"no-dupe-args": "error", | ||
"no-dupe-keys": "error", | ||
"no-duplicate-case": "error", | ||
"no-duplicate-imports": "error", | ||
"no-eq-null": "error", | ||
"no-eval": "error", | ||
"no-fallthrough": "error", | ||
"no-implied-eval": "error", | ||
"no-invalid-regexp": "error", | ||
"no-invalid-this": "error", | ||
"no-irregular-whitespace": "error", | ||
"no-lonely-if": "error", | ||
"no-multi-str": "error", | ||
"no-param-reassign": "error", | ||
"no-redeclare": "error", | ||
"no-self-assign": "error", | ||
"no-self-compare": "error", | ||
"no-trailing-spaces": "error", | ||
"no-unneeded-ternary": "error", | ||
"no-unreachable": "error", | ||
"no-unused-expressions": "error", | ||
"no-unused-vars": "off", | ||
"no-var": "error", | ||
"no-with": "error", | ||
"object-curly-newline": "error", | ||
"prefer-arrow-callback": "error", | ||
"prefer-const": "error", | ||
"prefer-promise-reject-errors": "error", | ||
"prefer-template": "error", | ||
"semi-spacing": "error", | ||
"strict": "error", | ||
"use-isnan": "error" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Build and push Docker image | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
branches: | ||
- 'gitmaster' | ||
|
||
jobs: | ||
docker: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Set up QEMU | ||
uses: docker/setup-qemu-action@v3 | ||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
- name: Login to Docker Hub | ||
uses: docker/login-action@v3 | ||
with: | ||
username: ${{ secrets.DOCKER_USERNAME }} | ||
password: ${{ secrets.DOCKER_TOKEN }} | ||
- name: Build and push | ||
uses: docker/build-push-action@v5 | ||
with: | ||
push: true | ||
tags: akpi816218/transconlang-discord:latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
node_modules | ||
|
||
*.env | ||
*..env.local | ||
|
||
*.db.json | ||
*.tmp.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.tmp.db.json | ||
*.cache.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"singleQuote": true, | ||
"useTabs": true, | ||
"tabWidth": 2, | ||
"trailingComma": "none", | ||
"arrowParens": "avoid", | ||
"bracketSpacing": true, | ||
"semi": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Start your image with a node base image | ||
FROM node:latest | ||
|
||
# The /app directory should act as the main application directory | ||
WORKDIR /app | ||
|
||
# Copy the app package and package-lock.json file | ||
COPY package*.json ./ | ||
|
||
# Copy local directories to the current local directory of our docker image (/app) | ||
COPY ./src ./src | ||
COPY ./botfiles ./botfiles | ||
COPY ./scripts ./scripts | ||
|
||
# Install node packages, install serve, build the app, and remove dependencies at the end | ||
RUN npm ci | ||
|
||
EXPOSE 8000 | ||
|
||
# Start the app using serve command | ||
CMD [ "npm", "start" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
# dictionary-discord-bot | ||
|
||
The official dictionary Discord bot for Kumilinwa, the trans constructed language! :3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { KnipConfig } from 'knip'; | ||
|
||
const config: KnipConfig = { | ||
entry: ['src/index.ts', 'src/{commands,events}/*.ts', 'scripts/*.ts'], | ||
project: ['src/**/*.ts'] | ||
}; | ||
|
||
export default config; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"author": "Akhil Pillai", | ||
"dependencies": { | ||
"discord.js": "^14.16.3", | ||
"dotenv": "^16.4.5", | ||
"express": "^4.21.1", | ||
"helmet": "^8.0.0", | ||
"jsoning": "^1.0.1", | ||
"node-schedule": "^2.1.1", | ||
"pino": "^9.5.0", | ||
"pino-pretty": "^13.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/dotenv": "^8.2.3", | ||
"@types/express": "^5.0.0", | ||
"@types/node-schedule": "^2.1.7", | ||
"@typescript-eslint/eslint-plugin": "^8.14.0", | ||
"@typescript-eslint/parser": "^8.14.0", | ||
"knip": "^5.37.1", | ||
"npm-check-updates": "^17.1.11", | ||
"prettier": "^3.3.3", | ||
"tsx": "^4.19.2", | ||
"typescript": "^5.6.3" | ||
}, | ||
"main": "src/index.ts", | ||
"type": "module", | ||
"private": true, | ||
"scripts": { | ||
"build-commands": "tsx scripts/buildCommands.ts", | ||
"build-image": "docker build -t akpi816218/transconlang-discord . && docker push akpi816218/transconlang-discord", | ||
"cache-langspec": "tsx scripts/cacheLangSpec.ts", | ||
"check": "tsc", | ||
"deploy": "npm ci && npm start", | ||
"deploy-full": "npm ci && npm urn build-commands && npm urn cache-langspec && npm start", | ||
"fmt": "prettier -w .", | ||
"knip": "knip", | ||
"lint": "eslint .", | ||
"start": "tsx src/index.ts", | ||
"up": "ncu -u && npm i" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* eslint-disable no-console */ | ||
import 'dotenv/config'; | ||
import { commandsPath, registerCommands } from './registerCommands'; | ||
import { argv } from 'process'; | ||
import { readdir } from 'fs/promises'; | ||
|
||
argv.shift(); | ||
argv.shift(); | ||
|
||
let commandFiles: string[]; | ||
if (argv.length == 0) { | ||
commandFiles = (await readdir(commandsPath)).filter(file => | ||
file.endsWith('.ts') | ||
); | ||
} else { | ||
commandFiles = (await readdir(commandsPath)).filter( | ||
file => file.endsWith('.ts') && argv.includes(file.replace('.ts', '')) | ||
); | ||
} | ||
if (commandFiles.length == 0) { | ||
console.log('No commands found'); | ||
process.exit(1); | ||
} | ||
|
||
console.log(`Registering ${commandFiles.length} commands...`); | ||
|
||
console.log( | ||
await ( | ||
await registerCommands(process.env.DISCORD_TOKEN as string, commandFiles) | ||
).getCommands() | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { LangSpecURL } from '@/config'; | ||
import { writeFile } from 'fs/promises'; | ||
import { dirname, join } from 'path'; | ||
import { fileURLToPath } from 'url'; | ||
|
||
await writeFile( | ||
join( | ||
dirname(fileURLToPath(import.meta.url)), | ||
'..', | ||
'src', | ||
'lib', | ||
'kumilinwa', | ||
'langspec.cache.json' | ||
), | ||
JSON.stringify(await fetch(LangSpecURL).then(res => res.json())) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { REST, Routes } from 'discord.js'; | ||
import { dirname, join } from 'path'; | ||
import { clientId } from '../src/config'; | ||
import { fileURLToPath } from 'url'; | ||
import { readdir } from 'fs/promises'; | ||
import Jsoning, { JSONValue } from 'jsoning'; | ||
import { Command } from '../src/lib/struct/discord/types'; | ||
|
||
const Dirname = dirname(fileURLToPath(import.meta.url)); | ||
|
||
export const commandsPath = join(Dirname, '..', 'src', 'commands'); | ||
|
||
export async function registerCommands( | ||
token: string, | ||
commandFiles?: string[] | ||
): Promise<{ | ||
data: unknown; | ||
getCommands: () => Promise<unknown>; | ||
rest: REST; | ||
}> { | ||
// eslint-disable-next-line no-param-reassign | ||
commandFiles = | ||
commandFiles ?? | ||
(await readdir(commandsPath)).filter(file => file.endsWith('.ts')); | ||
const commands = []; | ||
for (const file of commandFiles) | ||
commands.push( | ||
((await import(join(commandsPath, file))) as Command).data.toJSON() | ||
); | ||
let data: unknown; | ||
const rest = new REST().setToken(token); | ||
await rest.put(Routes.applicationCommands(clientId), { | ||
body: commands | ||
}); | ||
return { | ||
data, | ||
getCommands: async () => | ||
await rest.get(Routes.applicationCommands(clientId)), | ||
rest | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { MatchType, searchLangSpec } from '@/lib'; | ||
import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; | ||
|
||
export const data = new SlashCommandBuilder() | ||
.setName('search') | ||
.setDescription('Search for an entry in the dictionary') | ||
.addStringOption(option => | ||
option.setName('query').setDescription('The search query').setRequired(true) | ||
) | ||
.addStringOption(option => | ||
option | ||
.setName('match') | ||
.setDescription('The type of match to use (default: any)') | ||
.setRequired(false) | ||
.setChoices( | ||
{ name: 'Word', value: 'word' }, | ||
{ name: 'Meaning', value: 'meaning' } | ||
) | ||
) | ||
.addBooleanOption(option => | ||
option | ||
.setName('ephemeral') | ||
.setDescription('Whether to send an ephemeral response') | ||
.setRequired(false) | ||
); | ||
|
||
export async function execute(interaction: ChatInputCommandInteraction) { | ||
await interaction.deferReply({ | ||
ephemeral: interaction.options.getBoolean('ephemeral', false) ?? false | ||
}); | ||
const query = interaction.options.getString('query', true), | ||
match = interaction.options.getString('match', false); | ||
const results = await searchLangSpec( | ||
query, | ||
(match as MatchType) ?? undefined | ||
); | ||
const response = results.length | ||
? results.map(({ word, meaning }) => `${word}: ${meaning}`).join('\n') | ||
: 'No results found.'; | ||
await interaction.editReply( | ||
response.length > 2000 | ||
? `Whoa, that's a lot of results! Discord's char limit is set to 2000 per message. Try narrowing down your search.\nYour search query: ${query}` | ||
: response | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { | ||
ChatInputCommandInteraction, | ||
SlashCommandBuilder | ||
} from 'discord.js'; | ||
|
||
export const data = new SlashCommandBuilder() | ||
.setName('name') | ||
.setDescription('description'); | ||
|
||
export async function execute(interaction: ChatInputCommandInteraction) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { PermissionFlagsBits, PermissionsBitField } from 'discord.js'; | ||
|
||
export const clientId = '1307953747741245460'; | ||
|
||
export const DevIds = ['817214551740776479']; | ||
|
||
export const permissionsBits = new PermissionsBitField().add( | ||
PermissionFlagsBits.AddReactions, | ||
PermissionFlagsBits.EmbedLinks, | ||
PermissionFlagsBits.ReadMessageHistory, | ||
PermissionFlagsBits.SendMessages, | ||
PermissionFlagsBits.SendMessagesInThreads, | ||
PermissionFlagsBits.ViewChannel | ||
).bitfield; | ||
|
||
export const PORT = 8000; | ||
|
||
// ! change branch to main later | ||
export const LangSpecURL = | ||
'https://raw.githubusercontent.com/Transconlang/translang/refs/heads/rawspec/rawspec/0-complete.json'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
import 'dotenv/config'; | ||
import { | ||
ActivityType, | ||
Colors, | ||
EmbedBuilder, | ||
Events, | ||
OAuth2Scopes, | ||
PresenceUpdateStatus, | ||
TimestampStyles, | ||
codeBlock, | ||
time | ||
} from 'discord.js'; | ||
import { argv, stdout } from 'process'; | ||
import { dirname, join } from 'path'; | ||
import { fileURLToPath } from 'url'; | ||
import { readdir, rm, writeFile } from 'fs/promises'; | ||
import { Jsoning } from 'jsoning'; | ||
import { DevIds, LangSpecURL, permissionsBits, PORT } from './config'; | ||
import { | ||
Command, | ||
CommandClient, | ||
createServer, | ||
FullEntry, | ||
logger, | ||
Methods | ||
} from './lib'; | ||
import { scheduleJob } from 'node-schedule'; | ||
|
||
const Dirname = dirname(fileURLToPath(import.meta.url)); | ||
|
||
argv.shift(); | ||
argv.shift(); | ||
if (argv.includes('-d')) { | ||
logger.level = 'debug'; | ||
logger.debug('Debug mode enabled.'); | ||
} | ||
|
||
const StatsDB = new Jsoning('stats.tmp.db.json'); | ||
logger.debug('Created stats database.'); | ||
|
||
const client = new CommandClient({ | ||
intents: [], | ||
presence: { | ||
activities: [ | ||
{ | ||
name: 'Kumilinwa', | ||
type: ActivityType.Listening | ||
} | ||
], | ||
afk: false, | ||
status: PresenceUpdateStatus.Online | ||
} | ||
}); | ||
logger.debug('Created client instance.'); | ||
|
||
const server = createServer( | ||
{ | ||
handler: (_req, res) => | ||
res.redirect( | ||
client.generateInvite({ | ||
permissions: permissionsBits, | ||
scopes: [OAuth2Scopes.Bot] | ||
}) | ||
), | ||
method: Methods.GET, | ||
route: '/invite' | ||
}, | ||
{ | ||
handler: (_req, res) => res.redirect('/status'), | ||
method: Methods.GET, | ||
route: '/' | ||
}, | ||
{ | ||
handler: (_req, res) => res.sendStatus(client.isReady() ? 200 : 503), | ||
method: Methods.GET, | ||
route: '/status' | ||
}, | ||
{ | ||
handler: (req, res) => { | ||
if ( | ||
req.headers['content-type'] !== 'application/json' && | ||
req.headers['content-type'] != undefined | ||
) | ||
res.status(415).end(); | ||
else if (client.isReady()) | ||
res | ||
.status(200) | ||
.contentType('application/json') | ||
.send({ | ||
clientPing: client.ws.ping, | ||
clientReady: client.isReady(), | ||
commandCount: client.application!.commands.cache.size, | ||
guildCount: client.application!.approximateGuildCount, | ||
lastReady: client.readyAt.valueOf(), | ||
uptime: client.uptime | ||
}) | ||
.end(); | ||
else res.status(503).end(); | ||
}, | ||
method: Methods.GET, | ||
route: '/info' | ||
}, | ||
{ | ||
handler: (req, res) => { | ||
if ( | ||
req.headers['content-type'] !== 'application/json' && | ||
req.headers['content-type'] != undefined | ||
) | ||
res.status(415).end(); | ||
else if (client.isReady()) | ||
res | ||
.status(200) | ||
.contentType('application/json') | ||
.send({ | ||
commands: client.commands.map(command => ({ | ||
data: command.data.toJSON(), | ||
help: command.help?.toJSON() | ||
})), | ||
timestamp: Date.now() | ||
}) | ||
.end(); | ||
else res.status(503).end(); | ||
}, | ||
method: Methods.GET, | ||
route: '/commands' | ||
} | ||
); | ||
logger.debug('Created server instance.'); | ||
|
||
const commandsPath = join(Dirname, 'commands'); | ||
const commandFiles = (await readdir(commandsPath)).filter(file => | ||
file.endsWith('.ts') | ||
); | ||
logger.debug('Loaded command files.'); | ||
const cmndb = new Jsoning('cmnds.tmp.db.json'); | ||
for (const file of commandFiles) { | ||
const filePath = join(commandsPath, file); | ||
logger.debug(`Loading command ${filePath}`); | ||
const command: Command = await import(filePath); | ||
client.commands.set(command.data.name, command); | ||
if (command.help) | ||
await cmndb.set( | ||
command.data.name, | ||
// @ts-expect-error types | ||
command.help.toJSON() | ||
); | ||
} | ||
client.commands.freeze(); | ||
logger.info('Loaded commands.'); | ||
|
||
/** | ||
const eventsPath = join(Dirname, 'events'); | ||
const eventFiles = readdirSync(eventsPath).filter(file => file.endsWith('.ts')); | ||
for (const file of eventFiles) { | ||
const filePath = join(eventsPath, file); | ||
const event: Event = await import(filePath); | ||
if (event.once) | ||
client.once(event.name, async (...args) => await event.execute(...args)); | ||
else client.on(event.name, async (...args) => await event.execute(...args)); | ||
} | ||
logger.debug('Loaded events.'); | ||
*/ | ||
|
||
client | ||
.on(Events.ClientReady, () => logger.info('Client#ready')) | ||
.on(Events.InteractionCreate, async interaction => { | ||
if (interaction.user.bot) return; | ||
if (interaction.isChatInputCommand()) { | ||
const command = client.commands.get(interaction.commandName); | ||
if (!command) { | ||
await interaction.reply('Internal error: Command not found'); | ||
return; | ||
} | ||
try { | ||
await command.execute(interaction); | ||
} catch (e) { | ||
logger.error(e); | ||
if (interaction.replied || interaction.deferred) { | ||
await interaction.editReply( | ||
'There was an error while running this command.' | ||
); | ||
} else { | ||
await interaction.reply({ | ||
content: 'There was an error while running this command.', | ||
ephemeral: true | ||
}); | ||
} | ||
} | ||
} | ||
}) | ||
.on(Events.Debug, m => logger.debug(m)) | ||
.on(Events.Error, m => { | ||
logger.error(m); | ||
sendError(m); | ||
}) | ||
.on(Events.Warn, m => logger.warn(m)); | ||
logger.debug('Set up client events.'); | ||
|
||
await refreshCachedLangSpec(); | ||
await StatsDB.set('langSpecCacheAge', Date.now()); | ||
logger.debug('Cached language specification.'); | ||
|
||
await client | ||
.login(process.env.DISCORD_TOKEN) | ||
.then(() => logger.info('Logged in.')); | ||
|
||
process.on('SIGINT', async () => { | ||
sendError(new Error('SIGINT received.')); | ||
await client.destroy(); | ||
stdout.write('\n'); | ||
logger.info('Destroyed Client.'); | ||
await rm(join(Dirname, '..', 'stats.tmp.db.json')); | ||
await rm(join(Dirname, '..', 'cmnds.tmp.db.json')); | ||
logger.info('Removed temporary databases.'); | ||
process.exit(0); | ||
}); | ||
|
||
server.listen(process.env.PORT ?? PORT); | ||
logger.info(`Listening to HTTP server on port ${process.env.PORT ?? PORT}.`); | ||
|
||
process.on('uncaughtException', sendError); | ||
process.on('unhandledRejection', sendError); | ||
logger.debug('Set up error handling.'); | ||
|
||
// refresh cached kumilinwa spec every hour | ||
scheduleJob('0 * * * *', refreshCachedLangSpec); | ||
|
||
logger.info('Process setup complete.'); | ||
|
||
async function sendError(e: Error) { | ||
for (const devId of DevIds) { | ||
client.users.fetch(devId).then(user => { | ||
const date = new Date(); | ||
user.send({ | ||
embeds: [ | ||
new EmbedBuilder() | ||
.setTitle('Error Log') | ||
.setDescription(e.message) | ||
.addFields({ name: 'Stack Trace', value: codeBlock(e.stack ?? '') }) | ||
.addFields({ | ||
name: 'ISO 8601 Timestamp', | ||
value: date.toISOString() | ||
}) | ||
.addFields({ | ||
name: 'Localized DateTime', | ||
value: time(date, TimestampStyles.LongDateTime) | ||
}) | ||
.setColor(Colors.Red) | ||
.setTimestamp() | ||
] | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
async function refreshCachedLangSpec() { | ||
const data = (await fetch(LangSpecURL) | ||
.then(res => res.json()) | ||
.catch(e => sendError)) as FullEntry[]; | ||
await writeFile( | ||
join(Dirname, 'lib', 'kumilinwa', 'langspec.cache.json'), | ||
JSON.stringify(data) | ||
); | ||
await StatsDB.set('langSpecCacheAge', Date.now()); | ||
logger.debug('Cached language specification.'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export * from './misc/logger'; | ||
export * from './misc/server'; | ||
export * from './struct/discord/types'; | ||
export * from './struct/discord/Extend'; | ||
export * from './struct/CommandHelpEntry'; | ||
|
||
export * from './kumilinwa'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { FullEntry } from './types'; | ||
|
||
export const CompleteLangSpec = (await import( | ||
'./langspec.cache.json' | ||
)).default as FullEntry[]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './types'; | ||
export * from './import'; | ||
export * from './search'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"word":"mi","meaning":"cute","type":"adjective"},{"word":"wusita","meaning":"good","type":"adjective"},{"word":"disita","meaning":"bad","type":"adjective"},{"word":"nen","meaning":"not","type":"adjective"},{"word":"toransu","meaning":"different, other","type":"adjective"},{"word":"somo","meaning":"sleepy","type":"adjective"},{"word":"sekiso","meaning":"sexy, fuckable","type":"adjective"},{"word":"deja","meaning":"active","type":"adjective"},{"word":"gara","meaning":"all","type":"adjective"},{"word":"coko","meaning":"some","type":"adjective"},{"word":"boro","meaning":"any","type":"adjective"},{"word":"gurati","meaning":"negative freedom, free (price)","type":"adjective"},{"word":"lifeta","meaning":"positive freedom, liberated","type":"adjective"},{"word":"jala","meaning":"able, capable of","type":"adjective"},{"word":"mire","meaning":"mid, average","type":"adjective"},{"word":"misu","meaning":"liquid, fluid","type":"adjective"},{"word":"ten","meaning":"solid, rigid","type":"adjective"},{"word":"lufi","meaning":"gaseous","type":"adjective"},{"word":"nen","meaning":"no","type":"adverb"},{"word":"con","meaning":"soon","type":"adverb"},{"word":"huba","meaning":"overly","type":"adverb"},{"word":"sun","meaning":"why","type":"adverb"},{"word":"saci","meaning":"how","type":"adverb"},{"word":"tahi","meaning":"now","type":"adverb"},{"word":"kahi","meaning":"then","type":"adverb"},{"word":"sohi","meaning":"when","type":"adverb"},{"word":"tawa","meaning":"here","type":"adverb"},{"word":"kawa","meaning":"there","type":"adverb"},{"word":"sowa","meaning":"where","type":"adverb"},{"word":"pum","meaning":"and","type":"conjunction"},{"word":"ron","meaning":"or","type":"conjunction"},{"word":"no","meaning":"but","type":"conjunction"},{"word":"nen","meaning":"no","type":"interjection"},{"word":"henlo","meaning":"hello","type":"interjection"},{"word":"na","meaning":"hi","type":"interjection"},{"word":"konba","meaning":"goodbye","type":"interjection"},{"word":"3","meaning":"cute!","type":"interjection"},{"word":"ke","meaning":"ok","type":"interjection"},{"word":"biwe","meaning":"thanks","type":"interjection"},{"word":"hihi","meaning":"laughter","type":"interjection"},{"word":"kase","meaning":"cat","type":"noun"},{"word":"hun","meaning":"dog","type":"noun"},{"word":"luma","meaning":"person","type":"noun"},{"word":"mawa","meaning":"kiss","type":"noun"},{"word":"lanmi","meaning":"food","type":"noun"},{"word":"topi","meaning":"god, deity","type":"noun"},{"word":"wantu","meaning":"number","type":"noun"},{"word":"cosi","meaning":"choice, vote","type":"noun"},{"word":"falo","meaning":"speech, discussion, chat","type":"noun"},{"word":"linwa","meaning":"language","type":"noun"},{"word":"keso","meaning":"cheese","type":"noun"},{"word":"sekiso","meaning":"sex, act of fucking","type":"noun"},{"word":"lumaba","meaning":"friend","type":"noun"},{"word":"deja","meaning":"action","type":"noun"},{"word":"gade","meaning":"guess","type":"noun"},{"word":"tempu","meaning":"time","type":"noun"},{"word":"cikondu","meaning":"second","type":"noun"},{"word":"minedu","meaning":"minute","type":"noun"},{"word":"haweru","meaning":"hour","type":"noun"},{"word":"dawu","meaning":"day","type":"noun"},{"word":"natu","meaning":"night","type":"noun"},{"word":"murinu","meaning":"morning","type":"noun"},{"word":"ebeninku","meaning":"evening","type":"noun"},{"word":"monadu","meaning":"month","type":"noun"},{"word":"siconu","meaning":"season","type":"noun"},{"word":"waru","meaning":"year","type":"noun"},{"word":"diwaru","meaning":"decade","type":"noun"},{"word":"huwaru","meaning":"century","type":"noun"},{"word":"newaru","meaning":"millenium","type":"noun"},{"word":"nem","meaning":"mind","type":"noun"},{"word":"pen","meaning":"movement","type":"noun"},{"word":"nepen","meaning":"feeling","type":"noun"},{"word":"gurati","meaning":"negative freedom","type":"noun"},{"word":"lifeta","meaning":"positive freedom","type":"noun"},{"word":"kumi","meaning":"trans(gender)ness","type":"noun"},{"word":"jala","meaning":"ability","type":"noun"},{"word":"mire","meaning":"half, middle","type":"noun"},{"word":"misu","meaning":"liquid","type":"noun"},{"word":"lufi","meaning":"gas","type":"noun"},{"word":"ten","meaning":"solid","type":"noun"},{"word":"kin","meaning":"thing","type":"noun"},{"word":"meta","meaning":"measurment","type":"noun"},{"word":"core","meaning":"desire","type":"noun"},{"word":"jore","meaning":"need","type":"noun"},{"word":"lonki","meaning":"field of knowledge/study","type":"noun"},{"word":"tori","meaning":"kindness, love","type":"noun"},{"word":"toba","meaning":"word","type":"noun"},{"word":"Word","meaning":"Number","type":"number"},{"word":"**sero**","meaning":"0","type":"number"},{"word":"**wano**","meaning":"1","type":"number"},{"word":"**duwo**","meaning":"2","type":"number"},{"word":"**tero**","meaning":"3","type":"number"},{"word":"**karo**","meaning":"4","type":"number"},{"word":"**bimo**","meaning":"5","type":"number"},{"word":"**ciko**","meaning":"6","type":"number"},{"word":"**sebo**","meaning":"7","type":"number"},{"word":"**wonto**","meaning":"8","type":"number"},{"word":"**ninto**","meaning":"9","type":"number"},{"word":"**dinko**","meaning":"10","type":"number"},{"word":"dunko","meaning":"20","type":"number"},{"word":"tenko","meaning":"30","type":"number"},{"word":"kanko","meaning":"40","type":"number"},{"word":"binko","meaning":"50","type":"number"},{"word":"cinko","meaning":"60","type":"number"},{"word":"senko","meaning":"70","type":"number"},{"word":"wonko","meaning":"80","type":"number"},{"word":"ninko","meaning":"90","type":"number"},{"word":"**hundo**","meaning":"100","type":"number"},{"word":"dundo","meaning":"200","type":"number"},{"word":"tendo","meaning":"300","type":"number"},{"word":"kando","meaning":"400","type":"number"},{"word":"bindo","meaning":"500","type":"number"},{"word":"cindo","meaning":"600","type":"number"},{"word":"sendo","meaning":"700","type":"number"},{"word":"wondo","meaning":"800","type":"number"},{"word":"nindo","meaning":"900","type":"number"},{"word":"**neko**","meaning":"1000","type":"number"},{"word":"duko","meaning":"2000","type":"number"},{"word":"teko","meaning":"3000","type":"number"},{"word":"keko","meaning":"4000","type":"number"},{"word":"biko","meaning":"5000","type":"number"},{"word":"ciko","meaning":"6000","type":"number"},{"word":"seko","meaning":"7000","type":"number"},{"word":"woko","meaning":"8000","type":"number"},{"word":"niko","meaning":"9000","type":"number"},{"word":"diko","meaning":"10'000","type":"number"},{"word":"dudiko","meaning":"20'000","type":"number"},{"word":"tediko","meaning":"30'000","type":"number"},{"word":"kadiko","meaning":"40'000","type":"number"},{"word":"bidiko","meaning":"50'000","type":"number"},{"word":"cidiko","meaning":"60'000","type":"number"},{"word":"sediko","meaning":"70'000","type":"number"},{"word":"wodiko","meaning":"80'000","type":"number"},{"word":"nidiko","meaning":"90'000","type":"number"},{"word":"huko","meaning":"100'000","type":"number"},{"word":"duhuko","meaning":"200'000","type":"number"},{"word":"tehuko","meaning":"300'000","type":"number"},{"word":"kahuko","meaning":"400'000","type":"number"},{"word":"bihuko","meaning":"500'000","type":"number"},{"word":"cihuko","meaning":"600'000","type":"number"},{"word":"sehuko","meaning":"700'000","type":"number"},{"word":"wohuko","meaning":"800'000","type":"number"},{"word":"nihuko","meaning":"900'000","type":"number"},{"word":"**ranolono**","meaning":"1'000'000","type":"number"},{"word":"dunolono","meaning":"2'000'000","type":"number"},{"word":"tenolono","meaning":"3'000'000","type":"number"},{"word":"kenolono","meaning":"4'000'000","type":"number"},{"word":"binolono","meaning":"5'000'000","type":"number"},{"word":"cinolono","meaning":"6'000'000","type":"number"},{"word":"senolono","meaning":"7'000'000","type":"number"},{"word":"wonolono","meaning":"8'000'000","type":"number"},{"word":"ninolono","meaning":"9'000'000","type":"number"},{"word":"dinolono","meaning":"10'000'000","type":"number"},{"word":"dudinolono","meaning":"20'000'000","type":"number"},{"word":"tedinolono","meaning":"30'000'000","type":"number"},{"word":"kadinolono","meaning":"40'000'000","type":"number"},{"word":"bidinolono","meaning":"50'000'000","type":"number"},{"word":"cidinolono","meaning":"60'000'000","type":"number"},{"word":"sedinolono","meaning":"70'000'000","type":"number"},{"word":"wodinolono","meaning":"80'000'000","type":"number"},{"word":"nidinolono","meaning":"90'000'000","type":"number"},{"word":"hunolono","meaning":"100'000'000","type":"number"},{"word":"**rawolono**","meaning":"1'000'000'000","type":"number"},{"word":"diwolono","meaning":"10'000'000'000","type":"number"},{"word":"huwolono","meaning":"100'000'000'000","type":"number"},{"word":"**rerolono**","meaning":"1'000'000'000'000","type":"number"},{"word":"dirolono","meaning":"10'000'000'000'000","type":"number"},{"word":"hurolono","meaning":"100'000'000'000'000","type":"number"},{"word":"**ragolono**","meaning":"1'000'000'000'000'000","type":"number"},{"word":"digolono","meaning":"10'000'000'000'000'000","type":"number"},{"word":"hugolono","meaning":"100'000'000'000'000'000","type":"number"},{"word":"**rimolono**","meaning":"1'000'000'000'000'000'000","type":"number"},{"word":"dimolono","meaning":"10'000'000'000'000'000'000","type":"number"},{"word":"humolono","meaning":"100'000'000'000'000'000'000","type":"number"},{"word":"**rikolono**","meaning":"1'000'000'000'000'000'000'000","type":"number"},{"word":"dikolono","meaning":"10'000'000'000'000'000'000'000","type":"number"},{"word":"hukolono","meaning":"100'000'000'000'000'000'000'000","type":"number"},{"word":"**rebolono**","meaning":"1'000'000'000'000'000'000'000'000","type":"number"},{"word":"dibolono","meaning":"10'000'000'000'000'000'000'000'000","type":"number"},{"word":"hubolono","meaning":"100'000'000'000'000'000'000'000'000","type":"number"},{"word":"**rontolono**","meaning":"1'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"dintolono","meaning":"10'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"huntolono","meaning":"100'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"**rinolono**","meaning":"1'000'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"dimpolono","meaning":"10'000'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"humpolono","meaning":"100'000'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"**rinkolono**","meaning":"1'000'000'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"dinkolono","meaning":"100'000'000'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"hunkolono","meaning":"100'000'000'000'000'000'000'000'000'000'000'000","type":"number"},{"word":"mire-","meaning":"half, in the middle of","type":"prefix"},{"word":"gara-","meaning":"all/every","type":"prefix"},{"word":"coko-","meaning":"some","type":"prefix"},{"word":"boro-","meaning":"any","type":"prefix"},{"word":"ni-","meaning":"negation, other than","type":"prefix"},{"word":"mo-","meaning":"inversion, contrary meaning","type":"prefix"},{"word":"wu-","meaning":"denotes positive connotation","type":"prefix"},{"word":"di-","meaning":"denotes negative connotation","type":"prefix"},{"word":"ra","meaning":"in","type":"preposition"},{"word":"para","meaning":"for","type":"preposition"},{"word":"de","meaning":"of","type":"preposition"},{"word":"no","meaning":"but","type":"preposition"},{"word":"gon","meaning":"on","type":"preposition"},{"word":"huba","meaning":"over","type":"preposition"},{"word":"wuba","meaning":"under","type":"preposition"},{"word":"faba","meaning":"in front of","type":"preposition"},{"word":"caba","meaning":"behind","type":"preposition"},{"word":"tamba","meaning":"next to","type":"preposition"},{"word":"taba","meaning":"near","type":"preposition"},{"word":"kaba","meaning":"far","type":"preposition"},{"word":"—","meaning":"li","type":"pronoun"},{"word":"mid","meaning":"mida","type":"pronoun"},{"word":"—","meaning":"da","type":"pronoun"},{"word":"—","meaning":"dase","type":"pronoun"},{"word":"—","meaning":"dake","type":"pronoun"},{"word":"—","meaning":"daka","type":"pronoun"},{"word":"—","meaning":"daki","type":"pronoun"},{"word":"—","meaning":"daci","type":"pronoun"},{"word":"reda","meaning":"reflexive pronoun, referencing to the thing(s) already mentioned in the sentence","type":"pronoun"},{"word":"toransu","meaning":"other","type":"pronoun"},{"word":"gara","meaning":"all/every","type":"pronoun"},{"word":"coko","meaning":"some","type":"pronoun"},{"word":"boro","meaning":"any","type":"pronoun"},{"word":"mire","meaning":"half","type":"pronoun"},{"word":"Prefix for:","meaning":"—","type":"pronoun"},{"word":"-lu","meaning":"defines an agent noun","type":"suffix"},{"word":"-sa","meaning":"indicates plurality","type":"suffix"},{"word":"-ri","meaning":"establishes a place/event","type":"suffix"},{"word":"-dufen","meaning":"refers to an entity or construct that performs a specific action or function","type":"suffix"},{"word":"-fe","meaning":"connotes opposition","type":"suffix"},{"word":"-se","meaning":"establishes feminine characteristics/gender","type":"suffix"},{"word":"-ke","meaning":"establishes androfeminine characteristics/gender","type":"suffix"},{"word":"-ka","meaning":"establishes androgynous characteristics/gender","type":"suffix"},{"word":"-ki","meaning":"establishes andromasc characteristics/gender","type":"suffix"},{"word":"-ci","meaning":"establishes masculine characteristics/gender","type":"suffix"},{"word":"-pa","meaning":"marks word as possessive","type":"suffix"},{"word":"-lonki","meaning":"refers to a field of knowledge/study","type":"suffix"},{"word":"-falo","meaning":"indicates manner or characteristic of speech ","type":"suffix"},{"word":"-da","meaning":"refers to the past tense form of a verb","type":"suffix"},{"word":"-ne","meaning":"refers to the present tense form of a verb","type":"suffix"},{"word":"-lo","meaning":"refers to the future tense form of a verb","type":"suffix"},{"word":"-ku","meaning":"establishes verb as uncertain/questioning","type":"suffix"},{"word":"-do","meaning":"establishes verb as imperative","type":"suffix"},{"word":"-jala","meaning":"denotes ability or capacity","type":"suffix"},{"word":"mawa","meaning":"kiss","type":"verb"},{"word":"binta","meaning":"eat","type":"verb"},{"word":"sanu","meaning":"be","type":"verb"},{"word":"tenco","meaning":"have","type":"verb"},{"word":"somo","meaning":"sleep","type":"verb"},{"word":"cosi","meaning":"choose, vote","type":"verb"},{"word":"falo","meaning":"speak, say, tell","type":"verb"},{"word":"toransu","meaning":"differ","type":"verb"},{"word":"mahen","meaning":"make, create","type":"verb"},{"word":"sekiso","meaning":"have sex, fuck","type":"verb"},{"word":"deja","meaning":"do, act","type":"verb"},{"word":"gade","meaning":"guess","type":"verb"},{"word":"nem","meaning":"think","type":"verb"},{"word":"pen","meaning":"move","type":"verb"},{"word":"nepen","meaning":"feel","type":"verb"},{"word":"gurati","meaning":"induce negative freedom, reduce cost to none","type":"verb"},{"word":"lifeta","meaning":"induce positive freedom","type":"verb"},{"word":"jala","meaning":"can, be able to","type":"verb"},{"word":"meta","meaning":"measure","type":"verb"},{"word":"core","meaning":"want","type":"verb"},{"word":"jore","meaning":"need","type":"verb"}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { FullEntry } from './types'; | ||
import { CompleteLangSpec } from './import'; | ||
|
||
export async function searchLangSpec( | ||
query: string, | ||
matching?: MatchType | ||
): Promise<FullEntry[]> { | ||
const matchWord = matching === 'word' || !matching, | ||
matchMeaning = matching === 'meaning' || !matching; | ||
const results: FullEntry[] = []; | ||
for (const entry of CompleteLangSpec) | ||
if ( | ||
(matchWord && entry.word.includes(query)) || | ||
(matchMeaning && entry.meaning.includes(query)) | ||
) | ||
results.push(entry); | ||
return results; | ||
} | ||
|
||
export type MatchType = 'word' | 'meaning'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* The type of the entry in each separated file (i.e. verbs.json) | ||
*/ | ||
export interface Entry { | ||
word: string; | ||
meaning: string; | ||
} | ||
|
||
/** | ||
* The type of entry in the complete dictionary (0-complete.json) | ||
* @extends Entry Also contains the properties from the Entry type | ||
*/ | ||
export interface FullEntry extends Entry { | ||
type: WordType; | ||
} | ||
|
||
/** | ||
* The types of words there can be | ||
*/ | ||
export type WordType = | ||
| 'adjective' | ||
| 'adverb' | ||
| 'conjunction' | ||
| 'interjection' | ||
| 'noun' | ||
| 'number' | ||
| 'prefix' | ||
| 'preposition' | ||
| 'pronoun' | ||
| 'suffix' | ||
| 'verb'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { pino } from 'pino'; | ||
import pretty from 'pino-pretty'; | ||
|
||
export const logger = pino( | ||
pretty({ | ||
colorize: true | ||
}) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import express, { Request, Response } from 'express'; | ||
import helmet from 'helmet'; | ||
|
||
export enum Methods { | ||
DELETE = 'delete', | ||
GET = 'get', | ||
HEAD = 'head', | ||
PATCH = 'patch', | ||
POST = 'post', | ||
PUT = 'put' | ||
} | ||
|
||
interface Route { | ||
handler: (req: Request, res: Response) => void; | ||
method: Methods; | ||
route: string; | ||
} | ||
|
||
export function createServer(...routes: Route[]) { | ||
const app = express(); | ||
for (const { handler, method, route } of routes) { | ||
app[method](route, handler); | ||
} | ||
// cors | ||
app.use( | ||
helmet({ | ||
crossOriginResourcePolicy: { | ||
policy: 'same-site' | ||
} | ||
}) | ||
); | ||
return app; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { | ||
APIEmbedField, | ||
RestOrArray, | ||
inlineCode, | ||
normalizeArray | ||
} from 'discord.js'; | ||
|
||
/** | ||
* Represents a command entry in the help command | ||
* @class | ||
*/ | ||
export class CommandHelpEntry { | ||
description: string; | ||
name: string; | ||
_usage: string[] | undefined; | ||
|
||
/** | ||
* Creates a new command entry | ||
* @constructor | ||
* @param {string} name the name of the command | ||
* @param {string} description the description of the command | ||
* @param {string[]} usage the usage of the command | ||
*/ | ||
constructor( | ||
name: string, | ||
description: string, | ||
...usage: RestOrArray<string> | ||
) { | ||
/** | ||
* The name of the command | ||
* @type {string} | ||
* @private | ||
* @readonly | ||
*/ | ||
this.name = name; | ||
/** | ||
* The description of the command | ||
* @type {string} | ||
* @public | ||
* @readonly | ||
*/ | ||
this.description = description; | ||
/** | ||
* The usage of the command | ||
* @type {string[]} | ||
* @private | ||
* @readonly | ||
*/ | ||
this._usage = normalizeArray(usage); | ||
} | ||
|
||
/** | ||
* The usage of the command | ||
* @type {string[]} | ||
* @readonly | ||
*/ | ||
get usage(): string[] { | ||
if (!this._usage) return [inlineCode(`/${this.name}`)]; | ||
return this._usage.map(val => inlineCode(`/${this.name} ${val}`)); | ||
} | ||
|
||
/** | ||
* Converts the command entry to a Discord API embed field | ||
* @returns {APIEmbedField} | ||
*/ | ||
toDiscordAPIEmbedField(): APIEmbedField { | ||
return { | ||
name: this.name, | ||
value: `${this.description}\n${this.usage.join('\n')}` | ||
}; | ||
} | ||
|
||
/** | ||
* Converts the command entry to a JSON object | ||
* @returns {SerializedCommandHelpEntry} | ||
*/ | ||
toJSON(): SerializedCommandHelpEntry { | ||
return { | ||
description: this.description, | ||
name: this.name, | ||
usage: this._usage | ||
}; | ||
} | ||
|
||
/** | ||
* Creates a new command entry from a JSON object | ||
* @param json {{description: string, name: string, usage?: string[]} | ||
* @returns {CommandHelpEntry} | ||
*/ | ||
static fromJSON(json: SerializedCommandHelpEntry): CommandHelpEntry { | ||
return new CommandHelpEntry(json.name, json.description, json.usage ?? []); | ||
} | ||
} | ||
|
||
interface SerializedCommandHelpEntry { | ||
description: string; | ||
name: string; | ||
usage?: string[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Client, ClientOptions } from 'discord.js'; | ||
import { Collection, ReadonlyCollection } from '@discordjs/collection'; | ||
import { Command } from './types'; | ||
|
||
/** | ||
export | ||
*/ class ExtendedCollection<K, V> extends Collection<K, V> { | ||
constructor(entries?: readonly (readonly [K, V])[] | null) { | ||
super(entries); | ||
} | ||
public freeze(): ReadonlyCollection<K, V> { | ||
return Object.freeze(this); | ||
} | ||
} | ||
|
||
export class CommandClient extends Client { | ||
commands: ExtendedCollection<string, Command>; | ||
|
||
constructor(options: ClientOptions) { | ||
super(options); | ||
|
||
this.commands = new ExtendedCollection<string, Command>(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; | ||
import { CommandHelpEntry } from '../CommandHelpEntry'; | ||
|
||
export interface Event { | ||
name: string; | ||
once: boolean; | ||
execute: (...args: unknown[]) => Promise<void>; | ||
} | ||
|
||
export interface Command { | ||
data: SlashCommandBuilder; | ||
help?: CommandHelpEntry; | ||
execute: (interaction: ChatInputCommandInteraction) => Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"compilerOptions": { | ||
"baseUrl": "./", | ||
"esModuleInterop": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"module": "ESNext", | ||
"moduleResolution": "node", | ||
"strict": true, | ||
"skipLibCheck": true, | ||
"target": "ESNext", | ||
"lib": ["ESNext", "ES2022", "ES5", "ES6"], | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"alwaysStrict": true, | ||
"paths": { | ||
"@/*": ["./src/*"] | ||
} | ||
}, | ||
"include": ["./src/**/*", "./scripts/**/*", "./*.ts"], | ||
"exclude": ["node_modules"] | ||
} |