Files
SnowLuma/.pnpm-store/v11/files/ff/b50bb1542f228177563ec0a8366f53b95585ffbd50da032ec716ef4a55132bfd63becea9bc42131d925c71648dfc3506d80eae845a36eb1a18aea0576a0490
TanTan 2a69b23b47
Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Canceled after 0s
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

242 lines
6.5 KiB
Plaintext

import { buildRankedDictionary } from './utils/helper'
import {
TranslationKeys,
OptionsType,
OptionsDictionary,
OptionsL33tTable,
OptionsGraph,
RankedDictionaries,
Matchers,
Matcher,
UserInputsOptions,
RankedDictionary,
TimeEstimationValues,
} from './types'
import l33tTable from './data/l33tTable'
import translationKeys from './data/translationKeys'
import TrieNode from './matcher/dictionary/variants/matching/unmunger/TrieNode'
import l33tTableToTrieNode from './matcher/dictionary/variants/matching/unmunger/l33tTableToTrieNode'
import {
checkTimeEstimationValues,
timeEstimationValuesDefaults,
} from './TimeEstimates'
export default class Options {
public matchers: Matchers = {}
public l33tTable: OptionsL33tTable = l33tTable
public trieNodeRoot: TrieNode = l33tTableToTrieNode(l33tTable, new TrieNode())
public dictionary: OptionsDictionary = {
userInputs: [],
}
public rankedDictionaries: RankedDictionaries = {}
public rankedDictionariesMaxWordSize: Record<string, number> = {}
public translations: TranslationKeys = translationKeys
public graphs: OptionsGraph = {}
public useLevenshteinDistance = false
public levenshteinThreshold = 2
public l33tMaxSubstitutions = 100
public maxLength = 256
public wordSequenceNames = [
'cardinalNumbers',
'ordinalNumbers',
'daysOfWeek',
'months',
'seasons',
'timePeriods',
'rainbowColors',
'directions',
'intermediateDirections',
'sizeProgression',
'militaryAlphabet',
'planets',
'zodiacSigns',
'chineseZodiac',
]
timeEstimationValues: TimeEstimationValues = {
scoring: {
...timeEstimationValuesDefaults.scoring,
},
attackTime: {
...timeEstimationValuesDefaults.attackTime,
},
}
constructor(
options: OptionsType = {},
customMatchers: Record<string, Matcher> = {},
) {
this.setOptions(options)
Object.entries(customMatchers).forEach(([name, matcher]) => {
this.addMatcher(name, matcher)
})
}
public isWordSequence(key: string) {
return this.wordSequenceNames.some(
(name) => key === name || key.startsWith(`${name}-`),
)
}
// eslint-disable-next-line max-statements,complexity
private setOptions(options: OptionsType = {}) {
if (options.l33tTable) {
this.l33tTable = options.l33tTable
this.trieNodeRoot = l33tTableToTrieNode(options.l33tTable, new TrieNode())
}
if (options.dictionary) {
this.dictionary = options.dictionary
this.setRankedDictionaries()
}
if (options.translations) {
this.setTranslations(options.translations)
}
if (options.graphs) {
this.graphs = options.graphs
}
if (options.useLevenshteinDistance !== undefined) {
this.useLevenshteinDistance = options.useLevenshteinDistance
}
if (options.levenshteinThreshold !== undefined) {
this.levenshteinThreshold = options.levenshteinThreshold
}
if (options.l33tMaxSubstitutions !== undefined) {
this.l33tMaxSubstitutions = options.l33tMaxSubstitutions
}
if (options.maxLength !== undefined) {
this.maxLength = options.maxLength
}
if (options.timeEstimationValues !== undefined) {
checkTimeEstimationValues(options.timeEstimationValues)
this.timeEstimationValues = {
scoring: {
...options.timeEstimationValues.scoring,
},
attackTime: {
...options.timeEstimationValues.attackTime,
},
}
}
}
private setTranslations(translations: TranslationKeys) {
if (this.checkCustomTranslations(translations)) {
this.translations = translations
} else {
throw new Error('Invalid translations object fallback to keys')
}
}
private checkCustomTranslations(translations: TranslationKeys) {
let valid = true
Object.keys(translationKeys).forEach((type) => {
if (type in translations) {
const translationType = type as keyof typeof translationKeys
Object.keys(translationKeys[translationType]).forEach((key) => {
if (!(key in translations[translationType])) {
valid = false
}
const translation = (translations[translationType] as any)[key]
if (
typeof translation !== 'string' &&
typeof translation !== 'function'
) {
valid = false
}
})
} else {
valid = false
}
})
return valid
}
private setRankedDictionaries() {
const rankedDictionaries: RankedDictionaries = {}
const rankedDictionariesMaxWorkSize: Record<string, number> = {}
Object.keys(this.dictionary).forEach((name) => {
rankedDictionaries[name] = buildRankedDictionary(this.dictionary[name])
rankedDictionariesMaxWorkSize[name] =
this.getRankedDictionariesMaxWordSize(this.dictionary[name])
})
this.rankedDictionaries = rankedDictionaries
this.rankedDictionariesMaxWordSize = rankedDictionariesMaxWorkSize
}
private getRankedDictionariesMaxWordSize(list: (string | number)[]) {
const data = list.map((el) => {
if (typeof el !== 'string') {
return el.toString().length
}
return el.length
})
// do not use Math.max(...data) because it can result in max stack size error because every entry will be used as an argument
if (data.length === 0) {
return 0
}
return data.reduce((a, b) => Math.max(a, b), -Infinity)
}
private buildSanitizedRankedDictionary(list: (string | number)[]) {
const sanitizedInputs: string[] = []
list.forEach((input: string | number | boolean) => {
const inputType = typeof input
if (
inputType === 'string' ||
inputType === 'number' ||
inputType === 'boolean'
) {
sanitizedInputs.push(input.toString().toLowerCase())
}
})
return buildRankedDictionary(sanitizedInputs)
}
public getUserInputsOptions(
dictionary?: (string | number)[],
): UserInputsOptions {
let rankedDictionary: RankedDictionary = {}
let rankedDictionaryMaxWordSize = 0
if (dictionary) {
rankedDictionary = this.buildSanitizedRankedDictionary(dictionary)
rankedDictionaryMaxWordSize =
this.getRankedDictionariesMaxWordSize(dictionary)
}
return {
rankedDictionary,
rankedDictionaryMaxWordSize,
}
}
private addMatcher(name: string, matcher: Matcher) {
if (this.matchers[name]) {
console.info(`Matcher ${name} already exists`)
} else {
this.matchers[name] = matcher
}
}
}