omni: абузы (31 живой источник + классификатор), авто-добавление фидов, Telegram-бот, вкладка абузов; 589 источников
This commit is contained in:
149
omni/abuse.py
Normal file
149
omni/abuse.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""Разбор абузов: промо, триалы, ключи, сливы, софт, реселл.
|
||||
|
||||
Работает поверх уже собранной базы (items) и поверх новых абуз-источников:
|
||||
каждая запись получает набор тегов и вес, панель показывает только то,
|
||||
что реально пахнет абузом, а не весь поток.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sqlite3
|
||||
|
||||
from .config import DB
|
||||
|
||||
# тег -> (человеческое имя, вес, регулярки)
|
||||
RULES = [
|
||||
("keys", "ключи", 40, [
|
||||
r"sk-[A-Za-z0-9_\-]{20,}", r"gh[pousr]_[A-Za-z0-9]{20,}", r"xox[baprs]-[A-Za-z0-9\-]{10,}",
|
||||
r"(разда|выда|дарю|бесплатн|free|giveaway)[^.]{0,40}(ключ|key|токен|token)",
|
||||
r"(ключ|key|токен)[^.]{0,25}(бесплатн|free|дарю|раздач|giveaway)",
|
||||
]),
|
||||
("promo", "промо", 26, [
|
||||
r"промокод", r"promo\s?code", r"промо[\s-]?код", r"скидк[аи]\s+\d{1,3}", r"\d{1,3}\s?%\s?(off|скидк)",
|
||||
r"redeem\s?(code|код)", r"invite\s?code", r"реф(еральн|код)", r"бонус\s+на\s+сч[её]т", r"кэшб[еэ]к|cashback",
|
||||
]),
|
||||
("trial", "триалы", 24, [
|
||||
r"trial", r"триал", r"free\s+credits?", r"бесплатн\w*\s+(кредит|токен|баланс|доступ|период)",
|
||||
r"free\s+tier", r"\$\d+\s+free", r"试用", r"免费", r"пробн\w+\s+период", r"без\s+депозита", r"no\s+deposit",
|
||||
]),
|
||||
("leak", "сливы", 26, [
|
||||
r"слив(?!\s*(сок|ки|очн|тест|трафик))", r"leaked\s+(database|db|dump|combo)", r"db\s?dump",
|
||||
r"combo[\s-]?list", r"stealer\s?log", r"утечк\w*\s+баз", r"база\s+(пассов|почт|данных)",
|
||||
r"пароли\s+от\s+\w+\.(com|ru|net)",
|
||||
]),
|
||||
("tool", "софт", 18, [
|
||||
r"софт", r"кряк", r"crack(ed)?", r"bypass", r"обход\s+(блокиров|бан|защит)",
|
||||
r"авторег", r"autoreg", r"panel", r"панел[ьи]\s", r"рассылк", r"sender", r"скрипт",
|
||||
]),
|
||||
("resale", "продажа", 14, [
|
||||
r"продаю", r"продам", r"куплю\s+аккаунт", r"for\s+sale", r"цена\s*[:$]", r"\$\d+\s?/\s?(мес|mo|1k|1000)",
|
||||
r"реселлер", r"resell",
|
||||
]),
|
||||
("news", "новости", 6, [
|
||||
r"анонс", r"выпустил", r"released?", r"launch", r"обновлени", r"release notes",
|
||||
r"новая модель", r"new model",
|
||||
]),
|
||||
]
|
||||
|
||||
COMPILED = [(tag, label, w, [re.compile(p, re.I) for p in pats]) for tag, label, w, pats in RULES]
|
||||
TAG_LABEL = {t: l for t, l, _, _ in RULES}
|
||||
STRONG = {"keys", "promo", "trial", "resale"}
|
||||
# категории-источники, которые вообще имеют смысл для абузов
|
||||
ABUSE_CATS = {"abuse", "forum", "community", "company_cn"}
|
||||
# «статьи»: слова про ключи и утечки там — текст, а не раздача
|
||||
NOISE = re.compile(r"(blog|devto|medium|substack|newsletter|docs?|wiki)", re.I)
|
||||
|
||||
|
||||
def _source_meta():
|
||||
try:
|
||||
from . import sources as _s
|
||||
return {x.name: x.category for x in _s.all_sources()}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
SRC_CAT = _source_meta()
|
||||
|
||||
|
||||
def classify(title: str, text: str, tags: str = ""):
|
||||
blob = (" ".join([title or "", text or "", tags or ""]))[:4000]
|
||||
hits = []
|
||||
score = 0
|
||||
for tag, label, w, pats in COMPILED:
|
||||
n = 0
|
||||
for p in pats:
|
||||
m = p.search(blob)
|
||||
if m:
|
||||
n += 1
|
||||
if n:
|
||||
hits.append(tag)
|
||||
score += w * min(n, 3)
|
||||
return score, hits
|
||||
|
||||
|
||||
def _qualifies(source, title, text, tags):
|
||||
sc, hits = classify(title, text, tags)
|
||||
if not hits:
|
||||
return 0, []
|
||||
strong = bool(set(hits) & STRONG)
|
||||
cat = SRC_CAT.get(source)
|
||||
blob = (title or "") + " " + (text or "")
|
||||
raw_key = bool(re.search(r"(sk-[A-Za-z0-9_\-]{20,}|gh[pousr]_[A-Za-z0-9]{20,})", blob))
|
||||
if NOISE.search(source or "") and not raw_key:
|
||||
return 0, []
|
||||
if cat is None:
|
||||
# старые записи хранят категорию вместо имени источника
|
||||
if not raw_key and not str(source or "").startswith(("ab_", "tg_", "reddit_", "discourse_", "xenforo")):
|
||||
return 0, []
|
||||
elif cat not in ABUSE_CATS and not raw_key:
|
||||
return 0, []
|
||||
if strong and sc >= 24:
|
||||
return sc, hits
|
||||
if len(hits) >= 2 and sc >= 40:
|
||||
return sc, hits
|
||||
return 0, []
|
||||
|
||||
|
||||
def _rows(limit=400, tag=None, q=None, since_days=None):
|
||||
sql = "SELECT id, source, url, title, text, published, tags, score FROM items"
|
||||
where, args = [], []
|
||||
if q:
|
||||
where.append("(title LIKE ? OR text LIKE ? OR source LIKE ?)")
|
||||
args += ["%%%s%%" % q, "%%%s%%" % q, "%%%s%%" % q]
|
||||
if since_days:
|
||||
where.append("published >= date('now', ?)")
|
||||
args.append("-%d day" % int(since_days))
|
||||
if where:
|
||||
sql += " WHERE " + " AND ".join(where)
|
||||
sql += " ORDER BY id DESC LIMIT ?"
|
||||
args.append(int(limit) * 6)
|
||||
con = sqlite3.connect(str(DB), timeout=30)
|
||||
try:
|
||||
cur = con.execute(sql, args)
|
||||
out = []
|
||||
for r in cur:
|
||||
sc, hits = _qualifies(r[1], r[3], r[4], r[6] or "")
|
||||
if not sc:
|
||||
continue
|
||||
if tag and tag not in hits:
|
||||
continue
|
||||
out.append({"id": r[0], "source": r[1], "url": r[2], "title": r[3],
|
||||
"text": (r[4] or "")[:600], "published": r[5],
|
||||
"tags": hits, "abuse_score": sc, "rank": r[7]})
|
||||
if len(out) >= limit:
|
||||
break
|
||||
return out
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def scan(limit=120, tag=None, q=None, since_days=None, window=6000):
|
||||
"""limit — сколько отдать, window — сколько последних записей просканировать."""
|
||||
items = _rows(limit=window, tag=tag, q=q, since_days=since_days)
|
||||
items.sort(key=lambda x: (-x["abuse_score"], -(x["id"] or 0)))
|
||||
counts = {}
|
||||
for it in items:
|
||||
for t in it["tags"]:
|
||||
counts[t] = counts.get(t, 0) + 1
|
||||
return {"items": items[:limit], "total": len(items), "tags": counts,
|
||||
"labels": TAG_LABEL}
|
||||
Reference in New Issue
Block a user