This commit is contained in:
Ground-Zerro
2025-11-01 21:25:15 +11:00
parent b8ec267ae3
commit e11e5696cf
14 changed files with 1001 additions and 752 deletions

View File

@@ -71,17 +71,25 @@
### Функции
- Загрузка списка IP-адресов из файла.
- Агрегация IP-адресов в подсети с масками `/16`, `/24`, или объединение нескольких подсетей.
- Исключение IP-адресов Cloudflare из итогового списка (при необходимости).
- Извлечение IP-адресов из файла (файл может содержать любой текст - IP автоматически извлекаются).
- Исключение IP-адресов Cloudflare из итогового списка (опционально).
- Агрегация IP-адресов в подсети:
- `/16` (255.255.0.0)
- `/24` (255.255.255.0)
- Mix режим (`/24` + `/32`)
- Поддержка различных форматов маршрутизации:
- Windows (`route add`)
- Unix (`ip route`)
- Keenetic (`ip route` с интерфейсом)
- Mikrotik (`/ip firewall`)
- Только IP-адреса
- Windows route (`route add`)
- Linux route (`ip route`)
- Keenetic BAT (`route add` для bat-файлов)
- Keenetic CLI (`ip route` с интерфейсом)
- Mikrotik firewall (`/ip/firewall/address-list`)
- WireGuard
- OpenVPN
- CIDR (с указанием маски)
- OpenVPN (`push "route"`)
- CIDR нотация
- Автоматическое разделение больших файлов на части (для Keenetic BAT формата, max 999 строк).
- Удаление исходного файла после разделения на части.
- Проверка наличия входного файла с выводом инструкций при его отсутствии.
### Использование
@@ -91,7 +99,14 @@
pip install -r requirements.txt
```
2. Поместите файл c IP-адресами `ip.txt` в корневую директорию проекта. Файл может содержать любой текст и IP-адреса в любом виде - лишнее будет убрано автоматически.
2. Создайте файл `ip.txt` в директории со скриптом и добавьте в него IP-адреса (по одному на строку) или любой текст содержащий IP-адреса.
Пример содержимого `ip.txt`:
```
192.168.1.1
10.0.0.1
Какой-то текст с IP: 172.16.0.1
```
3. Запустите скрипт:
@@ -99,7 +114,18 @@
python convert.py
```
4. Следуйте подсказкам на экране.
4. Следуйте интерактивным подсказкам на экране:
- Выберите, нужно ли исключить IP-адреса Cloudflare (1 - да, Enter - нет)
- Выберите агрегацию подсетей (1 - /16, 2 - /24, 3 - mix, Enter - без агрегации)
- Выберите формат сохранения (1-8 или Enter для простого списка IP)
- При необходимости укажите шлюз/интерфейс/имя списка
5. Результат будет сохранен в файл `ip.txt` (или в несколько файлов, если был выбран формат с автоматическим разделением).
### Примечания
- Если файл `ip.txt` не найден, скрипт выведет подробную инструкцию по его созданию и корректно завершится.
- Для формата Keenetic BAT файл автоматически разделяется на части по 999 строк, исходный файл удаляется.
## split

View File

