Files
jellybit/internal/tgbot/parse.go
T
avandClaude Opus 4.8 b8017d65eb Приём/UI: пачка фиксов границ и парсинга (F7–F10, N2)
Пять независимых bugfix'ов из ревью приёма (docs/backlog/review-f7-f10-ingest-ui-fixes.md):

- F7: oversized .torrent через веб отдавал 500. Введён sentinel
  ingest.ErrTorrentTooLarge, classifyErr транслирует его в 400.
- F8: гонка fast-path attach с cancel. Пред-рид
  FindReingestBlockingByInfohash больше не короткозамыкает активную
  запись — авторитетное дедуп-решение принимает CreateDownloadIfNoActive
  под BEGIN IMMEDIATE; короткозамыкание оставлено только для терминальных
  desync-записей (target_missing/orphaned). F6-апгрейд сохранён.
- F9: magnet — регистронезависимый URN-префикс xt (RFC 2141);
  tgbot.ParseMessage срезает хвостовую пунктуацию, приклеенную жадным
  matchем.
- F10: cap контекста до 16 KiB в ingest.Ingest (единственное место
  слияния — покрывает все транспорты), рунобезопасная обрезка + маркер.
- N2: httpapi.shorten режет по рунам, не байтам — кириллица не рвётся в
  U+FFFD.

Добавлены юнит-тесты на каждое исправленное поведение.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 20:35:14 +03:00

83 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package tgbot — Telegram-адаптер: приём magnet/пересланных сообщений
// торрент-бота, подтверждение раскладки кнопками и исходящие пинги.
package tgbot
import (
"regexp"
"strings"
)
var (
magnetRe = regexp.MustCompile(`magnet:\?[^\s]+`)
// parenURL — markdown-хвост " (https://…)" в строках сообщения бота.
parenURL = regexp.MustCompile(`\s*\(https?://[^)]+\)`)
)
// magnetTrailingPunct — хвостовая пунктуация, которую жадный magnetRe (`[^\s]+`)
// приклеивает к ссылке, когда magnet стоит в конце предложения или в скобках
// («…magnet:?xt=…).»). Такой хвост сдвигает последний xt и ломает разбор
// (длина btih становится 41 → отказ). TrimRight трактует cutset как множество
// рун, поэтому многобайтовая «»» отсекается корректно.
const magnetTrailingPunct = ".,;:)]}>»"
// noisePrefixes — начала строк UI торрент-бота, которые в контекст не несём.
var noisePrefixes = []string{
"Открыть magnet", "или получить .torrent", "Оценить", "Следить",
"В закладки", "Добавить в закладки", "cправка", "справка",
"[список файлов]", "[список файлов]", "⚡", "сохранённая копия",
}
// ParseMessage извлекает из текста magnet-ссылку и человекочитаемый контекст
// (заголовок релиза без ссылок, команд и UI-мусора бота). ok=false, если
// magnet не найден. Текст может быть как «сырым» magnet, так и пересланным
// сообщением торрент-бота (формат — см. tmp/examples.md).
func ParseMessage(text string) (source, context string, ok bool) {
m := magnetRe.FindString(text)
if m == "" {
return "", "", false
}
// Срезаем хвостовую пунктуацию, приклеенную жадным матчем (см. константу).
m = strings.TrimRight(m, magnetTrailingPunct)
return m, cleanContext(text, m), true
}
// cleanContext оставляет содержательные строки (заголовок, метаданные),
// выкидывая ссылки, команды (/...) и UI-строки бота.
func cleanContext(text, magnet string) string {
var keep []string
for _, line := range strings.Split(text, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.Contains(line, magnet) {
continue
}
if isNoise(line) {
continue
}
line = strings.TrimSpace(parenURL.ReplaceAllString(line, ""))
if line != "" {
keep = append(keep, line)
}
}
return strings.Join(keep, "\n")
}
func isNoise(line string) bool {
if strings.HasPrefix(line, "/") {
return true
}
// Строки-ссылки и строки рейтинга/команд бота (содержат /g_ /r_ /us_ …).
if strings.Contains(line, "hashurl.ru") || strings.Contains(line, "exfreedomist.com") {
return true
}
for _, p := range noisePrefixes {
if strings.HasPrefix(line, p) {
return true
}
}
// Строки вида "👍: /g_ad035f или 👎🏿: /r_ad035f".
if strings.Contains(line, ": /") {
return true
}
return false
}