Skip to content

Commit de182c7

Browse files
committed
Initial commit
0 parents  commit de182c7

20 files changed

+3561
-0
lines changed

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DISCORD_TOKEN='TOKEN'
2+
DISCORD_WEBHOOK_URL='YOUR_WEBHOOK_HERE'
3+
4+
MYSQL_ADDRESS='YOUR_MYSQL_SERVER_ADDRESS'
5+
MYSQL_PORT='YOUR_MYSQL_SERVER_PORT'
6+
MYSQL_USER='YOUR_MYSQL_USER'
7+
MYSQL_PASSWORD='YOUR_MYSQL_PASSWORD'
8+
MYSQL_DATABASE='YOUR_DATABASE_NAME'
9+
10+
AUTH="AUTH_KEY_FOR_API"

.eslintrc.json

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{
2+
"extends": "eslint:recommended",
3+
"env": {
4+
"node": true,
5+
"es6": true
6+
},
7+
"parserOptions": {
8+
"sourceType": "module",
9+
"ecmaVersion": "latest"
10+
},
11+
"rules": {
12+
"arrow-spacing": [
13+
"warn",
14+
{
15+
"before": true,
16+
"after": true
17+
}
18+
],
19+
"brace-style": [
20+
"error",
21+
"stroustrup",
22+
{
23+
"allowSingleLine": true
24+
}
25+
],
26+
"comma-dangle": [
27+
"error",
28+
"always-multiline"
29+
],
30+
"comma-spacing": "error",
31+
"comma-style": "error",
32+
"curly": [
33+
"error",
34+
"multi-line",
35+
"consistent"
36+
],
37+
"dot-location": [
38+
"error",
39+
"property"
40+
],
41+
"handle-callback-err": "off",
42+
"indent": [
43+
"error",
44+
"tab"
45+
],
46+
"keyword-spacing": "error",
47+
"max-nested-callbacks": [
48+
"error",
49+
{
50+
"max": 4
51+
}
52+
],
53+
"max-statements-per-line": [
54+
"error",
55+
{
56+
"max": 2
57+
}
58+
],
59+
"no-console": "off",
60+
"no-empty-function": "error",
61+
"no-floating-decimal": "error",
62+
"no-inline-comments": "error",
63+
"no-lonely-if": "error",
64+
"no-multi-spaces": "error",
65+
"no-multiple-empty-lines": [
66+
"error",
67+
{
68+
"max": 2,
69+
"maxEOF": 1,
70+
"maxBOF": 0
71+
}
72+
],
73+
"no-shadow": [
74+
"error",
75+
{
76+
"allow": [
77+
"err",
78+
"resolve",
79+
"reject"
80+
]
81+
}
82+
],
83+
"no-trailing-spaces": [
84+
"error"
85+
],
86+
"no-var": "error",
87+
"object-curly-spacing": [
88+
"error",
89+
"always"
90+
],
91+
"prefer-const": "error",
92+
"quotes": [
93+
"error",
94+
"single"
95+
],
96+
"semi": [
97+
"error",
98+
"always"
99+
],
100+
"space-before-blocks": "error",
101+
"space-before-function-paren": [
102+
"error",
103+
{
104+
"anonymous": "never",
105+
"named": "never",
106+
"asyncArrow": "always"
107+
}
108+
],
109+
"space-in-parens": "error",
110+
"space-infix-ops": "error",
111+
"space-unary-ops": "error",
112+
"spaced-comment": "error",
113+
"yoda": "error"
114+
}
115+
}

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
assignees:
8+
- "GalvinPython"

