Files
SubMiner/src/config/resolve/integrations.ts

332 lines
12 KiB
TypeScript

import * as os from 'node:os';
import * as path from 'node:path';
import { ResolveContext } from './context';
import { asBoolean, asNumber, asString, isObject } from './shared';
function normalizeExternalProfilePath(value: string): string {
const trimmed = value.trim();
if (trimmed === '~') {
return os.homedir();
}
if (trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
return path.join(os.homedir(), trimmed.slice(2));
}
return trimmed;
}
export function applyIntegrationConfig(context: ResolveContext): void {
const { src, resolved, warn } = context;
if (isObject(src.ai)) {
const booleanKeys = ['enabled'] as const;
for (const key of booleanKeys) {
const value = asBoolean(src.ai[key]);
if (value !== undefined) {
resolved.ai[key] = value;
} else if (src.ai[key] !== undefined) {
warn(`ai.${key}`, src.ai[key], resolved.ai[key], 'Expected boolean.');
}
}
const stringKeys = ['apiKey', 'apiKeyCommand', 'baseUrl', 'model', 'systemPrompt'] as const;
for (const key of stringKeys) {
const value = asString(src.ai[key]);
if (value !== undefined) {
resolved.ai[key] = value;
} else if (src.ai[key] !== undefined) {
warn(`ai.${key}`, src.ai[key], resolved.ai[key], 'Expected string.');
}
}
const requestTimeoutMs = asNumber(src.ai.requestTimeoutMs);
if (
requestTimeoutMs !== undefined &&
Number.isInteger(requestTimeoutMs) &&
requestTimeoutMs > 0
) {
resolved.ai.requestTimeoutMs = requestTimeoutMs;
} else if (src.ai.requestTimeoutMs !== undefined) {
warn(
'ai.requestTimeoutMs',
src.ai.requestTimeoutMs,
resolved.ai.requestTimeoutMs,
'Expected positive integer.',
);
}
} else if (src.ai !== undefined) {
warn('ai', src.ai, resolved.ai, 'Expected object.');
}
if (isObject(src.anilist)) {
const enabled = asBoolean(src.anilist.enabled);
if (enabled !== undefined) {
resolved.anilist.enabled = enabled;
} else if (src.anilist.enabled !== undefined) {
warn('anilist.enabled', src.anilist.enabled, resolved.anilist.enabled, 'Expected boolean.');
}
const accessToken = asString(src.anilist.accessToken);
if (accessToken !== undefined) {
resolved.anilist.accessToken = accessToken;
} else if (src.anilist.accessToken !== undefined) {
warn(
'anilist.accessToken',
src.anilist.accessToken,
resolved.anilist.accessToken,
'Expected string.',
);
}
if (isObject(src.anilist.characterDictionary)) {
const characterDictionary = src.anilist.characterDictionary;
const dictionaryEnabled = asBoolean(characterDictionary.enabled);
if (dictionaryEnabled !== undefined) {
resolved.anilist.characterDictionary.enabled = dictionaryEnabled;
} else if (characterDictionary.enabled !== undefined) {
warn(
'anilist.characterDictionary.enabled',
characterDictionary.enabled,
resolved.anilist.characterDictionary.enabled,
'Expected boolean.',
);
}
const refreshTtlHours = asNumber(characterDictionary.refreshTtlHours);
if (refreshTtlHours !== undefined) {
const normalized = Math.min(24 * 365, Math.max(1, Math.floor(refreshTtlHours)));
if (normalized !== refreshTtlHours) {
warn(
'anilist.characterDictionary.refreshTtlHours',
characterDictionary.refreshTtlHours,
normalized,
'Out of range; clamped to 1..8760 hours.',
);
}
resolved.anilist.characterDictionary.refreshTtlHours = normalized;
} else if (characterDictionary.refreshTtlHours !== undefined) {
warn(
'anilist.characterDictionary.refreshTtlHours',
characterDictionary.refreshTtlHours,
resolved.anilist.characterDictionary.refreshTtlHours,
'Expected number.',
);
}
const maxLoaded = asNumber(characterDictionary.maxLoaded);
if (maxLoaded !== undefined) {
const normalized = Math.min(20, Math.max(1, Math.floor(maxLoaded)));
if (normalized !== maxLoaded) {
warn(
'anilist.characterDictionary.maxLoaded',
characterDictionary.maxLoaded,
normalized,
'Out of range; clamped to 1..20.',
);
}
resolved.anilist.characterDictionary.maxLoaded = normalized;
} else if (characterDictionary.maxLoaded !== undefined) {
warn(
'anilist.characterDictionary.maxLoaded',
characterDictionary.maxLoaded,
resolved.anilist.characterDictionary.maxLoaded,
'Expected number.',
);
}
const evictionPolicyRaw = asString(characterDictionary.evictionPolicy);
if (evictionPolicyRaw !== undefined) {
const evictionPolicy = evictionPolicyRaw.trim().toLowerCase();
if (evictionPolicy === 'disable' || evictionPolicy === 'delete') {
resolved.anilist.characterDictionary.evictionPolicy = evictionPolicy;
} else {
warn(
'anilist.characterDictionary.evictionPolicy',
characterDictionary.evictionPolicy,
resolved.anilist.characterDictionary.evictionPolicy,
"Expected one of: 'disable', 'delete'.",
);
}
} else if (characterDictionary.evictionPolicy !== undefined) {
warn(
'anilist.characterDictionary.evictionPolicy',
characterDictionary.evictionPolicy,
resolved.anilist.characterDictionary.evictionPolicy,
'Expected string.',
);
}
const profileScopeRaw = asString(characterDictionary.profileScope);
if (profileScopeRaw !== undefined) {
const profileScope = profileScopeRaw.trim().toLowerCase();
if (profileScope === 'all' || profileScope === 'active') {
resolved.anilist.characterDictionary.profileScope = profileScope;
} else {
warn(
'anilist.characterDictionary.profileScope',
characterDictionary.profileScope,
resolved.anilist.characterDictionary.profileScope,
"Expected one of: 'all', 'active'.",
);
}
} else if (characterDictionary.profileScope !== undefined) {
warn(
'anilist.characterDictionary.profileScope',
characterDictionary.profileScope,
resolved.anilist.characterDictionary.profileScope,
'Expected string.',
);
}
if (isObject(characterDictionary.collapsibleSections)) {
const collapsibleSections = characterDictionary.collapsibleSections;
const keys = ['description', 'characterInformation', 'voicedBy'] as const;
for (const key of keys) {
const value = asBoolean(collapsibleSections[key]);
if (value !== undefined) {
resolved.anilist.characterDictionary.collapsibleSections[key] = value;
} else if (collapsibleSections[key] !== undefined) {
warn(
`anilist.characterDictionary.collapsibleSections.${key}`,
collapsibleSections[key],
resolved.anilist.characterDictionary.collapsibleSections[key],
'Expected boolean.',
);
}
}
} else if (characterDictionary.collapsibleSections !== undefined) {
warn(
'anilist.characterDictionary.collapsibleSections',
characterDictionary.collapsibleSections,
resolved.anilist.characterDictionary.collapsibleSections,
'Expected object.',
);
}
} else if (src.anilist.characterDictionary !== undefined) {
warn(
'anilist.characterDictionary',
src.anilist.characterDictionary,
resolved.anilist.characterDictionary,
'Expected object.',
);
}
}
if (isObject(src.yomitan)) {
const externalProfilePath = asString(src.yomitan.externalProfilePath);
if (externalProfilePath !== undefined) {
resolved.yomitan.externalProfilePath = normalizeExternalProfilePath(externalProfilePath);
} else if (src.yomitan.externalProfilePath !== undefined) {
warn(
'yomitan.externalProfilePath',
src.yomitan.externalProfilePath,
resolved.yomitan.externalProfilePath,
'Expected string.',
);
}
} else if (src.yomitan !== undefined) {
warn('yomitan', src.yomitan, resolved.yomitan, 'Expected object.');
}
if (isObject(src.jellyfin)) {
const enabled = asBoolean(src.jellyfin.enabled);
if (enabled !== undefined) {
resolved.jellyfin.enabled = enabled;
} else if (src.jellyfin.enabled !== undefined) {
warn(
'jellyfin.enabled',
src.jellyfin.enabled,
resolved.jellyfin.enabled,
'Expected boolean.',
);
}
const stringKeys = [
'serverUrl',
'username',
'deviceId',
'clientName',
'clientVersion',
'defaultLibraryId',
'iconCacheDir',
'transcodeVideoCodec',
] as const;
for (const key of stringKeys) {
const value = asString(src.jellyfin[key]);
if (value !== undefined) {
resolved.jellyfin[key] = value as (typeof resolved.jellyfin)[typeof key];
} else if (src.jellyfin[key] !== undefined) {
warn(`jellyfin.${key}`, src.jellyfin[key], resolved.jellyfin[key], 'Expected string.');
}
}
const booleanKeys = [
'remoteControlEnabled',
'remoteControlAutoConnect',
'autoAnnounce',
'directPlayPreferred',
'pullPictures',
] as const;
for (const key of booleanKeys) {
const value = asBoolean(src.jellyfin[key]);
if (value !== undefined) {
resolved.jellyfin[key] = value as (typeof resolved.jellyfin)[typeof key];
} else if (src.jellyfin[key] !== undefined) {
warn(`jellyfin.${key}`, src.jellyfin[key], resolved.jellyfin[key], 'Expected boolean.');
}
}
if (Array.isArray(src.jellyfin.directPlayContainers)) {
resolved.jellyfin.directPlayContainers = src.jellyfin.directPlayContainers
.filter((item): item is string => typeof item === 'string')
.map((item) => item.trim().toLowerCase())
.filter((item) => item.length > 0);
} else if (src.jellyfin.directPlayContainers !== undefined) {
warn(
'jellyfin.directPlayContainers',
src.jellyfin.directPlayContainers,
resolved.jellyfin.directPlayContainers,
'Expected string array.',
);
}
}
if (isObject(src.discordPresence)) {
const enabled = asBoolean(src.discordPresence.enabled);
if (enabled !== undefined) {
resolved.discordPresence.enabled = enabled;
} else if (src.discordPresence.enabled !== undefined) {
warn(
'discordPresence.enabled',
src.discordPresence.enabled,
resolved.discordPresence.enabled,
'Expected boolean.',
);
}
const updateIntervalMs = asNumber(src.discordPresence.updateIntervalMs);
if (updateIntervalMs !== undefined) {
resolved.discordPresence.updateIntervalMs = Math.max(1_000, Math.floor(updateIntervalMs));
} else if (src.discordPresence.updateIntervalMs !== undefined) {
warn(
'discordPresence.updateIntervalMs',
src.discordPresence.updateIntervalMs,
resolved.discordPresence.updateIntervalMs,
'Expected number.',
);
}
const debounceMs = asNumber(src.discordPresence.debounceMs);
if (debounceMs !== undefined) {
resolved.discordPresence.debounceMs = Math.max(0, Math.floor(debounceMs));
} else if (src.discordPresence.debounceMs !== undefined) {
warn(
'discordPresence.debounceMs',
src.discordPresence.debounceMs,
resolved.discordPresence.debounceMs,
'Expected number.',
);
}
}
}