nord-config-generator 1.0.1__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nord-config-generator
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: A command-line tool for generating optimized NordVPN WireGuard configurations.
5
5
  Author-email: Ahmed Touhami <mustafachyi272@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nord-config-generator"
7
- version = "1.0.1"
7
+ version = "1.0.2"
8
8
  authors = [
9
9
  { name="Ahmed Touhami", email="mustafachyi272@gmail.com" },
10
10
  ]
@@ -25,6 +25,7 @@ class Server:
25
25
  station: str
26
26
  load: int
27
27
  country: str
28
+ country_code: str
28
29
  city: str
29
30
  latitude: float
30
31
  longitude: float
@@ -45,6 +46,7 @@ class GenerationStats:
45
46
  class NordVpnApiClient:
46
47
  NORD_API_BASE_URL = "https://api.nordvpn.com/v1"
47
48
  LOCATION_API_URL = "https://ipinfo.io/json"
49
+ COUNTRIES_API_URL = f"{NORD_API_BASE_URL}/servers/countries"
48
50
 
49
51
  def __init__(self, console_manager: ConsoleManager):
50
52
  self._console = console_manager
@@ -73,6 +75,10 @@ class NordVpnApiClient:
73
75
  data = await self._get(url, params=params)
74
76
  return data if isinstance(data, list) else []
75
77
 
78
+ async def get_countries(self) -> List[Dict[str, Any]]:
79
+ data = await self._get(self.COUNTRIES_API_URL)
80
+ return data if isinstance(data, list) else []
81
+
76
82
  async def get_user_geolocation(self) -> Optional[Tuple[float, float]]:
77
83
  data = await self._get(self.LOCATION_API_URL)
78
84
  if not isinstance(data, dict):
@@ -108,11 +114,13 @@ class ConfigurationOrchestrator:
108
114
  self.stats = GenerationStats()
109
115
 
110
116
  async def generate(self) -> Optional[Path]:
111
- user_location, all_servers_data = await self._fetch_remote_data()
112
- if not user_location or not all_servers_data:
117
+ user_location, all_servers_data, countries_data = await self._fetch_remote_data()
118
+ if not user_location or not all_servers_data or not countries_data:
113
119
  return None
114
120
 
115
- processed_servers = await self._process_server_data(all_servers_data, user_location)
121
+ country_code_map = {country['name']: country['code'].lower() for country in countries_data}
122
+ processed_servers = await self._process_server_data(all_servers_data, user_location, country_code_map)
123
+
116
124
  sorted_servers = sorted(processed_servers, key=lambda s: (s.load, s.distance))
117
125
  best_servers_by_location = self._get_best_servers(sorted_servers)
118
126
 
@@ -122,19 +130,20 @@ class ConfigurationOrchestrator:
122
130
  await self._save_all_configurations(sorted_servers, best_servers_by_location, servers_info)
123
131
  return self._output_dir
124
132
 
125
- async def _fetch_remote_data(self) -> Tuple[Optional[Tuple[float, float]], List[Dict[str, Any]]]:
133
+ async def _fetch_remote_data(self) -> Tuple[Optional[Tuple[float, float]], List[Dict[str, Any]], List[Dict[str, Any]]]:
126
134
  with self._console.create_progress_bar() as progress:
