Skip to content
Open
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dev:tsc": "chokidar \"src/**/*.ts\" -c \"pnpm build\"",
"dev:nodemon": "nodemon --watch built --ext js --delay 1500ms built/index.js",
"build": "tspc",
"test": "jest",
"test": "pnpm -s build && node --test test/*.test.mjs",
"lint": "biome lint src --write",
"format": "biome format src --write",
"preinstall": "npx only-allow pnpm"
Expand Down
98 changes: 62 additions & 36 deletions src/config-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,39 @@ type Config = {
[key: string]: any; // その他の設定項目
};

function loadConfigTemplate(): Config {
const templatePath = resolve('./example.config.toml');
export type ConfigCheckLogger = Pick<typeof console, 'log' | 'error'>;

export type StartupConfigCheckOptions = {
cwd?: string;
configPath?: string;
templatePath?: string;
logger?: ConfigCheckLogger;
exit?: (code: number) => never;
};

function resolvePathFromOptions(
opts: StartupConfigCheckOptions | undefined,
kind: 'config' | 'template'
): string {
const cwd = opts?.cwd ?? process.cwd();
if (kind === 'config') return opts?.configPath ?? resolve(cwd, 'config.toml');
return opts?.templatePath ?? resolve(cwd, 'example.config.toml');
}

function loadConfigTemplate(options?: StartupConfigCheckOptions): Config {
const templatePath = resolvePathFromOptions(options, 'template');

if (!existsSync(templatePath)) {
throw new Error(
'example.config.toml が見つかりません。テンプレートファイルが必要です。'
`${templatePath} が見つかりません。テンプレートファイルが必要です。`
);
}

try {
const templateData = readFileSync(templatePath, 'utf8');
return TOML.parse(templateData) as Config;
} catch (error) {
throw new Error(`example.config.toml の読み込みに失敗しました: ${error}`);
throw new Error(`${templatePath} の読み込みに失敗しました: ${error}`);
}
}

Expand Down Expand Up @@ -99,61 +118,68 @@ function formatValuePreview(value: any): string {
/**
* 設定ファイルの差分チェックと通知
*/
export function checkMissingConfigKeys(userConfig: Config): Config {
console.log('🔍 設定ファイルチェックを開始します');
export function checkMissingConfigKeys(
userConfig: Config,
options?: StartupConfigCheckOptions
): Config {
const logger = options?.logger ?? console;
logger.log('🔍 設定ファイルチェックを開始します');

try {
// テンプレートを読み込み
const template = loadConfigTemplate();
const template = loadConfigTemplate(options);

// 不足しているキーを検出
const missingKeys = findMissingKeys(userConfig, template);

if (missingKeys.length === 0) {
console.log('✅ 設定ファイルは完全です');
logger.log('✅ 設定ファイルは完全です');
return userConfig;
}

if (missingKeys.length > 0) {
console.log(
`\n📋 以下の設定項目が不足しています (${missingKeys.length}個):`
);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
logger.log(
`\n📋 以下の設定項目が不足しています (${missingKeys.length}個):`
);
logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

missingKeys.forEach((key) => {
const defaultValue = getNestedValue(template, key);
const valuePreview = formatValuePreview(defaultValue);
console.log(` 📝 ${key} = ${valuePreview}`);
});
missingKeys.forEach((key) => {
const defaultValue = getNestedValue(template, key);
const valuePreview = formatValuePreview(defaultValue);
logger.log(` 📝 ${key} = ${valuePreview}`);
});

console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('💡 これらの設定を config.toml に追加することをお勧めします');
console.log('📖 詳細は example.config.toml を参照してください');
}
logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
logger.log('💡 これらの設定を config.toml に追加することをお勧めします');
logger.log('📖 詳細は example.config.toml を参照してください');

return userConfig;
} catch (error) {
console.error('❌ 設定差分チェック中にエラーが発生しました:', error);
console.log('⚠️ 設定チェックをスキップして起動を続行します');
logger.error('❌ 設定差分チェック中にエラーが発生しました:', error);
logger.log('⚠️ 設定チェックをスキップして起動を続行します');
return userConfig;
}
}

/**
* 起動時の設定チェック(メイン関数)
*/
export function performStartupConfigCheck(): Config {
console.log('🚀 藍 (Ai) 起動中...');
console.log('📋 設定ファイルチェックを開始します');
export function performStartupConfigCheck(
options?: StartupConfigCheckOptions
): Config {
const logger = options?.logger ?? console;
const exit = options?.exit ?? ((code: number) => process.exit(code) as never);

logger.log('🚀 藍 (Ai) 起動中...');
logger.log('📋 設定ファイルチェックを開始します');

const configPath = resolve('./config.toml');
const configPath = resolvePathFromOptions(options, 'config');

if (!existsSync(configPath)) {
console.error('❌ config.toml が見つかりません');
console.log(
logger.error('❌ config.toml が見つかりません');
logger.log(
'💡 example.config.toml をコピーして config.toml を作成してください'
);
process.exit(1);
return exit(1);
}

try {
Expand All @@ -162,14 +188,14 @@ export function performStartupConfigCheck(): Config {
const userConfig = TOML.parse(configData) as Config;

// 設定差分チェックと通知
const checkedConfig = checkMissingConfigKeys(userConfig);
const checkedConfig = checkMissingConfigKeys(userConfig, options);

console.log('✅ 設定ファイルチェック完了');
console.log('🎉 Bot起動準備完了!');
logger.log('✅ 設定ファイルチェック完了');
logger.log('🎉 Bot起動準備完了!');

return checkedConfig;
} catch (error) {
console.error('❌ 設定ファイルの読み込みに失敗しました:', error);
process.exit(1);
logger.error('❌ 設定ファイルの読み込みに失敗しました:', error);
return exit(1);
}
}
41 changes: 41 additions & 0 deletions src/modules/info/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export type InfoFeatureFlags = {
keywordSearch: boolean;
reversi: boolean;
autoWord: boolean;
chart: boolean;
timeSignal: boolean;
serverMonitoring: boolean;
emojiCheck: boolean;
maze: boolean;
poll: boolean;
};

export type FeatureFlagsConfig = {
keywordEnabled?: boolean;
reversiEnabled?: boolean;
notingEnabled?: boolean;
chartEnabled?: boolean;
timeSignalEnabled?: boolean;
serverMonitoring?: boolean;
checkEmojisEnabled?: boolean;
mazeEnable?: boolean;
pollEnable?: boolean;
};

function isEnabledByDefaultUnlessFalse(value: boolean | undefined): boolean {
return value !== false;
}

export function resolveInfoFeatureFlags(cfg: FeatureFlagsConfig): InfoFeatureFlags {
return {
keywordSearch: cfg.keywordEnabled === true,
reversi: cfg.reversiEnabled === true,
autoWord: isEnabledByDefaultUnlessFalse(cfg.notingEnabled),
chart: isEnabledByDefaultUnlessFalse(cfg.chartEnabled),
timeSignal: isEnabledByDefaultUnlessFalse(cfg.timeSignalEnabled),
serverMonitoring: cfg.serverMonitoring === true,
emojiCheck: cfg.checkEmojisEnabled === true,
maze: cfg.mazeEnable === true,
poll: cfg.pollEnable === true,
};
}
28 changes: 15 additions & 13 deletions src/modules/info/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type DatabaseManager from '@/database/DatabaseManager.js';
import * as fs from 'fs';
import path from 'path';
import os from 'os';
import { resolveInfoFeatureFlags, type InfoFeatureFlags } from './feature-flags.js';

// 型定義
declare namespace NodeJS {
Expand Down Expand Up @@ -287,42 +288,42 @@ function formatBooleanSetting(value: boolean | undefined): string {
return value ? DEFAULTS.enabled : DEFAULTS.disabled;
}

function formatBasicFeatures(): string {
function formatBasicFeatures(flags: InfoFeatureFlags): string {
const lines: string[] = [
CONFIG_LABELS.sections.basicFeatures,
`- ${CONFIG_LABELS.basic.keywordEnabled}: ${formatBooleanSetting(
config.keywordEnabled
flags.keywordSearch
)}`,
`- ${CONFIG_LABELS.basic.reversiEnabled}: ${formatBooleanSetting(
config.reversiEnabled
flags.reversi
)}`,
`- ${CONFIG_LABELS.basic.notingEnabled}: ${formatBooleanSetting(
config.notingEnabled
flags.autoWord
)}`,
`- ${CONFIG_LABELS.basic.chartEnabled}: ${formatBooleanSetting(
config.chartEnabled
flags.chart
)}`,
`- ${CONFIG_LABELS.basic.timeSignalEnabled}: ${formatBooleanSetting(
config.timeSignalEnabled
flags.timeSignal
)}`,
`- ${CONFIG_LABELS.basic.serverMonitoring}: ${formatBooleanSetting(
config.serverMonitoring
flags.serverMonitoring
)}`,
`- ${CONFIG_LABELS.basic.checkEmojisEnabled}: ${formatBooleanSetting(
config.checkEmojisEnabled
flags.emojiCheck
)}`,
];
return lines.join('\n') + '\n';
}

function formatGameFeatures(): string {
function formatGameFeatures(flags: InfoFeatureFlags): string {
const lines: string[] = [
CONFIG_LABELS.sections.gameFeatures,
`- ${CONFIG_LABELS.game.mazeEnable}: ${formatBooleanSetting(
config.mazeEnable
flags.maze
)}`,
`- ${CONFIG_LABELS.game.pollEnable}: ${formatBooleanSetting(
config.pollEnable
flags.poll
)}`,
];
return lines.join('\n') + '\n';
Expand Down Expand Up @@ -507,10 +508,11 @@ function formatOtherSettings(): string {
}

function formatSafeConfigInfo(): string {
const flags = resolveInfoFeatureFlags(config);
let configInfo = `\n⚙️ **設定情報**\n`;

configInfo += formatBasicFeatures();
configInfo += formatGameFeatures();
configInfo += formatBasicFeatures(flags);
configInfo += formatGameFeatures(flags);
configInfo += formatPostSettings();
configInfo += formatAIFeatures();
configInfo += formatEarthquakeSettings();
Expand Down
Loading