Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
on: [push]
name: Run Tests
name: Run Tests & Lint
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
tests:
name: Run Tests
name: Run Tests & Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2

- run: bun install
- run: bun lint:check
- run: bun test
11 changes: 0 additions & 11 deletions .prettierrc

This file was deleted.

701 changes: 700 additions & 1 deletion bun.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from "@richardscull/eslint-config";

export default defineConfig({
fileCase: "kebabCase",
ignores: ["**/types/api/**"], // Generated files by hey-api
});
27 changes: 14 additions & 13 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import Database from "bun:sqlite"
import type { IConfig } from "./src/lib/configs/env"
import type { EmbedPresetsUtility } from "./src/utilities/embed-presets.utility"
import type { PaginationUtility } from "./src/utilities/pagination.utility"
import type { ActionStoreUtility } from "./src/utilities/action-store.utility"
import type Database from "bun:sqlite";

import type { IConfig } from "./src/lib/configs/env";
import type { ActionStoreUtility } from "./src/utilities/action-store.utility";
import type { EmbedPresetsUtility } from "./src/utilities/embed-presets.utility";
import type { PaginationUtility } from "./src/utilities/pagination.utility";

declare module "@sapphire/pieces" {
interface Container {
config: IConfig
config: IConfig;
sunrise: {
websocket: WebSocket
}
db: Database
websocket: WebSocket;
};
db: Database;
}
}

declare module "@sapphire/plugin-utilities-store" {
interface Utilities {
embedPresets: EmbedPresetsUtility
actionStore: ActionStoreUtility
pagination: PaginationUtility
embedPresets: EmbedPresetsUtility;
actionStore: ActionStoreUtility;
pagination: PaginationUtility;
}
}

declare module "discord.js" {
interface Client {
guild?: Guild
guild?: Guild;
}
}
4 changes: 2 additions & 2 deletions openapi-ts.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { config } from "./src/lib/configs/env"
import { config } from "./src/lib/configs/env";

export default {
input: `https://api.${config.sunrise.uri}/openapi/v1.json`,
Expand All @@ -14,4 +14,4 @@ export default {
name: "@hey-api/typescript",
},
],
}
};
22 changes: 15 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
"private": true,
"main": "src/index.ts",
"scripts": {
"start": "bun src/index.ts",
"dev": "bun --watch src/index.ts",
"generate:openapi": "dotenv -e .env -- openapi-ts"
},
"devDependencies": {
"@hey-api/openapi-ts": "^0.78.1",
"@types/bun": "latest",
"dotenv-cli": "^8.0.0"
"generate:openapi": "dotenv -e .env -- openapi-ts",
"lint": "eslint --fix",
"lint:check": "eslint",
"start": "bun src/index.ts"
},
"peerDependencies": {
"typescript": "^5"
Expand All @@ -29,5 +26,16 @@
"bun-sqlite-migrations": "^1.0.2",
"discord.js": "^14.21.0",
"fast-average-color-node": "^3.1.0"
},
"devDependencies": {
"@hey-api/openapi-ts": "^0.78.1",
"@richardscull/eslint-config": "^1.0.6",
"@types/bun": "latest",
"dotenv-cli": "^8.0.0",
"eslint": "^9.39.2",
"jiti": "^2.6.1",
"lint-staged": "^16.2.7",
"simple-git-hooks": "^2.13.1",
"typescript": "^5"
}
}
74 changes: 38 additions & 36 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import {
ApplicationCommandRegistries,
container,
LogLevel,
RegisterBehavior,
SapphireClient,
container,
} from "@sapphire/framework"
import { Time } from "@sapphire/time-utilities"
import { GatewayIntentBits } from "discord.js"
import { config } from "./lib/configs/env"
import { client as apiClient } from "./lib/types/api/client.gen"
import type { WebSocketEventType } from "./lib/types/api"
import { db } from "./database"
} from "@sapphire/framework";
import { Time } from "@sapphire/time-utilities";
import { GatewayIntentBits } from "discord.js";

import { db } from "./database";
import { config } from "./lib/configs/env";
import type { WebSocketEventType } from "./lib/types/api";
import { client as apiClient } from "./lib/types/api/client.gen";

export class SunshineClient extends SapphireClient {
private websocketHeartbeatTimeout: NodeJS.Timeout | null = null
private readonly serverApiURI = "api." + config.sunrise.uri
private websocketHeartbeatTimeout: NodeJS.Timeout | null = null;
private readonly serverApiURI = `api.${config.sunrise.uri}`;

public constructor() {
super({
Expand All @@ -26,64 +27,65 @@ export class SunshineClient extends SapphireClient {
enableLoaderTraceLoggings: true,
loadDefaultErrorListeners: false,
loadMessageCommandListeners: true,
})
});

this.init()
this.initWebsocket()
this.init();
this.initWebsocket();
}

private init() {
ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite)
ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite);

apiClient.setConfig({ baseUrl: "https://" + this.serverApiURI })
apiClient.setConfig({ baseUrl: `https://${this.serverApiURI}` });

container.config = config
container.db = db
container.config = config;
container.db = db;
}

private initWebsocket() {
container.sunrise = {
websocket: new WebSocket(`wss://${this.serverApiURI}/ws`),
}
};