127
- task = progress.add_task("Fetching remote data...", total=2)
128
- user_location, all_servers_data = await asyncio.gather(
135
+ task = progress.add_task("Fetching remote data...", total=3)
136
+ user_location, all_servers_data, countries_data = await asyncio.gather(
129
137
  self._api_client.get_user_geolocation(),
130
- self._api_client.get_all_servers()
138
+ self._api_client.get_all_servers(),
139
+ self._api_client.get_countries()
131
140
  )
132
- progress.update(task, advance=2)
133
- return user_location, all_servers_data
141
+ progress.update(task, advance=3)
142
+ return user_location, all_servers_data, countries_data
134
143
 
135
- async def _process_server_data(self, all_servers_data: List[Dict[str, Any]], user_location: Tuple[float, float]) -> List[Server]:
144
+ async def _process_server_data(self, all_servers_data: List[Dict[str, Any]], user_location: Tuple[float, float], country_code_map: Dict[str, str]) -> List[Server]:
136
145
  loop = asyncio.get_running_loop()
137
- parse_func = partial(self._parse_server_data, user_location=user_location)
146
+ parse_func = partial(self._parse_server_data, user_location=user_location, country_code_map=country_code_map)
138
147
  with ThreadPoolExecutor(max_workers=min(32, (os.cpu_count() or 1) + 4)) as executor:
139
148
  tasks = [loop.run_in_executor(executor, parse_func, s) for s in all_servers_data]
140
149
  processed_servers = await asyncio.gather(*tasks)
@@ -174,7 +183,7 @@ class ConfigurationOrchestrator:
174
183
  def _create_save_task(self, server: Server, subfolder: str, progress, task_id):
175
184
  config_str = self._generate_wireguard_config_string(server, self._preferences, self._private_key)
176
185
  path = self._output_dir / subfolder / self._sanitize_path_part(server.country) / self._sanitize_path_part(server.city)
177
- filename = f"{self._sanitize_path_part(server.name)}.conf"
186
+ filename = self._generate_compliant_filename(server)
178
187
  return self._save_config_file(config_str, path, filename, progress, task_id)
179
188
 
180
189
  async def _save_config_file(self, config_string: str, path: Path, filename: str, progress, task_id):
@@ -184,15 +193,31 @@ class ConfigurationOrchestrator:
184
193
  await f.write(config_string)
185
194
  progress.update(task_id, advance=1)
186
195
 
196
+ @staticmethod
197
+ def _generate_compliant_filename(server: Server) -> str:
198
+ server_number_match = re.search(r'\d+$', server.name)
199
+ if not server_number_match:
200
+ fallback_name = f"wg{server.station.replace('.', '')}"
201
+ return f"{fallback_name[:15]}.conf"
202
+
203
+ server_number = server_number_match.group(0)
204
+ base_name = f"{server.country_code}{server_number}"
205
+ return f"{base_name[:15]}.conf"
206
+
187
207
  @staticmethod
188
208
  def _generate_wireguard_config_string(server: Server, preferences: UserPreferences, private_key: str) -> str:
189
209
  endpoint = server.station if preferences.use_ip_for_endpoint else server.hostname
190
210
  return f"[Interface]\nPrivateKey = {private_key}\nAddress = 10.5.0.2/16\nDNS = {preferences.dns}\n\n[Peer]\nPublicKey = {server.public_key}\nAllowedIPs = 0.0.0.0/0, ::/0\nEndpoint = {endpoint}:51820\nPersistentKeepalive = {preferences.persistent_keepalive}"
191
211
 
192
212
  @staticmethod
193
- def _parse_server_data(server_data: Dict[str, Any], user_location: Tuple[float, float]) -> Optional[Server]:
213
+ def _parse_server_data(server_data: Dict[str, Any], user_location: Tuple[float, float], country_code_map: Dict[str, str]) -> Optional[Server]:
194
214
  try:
195
215
  location = server_data['locations'][0]
216
+ country_name = location['country']['name']
217
+ country_code = country_code_map.get(country_name)
218
+ if not country_code:
219
+ return None
220
+
196
221
  public_key = next(
197
222
  m['value'] for t in server_data['technologies']
198
223
  if t['identifier'] == 'wireguard_udp'
@@ -204,7 +229,8 @@ class ConfigurationOrchestrator:
204
229
  return Server(
205
230
  name=server_data['name'], hostname=server_data['hostname'],
206
231
  station=server_data['station'], load=int(server_data.get('load', 0)),
207
- country=location['country']['name'], city=location['country'].get('city', {}).get('name', 'Unknown'),
232
+ country=country_name, country_code=country_code,
233
+ city=location['country'].get('city', {}).get('name', 'Unknown'),
208
234
  latitude=location['latitude'], longitude=location['longitude'],
209
235
  public_key=public_key, distance=distance
210
236
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nord-config-generator
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: A command-line tool for generating optimized NordVPN WireGuard configurations.
5
5
  Author-email: Ahmed Touhami <mustafachyi272@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/mustafachyi/NordVPN-WireGuard-Config-Generator