dorksmith 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dorksmith-0.1.0 → dorksmith-0.2.0}/.gitignore +3 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/PKG-INFO +5 -5
- {dorksmith-0.1.0 → dorksmith-0.2.0}/README.md +4 -4
- {dorksmith-0.1.0 → dorksmith-0.2.0}/pyproject.toml +1 -1
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/__init__.py +1 -1
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/normalizer.py +24 -2
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/quoting.py +8 -2
- {dorksmith-0.1.0 → dorksmith-0.2.0}/tests/test_golden.py +1 -1
- dorksmith-0.2.0/tests/test_phrases.py +42 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/LICENSE +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/scripts/sync_catalogs.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/analyzer.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/catalogs.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/cli.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/data/filetypes.json +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/data/intents.json +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/data/operators.google.json +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/data/platforms.json +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/errors.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/generator.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/handles.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/infer.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/placeholders.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/py.typed +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/ranker.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/resolver.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/src/dorksmith/validator.py +0 -0
- {dorksmith-0.1.0 → dorksmith-0.2.0}/tests/test_unit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dorksmith
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Deterministic search-dork generator and OSINT/SOCMINT query engine. Catalog-driven, pure Python, no dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/aelena/dorksmith
|
|
6
6
|
Project-URL: Repository, https://github.com/aelena/dorksmith
|
|
@@ -78,7 +78,7 @@ validate_query("cache:example.com site:example.com or filetype:.pdf").warnings
|
|
|
78
78
|
# ["Lower-case 'or' is treated as an ordinary word; ...", "filetype: values take no leading dot ...", "cache: is deprecated: ..."]
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Results are dataclasses; `.to_dict()` gives the same camelCase shape
|
|
81
|
+
Results are dataclasses; `.to_dict()` gives the same camelCase shape the npm package and the web app use.
|
|
82
82
|
|
|
83
83
|
## API
|
|
84
84
|
|
|
@@ -89,11 +89,11 @@ Results are dataclasses; `.to_dict()` gives the same camelCase shape as the HTTP
|
|
|
89
89
|
| `expand_handle(username, *, categories=None, platform_ids=None, max_platforms=None, catalogs=None)` | Profile URLs (`status` always `not-checked`) and site-scoped queries. |
|
|
90
90
|
| `infer_input_type(text, known_extensions=None)` | Deterministic input-type suggestion. |
|
|
91
91
|
| `search_url(query, engine="google")` | Search-engine URL, percent-encoded locally. |
|
|
92
|
-
| `validate_catalogs(catalogs)` | Structural/cross-reference validation
|
|
92
|
+
| `validate_catalogs(catalogs)` | Structural/cross-reference validation of the catalogs. |
|
|
93
93
|
| `bundled_catalogs()`, `load_catalogs(path)`, `catalog_version()` | Embedded or custom catalogs. |
|
|
94
94
|
| lower-level: `normalize_text`, `try_normalize_domain`, `quote`, `safe_term`, `analyze_query`, `expand_template`, … | Building blocks for custom pipelines. |
|
|
95
95
|
|
|
96
|
-
`options` keys match the
|
|
96
|
+
`options` keys match the npm package: `fileTypes`, `excludeTerms`, `after`, `before`, `site`, `maxVariants`, `organization`, `location`, `role`, `displayName`.
|
|
97
97
|
|
|
98
98
|
### Bring your own catalogs
|
|
99
99
|
|
|
@@ -121,7 +121,7 @@ Exit codes: `0` ok, `2` invalid input (or a query with syntax errors for `valida
|
|
|
121
121
|
```bash
|
|
122
122
|
python -m pip install -e ".[test]"
|
|
123
123
|
python scripts/sync_catalogs.py # copies ../../data/*.json into src/dorksmith/data
|
|
124
|
-
python -m pytest -q # unit tests + the
|
|
124
|
+
python -m pytest -q # unit tests + the 153 golden fixtures from the monorepo
|
|
125
125
|
python -m build # sdist + wheel
|
|
126
126
|
```
|
|
127
127
|
|
|
@@ -28,7 +28,7 @@ validate_query("cache:example.com site:example.com or filetype:.pdf").warnings
|
|
|
28
28
|
# ["Lower-case 'or' is treated as an ordinary word; ...", "filetype: values take no leading dot ...", "cache: is deprecated: ..."]
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Results are dataclasses; `.to_dict()` gives the same camelCase shape
|
|
31
|
+
Results are dataclasses; `.to_dict()` gives the same camelCase shape the npm package and the web app use.
|
|
32
32
|
|
|
33
33
|
## API
|
|
34
34
|
|
|
@@ -39,11 +39,11 @@ Results are dataclasses; `.to_dict()` gives the same camelCase shape as the HTTP
|
|
|
39
39
|
| `expand_handle(username, *, categories=None, platform_ids=None, max_platforms=None, catalogs=None)` | Profile URLs (`status` always `not-checked`) and site-scoped queries. |
|
|
40
40
|
| `infer_input_type(text, known_extensions=None)` | Deterministic input-type suggestion. |
|
|
41
41
|
| `search_url(query, engine="google")` | Search-engine URL, percent-encoded locally. |
|
|
42
|
-
| `validate_catalogs(catalogs)` | Structural/cross-reference validation
|
|
42
|
+
| `validate_catalogs(catalogs)` | Structural/cross-reference validation of the catalogs. |
|
|
43
43
|
| `bundled_catalogs()`, `load_catalogs(path)`, `catalog_version()` | Embedded or custom catalogs. |
|
|
44
44
|
| lower-level: `normalize_text`, `try_normalize_domain`, `quote`, `safe_term`, `analyze_query`, `expand_template`, … | Building blocks for custom pipelines. |
|
|
45
45
|
|
|
46
|
-
`options` keys match the
|
|
46
|
+
`options` keys match the npm package: `fileTypes`, `excludeTerms`, `after`, `before`, `site`, `maxVariants`, `organization`, `location`, `role`, `displayName`.
|
|
47
47
|
|
|
48
48
|
### Bring your own catalogs
|
|
49
49
|
|
|
@@ -71,7 +71,7 @@ Exit codes: `0` ok, `2` invalid input (or a query with syntax errors for `valida
|
|
|
71
71
|
```bash
|
|
72
72
|
python -m pip install -e ".[test]"
|
|
73
73
|
python scripts/sync_catalogs.py # copies ../../data/*.json into src/dorksmith/data
|
|
74
|
-
python -m pytest -q # unit tests + the
|
|
74
|
+
python -m pytest -q # unit tests + the 153 golden fixtures from the monorepo
|
|
75
75
|
python -m build # sdist + wheel
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dorksmith"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Deterministic search-dork generator and OSINT/SOCMINT query engine. Catalog-driven, pure Python, no dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -29,7 +29,7 @@ from .placeholders import ALL_PLACEHOLDERS, OPTIONAL_BY_DEFAULT, placeholders_in
|
|
|
29
29
|
from .quoting import exclusion, looks_like_syntax, operator_value, or_group, quote, safe_term, safe_terms, unquote
|
|
30
30
|
from .validator import validate_catalogs
|
|
31
31
|
|
|
32
|
-
__version__ = "0.
|
|
32
|
+
__version__ = "0.2.0"
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def catalog_version(catalogs: Catalogs | None = None) -> str:
|
|
@@ -51,12 +51,34 @@ def normalize_text(value: str | None) -> str:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def words(normalized: str) -> tuple[list[str], bool]:
|
|
54
|
-
"""Split normalised text into
|
|
54
|
+
"""Split normalised text into terms.
|
|
55
|
+
|
|
56
|
+
A fully quoted input ("a b") is unwrapped; quoted phrases inside the text (alice "antonio elena")
|
|
57
|
+
are kept as single terms, quotes included, so templates preserve them.
|
|
58
|
+
"""
|
|
55
59
|
s = normalized
|
|
56
60
|
quoted = len(s) >= 2 and s[0] == '"' and s[-1] == '"' and s.find('"', 1) == len(s) - 1
|
|
57
61
|
if quoted:
|
|
58
62
|
s = s[1:-1].strip()
|
|
59
|
-
|
|
63
|
+
out: list[str] = []
|
|
64
|
+
i, n = 0, len(s)
|
|
65
|
+
while i < n:
|
|
66
|
+
if s[i] == " ":
|
|
67
|
+
i += 1
|
|
68
|
+
continue
|
|
69
|
+
if s[i] == '"':
|
|
70
|
+
end = s.find('"', i + 1)
|
|
71
|
+
inner = (s[i + 1:] if end < 0 else s[i + 1:end]).strip()
|
|
72
|
+
if inner:
|
|
73
|
+
out.append(f'"{inner}"')
|
|
74
|
+
i = n if end < 0 else end + 1
|
|
75
|
+
continue
|
|
76
|
+
j = i
|
|
77
|
+
while j < n and s[j] != " ":
|
|
78
|
+
j += 1
|
|
79
|
+
out.append(s[i:j])
|
|
80
|
+
i = j
|
|
81
|
+
return out, quoted
|
|
60
82
|
|
|
61
83
|
|
|
62
84
|
def try_normalize_domain(value: str | None) -> str | None:
|
|
@@ -27,6 +27,8 @@ def looks_like_syntax(s: str) -> bool:
|
|
|
27
27
|
return True
|
|
28
28
|
if s[0] in "-+~()|":
|
|
29
29
|
return True
|
|
30
|
+
if len(s) > 1 and s[0] in "@#": # social-handle / hashtag operators
|
|
31
|
+
return True
|
|
30
32
|
if s[-1] in "()":
|
|
31
33
|
return True
|
|
32
34
|
colon = s.find(":")
|
|
@@ -36,8 +38,12 @@ def looks_like_syntax(s: str) -> bool:
|
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
def safe_term(word: str | None) -> str:
|
|
39
|
-
"""A single
|
|
40
|
-
|
|
41
|
+
"""A single term emitted unquoted; terms that would parse as syntax are quoted so they stay literal,
|
|
42
|
+
and a term the user already quoted ("antonio elena") is kept as a quoted phrase."""
|
|
43
|
+
raw = (word or "").strip()
|
|
44
|
+
if len(raw) >= 2 and raw[0] == '"' and raw[-1] == '"':
|
|
45
|
+
return quote(raw)
|
|
46
|
+
s = raw.replace('"', "")
|
|
41
47
|
if not s:
|
|
42
48
|
return ""
|
|
43
49
|
if looks_like_syntax(s):
|
|
@@ -11,7 +11,7 @@ import pytest
|
|
|
11
11
|
|
|
12
12
|
from dorksmith import bundled_catalogs, generate, validate_catalogs
|
|
13
13
|
|
|
14
|
-
FIXTURES = Path(__file__).resolve().parents[3] / "tests" / "
|
|
14
|
+
FIXTURES = Path(__file__).resolve().parents[3] / "tests" / "golden"
|
|
15
15
|
FILES = sorted(FIXTURES.glob("*.json")) if FIXTURES.is_dir() else []
|
|
16
16
|
_covered: set[str] = set()
|
|
17
17
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Quoted phrases and social-style tokens inside free-text input."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dorksmith import analyze_query, bundled_catalogs, generate, safe_term
|
|
5
|
+
from dorksmith.normalizer import words
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_words_keeps_user_quoted_phrases() -> None:
|
|
9
|
+
assert words('aelena "antonio elena"')[0] == ["aelena", '"antonio elena"']
|
|
10
|
+
assert words('"antonio elena" aelena x')[0] == ['"antonio elena"', "aelena", "x"]
|
|
11
|
+
assert words('a "" b')[0] == ["a", "b"]
|
|
12
|
+
assert words('a "unterminated phrase')[0] == ["a", '"unterminated phrase"']
|
|
13
|
+
assert words('"whole input quoted"') == (["whole", "input", "quoted"], True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_safe_term_keeps_phrases_and_neutralises_social_tokens() -> None:
|
|
17
|
+
assert safe_term('"antonio elena"') == '"antonio elena"'
|
|
18
|
+
assert safe_term('"nested "quotes" inside"') == '"nested quotes inside"'
|
|
19
|
+
assert safe_term('""') == ""
|
|
20
|
+
assert safe_term("@aelena") == '"@aelena"'
|
|
21
|
+
assert safe_term("#osint") == '"#osint"'
|
|
22
|
+
assert safe_term("@") == "@"
|
|
23
|
+
assert safe_term("a@b") == "a@b"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_quoted_phrase_survives_variants() -> None:
|
|
27
|
+
r = generate('aelena "antonio elena"', "keyword", "general-discovery", options={"maxVariants": 12})
|
|
28
|
+
queries = [v.query for v in r.variants]
|
|
29
|
+
assert 'aelena "antonio elena"' in queries
|
|
30
|
+
assert '(aelena OR "antonio elena")' in queries
|
|
31
|
+
assert '"aelena antonio elena"' in queries
|
|
32
|
+
assert all('""' not in q for q in queries)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_leading_at_is_quoted_without_operator_warning() -> None:
|
|
36
|
+
r = generate("@aelena antonio elena", "keyword", "general-discovery", options={"maxVariants": 12})
|
|
37
|
+
for v in r.variants:
|
|
38
|
+
assert "@" not in v.operators, v.query
|
|
39
|
+
assert v.warnings == [], v.query
|
|
40
|
+
assert any(v.query == '"@aelena" antonio elena' for v in r.variants)
|
|
41
|
+
catalogs = bundled_catalogs()
|
|
42
|
+
assert analyze_query('"@aelena" antonio elena', catalogs.operators["google"], catalogs.operator_index("google")).operators == ['"']
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|