Yves Gugger 3485668b5e
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
feat: add Alpha Terminal HUNT/CFO modules and Analyze framework
Adds HUNT (Sniper/Trend/Forge), CFO dashboard (burn rate + kill list), and a plugin-based Analyze side panel with caching and SSRF hardening.
2025-12-15 16:15:58 +01:00

90 lines
1.9 KiB
Python

"""
Typo generator for Trend Surfer / brand typos.
No external APIs.
"""
from __future__ import annotations
import string
KEYBOARD_NEIGHBORS = {
# simplified QWERTY adjacency (enough for useful typos)
"q": "wa",
"w": "qase",
"e": "wsdr",
"r": "edft",
"t": "rfgy",
"y": "tghu",
"u": "yhji",
"i": "ujko",
"o": "iklp",
"p": "ol",
"a": "qwsz",
"s": "awedxz",
"d": "serfcx",
"f": "drtgvc",
"g": "ftyhbv",
"h": "gyujbn",
"j": "huikmn",
"k": "jiolm",
"l": "kop",
"z": "asx",
"x": "zsdc",
"c": "xdfv",
"v": "cfgb",
"b": "vghn",
"n": "bhjm",
"m": "njk",
}
def _normalize_brand(brand: str) -> str:
b = (brand or "").lower().strip()
b = "".join(ch for ch in b if ch in string.ascii_lowercase)
return b
def generate_typos(brand: str, *, limit: int = 100) -> list[str]:
b = _normalize_brand(brand)
if len(b) < 2:
return []
candidates: list[str] = []
seen = set()
def _add(s: str):
if s and s not in seen and s != b:
seen.add(s)
candidates.append(s)
# 1) single deletion
for i in range(len(b)):
_add(b[:i] + b[i + 1 :])
if len(candidates) >= limit:
return candidates
# 2) single insertion (duplicate char)
for i in range(len(b)):
_add(b[:i] + b[i] + b[i:])
if len(candidates) >= limit:
return candidates
# 3) adjacent transposition
for i in range(len(b) - 1):
_add(b[:i] + b[i + 1] + b[i] + b[i + 2 :])
if len(candidates) >= limit:
return candidates
# 4) neighbor substitution
for i, ch in enumerate(b):
neigh = KEYBOARD_NEIGHBORS.get(ch, "")
for n in neigh:
_add(b[:i] + n + b[i + 1 :])
if len(candidates) >= limit:
return candidates
return candidates[:limit]