File size: 3,583 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
/**
 * Pure parser for the proxy bulk-import textarea.
 *
 * Supported line formats (one proxy per line):
 *   1. Pipe-delimited:  NAME|HOST|PORT[|USERNAME|PASSWORD|TYPE|REGION|STATUS|NOTES]
 *   2. Auth-less short: HOST:PORT  → name is auto-generated as "Imported HOST:PORT"
 *
 * Lines starting with # and blank lines are skipped.
 */

export type ParsedProxyEntry = {
  name: string;
  host: string;
  port: number;
  username: string;
  password: string;
  type: string;
  region: string;
  status: string;
  notes: string;
};

export type ParseError = {
  line: number;
  reason: string;
};

export const VALID_PROXY_TYPES = new Set(["http", "https", "socks5"]);
export const VALID_PROXY_STATUSES = new Set(["active", "inactive"]);

export function parseBulkImportText(text: string): {
  entries: ParsedProxyEntry[];
  errors: ParseError[];
  skipped: number;
} {
  const lines = text.split("\n");
  const entries: ParsedProxyEntry[] = [];
  const errors: ParseError[] = [];
  let skipped = 0;

  for (let i = 0; i < lines.length; i++) {
    const raw = lines[i].trim();
    if (!raw || raw.startsWith("#")) {
      skipped++;
      continue;
    }

    const lineNum = i + 1;

    // Auth-less shorthand: HOST:PORT (no pipe characters, exactly one colon)
    if (!raw.includes("|")) {
      const colonIdx = raw.lastIndexOf(":");
      if (colonIdx > 0) {
        const host = raw.slice(0, colonIdx).trim();
        const portStr = raw.slice(colonIdx + 1).trim();
        const port = Number(portStr);
        if (!host) {
          errors.push({ line: lineNum, reason: "bulkImportErrorMissingHost" });
          continue;
        }
        if (!portStr || isNaN(port) || port < 1 || port > 65535) {
          errors.push({ line: lineNum, reason: "bulkImportErrorInvalidPort" });
          continue;
        }
        entries.push({
          name: `Imported ${host}:${portStr}`,
          host,
          port,
          username: "",
          password: "",
          type: "http",
          region: "",
          status: "active",
          notes: "",
        });
        continue;
      }
      errors.push({ line: lineNum, reason: "bulkImportErrorMissingHost" });
      continue;
    }

    // Full pipe-delimited format: NAME|HOST|PORT[|USERNAME|PASSWORD|TYPE|REGION|STATUS|NOTES]
    const parts = raw.split("|").map((p) => p.trim());
    const [name, host, portStr, username, password, type, region, status, notes] = parts;

    if (!name) {
      errors.push({ line: lineNum, reason: "bulkImportErrorMissingName" });
      continue;
    }
    if (!host) {
      errors.push({ line: lineNum, reason: "bulkImportErrorMissingHost" });
      continue;
    }
    const port = Number(portStr);
    if (!portStr || isNaN(port) || port < 1 || port > 65535) {
      errors.push({ line: lineNum, reason: "bulkImportErrorInvalidPort" });
      continue;
    }
    const normalizedType = (type || "socks5").toLowerCase();
    if (!VALID_PROXY_TYPES.has(normalizedType)) {
      errors.push({ line: lineNum, reason: "bulkImportErrorInvalidType" });
      continue;
    }
    const normalizedStatus = (status || "active").toLowerCase();
    if (!VALID_PROXY_STATUSES.has(normalizedStatus)) {
      errors.push({ line: lineNum, reason: "bulkImportErrorInvalidStatus" });
      continue;
    }

    entries.push({
      name,
      host,
      port,
      username: username || "",
      password: password || "",
      type: normalizedType,
      region: region || "",
      status: normalizedStatus,
      notes: notes || "",
    });
  }

  return { entries, errors, skipped };
}