Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2334da36d3 | |||
| f71c7b0e34 | |||
| ff6e132ab4 | |||
| 9919e74836 | |||
| 5b98a2e98a | |||
| 3f13fe1ec0 | |||
| 934af0a1e8 |
22 changed files with 1304 additions and 302 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -24,4 +24,4 @@ dist/
|
||||||
.err
|
.err
|
||||||
.vscode
|
.vscode
|
||||||
/run.sh
|
/run.sh
|
||||||
drizzle
|
|
||||||
|
|
|
||||||
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -1,5 +1,19 @@
|
||||||
# Changelog
|
# 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.
|
||||||
|
* Added [Wikicord](https://wikicord.wikioasis.org/) and [Italian Mapping Wiki](https://it.mappingwiki.org/) as sources for `/wiki`.
|
||||||
|
|
||||||
|
## 0.3.1 (February 20, 2026)
|
||||||
|
|
||||||
|
* Updated package-lock.json
|
||||||
|
|
||||||
## 0.3.0 (February 20, 2026)
|
## 0.3.0 (February 20, 2026)
|
||||||
|
|
||||||
* Now the bot may show list of server the bot is in at startup, given the flag `GUILD_DETAIL_SHOW=1` in env.
|
* Now the bot may show list of server the bot is in at startup, given the flag `GUILD_DETAIL_SHOW=1` in env.
|
||||||
|
|
|
||||||
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1091
package-lock.json
generated
1091
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "sknsybot",
|
"name": "sknsybot",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0.b5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
@ -14,15 +14,18 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"discord.js": "^14.14.1",
|
"discord.js": "^14.14.1",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^17.3.1",
|
||||||
"mysql": "^2.18.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"mysql2": "^3.12.0",
|
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
|
"pg": "^8.18.0",
|
||||||
"wikijs": "^6.4.1"
|
"wikijs": "^6.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chalk": "^0.4.31",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
|
"@types/pg": "^8.16.0",
|
||||||
|
"drizzle-kit": "^0.31.9",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./initConfig";
|
|
||||||
|
|
||||||
import { GatewayIntentBits, Events, Interaction, ChatInputCommandInteraction, Guild } from 'discord.js';
|
import { GatewayIntentBits, Events, Interaction, ChatInputCommandInteraction, Guild } from 'discord.js';
|
||||||
import { MyClient } from './client';
|
import { MyClient } from './client';
|
||||||
import commandList from './commandList';
|
import commandList from './commandList';
|
||||||
|
|
@ -43,6 +41,10 @@ client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error(`${chalk.red('Error in command')} ${chalk.bold(`/${commandName}`)}`);
|
console.error(`${chalk.red('Error in command')} ${chalk.bold(`/${commandName}`)}`);
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
|
await interaction.followUp({
|
||||||
|
content: `Errore nel comando /${commandName}. Contattare l'amministratore del bot.`,
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { ChatInputCommandInteraction, MessageFlags, SlashCommandBuilder } from '
|
||||||
import yroo from './commands/yroo';
|
import yroo from './commands/yroo';
|
||||||
import wiki from './commands/wiki';
|
import wiki from './commands/wiki';
|
||||||
import coal from './commands/coal';
|
import coal from './commands/coal';
|
||||||
|
import userinfo from './commands/userinfo';
|
||||||
import version from './commands/version';
|
import version from './commands/version';
|
||||||
|
|
||||||
function fakeCommand(name: string, description?: string) {
|
function fakeCommand(name: string, description?: string) {
|
||||||
|
|
@ -40,8 +41,8 @@ const commandList = [
|
||||||
wiki,
|
wiki,
|
||||||
coal,
|
coal,
|
||||||
version,
|
version,
|
||||||
|
userinfo,
|
||||||
fakeCommand('dict', 'Cerca una parola nel dizionario nassiryota'),
|
fakeCommand('dict', 'Cerca una parola nel dizionario nassiryota'),
|
||||||
fakeCommand('userinfo', 'Mostra informazioni sull\'utente'),
|
|
||||||
fakeCommand('bible', 'Leggi un versetto della Bibbia'),
|
fakeCommand('bible', 'Leggi un versetto della Bibbia'),
|
||||||
fakeCommand('rllaw', 'Leggi un articolo della legge italiana')
|
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 };
|
||||||
|
|
@ -5,11 +5,21 @@ const pageSources = {
|
||||||
'cittadeldank': {
|
'cittadeldank': {
|
||||||
url: 'https://wiki.cittadeldank.it',
|
url: 'https://wiki.cittadeldank.it',
|
||||||
name: 'Città del Dank'
|
name: 'Città del Dank'
|
||||||
|
},
|
||||||
|
'wikicord': {
|
||||||
|
url: 'https://wikicord.wikioasis.org/api.php',
|
||||||
|
name: 'Wikicord'
|
||||||
|
},
|
||||||
|
'mappingwikiit': {
|
||||||
|
url: 'https://it.mappingwiki.org',
|
||||||
|
name: 'Mapping Wiki IT'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageSourcesAuto = [
|
const pageSourcesAuto = [
|
||||||
'cittadeldank'
|
'cittadeldank',
|
||||||
|
'wikicord',
|
||||||
|
'mappingwikiit'
|
||||||
];
|
];
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
const data = new SlashCommandBuilder()
|
||||||
|
|
@ -23,6 +33,8 @@ const data = new SlashCommandBuilder()
|
||||||
.setDescription('Dove guardare')
|
.setDescription('Dove guardare')
|
||||||
.addChoices([
|
.addChoices([
|
||||||
{name: 'Città del Dank', value: 'cittadeldank'},
|
{name: 'Città del Dank', value: 'cittadeldank'},
|
||||||
|
{name: 'Wikicord', value: 'wikicord'},
|
||||||
|
{name: 'Mapping Wiki IT', value: 'mappingwikiit'},
|
||||||
{name: 'Automatico', value: 'auto'}
|
{name: 'Automatico', value: 'auto'}
|
||||||
])
|
])
|
||||||
.setRequired(false)
|
.setRequired(false)
|
||||||
|
|
@ -45,7 +57,13 @@ async function execute (interaction: ChatInputCommandInteraction) {
|
||||||
|
|
||||||
if (siteChoice === 'auto') {
|
if (siteChoice === 'auto') {
|
||||||
for (let site of pageSourcesAuto) {
|
for (let site of pageSourcesAuto) {
|
||||||
pageData = await fetchPageFromSite(site, pageName);
|
try {
|
||||||
|
pageData = await fetchPageFromSite(site, pageName);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Skipping source ${site}: ${error}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (pageData) break;
|
if (pageData) break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -57,7 +75,7 @@ async function execute (interaction: ChatInputCommandInteraction) {
|
||||||
const pageEmbed = new EmbedBuilder()
|
const pageEmbed = new EmbedBuilder()
|
||||||
.setTitle(pageData.title)
|
.setTitle(pageData.title)
|
||||||
.setURL(pageData.url)
|
.setURL(pageData.url)
|
||||||
.setDescription(pageData.summary)
|
.setDescription(pageData.summary || '...')
|
||||||
.setFooter({
|
.setFooter({
|
||||||
text: `Informazioni da ${pageData.origin}`
|
text: `Informazioni da ${pageData.origin}`
|
||||||
});
|
});
|
||||||
|
|
@ -71,7 +89,7 @@ async function execute (interaction: ChatInputCommandInteraction) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await interaction.followUp({
|
await interaction.followUp({
|
||||||
content: `Pagina **${pageName}** non trovata`
|
content: `Pagina **${pageName}** non trovata!`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
34
src/db/database.ts
Normal file
34
src/db/database.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
|
import * as schema from "./schema";
|
||||||
|
import { Pool } from "pg";
|
||||||
|
|
||||||
|
function urlToObj(url: string) {
|
||||||
|
if (!url) {
|
||||||
|
console.warn("DATABASE_URL is not set, expect the database to not connect");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { username, password, hostname, port, pathname } = URL.parse(url);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: username,
|
||||||
|
password,
|
||||||
|
host: hostname,
|
||||||
|
port: +(port || 5432),
|
||||||
|
database: pathname.split('/')[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const client = new Pool({
|
||||||
|
...urlToObj(process.env.DATABASE_URL!)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const db = drizzle({
|
||||||
|
client,
|
||||||
|
// casing: 'snake_case', [DOES NOT WORK ON DRIZZLEKIT]
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
import { config as configDotenv } from 'dotenv';
|
|
||||||
|
|
||||||
|
|
||||||
export default function init (){
|
|
||||||
configDotenv();
|
|
||||||
}
|
|
||||||
|
|
||||||
21
src/main.ts
21
src/main.ts
|
|
@ -1,7 +1,22 @@
|
||||||
import client from "./bot";
|
|
||||||
import init from "./initConfig";
|
|
||||||
|
|
||||||
init();
|
import { configDotenv } from "dotenv";
|
||||||
|
|
||||||
|
configDotenv();
|
||||||
|
|
||||||
|
const client = (await import("./bot")).default;
|
||||||
|
|
||||||
|
|
||||||
|
// query TEST
|
||||||
|
(async function () {
|
||||||
|
// async imports because yes
|
||||||
|
const { db } = await import("./db/database");
|
||||||
|
const { count } = await import("drizzle-orm");
|
||||||
|
const { users } = await import("./db/schema");
|
||||||
|
const chalk = (await import("chalk")).default;
|
||||||
|
const uCount = (await db.select({ count: count() }).from(users))[0].count;
|
||||||
|
console.log(`Watching over ${chalk.bold(uCount)} users`);
|
||||||
|
})().then(() => { });
|
||||||
|
// END query TEST
|
||||||
|
|
||||||
client.login(process.env.TOKEN);
|
client.login(process.env.TOKEN);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ function strToObjArr(s: string | object[]): object[] {
|
||||||
|
|
||||||
export type SectionObject = {title: string, content: string};
|
export type SectionObject = {title: string, content: string};
|
||||||
|
|
||||||
type WithPageId = {pageId: any};
|
|
||||||
|
|
||||||
export class MediaWikiClient {
|
export class MediaWikiClient {
|
||||||
apiUrl: string
|
apiUrl: string
|
||||||
siteName: string | null
|
siteName: string | null
|
||||||
|
|
@ -29,7 +27,7 @@ export class MediaWikiClient {
|
||||||
const wikiClient = wiki({
|
const wikiClient = wiki({
|
||||||
apiUrl: this.apiUrl,
|
apiUrl: this.apiUrl,
|
||||||
origin: null
|
origin: null
|
||||||
});
|
}) as any;
|
||||||
const page = await wikiClient.page(title);
|
const page = await wikiClient.page(title);
|
||||||
const pageId = page.raw.pageid;
|
const pageId = page.raw.pageid;
|
||||||
const pageUrl = page.url();
|
const pageUrl = page.url();
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import init from "./initConfig";
|
|
||||||
|
import { configDotenv } from "dotenv";
|
||||||
|
|
||||||
|
configDotenv();
|
||||||
|
|
||||||
import commandList from "./commandList";
|
import commandList from "./commandList";
|
||||||
import { REST, Routes } from 'discord.js';
|
import { REST, Routes } from 'discord.js';
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
function registerGlobal(rest: REST, clientId: string, commands: any){
|
function registerGlobal(rest: REST, clientId: string, commands: any){
|
||||||
rest.put(Routes.applicationCommands(clientId), { body: commands })
|
rest.put(Routes.applicationCommands(clientId), { body: commands })
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "preserve",
|
||||||
"rootDir": "src/",
|
"rootDir": "src/",
|
||||||
"outDir": "build/",
|
"outDir": "build/",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue