mirror of
https://github.com/xHyroM/bun-discord-bot.git
synced 2024-12-22 12:11:06 +01:00
feat: ping command, github command
This commit is contained in:
parent
06c5155164
commit
d89b80c60d
13 changed files with 122 additions and 19 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
files/config.toml
|
||||
requests.rest
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -6,4 +6,10 @@ port = 3000
|
|||
[client]
|
||||
public_key = "CLIENT PUBLIC KEY"
|
||||
token = "CLIENT TOKEN"
|
||||
id = "CLIENT USER ID"
|
||||
id = "CLIENT USER ID"
|
||||
|
||||
[commands]
|
||||
guild_id = "GUILD ID"
|
||||
|
||||
[api]
|
||||
github_personal_access_token = ""
|
7
files/utilities.toml
Normal file
7
files/utilities.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[github]
|
||||
repositories = [
|
||||
"oven-sh/bun",
|
||||
"oven-sh/docs",
|
||||
"Jarred-Sumner/bun-dependencies",
|
||||
"xHyroM/bun-discord-bot"
|
||||
]
|
|
@ -2,7 +2,8 @@
|
|||
"version": "0.0.0",
|
||||
"name": "bun-discord-bot",
|
||||
"scripts": {
|
||||
"start": "bun src/index.ts"
|
||||
"start": "bun src/index.ts",
|
||||
"cloudflare:tunnel": "sudo cloudflared tunnel run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "^0.1.2"
|
||||
|
|
81
src/commands/github.ts
Normal file
81
src/commands/github.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { APIApplicationCommandInteractionDataStringOption, ApplicationCommandOptionType, InteractionResponseType, MessageFlags } from 'discord-api-types/v10';
|
||||
import { Command } from '../structures/Command';
|
||||
// @ts-expect-error Types :(
|
||||
import utilities from '../../files/utilities.toml';
|
||||
// @ts-expect-error Types :(
|
||||
import config from '../../files/config.toml';
|
||||
import { githubIssuesAndPullRequests } from '../utils/regexes';
|
||||
import isNumeric from '../utils/isNumeric';
|
||||
import Collection from '@discordjs/collection';
|
||||
|
||||
const cooldowns: Collection<string, number> = new Collection();
|
||||
|
||||
new Command({
|
||||
name: 'github',
|
||||
description: 'Query an issue, pull request or direct link to Github Issue or PR',
|
||||
options: [
|
||||
{
|
||||
name: 'query',
|
||||
description: 'Issue, PR number or direct link to Github Issue or PR',
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
description: 'Project repository (default oven-sh/bun)',
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: false,
|
||||
choices: [
|
||||
...utilities.github.repositories.map(repository => new Object({
|
||||
name: repository.split('/')[1],
|
||||
value: repository
|
||||
}))
|
||||
]
|
||||
}
|
||||
],
|
||||
run: async(ctx) => {
|
||||
if (cooldowns.has(ctx.user.id) && cooldowns.get(ctx.user.id) < Date.now()) {
|
||||
return ctx.respond('⚠️ You are in cooldown.');
|
||||
}
|
||||
|
||||
const query: string = (ctx.options[0] as APIApplicationCommandInteractionDataStringOption).value;
|
||||
const repository: string = (ctx.options?.[1] as APIApplicationCommandInteractionDataStringOption)?.value || 'oven-sh/bun';
|
||||
|
||||
const repositorySplit = repository.split('/');
|
||||
const repositoryOwner = repositorySplit[0];
|
||||
const repositoryName = repositorySplit[1];
|
||||
|
||||
const isIssueOrPR = githubIssuesAndPullRequests(repositoryOwner, repositoryName).test(query);
|
||||
const isIssueOrPRNumber = isNumeric(query);
|
||||
|
||||
if (!isIssueOrPR && !isIssueOrPRNumber) {
|
||||
return ctx.respond({
|
||||
type: InteractionResponseType.ChannelMessageWithSource,
|
||||
data: {
|
||||
content: `\`❌\` Invalid issue or pull request \`${query}\``,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const issueUrl = `https://api.github.com/repos/${repositoryOwner}/${repositoryName}/issues/${isIssueOrPR ? query.split('/issues/')[1] : query}`;
|
||||
cooldowns.set(ctx.user.id, Date.now() + 60000);
|
||||
|
||||
const res = await fetch(issueUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'bun-discord-bot',
|
||||
'Authorization': config.api.github_personal_access_token
|
||||
}
|
||||
});
|
||||
|
||||
const data: any = await res.json();
|
||||
|
||||
// TODO: finish (pull request, issue)
|
||||
|
||||
return ctx.respond([
|
||||
`[#${data.number} ${repositoryOwner}/${repositoryName}](${data.html_url}) operation <timestamp>`,
|
||||
data.title
|
||||
].join('\n'));
|
||||
}
|
||||
})
|
|
@ -2,14 +2,13 @@ import { InteractionResponseType } from 'discord-api-types/v10';
|
|||
import { Command } from '../structures/Command';
|
||||
|
||||
new Command({
|
||||
name: 'help',
|
||||
description: 'Help command',
|
||||
guildId: '924395690451423332',
|
||||
run: (c) => {
|
||||
return c.respond({
|
||||
name: 'ping',
|
||||
description: 'pong',
|
||||
run: (ctx) => {
|
||||
return ctx.respond({
|
||||
type: InteractionResponseType.ChannelMessageWithSource,
|
||||
data: {
|
||||
content: 'hello'
|
||||
content: 'Pong 🏓'
|
||||
}
|
||||
})
|
||||
}
|
|
@ -5,7 +5,6 @@ import { findTags, getTag } from '../utils/tagsUtils';
|
|||
new Command({
|
||||
name: 'tags',
|
||||
description: 'Send a tag by name or alias',
|
||||
guildId: '924395690451423332',
|
||||
options: [
|
||||
{
|
||||
name: 'query',
|
||||
|
@ -23,13 +22,13 @@ new Command({
|
|||
required: false
|
||||
}
|
||||
],
|
||||
run: (c) => {
|
||||
const query: APIApplicationCommandInteractionDataStringOption = c.options[0] as APIApplicationCommandInteractionDataStringOption;
|
||||
const target = c?.resolved?.users?.[0];
|
||||
run: (ctx) => {
|
||||
const query: APIApplicationCommandInteractionDataStringOption = ctx.options[0] as APIApplicationCommandInteractionDataStringOption;
|
||||
const target = ctx?.resolved?.users?.[0];
|
||||
|
||||
const tag = getTag(query.value);
|
||||
if (!tag)
|
||||
return c.respond({
|
||||
return ctx.respond({
|
||||
type: InteractionResponseType.ChannelMessageWithSource,
|
||||
data: {
|
||||
content: `\`❌\` Could not find a tag \`${query.value}\``,
|
||||
|
@ -37,7 +36,7 @@ new Command({
|
|||
}
|
||||
});
|
||||
|
||||
return c.respond([
|
||||
return ctx.respond([
|
||||
target ? `*Tag suggestion for <@${target.id}>:*` : '',
|
||||
tag.content
|
||||
].join('\n'));
|
||||
|
|
|
@ -21,6 +21,7 @@ try {
|
|||
}
|
||||
|
||||
const app = new Hono();
|
||||
app.get('*', (c) => c.redirect('https://www.youtube.com/watch?v=FMhScnY0dME'));
|
||||
|
||||
app.post('/interaction', bodyParse(), async(c) => {
|
||||
const signature = c.req.headers.get('X-Signature-Ed25519');
|
||||
|
@ -62,6 +63,7 @@ app.post('/interaction', bodyParse(), async(c) => {
|
|||
if (interaction.type === InteractionType.ApplicationCommand && interaction.data.type === ApplicationCommandType.ChatInput) {
|
||||
return Commands.get(interaction.data.name).run(new CommandContext(
|
||||
c,
|
||||
interaction.user,
|
||||
interaction.data.options,
|
||||
interaction.data.resolved
|
||||
));
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// Taken from https://github.com/Garlic-Team/gcommands/blob/next/src/lib/structures/Command.ts
|
||||
|
||||
// @ts-expect-error Types :(
|
||||
import config from '../../files/config.toml';
|
||||
import { LocaleString } from 'discord-api-types/v10';
|
||||
import { Commands } from '../managers/CommandManager';
|
||||
import { CommandContext } from './contexts/CommandContext';
|
||||
|
@ -13,7 +15,7 @@ export interface CommandOptions {
|
|||
guildId?: string;
|
||||
defaultMemberPermissions?: string;
|
||||
options?: Option[] | OptionOptions[];
|
||||
run: (ctx: CommandContext) => Response;
|
||||
run: (ctx: CommandContext) => Response | Promise<Response>;
|
||||
}
|
||||
|
||||
export class Command {
|
||||
|
@ -21,10 +23,10 @@ export class Command {
|
|||
public nameLocalizations?: Record<LocaleString, string>;
|
||||
public description?: string;
|
||||
public descriptionLocalizations?: Record<LocaleString, string>;
|
||||
public guildId?: string;
|
||||
public guildId?: string = config.client.guild_id;
|
||||
public defaultMemberPermissions?: string;
|
||||
public options: Option[] | OptionOptions[];
|
||||
public run: (ctx: CommandContext) => Response;
|
||||
public run: (ctx: CommandContext) => Response | Promise<Response>;
|
||||
|
||||
public constructor(options: CommandOptions) {
|
||||
this.name = options.name;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { APIApplicationCommandInteractionDataOption, APIChatInputApplicationCommandInteractionDataResolved, APIInteractionResponse, InteractionResponseType } from 'discord-api-types/v10';
|
||||
import { APIApplicationCommandInteractionDataOption, APIChatInputApplicationCommandInteractionDataResolved, APIInteractionResponse, APIUser, InteractionResponseType } from 'discord-api-types/v10';
|
||||
import { Context } from 'hono';
|
||||
|
||||
export class CommandContext {
|
||||
public context: Context;
|
||||
public user?: APIUser;
|
||||
public options?: APIApplicationCommandInteractionDataOption[];
|
||||
public resolved?: APIChatInputApplicationCommandInteractionDataResolved;
|
||||
|
||||
public constructor(c: Context, options?: APIApplicationCommandInteractionDataOption[], resolved?: APIChatInputApplicationCommandInteractionDataResolved) {
|
||||
public constructor(c: Context, user?: APIUser, options?: APIApplicationCommandInteractionDataOption[], resolved?: APIChatInputApplicationCommandInteractionDataResolved) {
|
||||
this.context = c;
|
||||
this.user = user;
|
||||
this.options = options;
|
||||
this.resolved = resolved;
|
||||
}
|
||||
|
|
1
src/utils/isNumeric.ts
Normal file
1
src/utils/isNumeric.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export default (value: string) => /^-?\d+$/.test(value);
|
2
src/utils/regexes.ts
Normal file
2
src/utils/regexes.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const githubIssuesAndPullRequests = (owner: string, repository: string) =>
|
||||
new RegExp(`https?:\\\/\\\/github\\\.com\\\/${owner}\\\/${repository}\\\/(?:issues\\\/\\\d+|pull\\\/\d+)`, 'gm');
|
Loading…
Reference in a new issue