feat: initial commit

This commit is contained in:
2024-12-09 00:59:16 +01:00
commit e4c0d7a901
28 changed files with 482 additions and 0 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
dist
.env
.git
.sern
db

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/node_modules
/dist
.env
.sern/
/db

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"useTabs": false,
"printWidth": 800,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "es5",
"semi": true
}

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"editor.tabSize": 2,
"editor.detectIndentation": false,
"editor.insertSpaces": true,
}

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM oven/bun:1-alpine
WORKDIR /app
COPY . .
RUN bun install
RUN bun run docker:build
CMD ["bun", "start"]

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
# How to use
1.) Build
```
npm run build
```
2.) Run
```
node .
```
3.) Publish
```
npm run commands:publish
```

BIN
bun.lockb Executable file

Binary file not shown.

10
drizzle.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'sqlite', // 'mysql' | 'sqlite' | 'turso'
dbCredentials: {
url: './db/sqlite.db',
},
schema: './src/db/schema.ts',
out: './src/db/migrations',
});

View File

@@ -0,0 +1,6 @@
CREATE TABLE `birthday` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`date` text NOT NULL,
`userId` text NOT NULL
);

View File

@@ -0,0 +1 @@
ALTER TABLE `birthday` RENAME COLUMN "name" TO "authorId";

View File

@@ -0,0 +1,56 @@
{
"version": "6",
"dialect": "sqlite",
"id": "27b25008-8dd3-4ef6-992b-8586dd7fb4eb",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"birthday": {
"name": "birthday",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@@ -0,0 +1,58 @@
{
"version": "6",
"dialect": "sqlite",
"id": "afeb51df-0d38-42d3-a4f4-2f33045ecf7e",
"prevId": "27b25008-8dd3-4ef6-992b-8586dd7fb4eb",
"tables": {
"birthday": {
"name": "birthday",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"authorId": {
"name": "authorId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {
"\"birthday\".\"name\"": "\"birthday\".\"authorId\""
}
},
"internal": {
"indexes": {}
}
}

View File

@@ -0,0 +1,20 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1733584433042,
"tag": "0000_overconfident_rattler",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1733585283432,
"tag": "0001_steep_liz_osborn",
"breakpoints": true
}
]
}

44
package.json Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "ezbd",
"version": "1.0.0",
"private": true,
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "sern build && bun copy:json",
"docker:build": "mkdir -p db/kv && bun run build && bun db:migrate",
"copy:json": "copyfiles -u 1 src/**/*.json dist/",
"start": "bun run ./src/index.ts",
"dev": "bun run build && bun run ./src/index.ts",
"install": "sern build",
"commands:publish": "sern commands publish",
"db:generate": "bun drizzle-kit generate --dialect sqlite --schema ./src/db/schema.ts",
"db:migrate": "bun run ./src/db/migrate.ts",
"db:all": "bun db:generate && bun db:migrate"
},
"keywords": [
"typescript",
"sern",
"discord.js"
],
"dependencies": {
"@sern/cli": "^1.3.3",
"@sern/handler": "^4.0.0",
"@sern/publisher": "^1.1.1",
"discord.js": "latest",
"dotenv": "^16.3.1",
"drizzle-orm": "^0.37.0",
"unstorage": "^1.13.1"
},
"devDependencies": {
"@types/bun": "^1.1.14",
"@types/node": "^17.0.25",
"copyfiles": "^2.4.1",
"drizzle-kit": "^0.29.1",
"typescript": "^5.0"
},
"type": "module",
"trustedDependencies": [
"@parcel/watcher"
]
}

7
sern.config.json Normal file
View File

@@ -0,0 +1,7 @@
{
"language": "typescript",
"paths": {
"base": "src",
"commands": "commands"
}
}

70
src/commands/add.ts Normal file
View File

@@ -0,0 +1,70 @@
import { commandModule, CommandType } from '@sern/handler';
import { publishConfig } from '@sern/publisher';
import { ApplicationCommandOptionType } from 'discord.js';
import { birthdayTable } from '../db/schema';
import getDates from '../util/getDates';
import { and, eq } from 'drizzle-orm';
export default commandModule({
type: CommandType.Slash,
plugins: [
publishConfig({
contexts: [0, 1, 2],
integrationTypes: ['Guild', 'User'],
}),
],
description: 'Add a user to your database',
options: [
{
name: 'user',
type: ApplicationCommandOptionType.User,
description: 'The user you want to add',
required: true,
},
{
name: 'birthday',
type: ApplicationCommandOptionType.String,
description: 'Day and month',
required: true,
autocomplete: true,
command: {
onEvent: [],
execute: async (ctx) => {
const autocomplete = ctx.options.getFocused();
const choices = getDates()
.filter((d) => d.startsWith(autocomplete))
.slice(0, 25);
ctx.respond(choices.map((c) => ({ name: c, value: c })));
},
},
},
],
//alias : [],
execute: async (ctx, args) => {
try {
const db = args.deps['drizzle'];
const user = ctx.interaction.options.getUser('user', true);
const birthday = ctx.interaction.options.getString('birthday', true);
if (!getDates().includes(birthday)) {
ctx.reply({ content: 'Invalid date. Please choose a correct one from the autocomplete!', ephemeral: true });
return;
}
if (
(
await db
.select()
.from(birthdayTable)
.where(and(eq(birthdayTable.authorId, ctx.userId), eq(birthdayTable.userId, user.id)))
).length > 0
) {
return ctx.reply({ content: 'User already exists!', ephemeral: true });
}
await db.insert(birthdayTable).values({ authorId: ctx.userId, userId: user.id, date: birthday }).execute();
ctx.reply({ content: `Added ${user.username} to the database. Date will be ${birthday}`, ephemeral: true });
} catch (e) {
console.error(e);
}
},
});

