sources: авто-расширение из публичных API без ключей (46 живых), universal generic_json, bsky/mastodon, --only по префиксу, auto_cycle
Some checks failed
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled

This commit is contained in:
Vlad Gothbreach
2026-10-27 20:00:48 +03:00
parent f263d3ffff
commit 31a8dd8c4d
8 changed files with 650 additions and 17 deletions

View File

@@ -484,41 +484,123 @@ def _json_of(url, timeout=35):
def _generic_json(src, limit):
"""Любой JSON-эндпоинт: ищем список объектов и вытаскиваем title/url по частым ключам."""
"""Любой JSON-эндпоинт: список объектов (+extra: rows/title_key/url_key/text_key/date_key/base/slug_key/deref)."""
d = _json_of(src.url)
ex = src.extra or {}
rows = []
if isinstance(d, list):
rows = d
elif isinstance(d, dict):
for key in ("data", "items", "results", "papers", "models", "hits", "notes", "posts", "records"):
v = d.get(key)
if isinstance(v, list):
rows = v
break
path = ex.get("rows")
if path:
cur = d
for part in str(path).split("."):
cur = cur.get(part) if isinstance(cur, dict) else None
if isinstance(cur, list):
rows = cur
if not rows:
for key in ("data", "items", "results", "papers", "models", "hits", "notes",
"posts", "records", "objects", "crates", "hackathons"):
v = d.get(key)
if isinstance(v, list):
rows = v
break
if not rows:
for v in d.values():
if isinstance(v, list) and v and isinstance(v[0], dict):
rows = v
break
out = []
deref = ex.get("deref")
base = str(ex.get("base") or "")
title_keys = ([ex["title_key"]] if ex.get("title_key") else []) + \
["title", "name", "paper_title", "full_name", "id"]
url_keys = ([ex["url_key"]] if ex.get("url_key") else []) + \
["url", "link", "html_url", "paper_url", "web_url"]
text_keys = ([ex["text_key"]] if ex.get("text_key") else []) + \
["abstract", "summary", "description", "body", "content"]
date_keys = ([ex["date_key"]] if ex.get("date_key") else []) + \
["published", "created", "created_at", "date", "published_at"]
for r in rows[:limit]:
if not isinstance(r, dict):
continue
title = r.get("title") or r.get("name") or r.get("paper_title") or r.get("full_name") or r.get("id")
url = r.get("url") or r.get("link") or r.get("html_url") or r.get("paper_url") or r.get("id")
if deref and isinstance(r.get(deref), dict):
r = r[deref]
title = ""
for k in title_keys:
v = r.get(k)
if isinstance(v, str) and v.strip():
title = v.strip()
break
url = ""
for k in url_keys:
v = r.get(k)
if isinstance(v, str) and v.strip():
url = v.strip()
break
if base and (not url or not url.startswith("http")):
slug = ""
if ex.get("slug_key"):
slug = str(r.get(ex["slug_key"]) or "")
url = base.rstrip("/") + "/" + (url or slug).lstrip("/")
if isinstance(url, str) and url and not url.startswith("http"):
if "github.com" in src.url:
url = "https://github.com/" + url.lstrip("/")
elif "huggingface.co" in src.url:
url = "https://huggingface.co/" + url.lstrip("/")
text = r.get("abstract") or r.get("summary") or r.get("description") or r.get("body") or r.get("content") or ""
if isinstance(text, dict):
text = json.dumps(text, ensure_ascii=False)[:400]
published = r.get("published") or r.get("created") or r.get("created_at") or r.get("date") or ""
text = ""
for k in text_keys:
v = r.get(k)
if isinstance(v, str) and v.strip():
text = v
break
if isinstance(v, (dict, list)):
text = json.dumps(v, ensure_ascii=False)[:400]
break
published = ""
for k in date_keys:
v = r.get(k)
if isinstance(v, str) and v.strip():
published = v.strip()
break
if not title:
continue
out.append(_item(src, "entry", str(url or ""), str(title), str(text)[:800],
str(r.get("author") or r.get("owner") or ""), str(published)[:24]))
out.append(_item(src, "entry", str(url or ""), _txt(title)[:180], _txt(text)[:800],
str(r.get("author") or r.get("owner") or r.get("by") or ""),
str(published)[:24]))
return out
def _bsky_search(src, limit):
"""Bluesky: публичный поиск постов без ключа (api.bsky.app)."""
d = _json_of(src.url)
out = []
for p in (d.get("posts") or [])[:limit]:
if not isinstance(p, dict):
continue
rec = p.get("record") or {}
txt = _txt(str(rec.get("text") or ""))
author = str((p.get("author") or {}).get("handle") or "")
uri = str(p.get("uri") or "")
rkey = uri.rsplit("/", 1)[-1] if uri else ""
url = "https://bsky.app/profile/%s/post/%s" % (author, rkey) if author and rkey else uri
out.append(_item(src, "post", url, txt[:150], txt[:800], author,
str(rec.get("createdAt") or "")[:24]))
return out
def _mastodon_tag(src, limit):
"""Mastodon: публичная лента по тегу, без токена."""
d = _json_of(src.url)
rows = d if isinstance(d, list) else []
out = []
for r in rows[:limit]:
if not isinstance(r, dict):
continue
txt = _txt(str(r.get("content") or ""))
out.append(_item(src, "post", str(r.get("url") or ""), txt[:150], txt[:800],
str((r.get("account") or {}).get("acct") or ""),
str(r.get("created_at") or "")[:24]))
return out
@@ -840,6 +922,9 @@ def _github_search(src, limit):
+ "&sort=" + str(sort) + "&order=desc&per_page=" + str(max(1, min(int(limit), 50))))
hdr = {"Accept": "application/vnd.github+json", "User-Agent": "omni-parse"}
tok = ((config.CONFIG.get("keys") or {}).get("github") or [])
if not tok:
single = config.KEYS.get("GITHUB_TOKEN")
tok = [single] if single else []
if isinstance(tok, list) and tok:
hdr["Authorization"] = "Bearer " + str(tok[0])
r = net.get(url, headers=hdr)
@@ -917,6 +1002,8 @@ ADAPTERS = {
"newapi_pricing": _newapi_pricing,
"github_search": _github_search,
"hn_search": _hn_search,
"bsky_search": _bsky_search,
"mastodon_tag": _mastodon_tag,
}