"""Форумы без ключей: Discourse, HN (Algolia), Lobsters, XenForo, Telegram-веб.""" import html import json import re import urllib.parse from .. import net from ..model import Item DISCOURSE = [ "meta.discourse.org", "discuss.python.org", "community.openai.com", "forum.cursor.com", "discuss.huggingface.co", "community.cloudflare.com", "forum.gitlab.com", "discuss.elastic.co", ] XENFORO = ["www.blackhatworld.com", "xenforo.com/community"] TG_CHANNELS = ["Abuz_ai_soft", "AlStack", "iishkogatewaybot"] UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/141.0.0.0 Safari/537.36"} def available(name="forums"): return True, "Discourse/HN/Lobsters/TG-веб, ключи не нужны" def _strip(s): return re.sub(r"\s+", " ", html.unescape(re.sub(r"<[^>]+>", " ", s or ""))).strip() def _discourse(host, query, limit=8): out = [] url = f"https://{host}/search.json?q={urllib.parse.quote(query)}" r = net.get(url, headers=UA, json_mode=True) if r.status != 200: return out d = r.json() or {} for t in (d.get("topics") or [])[:limit]: slug = t.get("slug") or "t" tid = t.get("id") out.append(Item("forums", "thread", url=f"https://{host}/t/{slug}/{tid}", title=t.get("title", ""), text=_strip(t.get("blurb") or ""), source=host, published=str(t.get("created_at") or ""), score=float(t.get("posts_count") or 0) ** 0.4 + float(t.get("like_count") or 0) ** 0.3, extra={"posts": t.get("posts_count"), "views": t.get("views")})) for p in (d.get("posts") or [])[:limit]: if len(out) >= limit: break out.append(Item("forums", "post", url=f"https://{host}/t/{p.get('topic_id')}/{p.get('post_number', 1)}", title=(p.get("topic_title") or "")[:200], text=_strip(p.get("blurb") or ""), source=host, author=p.get("username", ""), published=str(p.get("created_at") or ""))) return out def _hn(query, limit=10): out = [] url = ("https://hn.algolia.com/api/v1/search?query=" + urllib.parse.quote(query) + f"&tags=story&hitsPerPage={min(limit, 30)}") r = net.get(url, headers=UA, json_mode=True) for h in ((r.json() or {}).get("hits") or []): title = h.get("title") or h.get("story_title") or "" if not title: continue it = Item("forums", "thread", url=h.get("url") or f"https://news.ycombinator.com/item?id={h.get('objectID')}", title=title, text=(h.get("story_text") or "")[:1500], source="news.ycombinator.com", author=h.get("author", ""), published=h.get("created_at", ""), extra={"points": h.get("points"), "comments": h.get("num_comments")}) it.score = float(h.get("points") or 0) ** 0.5 + float(h.get("num_comments") or 0) ** 0.4 out.append(it) return out def _lobsters(query, limit=8): out = [] r = net.get("https://lobste.rs/search.json?q=" + urllib.parse.quote(query) + "&what=stories&order=newest", headers=UA, json_mode=True) for s in ((r.json() if r.status == 200 else []) or [])[:limit]: it = Item("forums", "thread", url=s.get("comments_url") or s.get("url") or "", title=s.get("title", ""), text=_strip(s.get("description") or ""), source="lobste.rs", author=s.get("submitter_user", "") if isinstance(s.get("submitter_user"), str) else (s.get("submitter_user") or {}).get("username", ""), published=s.get("created_at", ""), extra={"score": s.get("score"), "comments": s.get("comment_count")}) it.score = float(s.get("score") or 0) ** 0.5 out.append(it) return out def _xenforo(host, query, limit=6): out = [] base = f"https://{host}" if "//" not in host else host url = f"{base}/search/?q={urllib.parse.quote(query)}&o=date" r = net.get(url, headers=UA) if r.status != 200: return out for m in re.finditer(r'