Files
SnowLuma/.pnpm-store/v11/files/a9/4656b4db645c616e1cfe2995ff050603db27733dfa9a1cec62cc14deb5400a3870bdc17938769ec31e3a65e5fb6f7153aa4acf9c322a54c7cbf426f8fee749
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

112 lines
3.8 KiB
Plaintext

import { extend, sorted } from '../../utils/helper.mjs';
import { MatcherBaseClass } from '../../types.mjs';
/*
* ------------------------------------------------------------------------------
* spatial match (qwerty/dvorak/keypad and so on) -----------------------------------------
* ------------------------------------------------------------------------------
*/
class MatchSpatial extends MatcherBaseClass {
constructor() {
super(...arguments);
this.SHIFTED_RX = /[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/;
}
match({
password
}) {
const matches = [];
Object.keys(this.options.graphs).forEach(graphName => {
const graph = this.options.graphs[graphName];
extend(matches, this.helper(password, graph, graphName));
});
return sorted(matches);
}
checkIfShifted(graphName, password, index) {
if (!graphName.includes('keypad') &&
// initial character is shifted
this.SHIFTED_RX.test(password.charAt(index))) {
return 1;
}
return 0;
}
// eslint-disable-next-line complexity, max-statements
helper(password, graph, graphName) {
let shiftedCount;
const matches = [];
let i = 0;
const passwordLength = password.length;
while (i < passwordLength - 1) {
let j = i + 1;
let lastDirection = null;
let turns = 0;
shiftedCount = this.checkIfShifted(graphName, password, i);
while (true) {
const prevChar = password.charAt(j - 1);
const adjacents = graph[prevChar] || [];
let found = false;
let curDirection = -1;
// consider growing pattern by one character if j hasn't gone over the edge.
if (j < passwordLength) {
const curChar = password.charAt(j);
const adjacentsLength = adjacents.length;
for (let k = 0; k < adjacentsLength; k += 1) {
const adjacent = adjacents[k];
curDirection += 1;
// eslint-disable-next-line max-depth
if (adjacent) {
const adjacentIndex = adjacent.indexOf(curChar);
// eslint-disable-next-line max-depth
if (adjacentIndex !== -1) {
found = true;
// eslint-disable-next-line max-depth
if (adjacentIndex === 1) {
// # index 1 in the adjacency means the key is shifted,
// # 0 means unshifted: A vs a, % vs 5, etc.
// # for example, 'q' is adjacent to the entry '2@'.
// # @ is shifted w/ index 1, 2 is unshifted.
shiftedCount += 1;
}
const foundDirection = curDirection;
// eslint-disable-next-line max-depth
if (lastDirection !== foundDirection) {
// # adding a turn is correct even in the initial
// case when last_direction is null:
// # every spatial pattern starts with a turn.
turns += 1;
lastDirection = foundDirection;
}
break;
}
}
}
}
// if the current pattern continued, extend j and try to grow again
if (found) {
j += 1;
// otherwise push the pattern discovered so far, if any...
} else {
// don't consider length 1 or 2 chains.
if (j - i > 2) {
matches.push({
pattern: 'spatial',
i,
j: j - 1,
token: password.slice(i, j),
graph: graphName,
turns,
shiftedCount
});
}
// ...and then start a new search for the rest of the password.
i = j;
break;
}
}
}
return matches;
}
}
export { MatchSpatial as default };
//# sourceMappingURL=matching.mjs.map