import ipaddress
import platform
import re
from sys import exit

from loguru import logger

dst = "C:/Windows/System32/drivers/etc/hosts"
if str(platform.system()) != "Windows":
	dst = "/etc/hosts"


def read_file(filename):
	"""
	读取文件
	:param filename:
	:return:
	"""
	txt = ''
	try:
		with open(filename, 'r') as f:
			lines = f.readlines()
			for line in lines:
				if not line.strip().startswith('#'):
					if len(line) >= 2:
						txt += line
			print(txt)
			s = txt.split("\n")
			if len(s) == 1:
				logger.warning(f"[ {dst} ]文件未配置任何有效记录")
			else:
				logger.info(f'当前数据量: {len(s) - 1}')
			return txt
	except Exception as e:
		logger.error(e)
		exit(1)


def is_ipv4_or_ipv6(ip_str):
	try:
		ip = ipaddress.ip_address(ip_str)
		if isinstance(ip, ipaddress.IPv4Address) or isinstance(ip, ipaddress.IPv6Address):
			logger.info("IP格式符合")
			return True
	except ValueError:
		logger.warning(f"IP地址格式错误: {ValueError}")
		return False


def is_valid_domain(domain):
	pattern = re.compile(r'^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$')
	return bool(pattern.match(domain))


def list_to_str(list_ip, list_name: list):
	logger.debug("列表1: ")
	print(list_ip)
	logger.debug("列表2: ")
	print(list_name)

	if len(list_ip) == 0 or len(list_name) == 0:
		logger.error("列表数据: 0")
		exit(4)
	txt = ''
	for index, vip in enumerate(list_ip):
		txt += f"\n{vip}\t{list_name[index]}"
		logger.debug(f"{vip}\t{list_name[index]}")
	return txt


def GenerateDictionary(txt: str):
	"""
	生成字典
	:param txt:
	:return: dict
	"""
	domain_dict = {}
	txt_sp = txt.split("\n")
	for i in txt_sp:
		if len(i) <= 10:
			continue
		info = str(i).split(" ")
		if len(info) != 2:
			info = str(i).split("\t")
		else:
			continue
		if len(info) != 2:
			continue
		logger.debug(info)
		if len(info[1]) >= 2 and is_valid_domain(info[1]) and is_ipv4_or_ipv6(info[0]):
			logger.info(f"符合格式: {info}")
			domain_dict[info[0]] = info[1]
		else:
			logger.warning(f"忽略内容: {info}")
	return domain_dict


def dict_to_string(domain_dict: dict):
	"""
	字典转字符串
	:param domain_dict:
	:return:
	"""
	txt = ''
	if len(domain_dict) == 0:
		logger.error('No domain...')
		return txt
	for key, value in domain_dict.items():
		txt += f"{key}\t{value}\n"
	return txt


def write_domain(txt: str):
	"""
	写入解析参数
	:param txt:
	:return:
	"""
	try:
		with open(file=dst, mode='w', encoding='utf-8') as f:
			f.write(txt)
	except IOError:
		logger.error(IOError)
		exit(3)
	print(read_file(filename=dst))


def delete_domain():
	"""
	删除解析
	:return:
	"""
	dict_ = {0: "domain"}
	data = GenerateDictionary(read_file(dst))
	if len(data) == 0:
		logger.warning("不存在数据,无需删除")
		return
	for index, value_ip in enumerate(data):
		print(f"[ {index} ] -> [ {value_ip}  : {str(data[value_ip])}]")
		dict_[int(index)] = str(value_ip)
		index += 1
	ids = input("请输入需要删除的记录值ID,默认: 0\n")
	if len(ids) == 0:
		ids = 0
	try:
		ids = int(ids)
	except ValueError:
		logger.warning(f"格式输入错误,仅支持数字: {ValueError}")
		return
	print(data)
	print(dict_)
	if 0 <= int(ids) <= index:
		try:
			del data[dict_[ids]]
		except KeyError:
			logger.error("不存在该记录")
			return
		write_domain(dict_to_string(data))


