Files
integrations/ts/integrations/adguard/adguard.discovery.ts
T

95 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-05-05 18:45:46 +00:00
import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IAdguardManualEntry, IAdguardSnapshot } from './adguard.types.js';
import { adguardDefaultPort } from './adguard.types.js';
export class AdguardManualMatcher implements IDiscoveryMatcher<IAdguardManualEntry> {
public id = 'adguard-manual-match';
public source = 'manual' as const;
public description = 'Recognize manual AdGuard Home setup entries by host, domain, manufacturer, model, or metadata.';
public async matches(inputArg: IAdguardManualEntry): Promise<IDiscoveryMatch> {
const matched = isAdguardHint(inputArg) || Boolean(inputArg.host);
if (!matched) {
return {
matched: false,
confidence: 'low',
reason: 'Manual entry does not contain AdGuard Home setup hints.',
};
}
const id = inputArg.id || inputArg.host && `${inputArg.host}:${inputArg.port || adguardDefaultPort}`;
return {
matched: true,
confidence: inputArg.host ? 'high' : 'medium',
reason: 'Manual entry can start AdGuard Home setup.',
normalizedDeviceId: id,
candidate: {
source: 'manual',
integrationDomain: 'adguard',
id,
host: inputArg.host,
port: inputArg.port || adguardDefaultPort,
name: inputArg.name || 'AdGuard Home',
manufacturer: 'AdGuard Team',
model: inputArg.model || 'AdGuard Home',
metadata: {
...inputArg.metadata,
ssl: inputArg.ssl,
verifySsl: inputArg.verifySsl,
basePath: inputArg.basePath,
snapshot: inputArg.snapshot,
},
},
};
}
}
export class AdguardCandidateValidator implements IDiscoveryValidator {
public id = 'adguard-candidate-validator';
public description = 'Validate AdGuard Home candidates before starting local HTTP setup.';
public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
const matched = isAdguardHint(candidateArg) || candidateArg.integrationDomain === 'adguard';
const snapshot = candidateArg.metadata?.snapshot;
const hasSnapshot = isAdguardSnapshot(snapshot);
const hasUsableAddress = Boolean(candidateArg.host && isValidPort(candidateArg.port || adguardDefaultPort));
return {
matched: matched && (hasUsableAddress || hasSnapshot),
confidence: matched && candidateArg.id ? 'certain' : matched && hasUsableAddress ? 'high' : matched ? 'medium' : 'low',
reason: matched
? hasUsableAddress || hasSnapshot ? 'Candidate has AdGuard Home metadata and a usable HTTP address or snapshot.' : 'Candidate has AdGuard Home metadata but no usable HTTP address.'
: 'Candidate is not AdGuard Home.',
candidate: matched && (hasUsableAddress || hasSnapshot) ? candidateArg : undefined,
normalizedDeviceId: candidateArg.id || candidateArg.host && `${candidateArg.host}:${candidateArg.port || adguardDefaultPort}`,
};
}
}
export const createAdguardDiscoveryDescriptor = (): DiscoveryDescriptor => {
return new DiscoveryDescriptor({
integrationDomain: 'adguard',
displayName: 'AdGuard Home',
})
.addMatcher(new AdguardManualMatcher())
.addValidator(new AdguardCandidateValidator());
};
const isAdguardHint = (valueArg: { integrationDomain?: string; manufacturer?: string; model?: string; name?: string; metadata?: Record<string, unknown> }): boolean => {
const manufacturer = valueArg.manufacturer?.toLowerCase() || '';
const model = valueArg.model?.toLowerCase() || '';
const name = valueArg.name?.toLowerCase() || '';
return valueArg.integrationDomain === 'adguard'
|| manufacturer.includes('adguard')
|| model.includes('adguard')
|| name.includes('adguard')
|| Boolean(valueArg.metadata?.adguard);
};
const isValidPort = (valueArg: number): boolean => {
return Number.isInteger(valueArg) && valueArg > 0 && valueArg <= 65535;
};
const isAdguardSnapshot = (valueArg: unknown): valueArg is IAdguardSnapshot => {
return typeof valueArg === 'object' && valueArg !== null && 'status' in valueArg && 'online' in valueArg;
};