Skip to content

Commit

Permalink
feat: ""working"" it runs. that's all. nothing fancy. ill work on thi…
Browse files Browse the repository at this point in the history
…s later when i can... hopefully.
  • Loading branch information
flleeppyy committed Jul 21, 2024
1 parent 9f2c838 commit 20a2091
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 75 deletions.
2 changes: 0 additions & 2 deletions .github/funding.yml

This file was deleted.

7 changes: 5 additions & 2 deletions config.example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ const config: HibikiConfig = {
defaultLocale: "en-GB",
clientOptions: {},
githubToken: "",
guildId: "",
forumId: "",
githubManagerConfig: {
guildId: "",
forumId: "",
repoString: "",
},

colours: {
primary: 0x64_8f_ff,
Expand Down
46 changes: 46 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ks from "eslint-config-ks";

export default ks(
{
prettier: true,
json: true,
typescript: true,
project: ["tsconfig.json"],
},
[
{
ignores: ["src/utils/dbSchemaParser.ts"],
files: ["src/**/*.ts"],
rules: {
"prettier/prettier": "warn",
"security/detect-object-injection": "off",
"import/no-named-as-default-member": "off",
"max-depth": ["error", 6],
"unicorn/prefer-ternary": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"@typescript-eslint/no-unnecessary-condition": "warn",
"unicorn/no-await-expression-member": "warn",
"unicorn/no-null": "off",
"@typescript-eslint/naming-convention": [
"warn",
{
format: ["PascalCase"],
trailingUnderscore: "allow",
selector: "typeLike",
},
],
},
},
],
);
37 changes: 26 additions & 11 deletions src/classes/Client.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-misused-promises */
/**
* @file Client
* @description Connects to Discord and handles global functions
*/

import type { HibikiCommand } from "./Command.js";
import type { HibikiEvent } from "./Event.js";
import type { HibikiLogger } from "./Logger.js";
import path from "node:path";
import url from "node:url";

import { Client, Collection, GatewayIntentBits } from "discord.js";

import { loadCommands, loadEvents, registerSlashCommands } from "../utils/loader.js";
import { logger } from "../utils/logger.js";
import type { HibikiCommand } from "./Command.js";
import type { HibikiEvent } from "./Event.js";
import { GithubManager } from "./GithubManager.js";
import { HibikiLocaleSystem } from "./LocaleSystem.js";
import { Client, Collection, GatewayIntentBits } from "discord.js";
import path from "node:path";
import url from "node:url";
import type { HibikiLogger } from "./Logger.js";

// Are we being sane or not?
const IS_PRODUCTION = process.env.NODE_ENV === "production";
Expand Down Expand Up @@ -40,11 +45,16 @@ export class HibikiClient extends Client {
// Hibiki's locale system
readonly localeSystem: HibikiLocaleSystem;

// Github manager
readonly githubManager: GithubManager;

constructor(config: HibikiConfig) {
super({ ...config.clientOptions, intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] });

this.config = config;

this.githubManager = new GithubManager(this, this.config.githubManagerConfig.repoString);

// Creates a new Locale System engine
this.localeSystem = new HibikiLocaleSystem(LOCALES_DIRECTORY, this.config.defaultLocale);
}
Expand All @@ -56,7 +66,7 @@ export class HibikiClient extends Client {
public init() {
try {
// Logs into the Discord API, I guess
this.login(this.config.token);
void this.login(this.config.token);

// Wait for the initial login before loading modules
this.once("ready", async () => {
Expand All @@ -65,12 +75,17 @@ export class HibikiClient extends Client {
await loadEvents(this, EVENTS_DIRECTORY);
await loadEvents(this, LOGGERS_DIRECTORY, true);

await this.githubManager.init({
forumId: this.config.githubManagerConfig.forumId,
guildId: this.config.githubManagerConfig.guildId,
});

// Registers commands
registerSlashCommands(this);
await registerSlashCommands(this);

logger.info(`Logged in as ${this.user?.tag} in ${this.guilds.cache.size} guilds on shard #${this.shard?.ids[0]}`);
logger.info(`${this.commands.size} commands loaded on shard #${this.shard?.ids[0]}`);
logger.info(`${this.events.size} events loaded on shard #${this.shard?.ids[0]}`);
logger.info(`Logged in as ${this.user?.tag} in ${this.guilds.cache.size} guilds`);
logger.info(`${this.commands.size} commands loaded`);
logger.info(`${this.events.size} events loaded`);
});
} catch (error) {
logger.error(`An error occured while initializing Hibiki: ${error}`);
Expand Down
11 changes: 8 additions & 3 deletions src/classes/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
* @module HibikiCommand
*/

import type { HibikiClient } from "./Client.js";
import type { APIApplicationCommandOption } from "discord-api-types/v10";
import type { ChatInputCommandInteraction, Message } from "discord.js";
import type { APIApplicationCommandOption } from "discord-api-types/v10";

import type { HibikiClient } from "./Client.js";

/**
* Hibiki command data in JSON form for slash command registration
Expand Down Expand Up @@ -63,7 +64,11 @@ export abstract class HibikiCommand {
* @param category The command category (matches the directory)
*/

protected constructor(protected bot: HibikiClient, public name: string, public category: string) {}
protected constructor(
protected bot: HibikiClient,
public name: string,
public category: string,
) {}

/**
* Converts a Hibiki command to Discord API-compatible JSON
Expand Down
5 changes: 4 additions & 1 deletion src/classes/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export abstract class HibikiEvent {
* @param name The event name, matching the filename
*/

constructor(protected bot: HibikiClient, public name: string) {}
constructor(
protected bot: HibikiClient,
public name: string,
) {}

/**
* Runs an event
Expand Down
47 changes: 47 additions & 0 deletions src/classes/GithubManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Octokit } from "@octokit/rest";
import { ChannelType, ForumChannel, Guild } from "discord.js";
import { hibikiUserAgent } from "utils/constants.js";

import { HibikiClient } from "./Client.js";

export class GithubManager {
private bot: HibikiClient;

private octokit: Octokit;

repoOwner: string;
repo: string;

forumChannel?: ForumChannel;
guild?: Guild;

/**
*
* @param bot Hibiki client
* @param repo Repository path, example: Blahblah/my-repo
*/
constructor(bot: HibikiClient, repo: string) {
this.bot = bot;

this.repoOwner = repo.split("/")[0];
this.repo = repo.split("/")[1];

this.octokit = new Octokit({
userAgent: hibikiUserAgent,
});
}

public init(config: { guildId: string; forumId: string }) {
// Resolve the guild!
const guild = this.bot.guilds.resolve(config.guildId);
if (!guild) throw new Error("Failed to resolve guild " + config.guildId + "!");

const forum = guild.channels.resolve(config.forumId);

if (!forum) throw new Error("Failed to resolve forum channel " + config.forumId + "!");
if (forum.type !== ChannelType.GuildForum) throw new Error("The channel " + forum.name + "is NOT a forum channel!");

this.guild = guild;
this.forumChannel = forum;
}
}
64 changes: 40 additions & 24 deletions src/classes/LocaleSystem.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/**
* @file LocaleSystem
* @description Handles loading, parsing, and getting locales
* @module HibikiLocaleSystem
*/

// We aren't handling user input except in rare cases.
/* eslint-disable security/detect-non-literal-regexp */

// Same reason as previous.
/* eslint-disable security/detect-non-literal-fs-filename */

/**
* @TODO: https://discord.com/developers/docs/interactions/application-commands#localization
* Localise our slash command stuff...
*/

import fs, { type PathLike } from "node:fs";

import type { getString, HibikiLocaleStrings } from "../typings/locales.js";
import type { HibikiClient } from "./Client.js";
import type { PathLike } from "node:fs";
import { logger } from "../utils/logger.js";
import fs from "node:fs";
import type { HibikiClient } from "./Client.js";

export class HibikiLocaleSystem {
// A JSON object containing locale names & strings
readonly locales: { [k: string]: { [k: string]: any } } = {};
// Setting this to anything other than any just creates more type erorrs than I'd like to deal with. TODO: Deal with these types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly locales: { [k: string]: { [k: string]: any } | undefined } = {};

// The default locale to fall back to
readonly defaultLocale: HibikiLocaleCode;
Expand All @@ -40,9 +49,15 @@ export class HibikiLocaleSystem {
* @param args Any args to pass to the string
*/

public getLocale(language: HibikiLocaleCode, fieldName: HibikiLocaleStrings, args?: { [x: string]: any }): string {
public getLocale(
language: HibikiLocaleCode,
fieldName: HibikiLocaleStrings,
// TODO: change to string | number | undefined, and deal with types accordingly for plurals
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: { [x: string]: any },
): string {
const category = fieldName.split(".");
let output = "";
let output: string | undefined = "";

// Gets the string output using the specified locale
output = this._findLocaleString(language, fieldName, category);
Expand All @@ -56,7 +71,7 @@ export class HibikiLocaleSystem {
const isDefault = language === this.defaultLocale;

// Throws the error with what field is missing
if (language) logger.warn(`${fieldName} is missing in the string table for ${language}.`);
logger.warn(`${fieldName} is missing in the string table for ${language}.`);
return isDefault ? fieldName : this.getLocale(this.defaultLocale, fieldName, args);
}

Expand All @@ -77,7 +92,7 @@ export class HibikiLocaleSystem {
const isOptional = optionalArgumentRegex.exec(output);

// Optional argument support
if (isOptional) output = output.replace(isOptional[1], args[argument] === undefined ? "" : isOptional[2]);
if (isOptional) output = output.replace(isOptional[1], args[argument] === undefined ? "" : isOptional[2].toString());
output = output.replace(argumentRegex, args[argument]);

// Checks to see if there are any plurals in the string
Expand All @@ -94,11 +109,11 @@ export class HibikiLocaleSystem {

// Fixes up plural
output = output.replace(plurals[0], plural);
} else if (!plurals) {
} else {
// Replaces dummy arguments with provided ones
output = output.replace(`{${argument}}`, args[argument]);
}
};
}
}

// A regex to check to see if any optional arguments were provided
Expand All @@ -118,15 +133,17 @@ export class HibikiLocaleSystem {
*/

public getLocaleFunction(language: HibikiLocaleCode): getString {
return (fieldName: HibikiLocaleStrings, args?: Record<string, any>) => this.getLocale(language, fieldName, args);
return (fieldName: HibikiLocaleStrings, args?: Record<string, unknown>) => this.getLocale(language, fieldName, args);
}

/**
* Returns what locale a user uses
* Returns what locale a user uses.
* Originally intended to do a database check, not sure what to do with it,
* so it will be left alone.
* @param user The User ID to search for a set locale for
*/

public async getUserLocale(user: DiscordSnowflake, bot: HibikiClient) {
public getUserLocale(_user: DiscordSnowflake, _bot: HibikiClient) {
return this.defaultLocale;
}

Expand All @@ -140,13 +157,13 @@ export class HibikiLocaleSystem {

for (const file of files) {
// Scans each locale file and loads it
if (file.isDirectory()) this._loadLocales(`${path}/${file.name}`);
if (file.isDirectory()) this._loadLocales(`${path.toString()}/${file.name}`);
else if (file.isFile()) {
// Reads the actual locale file
const subfile = fs.readFileSync(`${path}/${file.name}`)?.toString();
const subfile = fs.readFileSync(`${path.toString()}/${file.name}`).toString();

// Creates an empty locale object
const localeObject: Record<string, any> = {};
const localeObject: Record<string, object | string> = {};

// Parses the locale's JSON
const data: Record<string, string> = JSON.parse(subfile);
Expand All @@ -159,8 +176,7 @@ export class HibikiLocaleSystem {

// Reads each string in the category
for (const string of Object.entries(locale[1])) {
if ((string as [string, string])[1].length > 0)
localeObject[locale[0]][string[0]] = string[1];
if ((string as [string, string])[1].length > 0) (localeObject[locale[0]] as Record<string, unknown>)[string[0]] = string[1];
}
} else {
// Replaces empty strings
Expand All @@ -181,12 +197,12 @@ export class HibikiLocaleSystem {
* @param category The categories to search in
*/

private _findLocaleString(language: HibikiLocaleCode, fieldName: HibikiLocaleStrings, category: string[]) {
if (!this.locales?.[language]) return;
let output;
private _findLocaleString(language: HibikiLocaleCode, fieldName: HibikiLocaleStrings, category: string[]): string | undefined {
if (!this.locales[language]) return;
let output: string | undefined;

// Attempts to find the string if the category isn't provided
if (!this.locales?.[language]?.[category[0]] && !this.locales?.[fieldName]) {
if (!this.locales[language][category[0]] && !this.locales[fieldName]) {
// Looks through each language
for (const localeCategory of Object.getOwnPropertyNames(this.locales[language])) {
// Looks through the categories
Expand All @@ -196,10 +212,10 @@ export class HibikiLocaleSystem {
}

// Sets the output if the category exists
} else if (this.locales?.[language]?.[category[0]] && this.locales?.[language]?.[category[0]]?.[category[1]]) {
} else if (this.locales[language][category[0]] && this.locales[language][category[0]]?.[category[1]]) {
output = this.locales[language][category[0]][category[1]];
// Sets the locale if no category exists
} else if (this.locales?.[language]?.[fieldName]) {
} else if (this.locales[language][fieldName]) {
output = this.locales[language][fieldName];
}

Expand Down
Loading

0 comments on commit 20a2091

Please sign in to comment.