@adguard/agtree
Version:
Tool set for working with adblock filter lists
421 lines (418 loc) • 17.5 kB
JavaScript
/*
* AGTree v3.4.3 (build date: Thu, 11 Dec 2025 13:43:19 GMT)
* (c) 2025 Adguard Software Ltd.
* Released under the MIT license
* https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
*/
import { SpecificPlatform, GenericPlatform } from '../platforms.js';
import { AdblockProduct } from '../../utils/adblockers.js';
import { getBitCount } from '../../utils/bit-count.js';
/* eslint-disable no-bitwise */
/**
* @file Provides platform mapping and helper functions.
*/
/**
* Map of specific platforms string names to their corresponding enum values.
*/
const SPECIFIC_PLATFORM_MAP = new Map([
['adg_os_windows', SpecificPlatform.AdgOsWindows],
['adg_os_mac', SpecificPlatform.AdgOsMac],
['adg_os_android', SpecificPlatform.AdgOsAndroid],
['adg_ext_chrome', SpecificPlatform.AdgExtChrome],
['adg_ext_opera', SpecificPlatform.AdgExtOpera],
['adg_ext_edge', SpecificPlatform.AdgExtEdge],
['adg_ext_firefox', SpecificPlatform.AdgExtFirefox],
['adg_cb_android', SpecificPlatform.AdgCbAndroid],
['adg_cb_ios', SpecificPlatform.AdgCbIos],
['adg_cb_safari', SpecificPlatform.AdgCbSafari],
['ubo_ext_chrome', SpecificPlatform.UboExtChrome],
['ubo_ext_opera', SpecificPlatform.UboExtOpera],
['ubo_ext_edge', SpecificPlatform.UboExtEdge],
['ubo_ext_firefox', SpecificPlatform.UboExtFirefox],
['abp_ext_chrome', SpecificPlatform.AbpExtChrome],
['abp_ext_opera', SpecificPlatform.AbpExtOpera],
['abp_ext_edge', SpecificPlatform.AbpExtEdge],
['abp_ext_firefox', SpecificPlatform.AbpExtFirefox],
]);
/**
* Map of specific platforms enum values to their corresponding string names.
*
* @note Reverse of {@link SPECIFIC_PLATFORM_MAP}.
*/
const SPECIFIC_PLATFORM_MAP_REVERSE = new Map([...SPECIFIC_PLATFORM_MAP].map(([key, value]) => [value, key]));
/**
* Map of generic platforms string names to their corresponding enum values.
*/
const GENERIC_PLATFORM_MAP = new Map([
['adg_os_any', GenericPlatform.AdgOsAny],
['adg_safari_any', GenericPlatform.AdgSafariAny],
['adg_ext_chromium', GenericPlatform.AdgExtChromium],
['adg_ext_any', GenericPlatform.AdgExtAny],
['adg_any', GenericPlatform.AdgAny],
['ubo_ext_chromium', GenericPlatform.UboExtChromium],
['ubo_ext_any', GenericPlatform.UboExtAny],
['ubo_any', GenericPlatform.UboAny],
['abp_ext_chromium', GenericPlatform.AbpExtChromium],
['abp_ext_any', GenericPlatform.AbpExtAny],
['abp_any', GenericPlatform.AbpAny],
['any', GenericPlatform.Any],
]);
/**
* Map of products to their platform name prefixes.
* Used for filtering generic platforms by product.
*/
const PRODUCT_PREFIX_MAP = {
[AdblockProduct.Adg]: 'Adg',
[AdblockProduct.Ubo]: 'Ubo',
[AdblockProduct.Abp]: 'Abp',
};
const SPECIFIC_PLATFORM_HUMAN_READABLE_NAME_MAP = new Map([
[SpecificPlatform.AdgOsWindows, 'AdGuard App for Windows'],
[SpecificPlatform.AdgOsMac, 'AdGuard App for Mac'],
[SpecificPlatform.AdgOsAndroid, 'AdGuard App for Android'],
[SpecificPlatform.AdgExtChrome, 'AdGuard Browser Extension for Chrome'],
[SpecificPlatform.AdgExtOpera, 'AdGuard Browser Extension for Opera'],
[SpecificPlatform.AdgExtEdge, 'AdGuard Browser Extension for Edge'],
[SpecificPlatform.AdgExtFirefox, 'AdGuard Browser Extension for Firefox'],
[SpecificPlatform.AdgCbAndroid, 'AdGuard Content Blocker for Android'],
[SpecificPlatform.AdgCbIos, 'AdGuard Content Blocker for iOS'],
[SpecificPlatform.AdgCbSafari, 'AdGuard Content Blocker for Safari'],
[SpecificPlatform.UboExtChrome, 'uBlock Origin Browser Extension for Chrome'],
[SpecificPlatform.UboExtOpera, 'uBlock Origin Browser Extension for Opera'],
[SpecificPlatform.UboExtEdge, 'uBlock Origin Browser Extension for Edge'],
[SpecificPlatform.UboExtFirefox, 'uBlock Origin Browser Extension for Firefox'],
[SpecificPlatform.AbpExtChrome, 'AdBlock / Adblock Plus Browser Extension for Chrome'],
[SpecificPlatform.AbpExtOpera, 'AdBlock / Adblock Plus Browser Extension for Opera'],
[SpecificPlatform.AbpExtEdge, 'AdBlock / Adblock Plus Browser Extension for Edge'],
[SpecificPlatform.AbpExtFirefox, 'AdBlock / Adblock Plus Browser Extension for Firefox'],
]);
const GENERIC_PLATFORM_HUMAN_READABLE_NAME_MAP = new Map([
[GenericPlatform.AdgOsAny, 'Any System-level AdGuard App'],
[GenericPlatform.AdgSafariAny, 'Any AdGuard Content Blocker for Safari'],
[GenericPlatform.AdgExtChromium, 'Any AdGuard Browser Extension for Chromium'],
[GenericPlatform.AdgExtAny, 'Any AdGuard Browser Extension'],
[GenericPlatform.AdgAny, 'Any AdGuard product'],
[GenericPlatform.UboExtChromium, 'Any uBlock Origin Browser Extension for Chromium'],
[GenericPlatform.UboExtAny, 'Any uBlock Origin Browser Extension'],
[GenericPlatform.UboAny, 'Any uBlock Origin product'],
[GenericPlatform.AbpExtChromium, 'Any AdBlock / Adblock Plus Browser Extension for Chromium'],
[GenericPlatform.AbpExtAny, 'Any AdBlock / Adblock Plus Browser Extension'],
[GenericPlatform.AbpAny, 'Any AdBlock / Adblock Plus product'],
[GenericPlatform.Any, 'Any product'],
]);
/**
* Generic platforms for each product, ordered from most specific to least specific.
* Computed lazily on first access.
*/
let PRODUCT_GENERIC_PLATFORMS = null;
/**
* Map of products to their specific platforms.
* Cached after first call to avoid recomputing.
*/
let PRODUCT_SPECIFIC_PLATFORMS = null;
/**
* Initializes and returns the product generic platforms map.
* Filters all generic platforms by product prefix and sorts by specificity (fewer bits = more specific).
*
* @returns Map of products to their generic platforms, ordered from most to least specific.
*/
const getProductGenericPlatforms = () => {
if (PRODUCT_GENERIC_PLATFORMS !== null) {
return PRODUCT_GENERIC_PLATFORMS;
}
const result = {
[AdblockProduct.Adg]: [],
[AdblockProduct.Ubo]: [],
[AdblockProduct.Abp]: [],
};
// Iterate over all generic platforms and group by product prefix
const genericPlatformEntries = Object.entries(GenericPlatform);
for (const [name, platform] of genericPlatformEntries) {
// Check which product this platform belongs to
for (const [product, prefix] of Object.entries(PRODUCT_PREFIX_MAP)) {
if (name.startsWith(prefix)) {
result[product].push(platform);
break;
}
}
}
// Sort each product's platforms by specificity (fewer bits set = more specific)
for (const product of Object.keys(result)) {
result[product].sort((a, b) => {
const bitsA = getBitCount(a);
const bitsB = getBitCount(b);
return bitsA - bitsB; // Ascending: fewer bits first (more specific)
});
}
// Cache the result as readonly
PRODUCT_GENERIC_PLATFORMS = result;
return PRODUCT_GENERIC_PLATFORMS;
};
/**
* Check if the platform is a generic platform (or a combination of platforms).
*
* @param platform Platform to check.
*
* @returns True if the platform is a generic platform or combined platforms, false if it's a specific platform.
*/
const isGenericPlatform = (platform) => {
// if more than one bit is set, it's a generic platform or combined platforms
// Cast to number for bitwise operations
const num = platform;
return !!(num & (num - 1));
};
/**
* Check if the platform has multiple products specified.
* Multiple products means at least 2 of: AdgAny, AbpAny, UboAny.
*
* @param platform Platform to check.
*
* @returns True if at least 2 products are specified, false otherwise.
*/
const hasPlatformMultipleProducts = (platform) => {
const hasAdg = !!(platform & GenericPlatform.AdgAny);
const hasAbp = !!(platform & GenericPlatform.AbpAny);
const hasUbo = !!(platform & GenericPlatform.UboAny);
return ((hasAdg && hasAbp)
|| (hasAdg && hasUbo)
|| (hasAbp && hasUbo));
};
/**
* Converts a platform to its corresponding adblock products.
*
* Note: This conversion is less specific than the platform itself, as it only returns
* which products (AdGuard/uBlock/Abp) are present, dropping specific platform information
* (e.g., Windows vs Chrome extension).
*
* @param platform Platform to convert.
*
* @returns Array of AdblockProduct values:
* - Empty array `[]` if platform is 0 or no products are found
* - Array of specific products based on which products are present
* (e.g., `['AdGuard', 'UblockOrigin']` if both AdGuard and uBlock Origin are specified)
* - `['AdGuard', 'UblockOrigin', 'AdblockPlus']` for GenericPlatform.Any
*/
const platformToAdblockProduct = (platform) => {
const products = [];
if (platform & GenericPlatform.AdgAny) {
products.push(AdblockProduct.Adg);
}
if (platform & GenericPlatform.UboAny) {
products.push(AdblockProduct.Ubo);
}
if (platform & GenericPlatform.AbpAny) {
products.push(AdblockProduct.Abp);
}
return products;
};
/**
* Optimizes platform representation by combining specific platforms into generic ones.
* Returns the minimal set of platforms needed to represent the input.
*
* @param extractedPlatforms Platform bits for a single product.
* @param productGenericPlatforms Array of generic platforms for this product, ordered by specificity.
*
* @returns Optimized array of platforms.
*/
const optimizePlatformRepresentation = (extractedPlatforms, productGenericPlatforms) => {
if (extractedPlatforms === 0) {
return [];
}
// Check if the input exactly matches any single generic platform (already optimal)
for (const genericPlatform of productGenericPlatforms) {
if (extractedPlatforms === genericPlatform) {
return [genericPlatform];
}
}
const result = [];
let remainingBits = extractedPlatforms;
// Try to match generic platforms from most specific to least specific
for (const genericPlatform of productGenericPlatforms) {
const genericBits = genericPlatform;
// Check if all bits of this generic platform are present in remaining bits
if ((remainingBits & genericBits) === genericBits) {
result.push(genericPlatform);
// Remove the matched bits from remaining
remainingBits &= ~genericBits;
// If no bits remain, we're done
if (remainingBits === 0) {
break;
}
}
}
// If there are remaining bits, we need to add them as specific platforms
// This shouldn't normally happen if our generic platforms cover all combinations,
// but we handle it for safety
if (remainingBits !== 0) {
result.push(remainingBits);
}
return result;
};
/**
* Splits a platform by products, returning a record mapping each product to its platforms.
* This is useful for iterating over each product separately when validating or processing.
*
* The function optimizes the platform representation by combining specific platforms
* into generic ones when possible, returning the minimal set needed.
*
* @param platform Platform to split (can be single or multi-product).
*
* @returns Record mapping products to optimized platform arrays:
* - Empty object `{}` if platform is 0 or no products are found
* - Object with single product key for single-product platforms
* - Object with multiple product keys for multi-product platforms
* - Each array contains the minimal representation using generic platforms where possible
*
* @example
* ```typescript
* // Multi-product platform
* const platforms = getPlatformsByProduct(GenericPlatform.AdgAny | GenericPlatform.UboAny);
* // Returns: {
* // 'AdGuard': [GenericPlatform.AdgAny],
* // 'UblockOrigin': [GenericPlatform.UboAny]
* // }
*
* // Mixed specific and generic
* const mixed = SpecificPlatform.AdgExtChrome | SpecificPlatform.AdgExtFirefox | SpecificPlatform.AdgOsWindows;
* const result = getPlatformsByProduct(mixed);
* // Might return: { 'AdGuard': [GenericPlatform.AdgExtChromium, SpecificPlatform.AdgOsWindows] }
*
* // Iterate and validate for each product
* for (const [product, platformList] of Object.entries(platforms)) {
* for (const p of platformList) {
* const result = modifierValidator.validate(p, modifier);
* console.log(`${product}: ${result.valid}`);
* }
* }
* ```
*/
const getPlatformsByProduct = (platform) => {
const result = {};
const productPlatforms = getProductGenericPlatforms();
if (platform & GenericPlatform.AdgAny) {
const extracted = (platform & GenericPlatform.AdgAny);
result[AdblockProduct.Adg] = optimizePlatformRepresentation(extracted, productPlatforms[AdblockProduct.Adg]);
}
if (platform & GenericPlatform.UboAny) {
const extracted = (platform & GenericPlatform.UboAny);
result[AdblockProduct.Ubo] = optimizePlatformRepresentation(extracted, productPlatforms[AdblockProduct.Ubo]);
}
if (platform & GenericPlatform.AbpAny) {
const extracted = (platform & GenericPlatform.AbpAny);
result[AdblockProduct.Abp] = optimizePlatformRepresentation(extracted, productPlatforms[AdblockProduct.Abp]);
}
return result;
};
/**
* Returns the platform enum value for the given platform string name.
*
* @param platform Platform string name, e.g., 'adg_os_windows'.
*
* @returns Specific or generic platform enum value.
* @throws Error if the platform is unknown.
*/
const getPlatformId = (platform) => {
const specificPlatform = SPECIFIC_PLATFORM_MAP.get(platform);
if (specificPlatform) {
return specificPlatform;
}
const genericPlatform = GENERIC_PLATFORM_MAP.get(platform);
if (genericPlatform) {
return genericPlatform;
}
throw new Error(`Unknown platform: ${platform}`);
};
/**
* Returns the specific platform string name for the given platform enum value.
*
* @param platform Specific platform enum value.
*
* @returns Specific platform string name, e.g., 'adg_os_windows'.
* @throws Error if the platform is unknown.
*/
const getSpecificPlatformName = (platform) => {
const specificPlatform = SPECIFIC_PLATFORM_MAP_REVERSE.get(platform);
if (!specificPlatform) {
throw new Error(`Unknown platform: ${platform}`);
}
return specificPlatform;
};
/**
* Returns the human-readable platform name for the given platform enum value.
*
* @param platform Platform enum value.
*
* @returns Human-readable platform name, e.g., 'AdGuard for Windows'.
*
* @throws Error if the platform is unknown.
*/
const getHumanReadablePlatformName = (platform) => {
// Try specific platform first
const specificPlatform = SPECIFIC_PLATFORM_HUMAN_READABLE_NAME_MAP.get(platform);
if (specificPlatform) {
return specificPlatform;
}
// Then try generic platform
const genericPlatform = GENERIC_PLATFORM_HUMAN_READABLE_NAME_MAP.get(platform);
if (genericPlatform) {
return genericPlatform;
}
throw new Error(`Unknown platform: ${platform}`);
};
/**
* Gets all specific platforms for a given AdblockProduct.
* Results are cached after the first call.
*
* @param product AdblockProduct to get specific platforms for.
*
* @returns Array of all specific platforms for the given product.
*
* @example
* ```typescript
* const adgPlatforms = getProductSpecificPlatforms(AdblockProduct.Adg);
* // Returns: [AdgOsWindows, AdgOsMac, AdgOsAndroid, AdgExtChrome, ...]
* ```
*/
const getProductSpecificPlatforms = (product) => {
// Initialize cache if needed
if (PRODUCT_SPECIFIC_PLATFORMS === null) {
const result = {
[AdblockProduct.Adg]: [],
[AdblockProduct.Ubo]: [],
[AdblockProduct.Abp]: [],
};
// Iterate over all specific platforms and group by product prefix
const specificPlatformEntries = Object.entries(SpecificPlatform);
for (const [name, platform] of specificPlatformEntries) {
// Check which product this platform belongs to
for (const [prod, prefix] of Object.entries(PRODUCT_PREFIX_MAP)) {
if (name.startsWith(prefix)) {
result[prod].push(platform);
break;
}
}
}
// Cache the result as readonly
PRODUCT_SPECIFIC_PLATFORMS = result;
}
return PRODUCT_SPECIFIC_PLATFORMS[product];
};
/**
* Gets all available platform names from the platform maps.
*
* @returns Object containing arrays of all specific and generic platform names.
*
* @example
* ```typescript
* const { specificPlatformNames, genericPlatformNames } = getAllPlatformNames();
* // specificPlatformNames: ['adg_os_windows', 'adg_os_mac', 'adg_os_android', ...]
* // genericPlatformNames: ['adg_os_any', 'adg_safari_any', 'adg_ext_chromium', ...]
* ```
*/
const getAllPlatformNames = () => {
return {
specificPlatformNames: Array.from(SPECIFIC_PLATFORM_MAP.keys()),
genericPlatformNames: Array.from(GENERIC_PLATFORM_MAP.keys()),
};
};
export { GENERIC_PLATFORM_MAP, SPECIFIC_PLATFORM_MAP, SPECIFIC_PLATFORM_MAP_REVERSE, getAllPlatformNames, getHumanReadablePlatformName, getPlatformId, getPlatformsByProduct, getProductGenericPlatforms, getProductSpecificPlatforms, getSpecificPlatformName, hasPlatformMultipleProducts, isGenericPlatform, platformToAdblockProduct };