This commit is contained in:
2026-01-29 12:26:13 +01:00
parent ba0f116bc2
commit 6dbcadcaee
79 changed files with 2795 additions and 657 deletions

14
src/core/client.ts Normal file
View File

@@ -0,0 +1,14 @@
/**
* Extended Discord client with bot-specific functionality
*/
import { Client, Collection, type ClientOptions } from "discord.js";
import type { Command } from "../commands/types";
export class BotClient extends Client {
public commands: Collection<string, Command> = new Collection();
constructor(options: ClientOptions) {
super(options);
}
}

59
src/core/config.ts Normal file
View File

@@ -0,0 +1,59 @@
/**
* Application configuration
* Centralizes all environment variables and configuration settings
*/
interface BotConfig {
discord: {
token: string;
};
ai: {
replicateApiToken: string;
model: string;
maxTokens: number;
temperature: number;
};
bot: {
/** Chance of Joel responding without being mentioned (0-1) */
freeWillChance: number;
/** Chance of using memories for responses (0-1) */
memoryChance: number;
/** Minimum time between random user mentions (ms) */
mentionCooldown: number;
/** Chance of mentioning a random user (0-1) */
mentionProbability: number;
};
}
function getEnvOrThrow(key: string): string {
const value = Bun.env[key];
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
function getEnvOrDefault(key: string, defaultValue: string): string {
return Bun.env[key] ?? defaultValue;
}
export const config: BotConfig = {
discord: {
token: getEnvOrThrow("DISCORD_TOKEN"),
},
ai: {
replicateApiToken: getEnvOrThrow("REPLICATE_API_TOKEN"),
model: getEnvOrDefault(
"AI_MODEL",
"lucataco/dolphin-2.9-llama3-8b:ee173688d3b8d9e05a5b910f10fb9bab1e9348963ab224579bb90d9fce3fb00b"
),
maxTokens: parseInt(getEnvOrDefault("AI_MAX_TOKENS", "500")),
temperature: parseFloat(getEnvOrDefault("AI_TEMPERATURE", "1.2")),
},
bot: {
freeWillChance: 0.02,
memoryChance: 0.3,
mentionCooldown: 24 * 60 * 60 * 1000, // 24 hours
mentionProbability: 0.001,
},
};

7
src/core/index.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* Core module exports
*/
export { BotClient } from "./client";
export { config } from "./config";
export { createLogger } from "./logger";

136
src/core/logger.ts Normal file
View File

@@ -0,0 +1,136 @@
/**
* Simple logger with context support and colored output
*/
type LogLevel = "debug" | "info" | "warn" | "error";
// Log level priority (higher = more severe)
const levelPriority: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
};
// Get minimum log level from environment
function getMinLogLevel(): LogLevel {
const envLevel = (process.env.LOG_LEVEL ?? "debug").toLowerCase();
if (envLevel in levelPriority) {
return envLevel as LogLevel;
}
return "debug";
}
const minLogLevel = getMinLogLevel();
// ANSI color codes
const colors = {
reset: "\x1b[0m",
dim: "\x1b[2m",
bold: "\x1b[1m",
// Foreground colors
gray: "\x1b[90m",
white: "\x1b[37m",
cyan: "\x1b[36m",
green: "\x1b[32m",
yellow: "\x1b[33m",
red: "\x1b[31m",
magenta: "\x1b[35m",
} as const;
const levelColors: Record<LogLevel, string> = {
debug: colors.gray,
info: colors.green,
warn: colors.yellow,
error: colors.red,
};
const levelLabels: Record<LogLevel, string> = {
debug: "DEBUG",
info: "INFO ",
warn: "WARN ",
error: "ERROR",
};
/**
* Format a date as "YYYY-MM-DD HH:mm:ss"
*/
function formatDate(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
class Logger {
private context: string;
constructor(context: string) {
this.context = context;
}
private log(level: LogLevel, message: string, data?: unknown): void {
// Skip if below minimum log level
if (levelPriority[level] < levelPriority[minLogLevel]) {
return;
}
const timestamp = formatDate(new Date());
const levelColor = levelColors[level];
const label = levelLabels[level];
// Build colored output
const parts = [
`${colors.dim}${timestamp}${colors.reset}`,
`${levelColor}${label}${colors.reset}`,
`${colors.cyan}[${this.context}]${colors.reset}`,
message,
];
const output = parts.join(" ");
switch (level) {
case "debug":
console.debug(output, data !== undefined ? data : "");
break;
case "info":
console.log(output, data !== undefined ? data : "");
break;
case "warn":
console.warn(output, data !== undefined ? data : "");
break;
case "error":
console.error(output, data !== undefined ? data : "");
break;
}
}
debug(message: string, data?: unknown): void {
this.log("debug", message, data);
}
info(message: string, data?: unknown): void {
this.log("info", message, data);
}
warn(message: string, data?: unknown): void {
this.log("warn", message, data);
}
error(message: string, data?: unknown): void {
this.log("error", message, data);
}
child(context: string): Logger {
return new Logger(`${this.context}:${context}`);
}
}
export function createLogger(context: string): Logger {
return new Logger(context);
}