File size: 4,021 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { FreeProxyItem, FreeProxySyncResult, FreeProxyProvider } from "./types";
import { isPrivateHost } from "@/shared/network/outboundUrlGuard";

const DEFAULT_API_URL = "https://1proxy-api.aitradepulse.com/api/v1/proxies/advanced";
const DEFAULT_MAX = 500;
const DEFAULT_MIN_QUALITY = 50;
const DEFAULT_PAGE_SIZE = 100;
const MAX_CONSECUTIVE_FAILURES = 5;

type OneProxyApiProxy = {
  ip: string;
  port: number;
  protocol: string;
  country_code: string;
  latency_ms: number;
  anonymity: string;
  quality_score: number;
  last_validated: string;
};

type OneProxyApiResponse = {
  total: number;
  count: number;
  offset: number;
  proxies: OneProxyApiProxy[];
};

export class OneproxyProvider implements FreeProxyProvider {
  readonly id = "1proxy" as const;
  readonly name = "1proxy";

  private consecutiveFailures = 0;

  isEnabled(): boolean {
    return process.env.FREE_PROXY_1PROXY_ENABLED !== "false";
  }

  private getConfig() {
    return {
      apiUrl: process.env.FREE_PROXY_1PROXY_API_URL || DEFAULT_API_URL,
      maxProxies: parseInt(process.env.FREE_PROXY_1PROXY_MAX || "", 10) || DEFAULT_MAX,
      minQuality:
        parseInt(process.env.FREE_PROXY_1PROXY_MIN_QUALITY || "", 10) || DEFAULT_MIN_QUALITY,
    };
  }

  async sync(): Promise<FreeProxySyncResult> {
    if (!this.isEnabled()) {
      return { fetched: 0, added: 0, updated: 0, errors: ["1proxy provider disabled"] };
    }
    if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
      return {
        fetched: 0,
        added: 0,
        updated: 0,
        errors: [`Circuit breaker open: ${this.consecutiveFailures} consecutive failures`],
      };
    }

    const { upsertFreeProxy } = await import("../db/freeProxies");
    const { apiUrl, maxProxies, minQuality } = this.getConfig();
    const errors: string[] = [];
    let added = 0;
    let updated = 0;
    let fetched = 0;
    let offset = 0;

    try {
      while (fetched < maxProxies) {
        const limit = Math.min(DEFAULT_PAGE_SIZE, maxProxies - fetched);
        const url = `${apiUrl}?offset=${offset}&limit=${limit}&min_quality_score=${minQuality}`;
        const res = await fetch(url, { signal: AbortSignal.timeout(15000) });

        if (!res.ok) {
          const text = await res.text().catch(() => "");
          errors.push(`HTTP ${res.status}: ${text.slice(0, 100)}`);
          this.consecutiveFailures++;
          break;
        }

        const json = (await res.json()) as OneProxyApiResponse;
        if (!Array.isArray(json.proxies) || json.proxies.length === 0) break;

        for (const p of json.proxies) {
          if (!p.ip || isPrivateHost(p.ip)) {
            errors.push(`1proxy: skipped private/loopback host ${p.ip}`);
            continue;
          }
          const item: FreeProxyItem = {
            source: "1proxy",
            host: p.ip,
            port: p.port,
            type: (p.protocol?.toLowerCase() as FreeProxyItem["type"]) || "http",
            countryCode: p.country_code || null,
            qualityScore: p.quality_score ?? null,
            latencyMs: p.latency_ms ?? null,
            anonymity: p.anonymity || null,
            lastValidated: p.last_validated || new Date().toISOString(),
          };
          const result = await upsertFreeProxy(item);
          if (result.action === "created") added++;
          else updated++;
        }

        fetched += json.proxies.length;
        offset += json.proxies.length;
        if (json.proxies.length < limit) break;
      }
      this.consecutiveFailures = 0;
    } catch (err) {
      this.consecutiveFailures++;
      errors.push(err instanceof Error ? err.message : String(err));
    }

    return { fetched, added, updated, errors };
  }

  async list(filters: {
    protocol?: string;
    country?: string;
    minQuality?: number;
    limit?: number;
  }): Promise<FreeProxyItem[]> {
    const { listFreeProxiesBySource } = await import("../db/freeProxies");
    return listFreeProxiesBySource("1proxy", filters);
  }
}