-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscraper_methods.go
More file actions
326 lines (285 loc) · 8.55 KB
/
Copy pathscraper_methods.go
File metadata and controls
326 lines (285 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
package main
import (
"context"
"fmt"
"sort"
"strings"
"time"
"GoAnimeGUI/pkg/scrapers"
)
// TorrentSource representa uma fonte de torrent disponivel
type TorrentSource struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
IsBR bool `json:"isBr"`
Available bool `json:"available"`
}
// TorrentResult resultado de busca de torrent para o frontend
type TorrentResult struct {
Title string `json:"title"`
Magnet string `json:"magnet"`
Hash string `json:"hash"`
Size string `json:"size"`
Quality string `json:"quality"`
Seeders int `json:"seeders"`
Source string `json:"source"`
IsBR bool `json:"isBr"`
DualAudio bool `json:"dualAudio"`
}
// scraperRegistry registry compartilhado
var scraperRegistry *scrapers.ProviderRegistry
var nyaaProvider *scrapers.NyaaProvider
var redeTorrentProvider *scrapers.RedeTorrentProvider
// initScrapers inicializa os scrapers
func initScrapers() {
if scraperRegistry == nil {
scraperRegistry = scrapers.NewProviderRegistry()
nyaaProvider = scrapers.NewNyaaProvider()
redeTorrentProvider = scrapers.NewRedeTorrentProvider()
scraperRegistry.Register(nyaaProvider)
scraperRegistry.Register(redeTorrentProvider)
fmt.Println("[Scrapers] Nyaa + RedeTorrent inicializados")
}
}
// GetTorrentSources retorna as fontes de torrent disponiveis
func (a *App) GetTorrentSources() []TorrentSource {
initScrapers()
ctx, cancel := context.WithTimeout(a.ctx, 5*time.Second)
defer cancel()
sources := []TorrentSource{
{
ID: "all",
Name: "Todas as Fontes",
Description: "Busca em Nyaa + RedeTorrent",
IsBR: false,
Available: true,
},
{
ID: "nyaa",
Name: "Nyaa.si",
Description: "Internacional - Dual Audio, Legendado",
IsBR: false,
Available: nyaaProvider.IsAvailable(ctx),
},
{
ID: "redetorrent",
Name: "RedeTorrent",
Description: "BR - Dublado e Legendado PT-BR",
IsBR: true,
Available: redeTorrentProvider.IsAvailable(ctx),
},
}
return sources
}
// isBatchTorrent verifica se é um pack/batch (múltiplos eps) baseado no título/tamanho
func isBatchTorrent(title string, size string) bool {
titleLower := strings.ToLower(title)
// Padrões que indicam batch/pack
batchPatterns := []string{
"batch", "complete", "completo", "temporada", "season",
"01-", "1-", "e01-", "ep01-", "episode 01-",
"s01", "s02", "s1", "s2",
"~", " - ", "all episodes", "todos",
}
for _, pattern := range batchPatterns {
if strings.Contains(titleLower, pattern) {
return true
}
}
// Se tamanho > 5GB provavelmente é batch
if strings.Contains(size, "GiB") || strings.Contains(size, "GB") {
sizeStr := strings.ReplaceAll(size, "GiB", "")
sizeStr = strings.ReplaceAll(sizeStr, "GB", "")
sizeStr = strings.TrimSpace(sizeStr)
if sizeNum := parseFloat(sizeStr); sizeNum > 5.0 {
return true
}
}
return false
}
// parseFloat converte string para float
func parseFloat(s string) float64 {
var f float64
fmt.Sscanf(s, "%f", &f)
return f
}
// extractBaseAnimeName extrai o nome base do anime removendo episódio/qualidade
func extractBaseAnimeName(title string) string {
// Remove tags de fansub [SubGroup]
result := title
for strings.Contains(result, "[") && strings.Contains(result, "]") {
start := strings.Index(result, "[")
end := strings.Index(result, "]")
if start >= 0 && end > start {
result = result[:start] + result[end+1:]
} else {
break
}
}
// Remove extensão
result = strings.TrimSuffix(result, ".mkv")
result = strings.TrimSuffix(result, ".mp4")
// Remove número de episódio no final: " - 01", " E01", " Ep01", etc
patterns := []string{
" - ", " E", " Ep", " EP", " Episode ", " episode ",
}
for _, p := range patterns {
if idx := strings.LastIndex(result, p); idx > 0 {
suffix := result[idx:]
// Verifica se depois do pattern tem número
if len(suffix) > len(p) {
afterPattern := suffix[len(p):]
if len(afterPattern) > 0 && afterPattern[0] >= '0' && afterPattern[0] <= '9' {
result = result[:idx]
break
}
}
}
}
// Remove qualidade (1080p), (720p) etc
result = strings.TrimSpace(result)
for strings.HasSuffix(result, ")") {
start := strings.LastIndex(result, "(")
if start > 0 {
result = strings.TrimSpace(result[:start])
} else {
break
}
}
return strings.TrimSpace(result)
}
// SearchTorrents busca torrents em todas as fontes ou fonte especifica
// Agrupa resultados por anime, priorizando batches/packs
func (a *App) SearchTorrents(query string, sourceID string) []TorrentResult {
initScrapers()
ctx, cancel := context.WithTimeout(a.ctx, 30*time.Second)
defer cancel()
var rawResults []scrapers.AnimeResult
switch sourceID {
case "nyaa":
results, err := nyaaProvider.Search(ctx, query)
if err != nil {
fmt.Printf("[Scrapers] Nyaa erro: %v\n", err)
} else {
rawResults = results
fmt.Printf("[Scrapers] Nyaa: %d resultados\n", len(results))
}
case "redetorrent":
results, err := redeTorrentProvider.Search(ctx, query)
if err != nil {
fmt.Printf("[Scrapers] RedeTorrent erro: %v\n", err)
} else {
rawResults = results
fmt.Printf("[Scrapers] RedeTorrent: %d resultados\n", len(results))
}
default:
rawResults = scraperRegistry.SearchAll(ctx, query)
fmt.Printf("[Scrapers] Todas fontes: %d resultados\n", len(rawResults))
}
seen := make(map[string]bool)
var allResults []TorrentResult
// Primeiro passo: converter e marcar batches
for _, r := range rawResults {
if r.Hash == "" || seen[r.Hash] {
continue
}
seen[r.Hash] = true
isBR := r.HasPTBR || r.DualAudio || r.IsDubbed || r.BRScore >= 50 ||
strings.Contains(strings.ToLower(r.Source), "rede")
allResults = append(allResults, TorrentResult{
Title: r.Title,
Magnet: r.Magnet,
Hash: r.Hash,
Size: r.Size,
Quality: r.Quality,
Seeders: r.Seeders,
Source: r.Source,
IsBR: isBR,
DualAudio: r.DualAudio,
})
}
// Segundo passo: agrupar por anime base, priorizar batches
animeGroups := make(map[string][]TorrentResult)
for _, r := range allResults {
baseName := extractBaseAnimeName(r.Title)
if baseName == "" {
baseName = r.Title
}
// Normaliza para agrupar
key := strings.ToLower(baseName)
animeGroups[key] = append(animeGroups[key], r)
}
var results []TorrentResult
seenAnimes := make(map[string]bool)
// Para cada grupo de anime, pegar o melhor torrent (batch ou mais seeds)
for animeName, group := range animeGroups {
// Ordena: BR primeiro, depois batches, depois por seeds
sort.Slice(group, func(i, j int) bool {
// BR primeiro
if group[i].IsBR != group[j].IsBR {
return group[i].IsBR
}
// Batch primeiro
isBatchI := isBatchTorrent(group[i].Title, group[i].Size)
isBatchJ := isBatchTorrent(group[j].Title, group[j].Size)
if isBatchI != isBatchJ {
return isBatchI
}
// Mais seeds
return group[i].Seeders > group[j].Seeders
})
// Adiciona TODOS os batches e os 3 melhores episódios únicos
addedCount := 0
for _, r := range group {
isBatch := isBatchTorrent(r.Title, r.Size)
// Sempre adiciona batches
if isBatch {
if !seenAnimes[r.Hash] {
results = append(results, r)
seenAnimes[r.Hash] = true
}
continue
}
// Para episódios únicos, limite de 3 por anime
if addedCount < 3 {
if !seenAnimes[r.Hash] {
results = append(results, r)
seenAnimes[r.Hash] = true
addedCount++
}
}
}
_ = animeName // usado para debug se precisar
}
// Ordena resultado final
sort.Slice(results, func(i, j int) bool {
// BR primeiro
if results[i].IsBR != results[j].IsBR {
return results[i].IsBR
}
// Batches primeiro
isBatchI := isBatchTorrent(results[i].Title, results[i].Size)
isBatchJ := isBatchTorrent(results[j].Title, results[j].Size)
if isBatchI != isBatchJ {
return isBatchI
}
// Mais seeds
return results[i].Seeders > results[j].Seeders
})
fmt.Printf("[Scrapers] %d resultados agrupados para '%s' (fonte: %s, total bruto: %d)\n",
len(results), query, sourceID, len(allResults))
return results
}
// SearchTorrentsBR busca apenas em fontes BR (RedeTorrent)
func (a *App) SearchTorrentsBR(query string) []TorrentResult {
return a.SearchTorrents(query, "redetorrent")
}
// SearchTorrentsNyaa busca apenas no Nyaa
func (a *App) SearchTorrentsNyaa(query string) []TorrentResult {
return a.SearchTorrents(query, "nyaa")
}
// SearchTorrentsAll busca em todas as fontes
func (a *App) SearchTorrentsAll(query string) []TorrentResult {
return a.SearchTorrents(query, "all")
}