Skip to content

Commit abf814f

Browse files
authored
Merge pull request #9 from GalvinPython/dev
release: 0.1
2 parents 4956883 + b033549 commit abf814f

26 files changed

+1634
-525
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
DISCORD_TOKEN='TOKEN'
2-
DISCORD_WEBHOOK_URL='YOUR_WEBHOOK_HERE'
2+
DISCORD_TOKEN_DEV='DEV_TOKEN'
33

44
MYSQL_ADDRESS='YOUR_MYSQL_SERVER_ADDRESS'
55
MYSQL_PORT='YOUR_MYSQL_SERVER_PORT'

.github/dependabot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ updates:
66
interval: "weekly"
77
assignees:
88
- "GalvinPython"
9+
target-branch: "dev"

.github/workflows/eslint.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: ESLint Check
2+
3+
on:
4+
push:
5+
branches:
6+
- '*'
7+
pull_request:
8+
types: [opened, reopened, synchronize]
9+
10+
jobs:
11+
lint:
12+
name: ESLint
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v2
18+
19+
- name: Setup Node.js
20+
uses: actions/setup-node@v2
21+
with:
22+
node-version: '20'
23+
24+
- name: Install dependencies
25+
run: npm install
26+
27+
- name: Run ESLint
28+
run: npm run lint

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Galvin
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
11
# Chatr
2-
A Discord XP bot
2+
A free and open-sourced Discord XP Bot.
3+
![Bot](https://img.shields.io/badge/Invite%20Chatr-5865F2?style=for-the-badge&logo=discord&logoColor=white)
4+
![Discord](https://img.shields.io/discord/1249813817706283019?style=for-the-badge&logo=discord&logoColor=white&label=Support%20Server&color=%235865F2)
35

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+
Please report bugs in `bug-reports` on our server or open an issue on this repo!
7+
8+
# Features
9+
- Earn XP from your messages!
10+
- Customisable xp cooldown on messages
11+
- Online leaderboard
12+
- Rankcard
13+
- Transfer your points from other bots!
14+
- MEE6
15+
- Polaris
16+
- Lurkr
17+
- Other bots soon
18+
19+
> [!WARNING]
20+
> **Chatr** has entered Beta! (don't worry, we will deal with the headaches for you)
21+
22+
# Developer Instructions
23+
24+
This a project created using (Bun)[https://bun.sh]
625

726
To install dependencies:
827

928
```bash
1029
bun install
1130
```
1231

13-
To run:
14-
32+
Run the **API**
1533
```bash
16-
bun run index.ts
34+
bun run dev:api
35+
```
36+
Run the **Bot**
37+
```bash
38+
bun run dev:bot
1739
```
1840

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.
41+
42+
# Changelog
43+
## Beta 0.1
44+
Thanks to @ToastedDev for his contributions to the bot. Here are some changes that were made
45+
* General formatting fixes (#8)
46+
* Refactored the database to be more performant (#13)
47+
* Added a message cooldown (#14)
48+
* Added a rankcard to /xp (#17)
49+
* User management (#19)
50+
* Added syncing (#24)
51+
52+
# Roadmap
53+
* Rewritten site using NextJS
54+
* Auto-updating cached user information
55+
* Better privacy controls
56+
* Live updates
57+
* Track guilds and users xp
58+
59+
Want to add more features? Join our server (linked above) and add a post to `feature-requests`

api/db/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import mysql from "mysql2";
2+
3+
// Create a MySQL connection pool
4+
export const pool = mysql.createPool({
5+
host: process.env.MYSQL_ADDRESS as string,
6+
port: parseInt(process.env.MYSQL_PORT as string),
7+
user: process.env.MYSQL_USER as string,
8+
password: process.env.MYSQL_PASSWORD as string,
9+
database: process.env.MYSQL_DATABASE as string,
10+
});
11+
12+
export * from './init';
13+
export * from './queries/guilds';
14+
export * from './queries/users';
15+
export * from './queries/updates';

api/db/init.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { pool } from ".";
2+
3+
export async function initTables() {
4+
const createGuildsTable = `
5+
CREATE TABLE IF NOT EXISTS guilds (
6+
id VARCHAR(255) NOT NULL PRIMARY KEY,
7+
name VARCHAR(255),
8+
icon VARCHAR(255),
9+
members INT,
10+
cooldown INT DEFAULT 30000,
11+
updates_enabled BOOLEAN DEFAULT FALSE,
12+
updates_channel_id VARCHAR(255) DEFAULT NULL,
13+
is_in_guild BOOLEAN DEFAULT TRUE
14+
)
15+
`;
16+
const createUsersTable = `
17+
CREATE TABLE IF NOT EXISTS users (
18+
id VARCHAR(255) NOT NULL,
19+
guild_id VARCHAR(255) NOT NULL,
20+
name VARCHAR(255),
21+
nickname VARCHAR(255),
22+
pfp VARCHAR(255),
23+
xp INT DEFAULT 0,
24+
level INT DEFAULT 0,
25+
xp_needed_next_level INT,
26+
progress_next_level DECIMAL(6, 2),
27+
PRIMARY KEY (id, guild_id)
28+
)
29+
`;
30+
// FOREIGN KEY (guild_id) REFERENCES guilds(id)
31+
const createRolesTable = `
32+
CREATE TABLE IF NOT EXISTS roles (
33+
id VARCHAR(255) NOT NULL PRIMARY KEY,
34+
guild_id VARCHAR(255) NOT NULL,
35+
name VARCHAR(255),
36+
level INT NOT NULL
37+
)
38+
`;
39+
// FOREIGN KEY (guild_id) REFERENCES guilds(id)
40+
41+
pool.query(createGuildsTable, (err) => {
42+
if (err) {
43+
console.error("Error creating guilds table:", err);
44+
} else {
45+
console.log("Guilds table created");
46+
}
47+
});
48+
49+
pool.query(createUsersTable, (err) => {
50+
if (err) {
51+
console.error("Error creating users table:", err);
52+
} else {
53+
console.log("Users table created");
54+
}
55+
});
56+
57+
pool.query(createRolesTable, (err) => {
58+
if (err) {
59+
console.error("Error creating roles table:", err);
60+
} else {
61+
console.log("Roles table created");
62+
}
63+
});
64+
}

api/db/queries/guilds.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { QueryError } from "mysql2";
2+
import { pool } from "..";
3+
4+
export interface Guild {
5+
id: string;
6+
name: string;
7+
icon: string;
8+
members: number;
9+
cooldown: number;
10+
updates_enabled: 0 | 1;
11+
updates_channel_id: string | null;
12+
}
13+
14+
15+
export async function getGuild(guildId: string): Promise<[QueryError, null] | [null, Guild | null]> {
16+
return new Promise((resolve, reject) => {
17+
pool.query("SELECT * FROM guilds WHERE id = ? AND is_in_guild = ?", [guildId, true], (err, results) => {
18+
if (err) {
19+
reject([err, null]);
20+
} else {
21+
resolve([null, (results as Guild[])[0]]);
22+
}
23+
});
24+
});
25+
}
26+
27+
export async function updateGuild(guild: Omit<Guild, "cooldown" | "updates_enabled" | "updates_channel_id">): Promise<[QueryError | null, null] | [null, Guild[]]> {
28+
return new Promise((resolve, reject) => {
29+
pool.query(
30+
`
31+
INSERT INTO guilds (id, name, icon, members, is_in_guild)
32+
VALUES (?, ?, ?, ?, ?)
33+
ON DUPLICATE KEY UPDATE
34+
name = VALUES(name),
35+
icon = VALUES(icon),
36+
members = VALUES(members),
37+
is_in_guild = VALUES(is_in_guild)
38+
`,
39+
[
40+
guild.id,
41+
guild.name,
42+
guild.icon,
43+
guild.members,
44+
true,
45+
],
46+
(err, results) => {
47+
if (err) {
48+
reject([err, null]);
49+
} else {
50+
resolve([null, results as Guild[]]);
51+
}
52+
},
53+
);
54+
});
55+
}
56+
57+
export async function removeGuild(guildId: string): Promise<[QueryError, null] | [null, true]> {
58+
return new Promise((resolve, reject) => {
59+
pool.query("UPDATE guilds SET is_in_guild = ? WHERE id = ?", [false, guildId], (err) => {
60+
if (err) {
61+
reject([err, null]);
62+
} else {
63+
resolve([null, true]);
64+
}
65+
});
66+
});
67+
}
68+
69+
export async function setCooldown(guildId: string, cooldown: number): Promise<[QueryError, null] | [null, Guild]> {
70+
return new Promise((resolve, reject) => {
71+
pool.query("UPDATE guilds SET cooldown = ? WHERE id = ?", [cooldown, guildId], (err, results) => {
72+
if (err) {
73+
reject([err, null]);
74+
} else {
75+
resolve([null, (results as Guild[])[0]]);
76+
}
77+
});
78+
})
79+
}
80+
81+
interface BotInfo {
82+
total_guilds: number;
83+
total_members: number;
84+
user_count?: number;
85+
}
86+
87+
export async function getBotInfo(): Promise<[QueryError | null, BotInfo | null]> {
88+
return new Promise((resolve, reject) => {
89+
pool.query("SELECT COUNT(*) AS total_guilds, SUM(members) AS total_members FROM guilds", (err, results) => {
90+
if (err) {
91+
reject([err, null]);
92+
} else {
93+
const botInfo: BotInfo = {
94+
total_guilds: (results as BotInfo[])[0].total_guilds,
95+
total_members: (results as BotInfo[])[0].total_members ?? 0,
96+
};
97+
getUsersCount()
98+
.then(([userCountError, userCount]) => {
99+
if (userCountError) {
100+
reject([userCountError, null]);
101+
} else {
102+
botInfo.user_count = userCount;
103+
resolve([null, botInfo]);
104+
}
105+
})
106+
.catch((error) => {
107+
reject([error, null]);
108+
});
109+
}
110+
});
111+
});
112+
}
113+
114+
export async function getUsersCount(): Promise<[QueryError | null, number]> {
115+
return new Promise((resolve, reject) => {
116+
pool.query("SELECT COUNT(*) AS count FROM users", (err, results) => {
117+
if (err) {
118+
reject([err, null]);
119+
} else {
120+
resolve([null, (results as { count: number }[])[0].count]);
121+
}
122+
});
123+
});
124+
}

0 commit comments

Comments
 (0)