feat(tg): реальные абузы из Telegram без сессии — адаптер tg_web переписан (служебное/пустыши отсекаются, заголовок из тела, порог 12/8 для leak-каналов), 19 живых каналов из 87 проверенных, сигнал абуза в score (+до 1.8), tools/tg_discover.py, tools/fixtg.py, tools/purge_tg_junk.py, tools/rescore.py; реестр 869, база 7295 (ab_ 3135, tg_ 374); тесты 19
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-26 21:06:50 +03:00
parent f9719b9149
commit eaedd37bcd
11 changed files with 414 additions and 22 deletions

34
tools/fixtg.py Normal file
View File

@@ -0,0 +1,34 @@
"""Обновить заголовки/текст уже сохранённых tg-записей по свежему парсу.
python tools/fixtg.py ab_t_me,tg_leakbase,...
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from omni import feeds, sources, store # noqa: E402
def main():
want = set((sys.argv[1] if len(sys.argv) > 1 else "").split(","))
al = [s for s in sources.all_sources() if s.name in want]
total = 0
for src in al:
items = feeds.fetch(src)
c = store.conn()
upd = 0
for it in items:
cur = c.execute("SELECT id,title FROM items WHERE url=?", (it.url,)).fetchone()
if cur and (cur[1] or "") != (it.title or ""):
c.execute("UPDATE items SET title=?, text=? WHERE id=?", (it.title, it.text, cur[0]))
upd += 1
c.commit()
c.close()
total += upd
print(f" {src.name:16} записей {len(items):>3} обновлено {upd:>3}")
print("всего обновлено", total)
if __name__ == "__main__":
main()

58
tools/purge_tg_junk.py Normal file
View File

@@ -0,0 +1,58 @@
"""Чистка мусора из tg_веб-источников: служебные сообщения и пустыши.
Причина: старый адаптер _tg_web брал любой div.tgme_widget_message_text, включая
"Channel created", "Channel photo updated" и посты из одной ссылки.
python tools/purge_tg_junk.py # показать
python tools/purge_tg_junk.py --apply # удалить
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from omni import config, store # noqa: E402
SERVICE_RE = re.compile(
r"^(channel (created|photo|name|description|username|deleted)|pinned|"
r"message pinned|channel photo (updated|removed)|joined|left|invite link)", re.I)
def junk(title):
t = (title or "").strip()
if len(t) < 25:
return True
if SERVICE_RE.match(t):
return True
if sum(ch.isalpha() for ch in t) < 25:
return True
return False
def main():
apply = "--apply" in sys.argv
c = store.conn()
rows = c.execute("SELECT id, source, title FROM items WHERE source LIKE 'tg_%'").fetchall()
bad = [(i, s, t) for i, s, t in rows if junk(t)]
print(f"tg-записей {len(rows)}, мусора {len(bad)}")
for i, s, t in bad[:10]:
print(" -", s, "|", (t or "")[:60])
if apply and bad:
ids = [i for i, _s, _t in bad]
c.executemany("DELETE FROM items WHERE id=?", [(i,) for i in ids])
try:
c.executemany("DELETE FROM items_fts WHERE rowid=?", [(i,) for i in ids])
except Exception:
pass
try:
c.executemany("DELETE FROM bands WHERE item_id=?", [(i,) for i in ids])
except Exception:
pass
c.commit()
print("удалено", len(ids))
c.close()
if __name__ == "__main__":
main()

44
tools/rescore.py Normal file
View File

@@ -0,0 +1,44 @@
"""Пересчёт score всех записей текущей формулой rank.py (после правок веса).
python tools/rescore.py # показать топ-15 без записи
python tools/rescore.py --apply # обновить items.score
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from omni import config, rank, store # noqa: E402
class _It:
__slots__ = ("provider", "kind", "title", "text", "published", "price", "score")
def __init__(self, provider, kind, title, text, published, price):
self.provider, self.kind = provider, kind
self.title, self.text = title or "", text or ""
self.published, self.price, self.score = published or "", price, 0.0
def main():
apply = "--apply" in sys.argv
c = store.conn()
rows = c.execute("SELECT id, provider, kind, title, text, published, price, score FROM items").fetchall()
upd, top = [], []
for iid, prov, kind, title, text, pub, price, old in rows:
it = rank.score(_It(prov, kind, title, text, pub, price))
upd.append((it.score, iid))
top.append((it.score, old, prov, title or ""))
top.sort(reverse=True)
print(f"записей {len(rows)}; топ по новой формуле:")
for sc, old, prov, title in top[:15]:
print(f" {sc:6.2f} (было {old:5.2f}) {prov[:10]:10} {title[:72]}")
if apply:
c.executemany("UPDATE items SET score=? WHERE id=?", upd)
c.commit()
print("обновлено", len(upd))
c.close()
if __name__ == "__main__":
main()

78
tools/tg_discover.py Normal file
View File

@@ -0,0 +1,78 @@
"""Разведка публичных Telegram-каналов под абузы: t.me/s без сессии и ключей.
python tools/tg_discover.py name1,name2,... # проверить и показать
python tools/tg_discover.py --file data/tg_cand.txt # список из файла
python tools/tg_discover.py ... --min 3 # печатать только годные
Для каждого канала: HTTP-статус, сколько постов даёт парсер (после отсева
служебного и пустышей) и сколько из них новые для базы.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from omni import feeds, sources, store # noqa: E402
def candidates(argv):
"""Позиционные аргументы = имена/списки через запятую; --file/--min пропускаем."""
names = []
skip_next = False
for a in argv:
if skip_next:
skip_next = False
continue
if a in ("--file", "--min"):
skip_next = True
continue
if a.startswith("--"):
continue
names += [x.strip() for x in a.split(",") if x.strip()]
if "--file" in argv:
p = Path(argv[argv.index("--file") + 1])
names += [x.strip() for x in p.read_text(encoding="utf-8").splitlines() if x.strip()]
seen, out = set(), []
for n in names:
k = n.lower().lstrip("@")
if k and k not in seen:
seen.add(k)
out.append(n.lstrip("@"))
return out
def main():
args = sys.argv[1:]
min_items = 1
if "--min" in args:
min_items = int(args[args.index("--min") + 1])
todo = candidates(args)
print(f"кандидатов {len(todo)}")
good = []
for name in todo:
src = sources.Source("probe_" + name.lower(), "abuse", "html",
f"https://t.me/s/{name}", "tg_web", ("abuse",), 25, "", {})
try:
items = feeds.fetch(src)
except Exception as e:
print(f" {name:24} ошибка: {type(e).__name__}")
continue
c = store.conn()
urls = {r[0] for r in c.execute("SELECT url FROM items WHERE source=?", (src.name,))}
c.close()
fresh = sum(1 for it in items if it.url not in urls)
mark = ""
if fresh >= min_items and items:
good.append((name, len(items), fresh))
mark = " <-- ГОДЕН"
print(f" {name:24} постов {len(items):>3} новых {fresh:>3}{mark}")
print(f"\nгодных каналов: {len(good)}")
for name, n, f in sorted(good, key=lambda x: -x[1]):
print(f" {name:24} постов {n:>3} новых {f}")
if good:
Path("data/tg_good.txt").write_text("\n".join(g[0] for g in good), encoding="utf-8")
print("список: data/tg_good.txt")
if __name__ == "__main__":
main()