@@ -1,11 +1,12 @@
import asyncio
import ipaddress
import os
import re
from collections import defaultdict
import httpx
from colorama import Fore, Style, init
# Цвета
init(autoreset=True)
@@ -28,12 +29,9 @@ def red(text):
def magneta(text):
return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}"
def blue(text):
return f"{Fore.BLUE}{text}{Style.RESET_ALL}"
# IP шлюза для win и unix
def gateway_input(gateway):
if not gateway:
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
@@ -41,8 +39,6 @@ def gateway_input(gateway):
else:
return gateway
# IP шлюза и имя интерфейса для keenetic
def ken_gateway_input(ken_gateway):
if not ken_gateway:
input_ken_gateway = input(
@@ -51,8 +47,6 @@ def ken_gateway_input(ken_gateway):
else:
return ken_gateway
# Загрузка IP-адресов cloudflare
async def get_cloudflare_ips():
try:
async with httpx.AsyncClient() as client:
@@ -74,17 +68,22 @@ async def get_cloudflare_ips():
print("Ошибка при получении IP адресов Cloudflare:", e)
return set()
# Промт cloudflare фильтр
def check_include_cloudflare(cloudflare):
if cloudflare in ['yes', 'y', 'no', 'n']:
return cloudflare in ['yes', 'y']
return input(f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
f"\n{green('yes')} - исключить"
f"\n{green('Enter')} - оставить: ").strip().lower() in ['yes', 'y']
user_input = input(
f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
f"\n1. исключить"
f"\n{green('Enter')} - оставить"
f"\nВаш выбор: "
).strip()
if user_input == '1':
return True
else:
return False
# комментарий для microtik firewall
def mk_list_name_input(mk_list_name):
if not mk_list_name:
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
@@ -92,80 +91,117 @@ def mk_list_name_input(mk_list_name):
else:
return mk_list_name
# Уплотняем имена сервисов
def comment(selected_service):
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
# Промт на объединение IP в подсети
def subnet_input(subnet):
if not subnet:
subnet = input(
f"\n{yellow('Объединить IP-адреса в подсети?')} "
f"\n{green('16')} - сократить до /16 (255.255.0.0)"
f"\n{green('24')} - сократить до /24 (255.255.255.0)"
f"\n{green('mix')} - сократить до /24 (255.255.255.0) и /32 (255.255.255.255)"
f"\n{green('Enter')} - пропустить: "
).strip().lower()
choice = input(
f"\n{yellow('Объединить IP-адреса в подсети?')}"
f"\n1. сократить до {green('/16')} (255.255.0.0)"
f"\n2. сократить до {green('/24')} (255.255.255.0)"
f"\n3. сократить до {green('/24')} + {green('/32')} (255.255.255.0 и 255.255.255.255)"
f"\n{green('Enter')} - пропустить"
f"\nВаш выбор: "
).strip()
if choice == '1':
subnet = '16'
elif choice == '2':
subnet = '24'
elif choice == '3':
subnet = 'mix'
else:
subnet = '32'
return subnet if subnet in {'16', '24', 'mix'} else '32'
# Агрегация маршрутов
def group_ips_in_subnets(filename, subnet):
def group_ips_in_subnets_optimized(filename: str, subnet: str):
try:
with open(filename, 'r', encoding='utf-8') as file:
ips = {line.strip() for line in file if line.strip()} # Собираем уникальные IP адреса
ips = {line.strip() for line in file if line.strip()}
subnets = set()
def process_ips(subnet):
if subnet == "16":
for ip in ips:
try:
if subnet == "16":
# Преобразуем в /16 (два последних октета заменяются на 0.0)
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
subnets.add(f"{network.network_address}")
elif subnet == "24":
# Преобразуем в /24 (последний октет заменяется на 0)
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
subnets.add(f"{network.network_address}")
except ValueError as e:
print(f"Ошибка в IP адресе: {ip} - {e}")
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
subnets.add(str(network.network_address))
except ValueError:
continue
print(f"{Style.BRIGHT}IP-адреса агрегированы до /16 подсети{Style.RESET_ALL}")
if subnet in ["24", "16"]:
process_ips(subnet)
print(f"{Style.BRIGHT}IP-адреса агрегированы до /{subnet} подсети{Style.RESET_ALL}")
elif subnet == "24":
for ip in ips:
try:
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
subnets.add(str(network.network_address))
except ValueError:
continue
print(f"{Style.BRIGHT}IP-адреса агрегированы до /24 подсети{Style.RESET_ALL}")
elif subnet == "mix":
octet_groups = {}
octet_groups = defaultdict(list)
for ip in ips:
key = '.'.join(ip.split('.')[:3]) # Группировка по первым трем октетам
if key not in octet_groups:
octet_groups[key] = []
key = '.'.join(ip.split('.')[:3])
octet_groups[key].append(ip)
# IP-адреса с совпадающими первыми тремя октетами
network_24 = {key + '.0' for key, group in octet_groups.items() if
len(group) > 1} # Базовый IP для /24 подсетей
# Удаляем IP с совпадающими первыми тремя октетами из множества
ips -= {ip for group in octet_groups.values() if len(group) > 1 for ip in group}
# Оставляем только IP без указания маски для /24 и одиночных IP
subnets.update(ips) # IP без маски для одиночных IP
subnets.update(network_24) # Базовые IP для /24 подсетей
for key, group in octet_groups.items():
if len(group) > 1:
subnets.add(key + '.0')
else:
subnets.update(group)
print(f"{Style.BRIGHT}IP-адреса агрегированы до масок /24 и /32{Style.RESET_ALL}")
with open(filename, 'w', encoding='utf-8') as file:
for subnet in sorted(subnets):
file.write(subnet + '\n')
for subnet_ip in sorted(subnets, key=lambda x: ipaddress.IPv4Address(x.split('/')[0])):
file.write(subnet_ip + '\n')
except Exception as e:
print(f"Ошибка при обработке файла: {e}")
def split_file_by_lines(filename: str, max_lines: int = 999):
try:
with open(filename, 'r', encoding='utf-8') as file:
lines = file.readlines()
# Выбор формата сохранения результатов
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, subnet, ken_gateway):
total_lines = len(lines)
if total_lines <= max_lines:
return False
base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
extension = '.' + filename.rsplit('.', 1)[1] if '.' in filename else '.txt'
num_parts = (total_lines + max_lines - 1) // max_lines
print(f"\n{Style.BRIGHT}Результаты сохранены в файлы:{Style.RESET_ALL}")
for part in range(num_parts):
start_index = part * max_lines
end_index = min((part + 1) * max_lines, total_lines)
part_filename = f"{base_name}_p{part + 1}{extension}"
with open(part_filename, 'w', encoding='utf-8') as file:
file.writelines(lines[start_index:end_index])
print(f"{Style.BRIGHT}{part_filename} ({end_index - start_index} строк){Style.RESET_ALL}")
print(f"{Style.BRIGHT}Разделение завершено. Создано {num_parts} частей{Style.RESET_ALL}")
try:
os.remove(filename)
except Exception as e:
print(f"{red('Не удалось удалить исходный файл:')} {e}")
return True
except Exception as e:
print(f"{red('Ошибка при разделении файла:')} {e}")
return False
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
def read_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as file:
@@ -182,73 +218,83 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
else:
file.write('\n'.join(formatted_ips))
# Определение маски подсети
net_mask = subnet if subnet == "mix" else "255.255.0.0" if subnet == "16" else "255.255.255.0" if subnet == "24" else "255.255.255.255"
if not filetype:
filetype = input(f"""
user_input = input(f"""
{yellow('В каком формате сохранить файл?')}
{green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
{green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
{green('keenetic')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
{green('cidr')} - {cyan('IP')}/{subnet}
{green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")} comment="{comment(selected_service)}" address={cyan("IP")}/{subnet}
{green('ovpn')} - push "route {cyan('IP')} {net_mask}"
{green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
1. {green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
2. {green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
3. {green('keenetic bat')} - route add {cyan('IP')} mask {net_mask} 0.0.0.0
4. {green('keenetic cli')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
5. {green('cidr')} - {cyan('IP')}/{subnet}
6. {green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")}{f' comment="{comment(selected_service)}"' if mk_comment != "off" else ""} address={cyan("IP")}/{subnet}
7. {green('ovpn')} - push "route {cyan('IP')} {net_mask}"
8. {green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
{green('Enter')} - {cyan('IP')}
Ваш выбор: """)
Ваш выбор: """).strip()
mapping = {
'1': 'win',
'2': 'unix',
'3': 'keenetic bat',
'4': 'keenetic cli',
'5': 'cidr',
'6': 'mikrotik',
'7': 'ovpn',
'8': 'wireguard'
}
filetype = mapping.get(user_input, '')
ips = read_file(filename)
if not ips:
return
# Дополнительные запросы в зависимости от формата файла
if filetype in ['win', 'unix']: # Запрашиваем IP шлюза для win и unix
if filetype in ['win', 'unix']:
gateway = gateway_input(gateway)
elif filetype == 'keenetic': # Запрашиваем IP шлюза и имя интерфейса для keenetic
elif filetype == 'keenetic cli':
ken_gateway = ken_gateway_input(ken_gateway)
elif filetype == 'mikrotik': # Запрашиваем ввод комментария для microtik firewall
elif filetype == 'mikrotik':
mk_list_name = mk_list_name_input(mk_list_name)
# обычный формат
formatters = {
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
'unix': lambda ip: f"ip route {ip}/{subnet} {gateway}",
'keenetic': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
'keenetic bat': lambda ip: f"route add {ip} mask {net_mask} 0.0.0.0",
'keenetic cli': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
'cidr': lambda ip: f"{ip}/{subnet}",
'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
'mikrotik': lambda
ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={ip}/{subnet}',
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={ip}/{subnet}',
'wireguard': lambda ip: f"{ip}/{subnet}"
}
# mix формат
if subnet == "mix":
if filetype.lower() == 'win': # Обработка для win
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith(
'.0') else f"{ip.strip()} mask 255.255.255.255"
elif filetype.lower() == 'ovpn': # Обработка для ovpn
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith(
'.0') else f"{ip.strip()} 255.255.255.255"
else: # Обработка для остальных форматов
if filetype in ['win', 'keenetic bat']:
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith('.0') else f"{ip.strip()} mask 255.255.255.255"
elif filetype.lower() == 'ovpn':
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith('.0') else f"{ip.strip()} 255.255.255.255"
else:
mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32"
formatters.update({
'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}",
'unix': lambda ip: f"ip route {mix_formatter(ip)} {gateway}",
'keenetic': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}",
'keenetic bat': lambda ip: f"route add {mix_formatter(ip)} 0.0.0.0",
'keenetic cli': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}",
'cidr': lambda ip: f"{mix_formatter(ip)}",
'ovpn': lambda ip: f'push "route {mix_formatter(ip)}"',
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={mix_formatter(ip)}',
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={mix_formatter(ip)}',
'wireguard': lambda ip: f"{mix_formatter(ip)}"
})
# Запись в файл
if filetype.lower() in formatters:
write_file(filename, ips, formatters[filetype.lower()])
if filetype.lower() == 'keenetic bat':
return split_file_by_lines(filename, max_lines=999)
return False
# Стартуем
async def main():
filename = "ip.txt"
cloudflare = None
@@ -257,42 +303,45 @@ async def main():
gateway = None
selected_services = ["Service"]
mk_list_name = None
mk_comment = 'off'
ken_gateway = None
if not os.path.exists(filename):
print(f"\n{red(f'Ошибка: файл {filename} не найден!')}")
print(f"{yellow('Инструкция:')}")
print(f"1. Создайте файл {green(filename)} в текущей директории")
print(f"2. Добавьте в него IP-адреса (по одному на строку) или текст содержащий IP-адреса")
print(f"3. Запустите скрипт снова")
return
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
# Открываем файл и читаем строки
with open(filename, 'r') as file:
# Создаем множество для хранения уникальных IP-адресов
ips = set()
# Проходим по каждой строке файла
for line in file:
# Ищем все IP-адреса в строке
found_ips = ip_pattern.findall(line)
# Добавляем найденные IP-адреса в множество
ips.update(found_ips)
# Фильтр Cloudflare
include_cloudflare = check_include_cloudflare(cloudflare)
if include_cloudflare: # Загрузка IP-адресов Cloudflare
if include_cloudflare:
cloudflare_ips = await get_cloudflare_ips()
else:
cloudflare_ips = set()
# Удаляем IP-адреса Cloudflare
ips -= cloudflare_ips
with open(filename, 'w', encoding='utf-8') as file:
for ip in sorted(ips):
file.write(ip + '\n')
# Группировка IP-адресов в подсети
subnet = subnet_input(subnet)
if subnet != '32': # Если не '32', вызываем функцию для агрегации
group_ips_in_subnets(filename, subnet)
if subnet != '32':
group_ips_in_subnets_optimized(filename, subnet)
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, subnet, ken_gateway)
file_was_split = process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
if not file_was_split:
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL} {filename}")
if __name__ == "__main__":