Улучшена обработка метаданных ссылок
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m4s

This commit is contained in:
poignatov
2026-01-22 19:47:50 +03:00
parent 5a7c8b5d2f
commit d569960ec1
3 changed files with 52 additions and 10 deletions

View File

@@ -1 +1 @@
3.26.1 3.26.2

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"compress/gzip"
"context" "context"
"crypto/rand" "crypto/rand"
"database/sql" "database/sql"
@@ -12888,8 +12889,6 @@ func (a *App) extractLinkMetadataHandler(w http.ResponseWriter, r *http.Request)
return return
} }
log.Printf("Extracting metadata for URL: %s", req.URL)
// Валидация URL // Валидация URL
parsedURL, err := url.Parse(req.URL) parsedURL, err := url.Parse(req.URL)
if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" { if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" {
@@ -12925,20 +12924,41 @@ func (a *App) extractLinkMetadataHandler(w http.ResponseWriter, r *http.Request)
return return
} }
// Устанавливаем заголовки, имитирующие реальный браузер Chrome // Устанавливаем заголовки, максимально имитирующие реальный браузер Chrome
httpReq.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") // Актуальный User-Agent для Chrome на macOS
httpReq.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
// Accept заголовки для максимальной совместимости
httpReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") httpReq.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
httpReq.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7") httpReq.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
httpReq.Header.Set("Accept-Encoding", "gzip, deflate, br") httpReq.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
// Заголовки для имитации реального браузера
httpReq.Header.Set("Connection", "keep-alive") httpReq.Header.Set("Connection", "keep-alive")
httpReq.Header.Set("Upgrade-Insecure-Requests", "1") httpReq.Header.Set("Upgrade-Insecure-Requests", "1")
httpReq.Header.Set("Sec-Fetch-Dest", "document") httpReq.Header.Set("Sec-Fetch-Dest", "document")
httpReq.Header.Set("Sec-Fetch-Mode", "navigate") httpReq.Header.Set("Sec-Fetch-Mode", "navigate")
httpReq.Header.Set("Sec-Fetch-Site", "none") httpReq.Header.Set("Sec-Fetch-Site", "none")
httpReq.Header.Set("Sec-Fetch-User", "?1") httpReq.Header.Set("Sec-Fetch-User", "?1")
httpReq.Header.Set("Sec-Ch-Ua", `"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"`)
httpReq.Header.Set("Sec-Ch-Ua-Mobile", "?0")
httpReq.Header.Set("Sec-Ch-Ua-Platform", `"macOS"`)
// Дополнительные заголовки для лучшей совместимости
httpReq.Header.Set("Cache-Control", "max-age=0") httpReq.Header.Set("Cache-Control", "max-age=0")
httpReq.Header.Set("DNT", "1") httpReq.Header.Set("DNT", "1")
// Устанавливаем Referer - для некоторых сайтов это важно для обхода защиты
// Используем главную страницу домена как Referer, чтобы имитировать переход с главной
if parsedURL.Host != "" {
referer := fmt.Sprintf("%s://%s/", parsedURL.Scheme, parsedURL.Host)
httpReq.Header.Set("Referer", referer)
}
// Добавляем небольшую задержку перед запросом для имитации человеческого поведения
// Это может помочь избежать триггера капчи при слишком быстрых запросах
time.Sleep(100 * time.Millisecond)
resp, err := client.Do(httpReq) resp, err := client.Do(httpReq)
if err != nil { if err != nil {
log.Printf("Error fetching URL %s: %v", req.URL, err) log.Printf("Error fetching URL %s: %v", req.URL, err)
@@ -12947,9 +12967,6 @@ func (a *App) extractLinkMetadataHandler(w http.ResponseWriter, r *http.Request)
} }
defer resp.Body.Close() defer resp.Body.Close()
// Логируем статус код для отладки
log.Printf("Fetched URL %s, status: %d", req.URL, resp.StatusCode)
// Принимаем успешные статусы (200-299) и некоторые редиректы, которые могут содержать контент // Принимаем успешные статусы (200-299) и некоторые редиректы, которые могут содержать контент
if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// Для некоторых сайтов можно попробовать прочитать тело даже при не-200 статусе // Для некоторых сайтов можно попробовать прочитать тело даже при не-200 статусе
@@ -12960,6 +12977,8 @@ func (a *App) extractLinkMetadataHandler(w http.ResponseWriter, r *http.Request)
} }
// Ограничиваем размер ответа (первые 512KB) // Ограничиваем размер ответа (первые 512KB)
// ВАЖНО: Go's http.Client автоматически декодирует gzip, если заголовок Accept-Encoding установлен
// Но нужно убедиться, что мы читаем декодированные данные
limitedReader := io.LimitReader(resp.Body, 512*1024) limitedReader := io.LimitReader(resp.Body, 512*1024)
bodyBytes, err := io.ReadAll(limitedReader) bodyBytes, err := io.ReadAll(limitedReader)
if err != nil { if err != nil {
@@ -12968,6 +12987,20 @@ func (a *App) extractLinkMetadataHandler(w http.ResponseWriter, r *http.Request)
return return
} }
// Проверяем, является ли это gzip (магические байты: 1f 8b)
// Go's http.Client должен автоматически декодировать gzip, но на всякий случай проверяем
if len(bodyBytes) >= 2 && bodyBytes[0] == 0x1f && bodyBytes[1] == 0x8b {
// Пытаемся декодировать вручную, если автоматическое декодирование не сработало
gzipReader, err := gzip.NewReader(bytes.NewReader(bodyBytes))
if err == nil {
defer gzipReader.Close()
decompressed, err := io.ReadAll(gzipReader)
if err == nil {
bodyBytes = decompressed
}
}
}
body := string(bodyBytes) body := string(bodyBytes)
metadata := &LinkMetadataResponse{} metadata := &LinkMetadataResponse{}
@@ -13006,6 +13039,15 @@ func (a *App) extractLinkMetadataHandler(w http.ResponseWriter, r *http.Request)
titleRe := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`) titleRe := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`)
if matches := titleRe.FindStringSubmatch(body); len(matches) > 1 { if matches := titleRe.FindStringSubmatch(body); len(matches) > 1 {
metadata.Title = strings.TrimSpace(matches[1]) metadata.Title = strings.TrimSpace(matches[1])
// Проверяем, не попали ли мы на страницу капчи
if strings.Contains(strings.ToLower(metadata.Title), "робот") ||
strings.Contains(strings.ToLower(metadata.Title), "captcha") ||
strings.Contains(strings.ToLower(metadata.Title), "вы не робот") {
metadata.Title = ""
metadata.Image = ""
metadata.Description = ""
}
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "play-life-web", "name": "play-life-web",
"version": "3.26.1", "version": "3.26.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",