13
src/config.ts Normal file
View File

@@ -0,0 +1,13 @@
//CONFIG FILE: export only data here and do not cause side effects. Feel free to add your own configuration to this file.
//commands directory. REQUIRED
export const commands = './dist/commands'
// events directory.
// export const events = './dist/events'
// schedule tasks and declare them here
export const tasks = './dist/tasks'
// defaultPrefix: if omitted, sern will disable all text/prefix commands
// export const defaultPrefix = '?'

8
src/db/index.ts Normal file
View File

@@ -0,0 +1,8 @@
import { drizzle } from 'drizzle-orm/bun-sqlite';
import { Database } from 'bun:sqlite';
import * as schema from './schema'
const client = new Database('./db/sqlite.db');
const db = drizzle({ client, schema });
export { db };

6
src/db/kv.ts Normal file
View File

@@ -0,0 +1,6 @@
import { createStorage } from "unstorage";
import fsDriver from "unstorage/drivers/fs";
export const kv = createStorage({
driver: fsDriver({ base: "./db/kv" }),
});

8
src/db/migrate.ts Normal file
View File

@@ -0,0 +1,8 @@
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { Database } from "bun:sqlite";
const sqlite = new Database("./db/sqlite.db");
const db = drizzle(sqlite);
await migrate(db, { migrationsFolder: "./drizzle" });

8
src/db/schema.ts Normal file
View File

@@ -0,0 +1,8 @@
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const birthdayTable = sqliteTable("birthday", {
id: int().primaryKey({ autoIncrement: true }),
userId: text().notNull(),
date: text().notNull(),
authorId: text().notNull(),
});

25
src/dependencies.d.ts vendored Normal file
View File

@@ -0,0 +1,25 @@
/**
* This file serves as intellisense for sern projects.
* Types are declared here for dependencies to function properly
* Service(s) api rely on this file to provide a better developer experience.
*/
import type { CoreDependencies } from '@sern/handler';
import type { Client } from 'discord.js';
import type { Publisher } from '@sern/publisher';
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
import type { Storage, StorageValue } from 'unstorage';
/**
* Note: You usually would not need to modify this unless there is an urgent need to break the contracts provided.
* You would need to modify this to add your custom Services, however.
*/
declare global {
interface Dependencies extends CoreDependencies {
'@sern/client': Client;
publisher: Publisher;
'drizzle': BunSQLiteDatabase;
'cache': Storage<StorageValue>;
}
}
export {};

31
src/index.ts Normal file
View File

@@ -0,0 +1,31 @@
import 'dotenv/config';
import * as config from './config.js';
import { Client, GatewayIntentBits } from 'discord.js';
import { Sern, makeDependencies } from '@sern/handler';
import { Publisher } from '@sern/publisher';
import { db } from './db/index.js';
import { kv } from './db/kv.js';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
],
});
/**
* Where all of your dependencies are composed.
* '@sern/client' is usually your Discord Client.
* Use this function to access all of your dependencies.
* This is used for external event modules as well
*/
await makeDependencies(({ add }) => {
add('@sern/client', client);
add('publisher', (deps) => new Publisher(deps['@sern/modules'], deps['@sern/emitter'], deps['@sern/logger']!));
add('drizzle', db);
add('cache', kv);
});
//View docs for all options
Sern.init(config);
await client.login();

35
src/tasks/bdDm.ts Normal file
View File

@@ -0,0 +1,35 @@
import { scheduledTask } from '@sern/handler';
import { birthdayTable } from '../db/schema';
import currentDate from '../util/currentDate';
import { eq } from 'drizzle-orm';
// TODO: optimize
export default scheduledTask({
// on production, run every 2 hours
// on development, run every minute
trigger: process.env.NODE_ENV === 'production' ? '0 */2 * * *' : '* * * * *',
async execute(ctx, sdt) {
const client = sdt.deps['@sern/client'];
const db = sdt.deps['drizzle'];
const kv = sdt.deps['cache'];
const getAllSent = await kv.getKeys(`sent`);
for (const sent of getAllSent) {
const get = (await kv.get(sent))?.toString();
if (get !== currentDate()) {
await kv.del(sent);
}
}
const bdList = await db.select().from(birthdayTable).where(eq(birthdayTable.date, currentDate()));
for (const bd of bdList) {
const kvKey = `sent:${bd.authorId}:${bd.userId}`;
if (await kv.hasItem(kvKey)) continue;
const author = await client.users.fetch(bd.authorId);
const user = await client.users.fetch(bd.userId);
await kv.set(`sent:${bd.authorId}:${bd.userId}`, currentDate());
await author.send(`Today is ${user.username}'s birthday! (<@${user.id}>)`);
}
},
});