container.sunrise.websocket.addEventListener("open", () => {
container.logger.info("Server's Websocket: WebSocket connected!")
container.logger.info("Server's Websocket: WebSocket connected!");

this.websocketHeartbeatTimeout = setInterval(() => {
if (container.sunrise.websocket.readyState === WebSocket.OPEN) {
container.sunrise.websocket.send(JSON.stringify({ type: "ping" }))
container.logger.debug(`Sent heartbeat for server's websocket`)
container.sunrise.websocket.send(JSON.stringify({ type: "ping" }));
container.logger.debug(`Sent heartbeat for server's websocket`);
}
}, 30 * Time.Second)
})
}, 30 * Time.Second);
});

container.sunrise.websocket.addEventListener("error", (e) => {
container.logger.error(`Server's Websocket:`, e)
})
container.logger.error(`Server's Websocket:`, e);
});

container.sunrise.websocket.addEventListener("close", (e) => {
if (this.websocketHeartbeatTimeout) clearTimeout(this.websocketHeartbeatTimeout)
if (this.websocketHeartbeatTimeout)
clearTimeout(this.websocketHeartbeatTimeout);

container.logger.error(
`Server's Websocket: Connection Closed: "${e.reason}". Trying to reconnect`,
)
);

setTimeout(this.initWebsocket.bind(this), 5 * Time.Second)
})
setTimeout(this.initWebsocket.bind(this), 5 * Time.Second);
});

container.sunrise.websocket.addEventListener("message", (e) => {
const { data: dataRaw } = e
const { data: dataRaw } = e;

const data = JSON.parse(dataRaw) as {
type: WebSocketEventType
data: any
}
type: WebSocketEventType;
data: any;
};

if (!data.type || !data.data) {
return
return;
}

container.client.ws.emit(data.type, data.data)
})
container.client.ws.emit(data.type, data.data);
});
}
}
6 changes: 3 additions & 3 deletions src/commands/meow.command.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Command } from "@sapphire/framework"
import { Command } from "@sapphire/framework";

export class MeowCommand extends Command {
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) => builder.setName("meow").setDescription("meow?"))
registry.registerChatInputCommand(builder => builder.setName("meow").setDescription("meow?"));
}

public override chatInputRun(interaction: Command.ChatInputCommandInteraction) {
return interaction.reply("meow! 😺")
return interaction.reply("meow! 😺");
}
}
52 changes: 26 additions & 26 deletions src/commands/osu.command.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Command } from "@sapphire/framework"
import { Subcommand } from "@sapphire/plugin-subcommands"
import { ApplyOptions } from "@sapphire/decorators"
import { SlashCommandSubcommandBuilder } from "discord.js"

import * as profile from "../subcommands/osu/profile.subcommand"
import * as score from "../subcommands/osu/score.subcommand"
import * as link from "../subcommands/osu/link.subcommand"
import * as unlink from "../subcommands/osu/unlink.subcommand"
import * as recentScore from "../subcommands/osu/recent-score.subcommand"
import * as scores from "../subcommands/osu/scores.subcommand"
import { ApplyOptions } from "@sapphire/decorators";
import type { Command } from "@sapphire/framework";
import { Subcommand } from "@sapphire/plugin-subcommands";
import { SlashCommandSubcommandBuilder } from "discord.js";

import * as link from "../subcommands/osu/link.subcommand";
import * as profile from "../subcommands/osu/profile.subcommand";
import * as recentScore from "../subcommands/osu/recent-score.subcommand";
import * as score from "../subcommands/osu/score.subcommand";
import * as scores from "../subcommands/osu/scores.subcommand";
import * as unlink from "../subcommands/osu/unlink.subcommand";

const rawModules = [
{ add: profile.addProfileSubcommand, run: profile.chatInputRunProfileSubcommand },
Expand All @@ -17,44 +17,44 @@ const rawModules = [
{ add: unlink.addUnlinkSubcommand, run: unlink.chatInputRunUnlinkSubcommand },
{ add: recentScore.addRecentScoreSubcommand, run: recentScore.chatInputRunRecentScoreSubcommand },
{ add: scores.addScoresSubcommand, run: scores.chatInputRunScoresSubcommand },
]
];

const subcommandModules = rawModules.map((cmd) => {
const builder = new SlashCommandSubcommandBuilder()
const subcommandName = cmd.add(builder).name
const builder = new SlashCommandSubcommandBuilder();
const subcommandName = cmd.add(builder).name;

const camelName = subcommandName.replace(/-(\w)/g, (_, letter) => letter.toUpperCase())
return { ...cmd, name: subcommandName, camelName }
})
const camelName = subcommandName.replaceAll(/-(\w)/g, (_, letter) => letter.toUpperCase());
return { ...cmd, name: subcommandName, camelName };
});

const subcommandOptions = subcommandModules.map((cmd) => ({
const subcommandOptions = subcommandModules.map(cmd => ({
name: cmd.name,
chatInputRun: cmd.camelName,
}))
}));

@ApplyOptions<Subcommand.Options>({
subcommands: subcommandOptions,
})
export class OsuCommand extends Subcommand {
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) => {
const osu = builder.setName("osu").setDescription("Server's commands")
const osu = builder.setName("osu").setDescription("Server's commands");

for (const cmd of subcommandModules) {
osu.addSubcommand(cmd.add)
osu.addSubcommand(cmd.add);
}

return osu
})
return osu;
});
}

constructor(context: Subcommand.LoaderContext, options: Subcommand.Options) {
super(context, options)
super(context, options);

for (const cmd of subcommandModules) {
;(this as any)[cmd.name] = async (interaction: Subcommand.ChatInputCommandInteraction) => {
return (cmd.run as any).call(this, interaction)
}
return (cmd.run as any).call(this, interaction);
};
}
}
}
Loading