0.4.0b1 add database and /userinfo (incomplete)
This commit is contained in:
parent
3f13fe1ec0
commit
5b98a2e98a
18 changed files with 1194 additions and 243 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -24,4 +24,4 @@ dist/
|
|||
.err
|
||||
.vscode
|
||||
/run.sh
|
||||
drizzle
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 0.4.0 (TBA)
|
||||
|
||||
* Switched database to PostgreSQL
|
||||
* Added `/userinfo` command and balance
|
||||
|
||||
## 0.3.2 (February 20, 2026)
|
||||
|
||||
* Fixed `/wiki` loading non-existent pages.
|
||||
|
|
|
|||
14
drizzle.config.ts
Normal file
14
drizzle.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { configDotenv } from "dotenv";
|
||||
|
||||
configDotenv();
|
||||
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
schema: './src/db/schema.ts',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!
|
||||
}
|
||||
});
|
||||
26
drizzle/0000_strange_fabian_cortez.sql
Normal file
26
drizzle/0000_strange_fabian_cortez.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
CREATE TABLE "balances" (
|
||||
"userId" integer,
|
||||
"guildId" integer,
|
||||
"balance" bigint DEFAULT 0::bigint,
|
||||
"lastMessageHour" integer,
|
||||
CONSTRAINT "balances_userId_guildId_pk" PRIMARY KEY("userId","guildId")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "guilds" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"discordId" bigint,
|
||||
"displayName" varchar(80),
|
||||
CONSTRAINT "guilds_discordId_unique" UNIQUE("discordId")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"discordId" bigint,
|
||||
"username" varchar(34),
|
||||
"displayName" varchar(64),
|
||||
"reputation" smallint DEFAULT 0,
|
||||
CONSTRAINT "users_discordId_unique" UNIQUE("discordId")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "balances" ADD CONSTRAINT "balances_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "balances" ADD CONSTRAINT "balances_guildId_guilds_id_fk" FOREIGN KEY ("guildId") REFERENCES "public"."guilds"("id") ON DELETE no action ON UPDATE no action;
|
||||
183
drizzle/meta/0000_snapshot.json
Normal file
183
drizzle/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
{
|
||||
"id": "4602e85b-a3cf-4168-881e-a95e9316f325",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.balances": {
|
||||
"name": "balances",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"guildId": {
|
||||
"name": "guildId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"balance": {
|
||||
"name": "balance",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "0::bigint"
|
||||
},
|
||||
"lastMessageHour": {
|
||||
"name": "lastMessageHour",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"balances_userId_users_id_fk": {
|
||||
"name": "balances_userId_users_id_fk",
|
||||
"tableFrom": "balances",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"userId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"balances_guildId_guilds_id_fk": {
|
||||
"name": "balances_guildId_guilds_id_fk",
|
||||
"tableFrom": "balances",
|
||||
"tableTo": "guilds",
|
||||
"columnsFrom": [
|
||||
"guildId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"balances_userId_guildId_pk": {
|
||||
"name": "balances_userId_guildId_pk",
|
||||
"columns": [
|
||||
"userId",
|
||||
"guildId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.guilds": {
|
||||
"name": "guilds",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"discordId": {
|
||||
"name": "discordId",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"displayName": {
|
||||
"name": "displayName",
|
||||
"type": "varchar(80)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"guilds_discordId_unique": {
|
||||
"name": "guilds_discordId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"discordId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"discordId": {
|
||||
"name": "discordId",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(34)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"displayName": {
|
||||
"name": "displayName",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"reputation": {
|
||||
"name": "reputation",
|
||||
"type": "smallint",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_discordId_unique": {
|
||||
"name": "users_discordId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"discordId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1771929740316,
|
||||
"tag": "0000_strange_fabian_cortez",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
1004
package-lock.json
generated
1004
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sknsybot",
|
||||
"version": "0.3.2",
|
||||
"version": "0.4.0.b1",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
|
|
@ -15,14 +15,16 @@
|
|||
"chalk": "^5.4.1",
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^3.12.0",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"node-cron": "^3.0.3",
|
||||
"pg": "^8.18.0",
|
||||
"wikijs": "^6.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/pg": "^8.16.0",
|
||||
"drizzle-kit": "^0.31.9",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import "./initConfig";
|
||||
|
||||
import { GatewayIntentBits, Events, Interaction, ChatInputCommandInteraction, Guild, MessageFlags } from 'discord.js';
|
||||
import { GatewayIntentBits, Events, Interaction, ChatInputCommandInteraction, Guild } from 'discord.js';
|
||||
import { MyClient } from './client';
|
||||
import commandList from './commandList';
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ChatInputCommandInteraction, MessageFlags, SlashCommandBuilder } from '
|
|||
import yroo from './commands/yroo';
|
||||
import wiki from './commands/wiki';
|
||||
import coal from './commands/coal';
|
||||
import userinfo from './commands/userinfo';
|
||||
import version from './commands/version';
|
||||
|
||||
function fakeCommand(name: string, description?: string) {
|
||||
|
|
@ -40,8 +41,8 @@ const commandList = [
|
|||
wiki,
|
||||
coal,
|
||||
version,
|
||||
userinfo,
|
||||
fakeCommand('dict', 'Cerca una parola nel dizionario nassiryota'),
|
||||
fakeCommand('userinfo', 'Mostra informazioni sull\'utente'),
|
||||
fakeCommand('bible', 'Leggi un versetto della Bibbia'),
|
||||
fakeCommand('rllaw', 'Leggi un articolo della legge italiana')
|
||||
];
|
||||
|
|
|
|||
38
src/commands/userinfo.ts
Normal file
38
src/commands/userinfo.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
|
||||
import { ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js';
|
||||
import { findUser } from '../db/users';
|
||||
|
||||
const data = new SlashCommandBuilder()
|
||||
.setName("userinfo")
|
||||
.setDescription("Informazioni su un utente")
|
||||
.addUserOption((o) => o.setName("u")
|
||||
.setDescription("L'utente")
|
||||
.setRequired(true)
|
||||
)
|
||||
;
|
||||
|
||||
async function execute (interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
const user = interaction.options.getUser("u");
|
||||
|
||||
const dbUser = await findUser({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
globalName: user.globalName
|
||||
});
|
||||
|
||||
const uEmbed = new EmbedBuilder()
|
||||
.setTitle(`${user.globalName} (@${user.username})`)
|
||||
.setThumbnail(user.avatarURL())
|
||||
.addFields([
|
||||
{name: 'Bilancio', value: '<bilancio>', inline: true}
|
||||
]);
|
||||
|
||||
await interaction.followUp({
|
||||
embeds: [uEmbed]
|
||||
})
|
||||
}
|
||||
|
||||
export default { data, execute };
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
|
||||
import * as cron from 'node-cron';
|
||||
|
||||
|
||||
//cron.schedule('0 7 * * *', () => {
|
||||
// // TODO send good morning to channel
|
||||
//});
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
Copyright 2025 Sakuragasaki46
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
// TODO
|
||||
|
||||
16
src/db/database.ts
Normal file
16
src/db/database.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import * as schema from "./schema";
|
||||
import { Pool } from "pg";
|
||||
|
||||
const client = new Pool({
|
||||
connectionString: process.env.DATABASE_URL!
|
||||
});
|
||||
|
||||
export const db = drizzle({
|
||||
client,
|
||||
casing: 'snake_case',
|
||||
schema
|
||||
});
|
||||
|
||||
|
||||
38
src/db/schema.ts
Normal file
38
src/db/schema.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
import { sql } from "drizzle-orm";
|
||||
import * as p from "drizzle-orm/pg-core";
|
||||
|
||||
|
||||
export const users = p.pgTable('users', {
|
||||
id: p.serial().primaryKey(),
|
||||
discordId: p.bigint({mode: "bigint"}).unique(),
|
||||
username: p.varchar({length: 34}),
|
||||
displayName: p.varchar({length: 64}),
|
||||
/** reputation values:
|
||||
* 0 = unscreened
|
||||
* 1 = exempt
|
||||
* 2 = suspicious
|
||||
* 3 = dangerous
|
||||
*/
|
||||
reputation: p.smallint().default(0)
|
||||
});
|
||||
|
||||
export const guilds = p.pgTable('guilds', {
|
||||
id: p.serial().primaryKey(),
|
||||
discordId: p.bigint({mode: "bigint"}).unique(),
|
||||
displayName: p.varchar(({length: 80}))
|
||||
// TODO channel IDs
|
||||
});
|
||||
|
||||
export const balances = p.pgTable('balances', {
|
||||
userId: p.integer().references(() => users.id),
|
||||
guildId: p.integer().references(() => guilds.id),
|
||||
balance: p.bigint({mode: "bigint"}).default(sql`0::bigint`),
|
||||
lastMessageHour: p.integer(),
|
||||
}, (t) => [
|
||||
p.primaryKey({
|
||||
columns: [t.userId, t.guildId]
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
37
src/db/users.ts
Normal file
37
src/db/users.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import { db } from "./database";
|
||||
import { users } from "./schema";
|
||||
|
||||
|
||||
export type UserUpdate = {
|
||||
id: string | bigint,
|
||||
username: string
|
||||
globalName: string
|
||||
};
|
||||
|
||||
export type NewUser = typeof users.$inferInsert;
|
||||
|
||||
export async function findUser (userData: UserUpdate) {
|
||||
let user = await db.query.users.findFirst({
|
||||
where: () => eq(users.discordId, BigInt(userData.id))
|
||||
}) as NewUser;
|
||||
|
||||
if (!user) {
|
||||
// upsert the user
|
||||
user = await db.insert(users).values({
|
||||
discordId: BigInt(userData.id),
|
||||
username: userData.username,
|
||||
displayName: userData.globalName,
|
||||
} as NewUser).returning() as NewUser;
|
||||
} else if (
|
||||
user.username !== userData.username ||
|
||||
user.displayName !== userData.globalName
|
||||
) {
|
||||
await db.update(users).set({
|
||||
username: userData.username,
|
||||
displayName: userData.globalName
|
||||
}).where(eq(users.discordId, BigInt(userData.id)))
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
import { config as configDotenv } from 'dotenv';
|
||||
|
||||
|
||||
export default function init (){
|
||||
configDotenv();
|
||||
|
||||
if (!process.env.DATABASE_URL) {
|
||||
throw new Error('DATABASE_URL not set. Cowardly refusing to start up');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
15
src/main.ts
15
src/main.ts
|
|
@ -1,7 +1,20 @@
|
|||
import client from "./bot";
|
||||
|
||||
import init from "./initConfig";
|
||||
|
||||
init();
|
||||
|
||||
import client from "./bot";
|
||||
|
||||
// query TEST
|
||||
import { db } from "./db/database";
|
||||
import { count } from "drizzle-orm";
|
||||
import { users } from "./db/schema";
|
||||
import chalk from "chalk";
|
||||
(async function () {
|
||||
const uCount = await db.select({ count: count() }).from(users);
|
||||
console.log(`Watching over ${chalk.bold(uCount)} users`);
|
||||
})().then(() => { });
|
||||
// END query TEST
|
||||
|
||||
client.login(process.env.TOKEN);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue