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
This commit is contained in:
34
tools/fixtg.py
Normal file
34
tools/fixtg.py
Normal 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
58
tools/purge_tg_junk.py
Normal 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
44
tools/rescore.py
Normal 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
78
tools/tg_discover.py
Normal 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()
|
||||
Reference in New Issue
Block a user