@@ -31,6 +31,71 @@ func loadSubsFromFile(path string) []string {
3131 return urls
3232}
3333
34+ // dedupSubFile removes duplicate URLs from sub.txt (case-insensitive).
35+ func dedupSubFile (path string ) {
36+ data , err := os .ReadFile (path )
37+ if err != nil {
38+ return
39+ }
40+
41+ lines := strings .Split (string (data ), "\n " )
42+ seen := make (map [string ]bool )
43+ var unique []string
44+
45+ for _ , line := range lines {
46+ trimmed := strings .TrimSpace (line )
47+ if trimmed == "" || strings .HasPrefix (trimmed , "#" ) {
48+ unique = append (unique , line )
49+ continue
50+ }
51+ key := strings .ToLower (trimmed )
52+ if ! seen [key ] {
53+ seen [key ] = true
54+ unique = append (unique , line )
55+ }
56+ }
57+
58+ _ = os .WriteFile (path , []byte (strings .Join (unique , "\n " )), 0644 )
59+ }
60+
61+ // cleanSubFile removes failed URLs from sub.txt.
62+ func cleanSubFile (path string , failedURLs []string ) {
63+ if len (failedURLs ) == 0 {
64+ return
65+ }
66+
67+ data , err := os .ReadFile (path )
68+ if err != nil {
69+ return
70+ }
71+
72+ failedSet := make (map [string ]bool )
73+ for _ , u := range failedURLs {
74+ failedSet [strings .TrimSpace (u )] = true
75+ }
76+
77+ lines := strings .Split (string (data ), "\n " )
78+ var kept []string
79+ removed := 0
80+ for _ , line := range lines {
81+ trimmed := strings .TrimSpace (line )
82+ if trimmed == "" || strings .HasPrefix (trimmed , "#" ) {
83+ kept = append (kept , line )
84+ continue
85+ }
86+ if failedSet [trimmed ] {
87+ removed ++
88+ continue
89+ }
90+ kept = append (kept , line )
91+ }
92+
93+ if removed > 0 {
94+ _ = os .WriteFile (path , []byte (strings .Join (kept , "\n " )), 0644 )
95+ fmt .Printf (" sub.txt: removed %d failed/duplicate URLs\n " , removed )
96+ }
97+ }
98+
3499// ── fetchAll (legacy base64/text links) ──────────────────────────────────────
35100
36101func fetchAll (base64Links , textLinks []string ) ([]string , []string ) {
@@ -58,7 +123,7 @@ func fetchAll(base64Links, textLinks []string) ([]string, []string) {
58123
59124 total := len (jobs )
60125 numBatches := (total + batchSize - 1 ) / batchSize
61- fmt .Printf ("📡 Fetching %d sources in %d batches of %d (timeout=%s retries=%d)\n " ,
126+ fmt .Printf (" Fetching %d sources in %d batches of %d (timeout=%s retries=%d)\n " ,
62127 total , numBatches , batchSize , fetchTimeout , retryCount )
63128
64129 var mu sync.Mutex
@@ -127,15 +192,15 @@ func fetchAll(base64Links, textLinks []string) ([]string, []string) {
127192 }
128193 mu .Unlock ()
129194 }
130- fmt .Printf (" ✅ Fetch done — ok=%d fail=%d total_lines=%d\n " , okCount , failCount , len (lines ))
195+ fmt .Printf (" Fetch done — ok=%d fail=%d total_lines=%d\n " , okCount , failCount , len (lines ))
131196 return lines , failed
132197}
133198
134199// ── fetchAllFromSubs (sub.txt mode) ──────────────────────────────────────────
135200
136- func fetchAllFromSubs (subURLs []string ) ([]string , []string ) {
201+ func fetchAllFromSubs (subURLs []string ) ([]string , []string , [] string ) {
137202 const batchSize = 20
138- const fetchTimeout = 8 * time .Second
203+ const fetchTimeout = 20 * time .Second
139204
140205 retryCount := cfg .Validation .FetchRetryCount
141206 retryDelay := time .Duration (cfg .Validation .FetchRetryDelayMs ) * time .Millisecond
@@ -148,79 +213,93 @@ func fetchAllFromSubs(subURLs []string) ([]string, []string) {
148213
149214 total := len (subURLs )
150215 numBatches := (total + batchSize - 1 ) / batchSize
151- fmt .Printf ("📡 Fetching %d sources in %d batches (timeout=%s retries=%d)\n " ,
216+ fmt .Printf (" Fetching %d sources in %d batches (timeout=%s retries=%d)\n " ,
152217 total , numBatches , fetchTimeout , retryCount )
153218
154- var mu sync.Mutex
155219 var lines []string
156220 var failed []string
221+ var failedURLs []string
157222 var okCount , failCount int
158223
159- for batchIdx := 0 ; batchIdx < numBatches ; batchIdx ++ {
160- start := batchIdx * batchSize
161- end := start + batchSize
162- if end > total {
163- end = total
164- }
165- batch := subURLs [start :end ]
166-
167- var wg sync.WaitGroup
168- type batchResult struct {
169- url string
170- lines []string
171- err error
172- status int
173- }
174- results := make ([]batchResult , len (batch ))
175-
176- for i , u := range batch {
177- wg .Add (1 )
178- go func (idx int , rawURL string ) {
179- defer wg .Done ()
180- var fr fetchResult
181- for attempt := 0 ; attempt <= retryCount ; attempt ++ {
182- if attempt > 0 && retryDelay > 0 {
183- time .Sleep (retryDelay )
224+ fetchBatch := func (urls []string ) (ok int , fail int , fURLs []string ) {
225+ bNumBatches := (len (urls ) + batchSize - 1 ) / batchSize
226+ for batchIdx := 0 ; batchIdx < bNumBatches ; batchIdx ++ {
227+ start := batchIdx * batchSize
228+ end := start + batchSize
229+ if end > len (urls ) {
230+ end = len (urls )
231+ }
232+ batch := urls [start :end ]
233+
234+ var wg sync.WaitGroup
235+ type batchResult struct {
236+ url string
237+ lines []string
238+ err error
239+ status int
240+ }
241+ results := make ([]batchResult , len (batch ))
242+
243+ for i , u := range batch {
244+ wg .Add (1 )
245+ go func (idx int , rawURL string ) {
246+ defer wg .Done ()
247+ var fr fetchResult
248+ for attempt := 0 ; attempt <= retryCount ; attempt ++ {
249+ if attempt > 0 && retryDelay > 0 {
250+ time .Sleep (retryDelay )
251+ }
252+ fr = fetchRaw (rawURL , fetchTimeout )
253+ if fr .err == nil && fr .statusCode == http .StatusOK {
254+ break
255+ }
184256 }
185- fr = fetchRaw ( rawURL , fetchTimeout )
186- if fr . err == nil && fr .statusCode == http . StatusOK {
187- break
257+ if fr . err != nil || fr . statusCode != http . StatusOK {
258+ results [ idx ] = batchResult { url : rawURL , err : fr .err , status : fr . statusCode }
259+ return
188260 }
189- }
190- if fr .err != nil || fr .statusCode != http .StatusOK {
191- results [idx ] = batchResult {url : rawURL , err : fr .err , status : fr .statusCode }
192- return
193- }
194- extracted := smartDecode (fr .content )
195- results [idx ] = batchResult {url : rawURL , lines : extracted , status : fr .statusCode }
196- }(i , u )
197- }
198- wg .Wait ()
261+ extracted := smartDecode (fr .content )
262+ results [idx ] = batchResult {url : rawURL , lines : extracted , status : fr .statusCode }
263+ }(i , u )
264+ }
265+ wg .Wait ()
199266
200- mu .Lock ()
201- for _ , r := range results {
202- if r .err != nil || r .status != http .StatusOK {
203- status := "error"
204- if r .status > 0 {
205- status = fmt .Sprintf ("HTTP %d" , r .status )
267+ for _ , r := range results {
268+ if r .err != nil || r .status != http .StatusOK {
269+ fail ++
270+ fURLs = append (fURLs , r .url )
271+ continue
206272 }
207- failCount ++
208- failed = append ( failed , fmt . Sprintf ( "%s (%s)" , r . url , status ))
209- if gLog != nil {
210- gLog . writeLine ( fmt . Sprintf ( "[FETCH] FAIL %s status=%s" , r . url , status ))
273+ if len ( r . lines ) == 0 {
274+ fail ++
275+ fURLs = append ( fURLs , r . url )
276+ continue
211277 }
212- continue
278+ ok ++
279+ lines = append (lines , r .lines ... )
213280 }
214- okCount ++
215- if gLog != nil {
216- gLog .writeLine (fmt .Sprintf ("[FETCH] OK %s lines=%d" , r .url , len (r .lines )))
217- }
218- lines = append (lines , r .lines ... )
219281 }
220- mu . Unlock ()
282+ return
221283 }
222- fmt .Printf (" ✅ Fetch done — ok=%d fail=%d total_lines=%d\n " , okCount , failCount , len (lines ))
223- return lines , failed
284+
285+ // ── First pass ──
286+ okCount , failCount , failedURLs = fetchBatch (subURLs )
287+ failed = failedURLs
288+
289+ // ── Retry passes ──
290+ for pass := 0 ; pass < retryCount && len (failedURLs ) > 0 ; pass ++ {
291+ fmt .Printf (" Retry pass %d — retrying %d failed sources (delay=%s)...\n " ,
292+ pass + 1 , len (failedURLs ), retryDelay )
293+ time .Sleep (retryDelay )
294+ retryOK , retryFail , retryFailed := fetchBatch (failedURLs )
295+ okCount += retryOK
296+ failCount += retryFail
297+ failedURLs = retryFailed
298+ failed = retryFailed
299+ }
300+
301+ fmt .Printf (" Fetch done — ok=%d fail=%d total_lines=%d\n " , okCount , failCount , len (lines ))
302+ return lines , failed , failedURLs
224303}
225304
226305// ── fetchRaw ──────────────────────────────────────────────────────────────────
0 commit comments