.github/workflows/lockb.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# https://github.com/dependabot/dependabot-core/issues/6528#issuecomment-2013695799
2+
3+
name: 'Dependabot: Update bun.lockb'
4+
5+
on:
6+
pull_request:
7+
paths:
8+
- "package-lock.json"
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
update-bun-lockb:
15+
name: "Update bun.lockb"
16+
if: github.actor == 'dependabot[bot]'
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: oven-sh/setup-bun@v1
20+
- uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
ref: ${{ github.event.pull_request.head.ref }}
24+
- run: |
25+
bun install
26+
git add bun.lockb
27+
git config --global user.name 'dependabot[bot]'
28+
git config --global user.email 'dependabot[bot]@users.noreply.github.com'
29+
git commit --amend --no-edit
30+
git push --force

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.env

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Chatr
2+
A Discord XP bot
3+
4+
> [!CAUTION]
5+
> **Chatr** is currently in development and is open-sourced. The bot is functional in this state, however it shouldn't be used
6+
7+
To install dependencies:
8+
9+
```bash
10+
bun install
11+
```
12+
13+
To run:
14+
15+
```bash
16+
bun run index.ts
17+
```
18+
19+
This project was created using `bun init` in bun v1.1.10. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

bot/commands.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Commands taken from https://github.com/NiaAxern/discord-youtube-subscriber-count/blob/main/src/commands/utilities.ts
2+
3+
import client from '.';
4+
import { type CommandInteraction } from 'discord.js';
5+
import { heapStats } from 'bun:jsc';
6+
import { getGuildLeaderboard, makeGETRequest } from './utils/requestAPI';
7+
import convertToLevels from './utils/convertToLevels';
8+
import quickEmbed from './utils/quickEmbed';
9+
10+
interface Command {
11+
data: {
12+
options: any[];
13+
name: string;
14+
description: string;
15+
integration_types: number[];
16+
contexts: number[];
17+
};
18+
execute: (interaction: CommandInteraction) => Promise<void>;
19+
}
20+
21+
const commands: Record<string, Command> = {
22+
ping: {
23+
data: {
24+
options: [],
25+
name: 'ping',
26+
description: 'Check the ping of the bot!',
27+
integration_types: [0, 1],
28+
contexts: [0, 1, 2],
29+
},
30+
execute: async (interaction: { reply: (arg0: { ephemeral: boolean; content: string; }) => Promise<any>; client: { ws: { ping: any; }; }; }) => {
31+
await interaction
32+
.reply({
33+
ephemeral: false,
34+
content: `Ping: ${interaction.client.ws.ping}ms`,
35+
})
36+
.catch(console.error);
37+
},
38+
},
39+
help: {
40+
data: {
41+
options: [],
42+
name: 'help',
43+
description: 'Get help on what each command does!',
44+
integration_types: [0, 1],
45+
contexts: [0, 1, 2],
46+
},
47+
execute: async (interaction: { reply: (arg0: { ephemeral: boolean; content: string; }) => Promise<any>; }) => {
48+
await client.application?.commands?.fetch().catch(console.error);
49+
const chat_commands = client.application?.commands.cache.map((a) => {
50+
return `</${a.name}:${a.id}>: ${a.description}`;
51+
});
52+
await interaction
53+
.reply({
54+
ephemeral: true,
55+
content: `Commands:\n${chat_commands?.join('\n')}`,
56+
})
57+
.catch(console.error);
58+
},
59+
},
60+
sourcecode: {
61+
data: {
62+
options: [],
63+
name: 'sourcecode',
64+
description: "Get the link of the app's source code.",
65+
integration_types: [0, 1],
66+
contexts: [0, 1, 2],
67+
},
68+
execute: async (interaction: { reply: (arg0: { ephemeral: boolean; content: string; }) => Promise<any>; }) => {
69+
await interaction
70+
.reply({
71+
ephemeral: true,
72+
content: `[Github repository](https://github.com/GalvinPython/discord-autopublish)`,
73+
})
74+
.catch(console.error);
75+
},
76+
},
77+
uptime: {
78+
data: {
79+
options: [],
80+
name: 'uptime',
81+
description: 'Check the uptime of the bot!',
82+
integration_types: [0, 1],
83+
contexts: [0, 1, 2],
84+
},
85+
execute: async (interaction: { reply: (arg0: { ephemeral: boolean; content: string; }) => Promise<any>; }) => {
86+
await interaction
87+
.reply({
88+
ephemeral: false,
89+
content: `Uptime: ${(performance.now() / (86400 * 1000)).toFixed(
90+
2,
91+
)} days`,
92+
})
93+
.catch(console.error);
94+
},
95+
},
96+
usage: {
97+
data: {
98+
options: [],
99+
name: 'usage',
100+
description: 'Check the heap size and disk usage of the bot!',
101+
integration_types: [0, 1],
102+
contexts: [0, 1, 2],
103+
},
104+
execute: async (interaction: { reply: (arg0: { ephemeral: boolean; content: string; }) => Promise<any>; }) => {
105+
const heap = heapStats();
106+
Bun.gc(false);
107+
await interaction
108+
.reply({
109+
ephemeral: false,
110+
content: [
111+
`Heap size: ${(heap.heapSize / 1024 / 1024).toFixed(2)} MB / ${(
112+
heap.heapCapacity /
113+
1024 /
114+
1024
115+
).toFixed(2)} MB (${(heap.extraMemorySize / 1024 / 1024).toFixed(2,)} MB) (${heap.objectCount.toLocaleString()} objects, ${heap.protectedObjectCount.toLocaleString()} protected-objects)`,
116+
]
117+
.join('\n')
118+
.slice(0, 2000),
119+
})
120+
.catch(console.error);
121+
},
122+
},
123+
xp: {
124+
data: {
125+
options: [],
126+
name: 'xp',
127+
description: 'Get your XP and Points',
128+
integration_types: [0],
129+
contexts: [0, 2],
130+
},
131+
execute: async (interaction) => {
132+
if (interaction?.guildId) {
133+
const guild = interaction.guild?.id
134+
const user = interaction.user.id
135+
const xp = await makeGETRequest(guild as string, user)
136+
await interaction.reply({
137+
ephemeral: false,
138+
content: `<@${user}> you have ${xp.xp} XP! (Level ${convertToLevels(xp.xp)})`
139+
})
140+
}
141+
}
142+
},
143+
top: {
144+
data: {
145+
options: [],
146+
name: 'top',
147+
description: 'Get the top users for the server',
148+
integration_types: [0],
149+
contexts: [0, 2],
150+
},
151+
execute: async (interaction) => {
152+
if (interaction?.guildId) {
153+
const guild = interaction.guild?.id;
154+
155+
try {
156+
const leaderboard = await getGuildLeaderboard(guild as string);
157+
158+
if (leaderboard.length === 0) {
159+
await interaction.reply('No leaderboard data available.');
160+
return;
161+
}
162+
163+
// Create a new embed using the custom embed function
164+
const leaderboardEmbed = quickEmbed({
165+
color: 'Blurple',
166+
title: `Leaderboard for ${interaction.guild?.name}`,
167+
description: 'Top 10 Users'
168+
}, interaction);
169+
170+
// Add a field for each user with a mention
171+
leaderboard.forEach((entry: { user_id: any; xp: any; }, index: number) => {
172+
leaderboardEmbed.addFields([
173+
{
174+
name: `${index + 1}.`,
175+
value: `<@${entry.user_id}>: ${entry.xp} XP`,
176+
inline: false
177+
}
178+
]);
179+
});
180+
181+
// Send the embed
182+
await interaction.reply({ embeds: [leaderboardEmbed] });
183+
} catch (error) {
184+
console.error('Error executing command:', error);
185+
await interaction.reply('There was an error retrieving the leaderboard.');
186+
}
187+
} else {
188+
await interaction.reply('This command can only be used in a guild.');
189+
}
190+
}
191+
}
192+
};
193+
194+
// Convert commands to a Map
195+
const commandsMap = new Map<string, Command>();
196+
for (const key in commands) {
197+
if (commands.hasOwnProperty(key)) {
198+
const command = commands[key];
199+
console.log('loading ' + key);
200+
commandsMap.set(key, command);
201+
}
202+
}
203+
204+
export default commandsMap;

0 commit comments

Comments
 (0)