Files
SubMiner/src/types/config.ts
sudacode 33319a102d feat(discord): add configurable presence style presets
Replace the hardcoded "Mining and crafting (Anki cards)" meme message
with a preset system. New `discordPresence.presenceStyle` option
supports four presets: "default" (clean bilingual), "meme" (the OG
Minecraft joke), "japanese" (fully JP), and "minimal". The default
preset shows "Sentence Mining" with 日本語学習中 as the small image
tooltip. Existing users can set presenceStyle to "meme" to keep the
old behavior.
2026-03-29 15:28:01 -07:00

342 lines
8.8 KiB
TypeScript

import type { AnkiConnectConfig } from './anki';
import type {
AiConfig,
AiFeatureConfig,
AnilistCharacterDictionaryCollapsibleSectionsConfig,
AnilistCharacterDictionaryEvictionPolicy,
AnilistCharacterDictionaryProfileScope,
AnilistConfig,
DiscordPresenceConfig,
ImmersionTrackingConfig,
ImmersionTrackingRetentionMode,
ImmersionTrackingRetentionPreset,
JellyfinConfig,
JimakuConfig,
JimakuLanguagePreference,
StatsConfig,
YomitanConfig,
YoutubeConfig,
YoutubeSubgenConfig,
} from './integrations';
import type {
ControllerButtonIndicesConfig,
ControllerConfig,
ControllerTriggerInputMode,
Keybinding,
ResolvedControllerBindingsConfig,
} from './runtime';
import type {
FrequencyDictionaryMatchMode,
FrequencyDictionaryMode,
NPlusOneMatchMode,
SecondarySubConfig,
SubtitlePosition,
SubtitleSidebarConfig,
SubtitleStyleConfig,
} from './subtitle';
export interface WebSocketConfig {
enabled?: boolean | 'auto';
port?: number;
}
export interface AnnotationWebSocketConfig {
enabled?: boolean;
port?: number;
}
export interface TexthookerConfig {
launchAtStartup?: boolean;
openBrowser?: boolean;
}
export type SubsyncMode = 'auto' | 'manual';
export interface SubsyncConfig {
defaultMode?: SubsyncMode;
alass_path?: string;
ffsubsync_path?: string;
ffmpeg_path?: string;
replace?: boolean;
}
export interface StartupWarmupsConfig {
lowPowerMode?: boolean;
mecab?: boolean;
yomitanExtension?: boolean;
subtitleDictionaries?: boolean;
jellyfinRemoteSession?: boolean;
}
export interface ShortcutsConfig {
toggleVisibleOverlayGlobal?: string | null;
copySubtitle?: string | null;
copySubtitleMultiple?: string | null;
updateLastCardFromClipboard?: string | null;
triggerFieldGrouping?: string | null;
triggerSubsync?: string | null;
mineSentence?: string | null;
mineSentenceMultiple?: string | null;
multiCopyTimeoutMs?: number;
toggleSecondarySub?: string | null;
markAudioCard?: string | null;
openRuntimeOptions?: string | null;
openJimaku?: string | null;
}
export interface Config {
subtitlePosition?: SubtitlePosition;
keybindings?: Keybinding[];
websocket?: WebSocketConfig;
annotationWebsocket?: AnnotationWebSocketConfig;
texthooker?: TexthookerConfig;
controller?: ControllerConfig;
ankiConnect?: AnkiConnectConfig;
shortcuts?: ShortcutsConfig;
secondarySub?: SecondarySubConfig;
subsync?: SubsyncConfig;
startupWarmups?: StartupWarmupsConfig;
subtitleStyle?: SubtitleStyleConfig;
subtitleSidebar?: SubtitleSidebarConfig;
auto_start_overlay?: boolean;
jimaku?: JimakuConfig;
anilist?: AnilistConfig;
yomitan?: YomitanConfig;
jellyfin?: JellyfinConfig;
discordPresence?: DiscordPresenceConfig;
ai?: AiConfig;
youtube?: YoutubeConfig;
youtubeSubgen?: YoutubeSubgenConfig;
immersionTracking?: ImmersionTrackingConfig;
stats?: StatsConfig;
logging?: {
level?: 'debug' | 'info' | 'warn' | 'error';
};
}
export type RawConfig = Config;
export interface ResolvedConfig {
subtitlePosition: SubtitlePosition;
keybindings: Keybinding[];
websocket: Required<WebSocketConfig>;
annotationWebsocket: Required<AnnotationWebSocketConfig>;
texthooker: Required<TexthookerConfig>;
controller: {
enabled: boolean;
preferredGamepadId: string;
preferredGamepadLabel: string;
smoothScroll: boolean;
scrollPixelsPerSecond: number;
horizontalJumpPixels: number;
stickDeadzone: number;
triggerInputMode: ControllerTriggerInputMode;
triggerDeadzone: number;
repeatDelayMs: number;
repeatIntervalMs: number;
buttonIndices: Required<ControllerButtonIndicesConfig>;
bindings: Required<ResolvedControllerBindingsConfig>;
};
ankiConnect: AnkiConnectConfig & {
enabled: boolean;
url: string;
pollingRate: number;
proxy: {
enabled: boolean;
host: string;
port: number;
upstreamUrl: string;
};
tags: string[];
fields: {
word: string;
audio: string;
image: string;
sentence: string;
miscInfo: string;
translation: string;
};
ai: AiFeatureConfig & {
enabled: boolean;
};
media: {
generateAudio: boolean;
generateImage: boolean;
imageType: 'static' | 'avif';
imageFormat: 'jpg' | 'png' | 'webp';
imageQuality: number;
imageMaxWidth?: number;
imageMaxHeight?: number;
animatedFps: number;
animatedMaxWidth: number;
animatedMaxHeight?: number;
animatedCrf: number;
syncAnimatedImageToWordAudio: boolean;
audioPadding: number;
fallbackDuration: number;
maxMediaDuration: number;
};
knownWords: {
highlightEnabled: boolean;
refreshMinutes: number;
addMinedWordsImmediately: boolean;
matchMode: NPlusOneMatchMode;
decks: Record<string, string[]>;
color: string;
};
nPlusOne: {
nPlusOne: string;
minSentenceWords: number;
};
behavior: {
overwriteAudio: boolean;
overwriteImage: boolean;
mediaInsertMode: 'append' | 'prepend';
highlightWord: boolean;
notificationType: 'osd' | 'system' | 'both' | 'none';
autoUpdateNewCards: boolean;
};
metadata: {
pattern: string;
};
isLapis: {
enabled: boolean;
sentenceCardModel: string;
};
isKiku: {
enabled: boolean;
fieldGrouping: 'auto' | 'manual' | 'disabled';
deleteDuplicateInAuto: boolean;
};
};
shortcuts: Required<ShortcutsConfig>;
secondarySub: Required<SecondarySubConfig>;
subsync: Required<SubsyncConfig>;
startupWarmups: {
lowPowerMode: boolean;
mecab: boolean;
yomitanExtension: boolean;
subtitleDictionaries: boolean;
jellyfinRemoteSession: boolean;
};
subtitleStyle: Required<Omit<SubtitleStyleConfig, 'secondary' | 'frequencyDictionary'>> & {
secondary: Required<NonNullable<SubtitleStyleConfig['secondary']>>;
frequencyDictionary: {
enabled: boolean;
sourcePath: string;
topX: number;
mode: FrequencyDictionaryMode;
matchMode: FrequencyDictionaryMatchMode;
singleColor: string;
bandedColors: [string, string, string, string, string];
};
};
subtitleSidebar: Required<SubtitleSidebarConfig>;
auto_start_overlay: boolean;
jimaku: JimakuConfig & {
apiBaseUrl: string;
languagePreference: JimakuLanguagePreference;
maxEntryResults: number;
};
anilist: {
enabled: boolean;
accessToken: string;
characterDictionary: {
enabled: boolean;
refreshTtlHours: number;
maxLoaded: number;
evictionPolicy: AnilistCharacterDictionaryEvictionPolicy;
profileScope: AnilistCharacterDictionaryProfileScope;
collapsibleSections: Required<AnilistCharacterDictionaryCollapsibleSectionsConfig>;
};
};
yomitan: {
externalProfilePath: string;
};
jellyfin: {
enabled: boolean;
serverUrl: string;
username: string;
deviceId: string;
clientName: string;
clientVersion: string;
defaultLibraryId: string;
remoteControlEnabled: boolean;
remoteControlAutoConnect: boolean;
autoAnnounce: boolean;
remoteControlDeviceName: string;
pullPictures: boolean;
iconCacheDir: string;
directPlayPreferred: boolean;
directPlayContainers: string[];
transcodeVideoCodec: string;
};
discordPresence: {
enabled: boolean;
presenceStyle: import('./integrations').DiscordPresenceStylePreset;
updateIntervalMs: number;
debounceMs: number;
};
ai: AiConfig & {
enabled: boolean;
apiKey: string;
apiKeyCommand: string;
baseUrl: string;
model: string;
systemPrompt: string;
requestTimeoutMs: number;
};
youtube: YoutubeConfig & {
primarySubLanguages: string[];
};
youtubeSubgen: YoutubeSubgenConfig & {
whisperBin: string;
whisperModel: string;
whisperVadModel: string;
whisperThreads: number;
fixWithAi: boolean;
ai: AiFeatureConfig;
};
immersionTracking: {
enabled: boolean;
dbPath?: string;
batchSize: number;
flushIntervalMs: number;
queueCap: number;
payloadCapBytes: number;
maintenanceIntervalMs: number;
retentionMode: ImmersionTrackingRetentionMode;
retentionPreset: ImmersionTrackingRetentionPreset;
retention: {
eventsDays: number;
telemetryDays: number;
sessionsDays: number;
dailyRollupsDays: number;
monthlyRollupsDays: number;
vacuumIntervalDays: number;
};
lifetimeSummaries: {
global: boolean;
anime: boolean;
media: boolean;
};
};
stats: {
toggleKey: string;
markWatchedKey: string;
serverPort: number;
autoStartServer: boolean;
autoOpenBrowser: boolean;
};
logging: {
level: 'debug' | 'info' | 'warn' | 'error';
};
}
export interface ConfigValidationWarning {
path: string;
value: unknown;
fallback: unknown;
message: string;
}