FiveM Natives + Docs Nachschlagewerk
7359 Natives (GTA5 + Cfx) und 342 Doc-Seiten von docs.fivem.net in einer SQLite-DB mit FTS5-Volltextsuche, dazu ein dependency-freies Python-CLI und eine SKILL.md fuer Claude Code. - build.py zieht runtime.fivem.net/doc/natives*.json und citizenfx/fivem-docs und loest die Hugo-Shortcodes der Docs auf (code, native_link, alert, events) - lua_name folgt exakt der Regel aus FiveM ext/natives/codegen_out_lua.lua - fivem.py: show / search / ns / docs / doc / stats, optional --json - data/natives.jsonl als grep-barer Fallback Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.db binary
|
||||
*.jsonl text eol=lf
|
||||
@@ -0,0 +1,3 @@
|
||||
.cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -0,0 +1,141 @@
|
||||
# fivem-natives-db
|
||||
|
||||
Offline-Nachschlagewerk für **FiveM Natives** und **docs.fivem.net** — als SQLite-Datenbank
|
||||
mit Volltextsuche (FTS5), CLI und fertigem Claude-Code-Skill.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Natives | **7.359** (6.416 GTA5 + 943 Cfx) |
|
||||
| Doc-Seiten | **342** aus `citizenfx/fivem-docs` |
|
||||
| Größe | ~11 MB SQLite + ~4 MB JSONL |
|
||||
| Dependencies | keine — nur Python 3.8+ Stdlib |
|
||||
|
||||
## Installation als Claude-Code-Skill
|
||||
|
||||
Ins Skill-Verzeichnis klonen, dann findet Claude Code die `SKILL.md` automatisch:
|
||||
|
||||
```bash
|
||||
git clone https://git.d4rkst3r.de/D4rkst3r/fivem-natives-db.git ~/.claude/skills/fivem-natives
|
||||
```
|
||||
|
||||
Unter Windows (PowerShell):
|
||||
|
||||
```powershell
|
||||
git clone https://git.d4rkst3r.de/D4rkst3r/fivem-natives-db.git "$env:USERPROFILE\.claude\skills\fivem-natives"
|
||||
```
|
||||
|
||||
Danach greift Claude bei Fragen zu Natives, Hashes, Signaturen oder FiveM-Docs
|
||||
automatisch auf die lokale DB zu, statt die Doku zu raten oder online zu fetchen.
|
||||
|
||||
Alternativ irgendwohin klonen und `fivem.py` direkt benutzen.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
python fivem.py ragdoll # kombinierte Suche (Natives + Docs)
|
||||
python fivem.py show SetPedToRagdoll # volles Detail
|
||||
python fivem.py show 0xAE99FB955581844A # ... auch per Hash oder jhash
|
||||
python fivem.py search vehicle engine --apiset client --limit 20
|
||||
python fivem.py ns # alle 45 Namespaces
|
||||
python fivem.py ns VEHICLE # Natives eines Namespace
|
||||
python fivem.py docs fxmanifest # Docs-Volltextsuche
|
||||
python fivem.py doc scripting-reference/resource-manifest
|
||||
python fivem.py stats
|
||||
```
|
||||
|
||||
`show` liefert Signatur, Parameter mit Typ und Beschreibung, Rückgabewert,
|
||||
Beschreibungstext und die offiziellen Codebeispiele:
|
||||
|
||||
```
|
||||
## SetPedToRagdoll
|
||||
`0xAE99FB955581844A` **PED** | **client** | **gta5**
|
||||
Native-Name: `SET_PED_TO_RAGDOLL`
|
||||
|
||||
void SetPedToRagdoll(Ped ped, int minTime, int maxTime, int ragdollType,
|
||||
BOOL bAbortIfInjured, BOOL bAbortIfDead, BOOL bForceScriptControl)
|
||||
```
|
||||
|
||||
Mit `--json` gibt es maschinenlesbare Ausgabe.
|
||||
|
||||
## Schema
|
||||
|
||||
`data/natives.db`:
|
||||
|
||||
| Tabelle | Inhalt |
|
||||
|---|---|
|
||||
| `natives` | ein Eintrag pro Native — s.u. |
|
||||
| `natives_fts` | FTS5 über `name`, `lua_name`, `ns`, `description`, `param_names`, `aliases`, `hash` |
|
||||
| `docs` | `path`, `slug`, `section`, `title`, `weight`, `url`, `body` (Shortcodes aufgelöst), `raw` |
|
||||
| `docs_fts` | FTS5 über `title`, `section`, `slug`, `body` |
|
||||
| `meta` | `built_at`, `native_count`, `doc_count`, `sources`, `schema_version` |
|
||||
|
||||
Spalten in `natives`:
|
||||
|
||||
`hash`, `jhash`, `name` (Original, z.B. `SET_ENTITY_COORDS`), `lua_name`
|
||||
(Aufrufname, z.B. `SetEntityCoords`), `ns`, `source` (`gta5`|`cfx`), `game`,
|
||||
`apiset` (`client`|`server`|`shared`), `results`, `results_description`,
|
||||
`description`, `signature`, `params_json`, `param_names`, `examples_json`,
|
||||
`aliases`, `url`.
|
||||
|
||||
Direkt per SQL nutzbar:
|
||||
|
||||
```sql
|
||||
SELECT n.lua_name, n.ns, n.signature
|
||||
FROM natives_fts f JOIN natives n ON n.id = f.rowid
|
||||
WHERE natives_fts MATCH 'vehicle AND engine'
|
||||
AND n.apiset = 'client'
|
||||
ORDER BY rank LIMIT 20;
|
||||
```
|
||||
|
||||
`data/natives.jsonl` enthält dieselben Natives als eine JSON-Zeile pro Native —
|
||||
praktisch für `grep`, wenn kein Python zur Hand ist.
|
||||
|
||||
## Rebuild
|
||||
|
||||
```bash
|
||||
python build.py --refresh
|
||||
```
|
||||
|
||||
Quellen:
|
||||
|
||||
- `https://runtime.fivem.net/doc/natives.json` — GTA5-Natives
|
||||
- `https://runtime.fivem.net/doc/natives_cfx.json` — Cfx/FiveM-Natives
|
||||
- `https://github.com/citizenfx/fivem-docs` — Markdown von docs.fivem.net
|
||||
- `https://runtime.fivem.net/doc/events/{client,server}.html.json` — Event-Referenz
|
||||
|
||||
Ohne `--refresh` wird aus `.cache/` gebaut (nicht eingecheckt).
|
||||
|
||||
Beim Build werden die Hugo-Shortcodes der Docs aufgelöst, damit der Inhalt
|
||||
durchsuchbar ist: `code` (bindet Beispieldateien ein), `native_link` (wird zum
|
||||
Link auf docs.fivem.net), `alert` (wird zum Blockquote), `rmv`/`rmv2`, `events`
|
||||
(zieht die Client-/Server-Event-Referenz), `youtube`/`video`/`forum_topic`.
|
||||
|
||||
## Namenskonvertierung
|
||||
|
||||
`lua_name` folgt exakt der Regel aus FiveM `ext/natives/codegen_out_lua.lua`:
|
||||
|
||||
```
|
||||
name:lower():gsub('_(%a)', upper):gsub('^%l', upper)
|
||||
```
|
||||
|
||||
Ein Unterstrich vor einer **Ziffer** bleibt deshalb erhalten:
|
||||
|
||||
| Native | Lua |
|
||||
|---|---|
|
||||
| `SET_ENTITY_COORDS` | `SetEntityCoords` |
|
||||
| `_FORCE_VEHICLE_ENGINE_SYNTH` | `ForceVehicleEngineSynth` |
|
||||
| `DRAW_SCALEFORM_MOVIE_3D_SOLID` | `DrawScaleformMovie_3dSolid` |
|
||||
| `UI3DSCENE_IS_AVAILABLE` | `Ui3dsceneIsAvailable` |
|
||||
| (unbenannt) | `N_0x<hash>` |
|
||||
|
||||
## Hinweis zur Repo-Größe
|
||||
|
||||
`data/` ist eingecheckt, damit ein Clone sofort einsatzbereit ist. Jeder Rebuild
|
||||
legt ~15 MB in die Git-History. Bei häufigen Updates den Data-Commit amenden oder
|
||||
die History gelegentlich squashen.
|
||||
|
||||
## Lizenz
|
||||
|
||||
Der Code hier ist frei verwendbar. Die Daten stammen von Cfx.re:
|
||||
Natives-Metadaten und `citizenfx/fivem-docs` stehen unter den jeweiligen
|
||||
Lizenzen der Upstream-Projekte.
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: fivem-natives
|
||||
description: Nachschlagewerk für FiveM/GTA5 Natives und docs.fivem.net. Nutzen, sobald ein FiveM-Native, ein Hash (0x...), eine Native-Signatur, ein Parameter oder ein Rückgabewert geprüft werden soll, wenn unklar ist ob ein Native client-, server- oder shared-seitig ist, wenn ein Native über die Beschreibung gesucht wird ("wie setze ich einen Ped in Ragdoll"), oder wenn etwas aus den FiveM-Docs gebraucht wird (fxmanifest, Resource-Manifest-Einträge, Server-Convars, Events, Scripting-Runtimes, Server-Setup, Streaming/Assets, Game-Referenzen).
|
||||
---
|
||||
|
||||
# FiveM Natives & Docs
|
||||
|
||||
Lokale SQLite-Datenbank mit **7.359 Natives** (GTA5 + Cfx) und **342 Doc-Seiten** von
|
||||
docs.fivem.net. Offline, keine Dependencies außer Python 3.
|
||||
|
||||
**Immer diese Datenbank statt WebFetch/WebSearch verwenden** — sie ist schneller,
|
||||
vollständig und enthält die Beschreibungen, Parametertypen und Beispiele aus den
|
||||
offiziellen Quellen.
|
||||
|
||||
## Aufruf
|
||||
|
||||
Alle Kommandos relativ zum Skill-Verzeichnis (`$SKILL_DIR = Ordner dieser SKILL.md`):
|
||||
|
||||
```bash
|
||||
python "$SKILL_DIR/fivem.py" <kommando> [args]
|
||||
```
|
||||
|
||||
## Kommandos
|
||||
|
||||
| Kommando | Zweck |
|
||||
|---|---|
|
||||
| `fivem.py <freitext>` | Kombinierte Suche über Natives **und** Docs (Default) |
|
||||
| `fivem.py show <Name\|Hash>` | Volles Detail: Signatur, Parameter, Rückgabe, Beschreibung, Beispiele |
|
||||
| `fivem.py search <worte>` | Nur Natives, mit `--apiset client\|server\|shared`, `--ns VEHICLE`, `--limit N`, `--json` |
|
||||
| `fivem.py ns` / `fivem.py ns PED` | Namespaces auflisten / alle Natives eines Namespace |
|
||||
| `fivem.py docs <worte>` | Volltextsuche in docs.fivem.net (`--section scripting-reference`) |
|
||||
| `fivem.py doc <slug>` | Komplette Doc-Seite ausgeben |
|
||||
| `fivem.py stats` | Umfang / Build-Datum |
|
||||
|
||||
## Typische Abläufe
|
||||
|
||||
**Signatur eines bekannten Natives prüfen** — vor dem Schreiben von Native-Aufrufen:
|
||||
```bash
|
||||
python "$SKILL_DIR/fivem.py" show SetEntityCoords
|
||||
python "$SKILL_DIR/fivem.py" show 0xAE99FB955581844A
|
||||
```
|
||||
Akzeptiert Lua-Name (`SetPedToRagdoll`), Original-Name (`SET_PED_TO_RAGDOLL`),
|
||||
Hash und jhash — Groß-/Kleinschreibung egal.
|
||||
|
||||
**Native über die Funktion suchen**, wenn der Name unbekannt ist:
|
||||
```bash
|
||||
python "$SKILL_DIR/fivem.py" search vehicle engine health
|
||||
python "$SKILL_DIR/fivem.py" search identifier --apiset server
|
||||
```
|
||||
|
||||
**Server- vs. Client-Kontext klären** — das Feld `apiset` ist maßgeblich:
|
||||
```bash
|
||||
python "$SKILL_DIR/fivem.py" search player ping --apiset server
|
||||
```
|
||||
|
||||
**Docs nachschlagen**:
|
||||
```bash
|
||||
python "$SKILL_DIR/fivem.py" docs fxmanifest
|
||||
python "$SKILL_DIR/fivem.py" doc scripting-reference/resource-manifest
|
||||
python "$SKILL_DIR/fivem.py" docs convar --section server-manual
|
||||
```
|
||||
|
||||
## Wichtig beim Interpretieren
|
||||
|
||||
- **`apiset`** sagt, wo das Native läuft: `client`, `server` oder `shared`.
|
||||
Alle GTA5-Natives sind `client`; Cfx-Natives können server- oder shared-seitig sein.
|
||||
- **Gleicher Name, zwei Einträge**: viele Natives existieren zusätzlich als
|
||||
server-seitiges RPC-Pendant (z.B. `SetPedToRagdoll` als `0xAE99FB955581844A`
|
||||
client und `0x83CB5052` server). `show` gibt beide aus — den passenden auswählen.
|
||||
- **`lua_name`** ist der Bezeichner, der in Lua/JS tatsächlich aufgerufen wird.
|
||||
Achtung auf Sonderfälle wie `DrawScaleformMovie_3d` (Unterstrich vor Ziffern
|
||||
bleibt) und `World3dToScreen2d`.
|
||||
- **Unbenannte Natives** heißen `N_0x<hash>`; per `Citizen.InvokeNative(0x..., ...)`
|
||||
aufrufbar.
|
||||
- Parameter mit Namen wie `p4`, `p19` sind in den offiziellen Docs undokumentiert —
|
||||
das im Code kenntlich machen statt zu raten.
|
||||
|
||||
## Fallback ohne Python
|
||||
|
||||
`data/natives.jsonl` enthält ein Native pro Zeile und ist direkt grep-bar:
|
||||
```bash
|
||||
grep -i '"lua_name": "SetPedToRagdoll"' "$SKILL_DIR/data/natives.jsonl"
|
||||
```
|
||||
|
||||
## Daten aktualisieren
|
||||
|
||||
```bash
|
||||
python "$SKILL_DIR/build.py" --refresh
|
||||
```
|
||||
Zieht `runtime.fivem.net/doc/natives*.json` und `github.com/citizenfx/fivem-docs` neu.
|
||||
@@ -0,0 +1,434 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Baut data/natives.db (SQLite + FTS5) aus:
|
||||
- https://runtime.fivem.net/doc/natives.json (GTA5 natives)
|
||||
- https://runtime.fivem.net/doc/natives_cfx.json (Cfx/FiveM natives)
|
||||
- https://github.com/citizenfx/fivem-docs (docs.fivem.net Markdown)
|
||||
|
||||
Nur Python-Stdlib. Aufruf: python build.py [--refresh]
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
from html.parser import HTMLParser
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
DATA = os.path.join(ROOT, "data")
|
||||
DB = os.path.join(DATA, "natives.db")
|
||||
CACHE = os.path.join(ROOT, ".cache")
|
||||
|
||||
NATIVE_SOURCES = [
|
||||
("gta5", "https://runtime.fivem.net/doc/natives.json"),
|
||||
("cfx", "https://runtime.fivem.net/doc/natives_cfx.json"),
|
||||
]
|
||||
DOCS_REPO = "https://github.com/citizenfx/fivem-docs.git"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- helpers
|
||||
def log(*a):
|
||||
print("[build]", *a, flush=True)
|
||||
|
||||
|
||||
def fetch(url, dest):
|
||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||
log("fetch", url)
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "fivem-natives-db/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=180) as r, open(dest, "wb") as f:
|
||||
shutil.copyfileobj(r, f)
|
||||
return dest
|
||||
|
||||
|
||||
def pascal(name):
|
||||
"""SET_ENTITY_COORDS -> SetEntityCoords.
|
||||
|
||||
Repliziert exakt die Regel aus FiveM ext/natives/codegen_out_lua.lua:
|
||||
name:lower():gsub('_(%a)', upper):gsub('^%l', upper)
|
||||
Wichtig: ein Unterstrich vor einer *Ziffer* bleibt erhalten, deshalb wird
|
||||
DRAW_SCALEFORM_MOVIE_3D zu DrawScaleformMovie_3d (nicht ...Movie3D).
|
||||
"""
|
||||
if not name:
|
||||
return ""
|
||||
s = re.sub(r"_([a-z])", lambda m: m.group(1).upper(), name.lower())
|
||||
return s[:1].upper() + s[1:]
|
||||
|
||||
|
||||
def lua_name(nat):
|
||||
n = nat.get("name")
|
||||
if n:
|
||||
return pascal(n)
|
||||
# Unbenannte Natives werden von den Runtimes als N_0x<hash> exportiert
|
||||
return "N_" + nat["hash"].lower()
|
||||
|
||||
|
||||
def signature(nat, lname):
|
||||
ps = ", ".join(
|
||||
"%s %s" % (p.get("type", "Any"), p.get("name", "arg"))
|
||||
for p in nat.get("params", [])
|
||||
)
|
||||
return "%s %s(%s)" % (nat.get("results") or "void", lname, ps)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- natives
|
||||
def load_natives(refresh=False):
|
||||
rows = []
|
||||
for source, url in NATIVE_SOURCES:
|
||||
path = os.path.join(CACHE, os.path.basename(url))
|
||||
if refresh or not os.path.exists(path):
|
||||
fetch(url, path)
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
doc = json.load(f)
|
||||
for ns, natives in doc.items():
|
||||
for h, nat in natives.items():
|
||||
nat.setdefault("hash", h)
|
||||
lname = lua_name(nat)
|
||||
params = nat.get("params") or []
|
||||
rows.append({
|
||||
"hash": nat["hash"],
|
||||
"jhash": nat.get("jhash") or "",
|
||||
"name": nat.get("name") or "",
|
||||
"lua_name": lname,
|
||||
"ns": ns,
|
||||
"source": source,
|
||||
"game": nat.get("game") or ("gta5" if source == "gta5" else ""),
|
||||
"apiset": nat.get("apiset") or ("client" if source == "gta5" else ""),
|
||||
"results": nat.get("results") or "void",
|
||||
"results_description": nat.get("resultsDescription") or "",
|
||||
"description": (nat.get("description") or "").strip(),
|
||||
"signature": signature(nat, lname),
|
||||
"params_json": json.dumps(params, ensure_ascii=False),
|
||||
"param_names": " ".join(p.get("name", "") for p in params),
|
||||
"examples_json": json.dumps(nat.get("examples") or [], ensure_ascii=False),
|
||||
"aliases": " ".join(nat.get("aliases") or []),
|
||||
"url": "https://docs.fivem.net/natives/?_%s" % nat["hash"],
|
||||
})
|
||||
log("natives:", len(rows))
|
||||
return rows
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- html -> text
|
||||
class HtmlToText(HTMLParser):
|
||||
"""Minimaler Konverter fuer die Event-Referenz (TypeDoc-HTML)."""
|
||||
|
||||
BLOCK = {"p", "div", "section", "li", "tr", "br", "ul", "ol", "table", "pre"}
|
||||
HEAD = {"h1": "#", "h2": "##", "h3": "###", "h4": "####", "h5": "#####"}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(convert_charrefs=True)
|
||||
self.parts = []
|
||||
self._skip = 0
|
||||
self._pre = 0
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag in ("script", "style"):
|
||||
self._skip += 1
|
||||
elif tag in self.HEAD:
|
||||
self.parts.append("\n\n%s " % self.HEAD[tag])
|
||||
elif tag == "li":
|
||||
self.parts.append("\n- ")
|
||||
elif tag in ("pre", "code"):
|
||||
self._pre += 1
|
||||
self.parts.append("`" if tag == "code" else "\n```\n")
|
||||
elif tag in self.BLOCK:
|
||||
self.parts.append("\n")
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in ("script", "style"):
|
||||
self._skip = max(0, self._skip - 1)
|
||||
elif tag in ("pre", "code"):
|
||||
self._pre = max(0, self._pre - 1)
|
||||
self.parts.append("`" if tag == "code" else "\n```\n")
|
||||
elif tag in self.HEAD or tag in self.BLOCK:
|
||||
self.parts.append("\n")
|
||||
|
||||
def handle_data(self, data):
|
||||
if not self._skip:
|
||||
self.parts.append(data if self._pre else re.sub(r"\s+", " ", data))
|
||||
|
||||
def text(self):
|
||||
out = "".join(self.parts)
|
||||
out = re.sub(r"``\s*``", "", out)
|
||||
out = re.sub(r"\n{3,}", "\n\n", out)
|
||||
return "\n".join(l.rstrip() for l in out.splitlines()).strip()
|
||||
|
||||
|
||||
def html_to_text(html):
|
||||
p = HtmlToText()
|
||||
p.feed(html)
|
||||
p.close()
|
||||
return p.text()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- shortcodes
|
||||
FRONTMATTER = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.S)
|
||||
PAIRED_SC = re.compile(
|
||||
r"\{\{[%<]\s*(alert|steps)\b(.*?)[%>]\}\}(.*?)\{\{[%<]\s*/\s*\1\s*[%>]\}\}", re.S)
|
||||
SINGLE_SC = re.compile(r"\{\{[%<]\s*([A-Za-z0-9_-]+)((?:[^%>]|%(?!\}\})|>(?!\}))*?)\s*[%>]\}\}", re.S)
|
||||
ATTR = re.compile(r'([A-Za-z0-9_-]+)\s*=\s*"([^"]*)"')
|
||||
POSITIONAL = re.compile(r'"([^"]*)"')
|
||||
|
||||
|
||||
def parse_attrs(s):
|
||||
named = dict(ATTR.findall(s))
|
||||
pos = [v for v in POSITIONAL.findall(ATTR.sub("", s))]
|
||||
return named, pos
|
||||
|
||||
|
||||
class Shortcodes:
|
||||
"""Loest die Hugo-Shortcodes der fivem-docs zu reinem Markdown auf."""
|
||||
|
||||
def __init__(self, repo_root, native_hash_by_name):
|
||||
self.root = repo_root
|
||||
self.hashes = native_hash_by_name
|
||||
self._events = {}
|
||||
self._rmv = self._read_static("/static/resource_manifest_version.txt")
|
||||
self._rmv2 = self._read_static("/static/resource_manifest_version2.txt")
|
||||
|
||||
def _read_static(self, rel):
|
||||
path = os.path.join(self.root, rel.lstrip("/").replace("/", os.sep))
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
||||
return f.read().strip()
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
def _event_doc(self, kind):
|
||||
if kind not in self._events:
|
||||
cached = os.path.join(CACHE, "events_%s.json" % kind)
|
||||
if not os.path.exists(cached):
|
||||
try:
|
||||
fetch("https://runtime.fivem.net/doc/events/%s.html.json" % kind, cached)
|
||||
except Exception as exc: # Netzwerk optional
|
||||
log("events %s nicht geladen: %s" % (kind, exc))
|
||||
self._events[kind] = ""
|
||||
return ""
|
||||
with open(cached, "r", encoding="utf-8") as f:
|
||||
self._events[kind] = html_to_text(json.load(f).get("content", ""))
|
||||
return self._events[kind]
|
||||
|
||||
# -- einzelne Shortcodes ------------------------------------------------
|
||||
def _sc(self, name, attrs, pos):
|
||||
if name == "code":
|
||||
body = self._read_static(attrs.get("file", "")).replace("{{RMV}}", self._rmv)
|
||||
return "\n```%s\n%s\n```\n" % (attrs.get("language", ""), body)
|
||||
if name == "rmv":
|
||||
return self._rmv
|
||||
if name == "rmv2":
|
||||
return self._rmv2
|
||||
if name == "native_link":
|
||||
n = (pos[0] if pos else "").strip()
|
||||
h = self.hashes.get(n)
|
||||
return "[%s](https://docs.fivem.net/natives/?_%s)" % (n, h) if h else "`%s`" % n
|
||||
if name == "youtube":
|
||||
vid = attrs.get("id") or (pos[0] if pos else "")
|
||||
return "\n[Video: https://www.youtube.com/watch?v=%s]\n" % vid
|
||||
if name == "video":
|
||||
return "\n[Video: %s]\n" % attrs.get("src", "")
|
||||
if name == "events":
|
||||
return "\n" + self._event_doc((pos[0] if pos else "client").strip()) + "\n"
|
||||
if name == "forum_topic":
|
||||
return "\n[Community-Post: https://forum.cfx.re/t/%s]\n" % (pos[0] if pos else "")
|
||||
if name in ("article-nav", "steps"):
|
||||
return ""
|
||||
return ""
|
||||
|
||||
def _paired(self, m):
|
||||
name, raw, inner = m.group(1), m.group(2), m.group(3)
|
||||
inner = self.render(inner).strip()
|
||||
if name == "steps":
|
||||
return "\n" + inner + "\n"
|
||||
attrs, _ = parse_attrs(raw)
|
||||
title = attrs.get("title", "")
|
||||
head = "> **%s**\n" % title if title else ""
|
||||
body = "\n".join("> " + l for l in inner.splitlines())
|
||||
return "\n%s%s\n" % (head, body)
|
||||
|
||||
def render(self, text):
|
||||
prev = None
|
||||
while prev != text:
|
||||
prev = text
|
||||
text = PAIRED_SC.sub(self._paired, text)
|
||||
return SINGLE_SC.sub(
|
||||
lambda m: self._sc(m.group(1), *parse_attrs(m.group(2))), text)
|
||||
|
||||
|
||||
def load_docs(natives, refresh=False):
|
||||
src = os.path.join(CACHE, "fivem-docs")
|
||||
if not os.path.isdir(src):
|
||||
os.makedirs(CACHE, exist_ok=True)
|
||||
log("clone", DOCS_REPO)
|
||||
subprocess.run(["git", "clone", "--depth", "1", "-q", DOCS_REPO, src], check=True)
|
||||
elif refresh:
|
||||
subprocess.run(["git", "-C", src, "pull", "-q"], check=False)
|
||||
|
||||
# native_link braucht Name -> Hash (inkl. Aliases)
|
||||
hashes = {}
|
||||
for n in natives:
|
||||
if n["name"]:
|
||||
hashes.setdefault(n["name"], n["hash"])
|
||||
for a in n["aliases"].split():
|
||||
hashes.setdefault(a, n["hash"])
|
||||
shortcodes = Shortcodes(src, hashes)
|
||||
|
||||
base = os.path.join(src, "content", "docs")
|
||||
rows = []
|
||||
for dirpath, _dirs, files in os.walk(base):
|
||||
for fn in sorted(files):
|
||||
if not fn.endswith(".md"):
|
||||
continue
|
||||
full = os.path.join(dirpath, fn)
|
||||
rel = os.path.relpath(full, base).replace("\\", "/")
|
||||
with open(full, "r", encoding="utf-8", errors="replace") as f:
|
||||
raw = f.read()
|
||||
|
||||
title, weight = "", 0
|
||||
body = raw
|
||||
m = FRONTMATTER.match(raw)
|
||||
if m:
|
||||
body = raw[m.end():]
|
||||
for line in m.group(1).splitlines():
|
||||
low = line.lower()
|
||||
if low.startswith("title:"):
|
||||
title = line.split(":", 1)[1].strip().strip('"').strip("'")
|
||||
elif low.startswith("weight:"):
|
||||
try:
|
||||
weight = int(line.split(":", 1)[1].strip())
|
||||
except ValueError:
|
||||
pass
|
||||
if not title:
|
||||
title = os.path.splitext(fn)[0].replace("-", " ").title()
|
||||
|
||||
slug = rel[:-3]
|
||||
if slug.endswith("/_index") or slug == "_index":
|
||||
slug = slug[:-len("_index")].rstrip("/")
|
||||
section = slug.split("/")[0] if slug else "root"
|
||||
url = "https://docs.fivem.net/docs/" + (slug + "/" if slug else "")
|
||||
|
||||
rows.append({
|
||||
"path": rel,
|
||||
"slug": slug,
|
||||
"section": section,
|
||||
"title": title,
|
||||
"weight": weight,
|
||||
"url": url,
|
||||
"body": shortcodes.render(body).strip(),
|
||||
"raw": body.strip(),
|
||||
})
|
||||
log("docs:", len(rows))
|
||||
return rows
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- schema
|
||||
SCHEMA = """
|
||||
DROP TABLE IF EXISTS natives_fts;
|
||||
DROP TABLE IF EXISTS natives;
|
||||
DROP TABLE IF EXISTS docs_fts;
|
||||
DROP TABLE IF EXISTS docs;
|
||||
DROP TABLE IF EXISTS meta;
|
||||
|
||||
CREATE TABLE natives (
|
||||
id INTEGER PRIMARY KEY,
|
||||
hash TEXT NOT NULL,
|
||||
jhash TEXT,
|
||||
name TEXT,
|
||||
lua_name TEXT,
|
||||
ns TEXT,
|
||||
source TEXT, -- gta5 | cfx
|
||||
game TEXT, -- gta5 | rdr3 | ny | ''
|
||||
apiset TEXT, -- client | server | shared
|
||||
results TEXT,
|
||||
results_description TEXT,
|
||||
description TEXT,
|
||||
signature TEXT,
|
||||
params_json TEXT,
|
||||
param_names TEXT,
|
||||
examples_json TEXT,
|
||||
aliases TEXT,
|
||||
url TEXT
|
||||
);
|
||||
CREATE INDEX idx_nat_hash ON natives(hash);
|
||||
CREATE INDEX idx_nat_name ON natives(name);
|
||||
CREATE INDEX idx_nat_lua ON natives(lua_name);
|
||||
CREATE INDEX idx_nat_ns ON natives(ns);
|
||||
CREATE INDEX idx_nat_api ON natives(apiset);
|
||||
|
||||
CREATE VIRTUAL TABLE natives_fts USING fts5(
|
||||
name, lua_name, ns, description, param_names, aliases, hash,
|
||||
content='natives', content_rowid='id',
|
||||
tokenize="unicode61 tokenchars '_'"
|
||||
);
|
||||
|
||||
CREATE TABLE docs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
path TEXT, slug TEXT, section TEXT, title TEXT,
|
||||
weight INTEGER, url TEXT, body TEXT, raw TEXT
|
||||
);
|
||||
CREATE INDEX idx_docs_slug ON docs(slug);
|
||||
CREATE INDEX idx_docs_sect ON docs(section);
|
||||
|
||||
CREATE VIRTUAL TABLE docs_fts USING fts5(
|
||||
title, section, slug, body,
|
||||
content='docs', content_rowid='id',
|
||||
tokenize="unicode61 tokenchars '_'"
|
||||
);
|
||||
|
||||
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT);
|
||||
"""
|
||||
|
||||
|
||||
def build(refresh=False):
|
||||
os.makedirs(DATA, exist_ok=True)
|
||||
natives = load_natives(refresh)
|
||||
docs = load_docs(natives, refresh)
|
||||
|
||||
if os.path.exists(DB):
|
||||
os.remove(DB)
|
||||
con = sqlite3.connect(DB)
|
||||
con.executescript(SCHEMA)
|
||||
|
||||
ncols = ["hash", "jhash", "name", "lua_name", "ns", "source", "game", "apiset",
|
||||
"results", "results_description", "description", "signature",
|
||||
"params_json", "param_names", "examples_json", "aliases", "url"]
|
||||
con.executemany(
|
||||
"INSERT INTO natives (%s) VALUES (%s)" % (",".join(ncols), ",".join("?" * len(ncols))),
|
||||
[[r[c] for c in ncols] for r in natives],
|
||||
)
|
||||
con.execute("INSERT INTO natives_fts(natives_fts) VALUES('rebuild')")
|
||||
|
||||
dcols = ["path", "slug", "section", "title", "weight", "url", "body", "raw"]
|
||||
con.executemany(
|
||||
"INSERT INTO docs (%s) VALUES (%s)" % (",".join(dcols), ",".join("?" * len(dcols))),
|
||||
[[r[c] for c in dcols] for r in docs],
|
||||
)
|
||||
con.execute("INSERT INTO docs_fts(docs_fts) VALUES('rebuild')")
|
||||
|
||||
con.executemany("INSERT INTO meta (key,value) VALUES (?,?)", [
|
||||
("built_at", datetime.now(timezone.utc).isoformat(timespec="seconds")),
|
||||
("native_count", str(len(natives))),
|
||||
("doc_count", str(len(docs))),
|
||||
("sources", json.dumps([u for _, u in NATIVE_SOURCES] + [DOCS_REPO])),
|
||||
("schema_version", "1"),
|
||||
])
|
||||
con.commit()
|
||||
con.execute("VACUUM")
|
||||
con.close()
|
||||
|
||||
# Grep-freundlicher Export (eine Zeile pro Native)
|
||||
out = os.path.join(DATA, "natives.jsonl")
|
||||
with open(out, "w", encoding="utf-8", newline="\n") as f:
|
||||
for r in sorted(natives, key=lambda x: (x["ns"], x["name"] or x["hash"])):
|
||||
slim = {k: r[k] for k in ("hash", "name", "lua_name", "ns", "apiset", "game",
|
||||
"signature", "description", "aliases", "url")}
|
||||
slim["params"] = json.loads(r["params_json"])
|
||||
f.write(json.dumps(slim, ensure_ascii=False) + "\n")
|
||||
|
||||
log("ok ->", DB, "%.1f MB" % (os.path.getsize(DB) / 1e6))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build(refresh="--refresh" in sys.argv)
|
||||
Binary file not shown.
+7359
File diff suppressed because one or more lines are too long
@@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
fivem.py - Nachschlagewerk fuer FiveM Natives + docs.fivem.net.
|
||||
|
||||
Liest data/natives.db (SQLite + FTS5). Nur Python-Stdlib, keine Dependencies.
|
||||
|
||||
python fivem.py ragdoll # Freitextsuche (Natives + Docs)
|
||||
python fivem.py show SetPedToRagdoll # volles Detail zu einem Native
|
||||
python fivem.py show 0xAE99FB955581844A # ... auch per Hash
|
||||
python fivem.py search vehicle engine --apiset client --limit 20
|
||||
python fivem.py ns VEHICLE # alle Natives eines Namespace
|
||||
python fivem.py ns # alle Namespaces
|
||||
python fivem.py docs fxmanifest # Docs-Volltextsuche
|
||||
python fivem.py doc scripting-reference/resource-manifest/resource-manifest
|
||||
python fivem.py stats
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "natives.db")
|
||||
|
||||
# Docs/Natives enthalten UTF-8; Windows-Konsolen sind oft cp1252.
|
||||
for _stream in (sys.stdout, sys.stderr):
|
||||
if hasattr(_stream, "reconfigure"):
|
||||
try:
|
||||
_stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
SUBCOMMANDS = {"show", "search", "ns", "docs", "doc", "stats", "lookup"}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ db
|
||||
def connect():
|
||||
if not os.path.exists(DB):
|
||||
sys.exit("natives.db fehlt - erst 'python build.py' ausfuehren (%s)" % DB)
|
||||
con = sqlite3.connect("file:%s?mode=ro" % DB.replace("\\", "/"), uri=True)
|
||||
con.row_factory = sqlite3.Row
|
||||
return con
|
||||
|
||||
|
||||
def fts_query(text):
|
||||
"""Freitext -> sichere FTS5-Query mit Prefix-Matching."""
|
||||
tokens = [t for t in re.split(r"[^0-9A-Za-z_]+", text) if t]
|
||||
if not tokens:
|
||||
return None
|
||||
return " AND ".join('"%s"*' % t.replace('"', "") for t in tokens)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ render
|
||||
def render_native(r, examples=True):
|
||||
out = []
|
||||
title = r["lua_name"] or r["hash"]
|
||||
out.append("## %s" % title)
|
||||
tags = [r["ns"], r["apiset"] or "?"]
|
||||
if r["game"]:
|
||||
tags.append(r["game"])
|
||||
if r["source"] == "cfx":
|
||||
tags.append("cfx")
|
||||
out.append("`%s` %s" % (r["hash"], " | ".join("**%s**" % t for t in tags)))
|
||||
if r["name"] and r["name"] != r["lua_name"]:
|
||||
out.append("Native-Name: `%s`" % r["name"])
|
||||
if r["jhash"]:
|
||||
out.append("jhash: `%s`" % r["jhash"])
|
||||
if r["aliases"]:
|
||||
out.append("Aliases: %s" % ", ".join("`%s`" % a for a in r["aliases"].split()))
|
||||
out.append("")
|
||||
out.append("```")
|
||||
out.append(r["signature"])
|
||||
out.append("```")
|
||||
|
||||
params = json.loads(r["params_json"] or "[]")
|
||||
if params:
|
||||
out.append("")
|
||||
out.append("### Parameter")
|
||||
for p in params:
|
||||
desc = (p.get("description") or "").replace("\n", " ").strip()
|
||||
out.append("- `%s` (%s)%s" % (p.get("name", "?"), p.get("type", "?"),
|
||||
" - " + desc if desc else ""))
|
||||
if r["results"] and r["results"] != "void":
|
||||
rd = r["results_description"] or ""
|
||||
out.append("")
|
||||
out.append("### Rueckgabe")
|
||||
out.append("`%s`%s" % (r["results"], " - " + rd.replace("\n", " ") if rd else ""))
|
||||
|
||||
if r["description"]:
|
||||
out.append("")
|
||||
out.append("### Beschreibung")
|
||||
out.append(r["description"])
|
||||
|
||||
if examples:
|
||||
ex = json.loads(r["examples_json"] or "[]")
|
||||
if ex:
|
||||
out.append("")
|
||||
out.append("### Beispiele")
|
||||
for e in ex:
|
||||
out.append("```%s" % (e.get("lang") or ""))
|
||||
out.append((e.get("code") or "").replace("\r\n", "\n").rstrip())
|
||||
out.append("```")
|
||||
|
||||
out.append("")
|
||||
out.append("Docs: %s" % r["url"])
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def summarize(description):
|
||||
"""Erste sinnvolle Zeile - Code-Fences und Leerzeilen ueberspringen."""
|
||||
in_fence = False
|
||||
for line in (description or "").splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("```"):
|
||||
in_fence = not in_fence
|
||||
continue
|
||||
if in_fence or not line:
|
||||
continue
|
||||
return line[:140]
|
||||
return "(keine Beschreibung)"
|
||||
|
||||
|
||||
def render_native_line(r):
|
||||
flags = r["apiset"] or "?"
|
||||
if r["source"] == "cfx":
|
||||
flags += ",cfx"
|
||||
return "- **%s** `%s` [%s/%s] - %s" % (
|
||||
r["lua_name"], r["hash"], r["ns"], flags, summarize(r["description"]),
|
||||
)
|
||||
|
||||
|
||||
def render_doc_line(r):
|
||||
return "- **%s** `%s`\n %s" % (r["title"], r["slug"], r["url"])
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ cmds
|
||||
def cmd_show(con, args):
|
||||
key = args.query.strip()
|
||||
rows = con.execute(
|
||||
"""SELECT * FROM natives
|
||||
WHERE lua_name = ?1 COLLATE NOCASE
|
||||
OR name = ?1 COLLATE NOCASE
|
||||
OR hash = ?1 COLLATE NOCASE
|
||||
OR jhash = ?1 COLLATE NOCASE
|
||||
ORDER BY (source='cfx') DESC""",
|
||||
(key,),
|
||||
).fetchall()
|
||||
if not rows:
|
||||
# Fallback: 0x-Praefix vergessen / Teiltreffer
|
||||
rows = con.execute(
|
||||
"SELECT * FROM natives WHERE lua_name LIKE ?1 OR name LIKE ?1 LIMIT 25",
|
||||
("%" + key + "%",),
|
||||
).fetchall()
|
||||
if len(rows) != 1:
|
||||
if not rows:
|
||||
print("Kein Native '%s' gefunden. Versuch: fivem.py search %s" % (key, key))
|
||||
return 1
|
||||
print("Mehrdeutig - %d Treffer:\n" % len(rows))
|
||||
print("\n".join(render_native_line(r) for r in rows))
|
||||
return 0
|
||||
if args.json:
|
||||
print(json.dumps([dict(r) for r in rows], ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
print("\n\n---\n\n".join(render_native(r, examples=not args.no_examples) for r in rows))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_search(con, args):
|
||||
q = fts_query(" ".join(args.query))
|
||||
if not q:
|
||||
return 1
|
||||
sql = ["SELECT n.* FROM natives_fts f JOIN natives n ON n.id = f.rowid",
|
||||
"WHERE natives_fts MATCH ?"]
|
||||
params = [q]
|
||||
if args.apiset:
|
||||
sql.append("AND n.apiset = ?")
|
||||
params.append(args.apiset)
|
||||
if args.ns:
|
||||
sql.append("AND n.ns = ?")
|
||||
params.append(args.ns.upper())
|
||||
sql.append("ORDER BY rank LIMIT ?")
|
||||
params.append(args.limit)
|
||||
rows = con.execute(" ".join(sql), params).fetchall()
|
||||
if args.json:
|
||||
print(json.dumps([dict(r) for r in rows], ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if not rows:
|
||||
print("Keine Natives gefunden fuer: %s" % " ".join(args.query))
|
||||
return 0
|
||||
print("# Natives (%d)\n" % len(rows))
|
||||
print("\n".join(render_native_line(r) for r in rows))
|
||||
print("\nDetails: python fivem.py show <Name>")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_ns(con, args):
|
||||
if not args.name:
|
||||
rows = con.execute(
|
||||
"SELECT ns, COUNT(*) c, SUM(apiset='server') srv FROM natives GROUP BY ns ORDER BY ns"
|
||||
).fetchall()
|
||||
print("# Namespaces (%d)\n" % len(rows))
|
||||
for r in rows:
|
||||
extra = " (%d server)" % r["srv"] if r["srv"] else ""
|
||||
print("- **%s** - %d Natives%s" % (r["ns"], r["c"], extra))
|
||||
return 0
|
||||
rows = con.execute(
|
||||
"SELECT * FROM natives WHERE ns = ? ORDER BY lua_name LIMIT ?",
|
||||
(args.name.upper(), args.limit),
|
||||
).fetchall()
|
||||
if not rows:
|
||||
print("Namespace '%s' unbekannt. Liste: python fivem.py ns" % args.name)
|
||||
return 1
|
||||
print("# %s (%d gezeigt)\n" % (args.name.upper(), len(rows)))
|
||||
print("\n".join(render_native_line(r) for r in rows))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_docs(con, args):
|
||||
q = fts_query(" ".join(args.query))
|
||||
if not q:
|
||||
return 1
|
||||
sql = ["SELECT d.*, snippet(docs_fts, 3, '**', '**', ' ... ', 24) AS snip",
|
||||
"FROM docs_fts f JOIN docs d ON d.id = f.rowid WHERE docs_fts MATCH ?"]
|
||||
params = [q]
|
||||
if args.section:
|
||||
sql.append("AND d.section = ?")
|
||||
params.append(args.section)
|
||||
sql.append("ORDER BY rank LIMIT ?")
|
||||
params.append(args.limit)
|
||||
rows = con.execute(" ".join(sql), params).fetchall()
|
||||
if not rows:
|
||||
print("Keine Docs gefunden fuer: %s" % " ".join(args.query))
|
||||
return 0
|
||||
print("# Docs (%d)\n" % len(rows))
|
||||
for r in rows:
|
||||
print("- **%s** `%s`" % (r["title"], r["slug"]))
|
||||
print(" %s" % r["snip"].replace("\n", " "))
|
||||
print(" %s" % r["url"])
|
||||
print("\nVolltext: python fivem.py doc <slug>")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_doc(con, args):
|
||||
slug = args.slug.strip().strip("/")
|
||||
r = con.execute("SELECT * FROM docs WHERE slug = ? OR path = ?", (slug, slug)).fetchone()
|
||||
if not r:
|
||||
rows = con.execute("SELECT * FROM docs WHERE slug LIKE ? LIMIT 15",
|
||||
("%" + slug + "%",)).fetchall()
|
||||
if len(rows) == 1:
|
||||
r = rows[0]
|
||||
else:
|
||||
print("Kein eindeutiges Dokument '%s'." % slug)
|
||||
if rows:
|
||||
print("\n" + "\n".join(render_doc_line(x) for x in rows))
|
||||
return 1
|
||||
print("# %s\n" % r["title"])
|
||||
print("Quelle: %s\n" % r["url"])
|
||||
print(r["raw"] if args.raw else r["body"])
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_stats(con, args):
|
||||
meta = dict(con.execute("SELECT key, value FROM meta").fetchall())
|
||||
print("# fivem-natives-db\n")
|
||||
print("- Gebaut: %s" % meta.get("built_at"))
|
||||
print("- Natives: %s" % meta.get("native_count"))
|
||||
print("- Doc-Seiten: %s" % meta.get("doc_count"))
|
||||
print("\n## Natives nach apiset")
|
||||
for r in con.execute("SELECT apiset, COUNT(*) c FROM natives GROUP BY apiset ORDER BY c DESC"):
|
||||
print("- %s: %d" % (r["apiset"] or "?", r["c"]))
|
||||
print("\n## Doc-Sektionen")
|
||||
for r in con.execute("SELECT section, COUNT(*) c FROM docs GROUP BY section ORDER BY section"):
|
||||
print("- %s: %d" % (r["section"], r["c"]))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_lookup(con, args):
|
||||
"""Kombinierte Suche: exakter Native, sonst Natives + Docs."""
|
||||
key = " ".join(args.query)
|
||||
exact = con.execute(
|
||||
"""SELECT * FROM natives
|
||||
WHERE lua_name = ?1 COLLATE NOCASE OR name = ?1 COLLATE NOCASE
|
||||
OR hash = ?1 COLLATE NOCASE""",
|
||||
(key.strip(),),
|
||||
).fetchall()
|
||||
if exact:
|
||||
print("\n\n---\n\n".join(render_native(r) for r in exact))
|
||||
return 0
|
||||
|
||||
q = fts_query(key)
|
||||
if not q:
|
||||
return 1
|
||||
nat = con.execute(
|
||||
"SELECT n.* FROM natives_fts f JOIN natives n ON n.id=f.rowid "
|
||||
"WHERE natives_fts MATCH ? ORDER BY rank LIMIT ?", (q, args.limit)
|
||||
).fetchall()
|
||||
doc = con.execute(
|
||||
"SELECT d.* FROM docs_fts f JOIN docs d ON d.id=f.rowid "
|
||||
"WHERE docs_fts MATCH ? ORDER BY rank LIMIT ?", (q, max(3, args.limit // 2))
|
||||
).fetchall()
|
||||
|
||||
if nat:
|
||||
print("# Natives (%d)\n" % len(nat))
|
||||
print("\n".join(render_native_line(r) for r in nat))
|
||||
if doc:
|
||||
print("\n# Docs (%d)\n" % len(doc))
|
||||
print("\n".join(render_doc_line(r) for r in doc))
|
||||
if not nat and not doc:
|
||||
print("Nichts gefunden fuer: %s" % key)
|
||||
else:
|
||||
print("\nDetails: python fivem.py show <Name> | python fivem.py doc <slug>")
|
||||
return 0
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ main
|
||||
def main(argv):
|
||||
p = argparse.ArgumentParser(prog="fivem.py", description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = p.add_subparsers(dest="cmd")
|
||||
|
||||
s = sub.add_parser("show", help="Volles Detail zu einem Native (Name oder Hash)")
|
||||
s.add_argument("query")
|
||||
s.add_argument("--json", action="store_true")
|
||||
s.add_argument("--no-examples", action="store_true")
|
||||
s.set_defaults(fn=cmd_show)
|
||||
|
||||
s = sub.add_parser("search", help="Natives durchsuchen")
|
||||
s.add_argument("query", nargs="+")
|
||||
s.add_argument("--apiset", choices=["client", "server", "shared"])
|
||||
s.add_argument("--ns")
|
||||
s.add_argument("--limit", type=int, default=25)
|
||||
s.add_argument("--json", action="store_true")
|
||||
s.set_defaults(fn=cmd_search)
|
||||
|
||||
s = sub.add_parser("ns", help="Namespaces auflisten / Natives eines Namespace")
|
||||
s.add_argument("name", nargs="?")
|
||||
s.add_argument("--limit", type=int, default=400)
|
||||
s.set_defaults(fn=cmd_ns)
|
||||
|
||||
s = sub.add_parser("docs", help="docs.fivem.net durchsuchen")
|
||||
s.add_argument("query", nargs="+")
|
||||
s.add_argument("--section")
|
||||
s.add_argument("--limit", type=int, default=10)
|
||||
s.set_defaults(fn=cmd_docs)
|
||||
|
||||
s = sub.add_parser("doc", help="Doc-Seite komplett ausgeben")
|
||||
s.add_argument("slug")
|
||||
s.add_argument("--raw", action="store_true", help="Hugo-Shortcodes beibehalten")
|
||||
s.set_defaults(fn=cmd_doc)
|
||||
|
||||
s = sub.add_parser("stats", help="Umfang der Datenbank")
|
||||
s.set_defaults(fn=cmd_stats)
|
||||
|
||||
s = sub.add_parser("lookup", help="Kombinierte Suche (Default)")
|
||||
s.add_argument("query", nargs="+")
|
||||
s.add_argument("--limit", type=int, default=15)
|
||||
s.set_defaults(fn=cmd_lookup)
|
||||
|
||||
# Ohne Subcommand: alles als lookup behandeln
|
||||
if argv and argv[0] not in SUBCOMMANDS and not argv[0].startswith("-"):
|
||||
argv = ["lookup"] + argv
|
||||
args = p.parse_args(argv)
|
||||
if not getattr(args, "fn", None):
|
||||
p.print_help()
|
||||
return 1
|
||||
con = connect()
|
||||
try:
|
||||
return args.fn(con, args)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user