4
src/util/currentDate.ts Normal file
View File

@@ -0,0 +1,4 @@
export default function currentDate() {
const date = new Date();
return `${date.getDate()}-${date.getMonth() + 1}`;
}

1
src/util/dates.json Normal file
View File

@@ -0,0 +1 @@
["1-1","2-1","3-1","4-1","5-1","6-1","7-1","8-1","9-1","10-1","11-1","12-1","13-1","14-1","15-1","16-1","17-1","18-1","19-1","20-1","21-1","22-1","23-1","24-1","25-1","26-1","27-1","28-1","29-1","30-1","31-1","1-2","2-2","3-2","4-2","5-2","6-2","7-2","8-2","9-2","10-2","11-2","12-2","13-2","14-2","15-2","16-2","17-2","18-2","19-2","20-2","21-2","22-2","23-2","24-2","25-2","26-2","27-2","28-2","1-3","2-3","3-3","4-3","5-3","6-3","7-3","8-3","9-3","10-3","11-3","12-3","13-3","14-3","15-3","16-3","17-3","18-3","19-3","20-3","21-3","22-3","23-3","24-3","25-3","26-3","27-3","28-3","29-3","30-3","31-3","1-4","2-4","3-4","4-4","5-4","6-4","7-4","8-4","9-4","10-4","11-4","12-4","13-4","14-4","15-4","16-4","17-4","18-4","19-4","20-4","21-4","22-4","23-4","24-4","25-4","26-4","27-4","28-4","29-4","30-4","1-5","2-5","3-5","4-5","5-5","6-5","7-5","8-5","9-5","10-5","11-5","12-5","13-5","14-5","15-5","16-5","17-5","18-5","19-5","20-5","21-5","22-5","23-5","24-5","25-5","26-5","27-5","28-5","29-5","30-5","31-5","1-6","2-6","3-6","4-6","5-6","6-6","7-6","8-6","9-6","10-6","11-6","12-6","13-6","14-6","15-6","16-6","17-6","18-6","19-6","20-6","21-6","22-6","23-6","24-6","25-6","26-6","27-6","28-6","29-6","30-6","1-7","2-7","3-7","4-7","5-7","6-7","7-7","8-7","9-7","10-7","11-7","12-7","13-7","14-7","15-7","16-7","17-7","18-7","19-7","20-7","21-7","22-7","23-7","24-7","25-7","26-7","27-7","28-7","29-7","30-7","31-7","1-8","2-8","3-8","4-8","5-8","6-8","7-8","8-8","9-8","10-8","11-8","12-8","13-8","14-8","15-8","16-8","17-8","18-8","19-8","20-8","21-8","22-8","23-8","24-8","25-8","26-8","27-8","28-8","29-8","30-8","31-8","1-9","2-9","3-9","4-9","5-9","6-9","7-9","8-9","9-9","10-9","11-9","12-9","13-9","14-9","15-9","16-9","17-9","18-9","19-9","20-9","21-9","22-9","23-9","24-9","25-9","26-9","27-9","28-9","29-9","30-9","1-10","2-10","3-10","4-10","5-10","6-10","7-10","8-10","9-10","10-10","11-10","12-10","13-10","14-10","15-10","16-10","17-10","18-10","19-10","20-10","21-10","22-10","23-10","24-10","25-10","26-10","27-10","28-10","29-10","30-10","31-10","1-11","2-11","3-11","4-11","5-11","6-11","7-11","8-11","9-11","10-11","11-11","12-11","13-11","14-11","15-11","16-11","17-11","18-11","19-11","20-11","21-11","22-11","23-11","24-11","25-11","26-11","27-11","28-11","29-11","30-11","1-12","2-12","3-12","4-12","5-12","6-12","7-12","8-12","9-12","10-12","11-12","12-12","13-12","14-12","15-12","16-12","17-12","18-12","19-12","20-12","21-12","22-12","23-12","24-12","25-12","26-12","27-12","28-12","29-12","30-12","31-12"]

12
src/util/getDates.ts Normal file
View File

@@ -0,0 +1,12 @@
import Dates from './dates.json';
export default function getDates(isAmerican = false) {
if (isAmerican) {
return Dates.map(d => {
const [day, month] = d.split('-');
return `${month}-${day}`;
})
} else {
return Dates;
}
}

9
tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "./.sern/tsconfig.json",
"compilerOptions": {
"moduleResolution": "node",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
},
"include": ["src/**/*.json"],
}