def add_hosts():
	"""
	添加解析记录到hosts文件
	:return:
	"""
	# 获取新数据
	ip = input("请输入需要解析的IP\n")
	domain = input("请输入需要解析的域名,默认: tt.cc\n")
	if len(ip) <= 6:
		logger.error("IP长度不满足")
		return
	if len(domain) == 0:
		domain = "tt.cc"
	if not is_ipv4_or_ipv6(ip):
		return
	if not is_valid_domain(domain):
		logger.warning("Invalid domain")
		return
	data = GenerateDictionary(read_file(dst))
	if len(data) != 0:
		logger.debug("正在检查是否已存在")
		for vip in data:
			print(f"{vip} {data[vip]}")
			if str(vip).lower() == str(ip).lower():
				logger.warning(f"IP已存在,请先删除再添加: {vip} {data[vip]}")
				return
			if str(domain).lower() == str(data[vip]).lower():
				logger.warning(f"域名已存在,请先删除再添加{vip} {data[vip]}")
				return
		data[ip] = domain
		write_domain(dict_to_string(data))
	else:
		write_domain(f"{ip}\t{domain}\n")


if __name__ == '__main__':
	while True:
		print("请选择需要的操作")
		print("[1] 添加解析")
		print("[2] 删除单个解析")
		print("[3] 清空解析")
		print("[4] 查看当前配置")
		se = input("请输入序号\n")
		try:
			se = int(se)
		except ValueError:
			print("输入错误,请重新输入")
			continue
		if int(se) == 1:
			add_hosts()
		elif int(se) == 2:
			delete_domain()
		elif int(se) == 3:
			write_domain(txt="\n")
			logger.info("清空完成...")
		elif int(se) == 4:
			c = GenerateDictionary(read_file(dst))
			print("------------当前配置如下-----")
			print(c)
			print("-------------------------\n")
		else:
			print("输入错误,请重新输入")

效果

PS D:\code\coding\ywgl\tools\运维自动化> python .\HOSTS更新.py
请选择需要的操作
[1] 添加解析
[2] 删除单个解析
[3] 清空解析
[4] 查看当前配置
请输入序号
4
172.22.10.4     tt.cc

2024-03-19 13:23:28.301 | INFO     | __main__:read_file:32 - 当前数据量: 1
2024-03-19 13:23:28.302 | DEBUG    | __main__:GenerateDictionary:89 - ['172.22.10.4', 'tt.cc']
2024-03-19 13:23:28.302 | INFO     | __main__:is_ipv4_or_ipv6:43 - IP格式符合
2024-03-19 13:23:28.302 | INFO     | __main__:GenerateDictionary:91 - 符合格式: ['172.22.10.4', 'tt.cc']
------------当前配置如下-----
{'172.22.10.4': 'tt.cc'}
-------------------------

请选择需要的操作
[1] 添加解析
[2] 删除单个解析
[3] 清空解析
[4] 查看当前配置
请输入序号
2
172.22.10.4     tt.cc

2024-03-19 13:23:31.129 | INFO     | __main__:read_file:32 - 当前数据量: 1
2024-03-19 13:23:31.130 | DEBUG    | __main__:GenerateDictionary:89 - ['172.22.10.4', 'tt.cc']
2024-03-19 13:23:31.130 | INFO     | __main__:is_ipv4_or_ipv6:43 - IP格式符合
2024-03-19 13:23:31.130 | INFO     | __main__:GenerateDictionary:91 - 符合格式: ['172.22.10.4', 'tt.cc']
[ 0 ] -> [ 172.22.10.4  : tt.cc]
请输入需要删除的记录值ID,默认: 0

{'172.22.10.4': 'tt.cc'}
{0: '172.22.10.4'}
2024-03-19 13:23:33.354 | ERROR    | __main__:dict_to_string:106 - No domain...

2024-03-19 13:23:33.370 | WARNING  | __main__:read_file:30 - [ C:/Windows/System32/drivers/etc/hosts ]文件未配置任何有效记录

请选择需要的操作
[1] 添加解析
[2] 删除单个解析
[3] 清空解析
[4] 查看当前配置
请输入序号
4

2024-03-19 13:23:38.885 | WARNING  | __main__:read_file:30 - [ C:/Windows/System32/drivers/etc/hosts ]文件未配置任何有效记录
------------当前配置如下-----
{}
-------------------------

请选择需要的操作
[1] 添加解析
[2] 删除单个解析
[3] 清空解析
[4] 查看当前配置
请输入序号