mirror of
https://github.com/honmashironeko/ProxyCat.git
synced 2025-06-21 10:23:01 +00:00
🎨 feat(getip): 重构 getip.py 并更新代理获取逻辑- 在 getip.py 中添加类型注解和函数文档字符串
- 修改 newip 函数以返回代理 IP 池列表 - 更新 ProxyCat-V1.9.py 中的代理加载逻辑 -调整 README.md 中关于 getip.py 的说明
This commit is contained in:
parent
18dd05a785
commit
da2f4c8835
@ -7,6 +7,8 @@ from banner import print_banner
|
||||
from itertools import cycle
|
||||
|
||||
init(autoreset=True)
|
||||
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
COLORS = {
|
||||
logging.INFO: Fore.GREEN,
|
||||
@ -20,6 +22,7 @@ class ColoredFormatter(logging.Formatter):
|
||||
record.msg = f"{log_color}{record.msg}{Style.RESET_ALL}"
|
||||
return super().format(record)
|
||||
|
||||
|
||||
log_format = '%(asctime)s - %(levelname)s - %(message)s'
|
||||
formatter = ColoredFormatter(log_format)
|
||||
|
||||
@ -28,14 +31,17 @@ console_handler.setFormatter(formatter)
|
||||
|
||||
logging.basicConfig(level=logging.INFO, handlers=[console_handler])
|
||||
|
||||
|
||||
def load_proxies(file_path='ip.txt'):
|
||||
with open(file_path, 'r') as file:
|
||||
return [line.strip() for line in file if '://' in line]
|
||||
|
||||
|
||||
def validate_proxy(proxy):
|
||||
pattern = re.compile(r'^(?P<scheme>socks5|http|https)://(?P<host>[^:]+):(?P<port>\d+)$')
|
||||
return pattern.match(proxy) is not None
|
||||
|
||||
|
||||
class AsyncProxyServer:
|
||||
def __init__(self, config):
|
||||
self.config = {**DEFAULT_CONFIG, **config}
|
||||
@ -54,20 +60,9 @@ class AsyncProxyServer:
|
||||
self.proxy_failed = False
|
||||
|
||||
def load_proxies(self):
|
||||
proxies = load_proxies(self.proxy_file)
|
||||
proxies = getip.newip() if self.use_getip else load_proxies(self.proxy_file)
|
||||
valid_proxies = [p for p in proxies if validate_proxy(p)]
|
||||
|
||||
if self.use_getip:
|
||||
valid_proxies = []
|
||||
for _ in range(4):
|
||||
new_ip = getip.newip()
|
||||
if validate_proxy(new_ip):
|
||||
valid_proxies.append(new_ip)
|
||||
break
|
||||
else:
|
||||
logging.error("多次尝试获取有效代理失败,退出程序")
|
||||
exit(1)
|
||||
|
||||
|
||||
return valid_proxies
|
||||
|
||||
async def get_next_proxy(self):
|
||||
@ -75,7 +70,7 @@ class AsyncProxyServer:
|
||||
return random.choice(self.proxies)
|
||||
elif self.mode == 'custom':
|
||||
return await self.custom_proxy_switch()
|
||||
|
||||
|
||||
if time.time() - self.last_switch_time >= self.interval:
|
||||
await self.get_proxy()
|
||||
return self.current_proxy
|
||||
@ -90,7 +85,8 @@ class AsyncProxyServer:
|
||||
return self.proxies[0] if self.proxies else "没有可用的代理"
|
||||
|
||||
def time_until_next_switch(self):
|
||||
return float('inf') if self.mode == 'load_balance' else max(0, self.interval - (time.time() - self.last_switch_time))
|
||||
return float('inf') if self.mode == 'load_balance' else max(0, self.interval - (
|
||||
time.time() - self.last_switch_time))
|
||||
|
||||
async def acquire(self):
|
||||
await self.rate_limiter.put(None)
|
||||
@ -103,10 +99,10 @@ class AsyncProxyServer:
|
||||
first_byte = await reader.read(1)
|
||||
if not first_byte:
|
||||
return
|
||||
|
||||
|
||||
if first_byte == b'\x05':
|
||||
await self.handle_socks5_connection(reader, writer)
|
||||
else:
|
||||
else:
|
||||
await self._handle_client_impl(reader, writer, first_byte)
|
||||
except asyncio.CancelledError:
|
||||
logging.info("客户端处理取消")
|
||||
@ -128,34 +124,34 @@ class AsyncProxyServer:
|
||||
if auth_version != b'\x01':
|
||||
writer.close()
|
||||
return
|
||||
|
||||
|
||||
ulen = ord(await reader.readexactly(1))
|
||||
username = await reader.readexactly(ulen)
|
||||
plen = ord(await reader.readexactly(1))
|
||||
password = await reader.readexactly(plen)
|
||||
|
||||
if username.decode() != self.username or password.decode() != self.password:
|
||||
writer.write(b'\x01\x01')
|
||||
writer.write(b'\x01\x01')
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
return
|
||||
|
||||
|
||||
writer.write(b'\x01\x00')
|
||||
await writer.drain()
|
||||
|
||||
version, cmd, _, atyp = struct.unpack('!BBBB', await reader.readexactly(4))
|
||||
if cmd != 1:
|
||||
if cmd != 1:
|
||||
writer.write(b'\x05\x07\x00\x01\x00\x00\x00\x00\x00\x00')
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
return
|
||||
|
||||
if atyp == 1:
|
||||
if atyp == 1:
|
||||
dst_addr = socket.inet_ntoa(await reader.readexactly(4))
|
||||
elif atyp == 3:
|
||||
addr_len = ord(await reader.readexactly(1))
|
||||
dst_addr = (await reader.readexactly(addr_len)).decode()
|
||||
elif atyp == 4:
|
||||
elif atyp == 4:
|
||||
dst_addr = socket.inet_ntop(socket.AF_INET6, await reader.readexactly(16))
|
||||
else:
|
||||
writer.write(b'\x05\x08\x00\x01\x00\x00\x00\x00\x00\x00')
|
||||
@ -178,10 +174,13 @@ class AsyncProxyServer:
|
||||
remote_writer.write(b'\x05\x01\x00')
|
||||
await remote_writer.drain()
|
||||
await remote_reader.readexactly(2)
|
||||
|
||||
remote_writer.write(b'\x05\x01\x00' + (b'\x03' + len(dst_addr).to_bytes(1, 'big') + dst_addr.encode() if isinstance(dst_addr, str) else b'\x01' + socket.inet_aton(dst_addr)) + struct.pack('!H', dst_port))
|
||||
|
||||
remote_writer.write(b'\x05\x01\x00' + (
|
||||
b'\x03' + len(dst_addr).to_bytes(1, 'big') + dst_addr.encode() if isinstance(dst_addr,
|
||||
str) else b'\x01' + socket.inet_aton(
|
||||
dst_addr)) + struct.pack('!H', dst_port))
|
||||
await remote_writer.drain()
|
||||
|
||||
|
||||
await remote_reader.readexactly(10)
|
||||
elif proxy_type in ['http', 'https']:
|
||||
connect_request = f'CONNECT {dst_addr}:{dst_port} HTTP/1.1\r\nHost: {dst_addr}:{dst_port}\r\n'
|
||||
@ -190,7 +189,7 @@ class AsyncProxyServer:
|
||||
connect_request += '\r\n'
|
||||
remote_writer.write(connect_request.encode())
|
||||
await remote_writer.drain()
|
||||
|
||||
|
||||
while True:
|
||||
line = await remote_reader.readline()
|
||||
if line == b'\r\n':
|
||||
@ -217,7 +216,7 @@ class AsyncProxyServer:
|
||||
try:
|
||||
method, path, _ = request_line.decode('utf-8', errors='ignore').split()
|
||||
except ValueError:
|
||||
#logging.error(f"无效的请求行: {request_line}")
|
||||
# logging.error(f"无效的请求行: {request_line}")
|
||||
return
|
||||
|
||||
headers = {}
|
||||
@ -231,11 +230,12 @@ class AsyncProxyServer:
|
||||
name, value = line.decode('utf-8', errors='ignore').strip().split(': ', 1)
|
||||
headers[name.lower()] = value
|
||||
except ValueError:
|
||||
#logging.error(f"无效的请求行: {line}")
|
||||
# logging.error(f"无效的请求行: {line}")
|
||||
continue
|
||||
|
||||
if self.auth_required and not self._authenticate(headers):
|
||||
writer.write(b'HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="Proxy"\r\n\r\n')
|
||||
writer.write(
|
||||
b'HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="Proxy"\r\n\r\n')
|
||||
await writer.drain()
|
||||
return
|
||||
|
||||
@ -251,7 +251,7 @@ class AsyncProxyServer:
|
||||
def _authenticate(self, headers):
|
||||
if not self.auth_required:
|
||||
return True
|
||||
|
||||
|
||||
auth = headers.get('proxy-authorization')
|
||||
if not auth:
|
||||
return False
|
||||
@ -281,7 +281,8 @@ class AsyncProxyServer:
|
||||
proxy_port = int(proxy_port)
|
||||
|
||||
try:
|
||||
remote_reader, remote_writer = await asyncio.wait_for(asyncio.open_connection(proxy_host, proxy_port),timeout=10)
|
||||
remote_reader, remote_writer = await asyncio.wait_for(asyncio.open_connection(proxy_host, proxy_port),
|
||||
timeout=10)
|
||||
|
||||
if proxy_type == 'http':
|
||||
connect_headers = [f'CONNECT {host}:{port} HTTP/1.1', f'Host: {host}:{port}']
|
||||
@ -300,7 +301,8 @@ class AsyncProxyServer:
|
||||
remote_writer.write(b'\x05\x01\x00')
|
||||
await remote_writer.drain()
|
||||
if (await remote_reader.read(2))[1] == 0:
|
||||
remote_writer.write(b'\x05\x01\x00\x03' + len(host).to_bytes(1, 'big') + host.encode() + port.to_bytes(2, 'big'))
|
||||
remote_writer.write(
|
||||
b'\x05\x01\x00\x03' + len(host).to_bytes(1, 'big') + host.encode() + port.to_bytes(2, 'big'))
|
||||
await remote_writer.drain()
|
||||
if (await remote_reader.read(10))[1] != 0:
|
||||
raise Exception("Bad Gateway")
|
||||
@ -320,9 +322,9 @@ class AsyncProxyServer:
|
||||
await writer.drain()
|
||||
except Exception as e:
|
||||
logging.error(f"代理地址失效,切换代理地址")
|
||||
if not self.proxy_failed:
|
||||
self.proxy_failed = True
|
||||
await self.get_proxy()
|
||||
if not self.proxy_failed:
|
||||
self.proxy_failed = True
|
||||
await self.get_proxy()
|
||||
else:
|
||||
self.proxy_failed = False
|
||||
|
||||
@ -356,20 +358,20 @@ class AsyncProxyServer:
|
||||
proxy = await self.get_next_proxy()
|
||||
proxy_type, proxy_addr = proxy.split('://')
|
||||
proxy_auth, proxy_host_port = self._split_proxy_auth(proxy_addr)
|
||||
|
||||
|
||||
client_kwargs = {
|
||||
"limits": httpx.Limits(max_keepalive_connections=500, max_connections=3000),
|
||||
"timeout": 30,
|
||||
}
|
||||
|
||||
|
||||
if proxy_type in ['http', 'https']:
|
||||
client_kwargs["proxies"] = {proxy_type: f"{proxy_type}://{proxy_host_port}"}
|
||||
elif proxy_type == 'socks5':
|
||||
client_kwargs["transport"] = httpx.AsyncHTTPTransport(proxy=f"{proxy_type}://{proxy_host_port}")
|
||||
|
||||
|
||||
if proxy_auth:
|
||||
headers['Proxy-Authorization'] = f'Basic {base64.b64encode(proxy_auth.encode()).decode()}'
|
||||
|
||||
|
||||
async with httpx.AsyncClient(**client_kwargs) as client:
|
||||
try:
|
||||
async with client.stream(method, path, headers=headers, content=body) as response:
|
||||
@ -400,6 +402,7 @@ class AsyncProxyServer:
|
||||
writer.write(b'0\r\n\r\n')
|
||||
await writer.drain()
|
||||
|
||||
|
||||
def update_status(server):
|
||||
while True:
|
||||
if server.mode == 'load_balance':
|
||||
@ -410,6 +413,7 @@ def update_status(server):
|
||||
print(status, end='', flush=True)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
async def handle_client_wrapper(server, reader, writer, clients):
|
||||
task = asyncio.create_task(server.handle_client(reader, writer))
|
||||
clients.add(task)
|
||||
@ -420,11 +424,13 @@ async def handle_client_wrapper(server, reader, writer, clients):
|
||||
finally:
|
||||
clients.remove(task)
|
||||
|
||||
|
||||
async def run_server(server):
|
||||
clients = set()
|
||||
server_instance = None
|
||||
try:
|
||||
server_instance = await asyncio.start_server(lambda r, w: handle_client_wrapper(server, r, w, clients),'0.0.0.0', int(server.config['port']))
|
||||
server_instance = await asyncio.start_server(lambda r, w: handle_client_wrapper(server, r, w, clients),
|
||||
'0.0.0.0', int(server.config['port']))
|
||||
async with server_instance:
|
||||
await server_instance.serve_forever()
|
||||
except asyncio.CancelledError:
|
||||
@ -437,6 +443,7 @@ async def run_server(server):
|
||||
client.cancel()
|
||||
await asyncio.gather(*clients, return_exceptions=True)
|
||||
|
||||
|
||||
async def run_proxy_check(server):
|
||||
if server.config.get('check_proxies', 'False').lower() == 'true':
|
||||
logging.info("开始检测代理地址...")
|
||||
@ -451,20 +458,21 @@ async def run_proxy_check(server):
|
||||
else:
|
||||
logging.info("代理检测已禁用")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=logoprint.logos())
|
||||
parser.add_argument('-c', '--config', default='config.ini', help='配置文件路径')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
config = load_config(args.config)
|
||||
server = AsyncProxyServer(config)
|
||||
print_banner(config)
|
||||
asyncio.run(check_for_updates())
|
||||
asyncio.run(run_proxy_check(server))
|
||||
|
||||
|
||||
status_thread = threading.Thread(target=update_status, args=(server,), daemon=True)
|
||||
status_thread.start()
|
||||
|
||||
|
||||
try:
|
||||
asyncio.run(run_server(server))
|
||||
except KeyboardInterrupt:
|
||||
|
@ -1,12 +1,15 @@
|
||||
import requests
|
||||
from typing import List
|
||||
|
||||
def newip():
|
||||
|
||||
def newip() -> List[str]:
|
||||
"""
|
||||
自定义获取新的代理IP池
|
||||
|
||||
:return: 返回代理IP池列表,形如:['http://127.0.0.1:10809', 'socks5://127.0.0.1:10808']
|
||||
"""
|
||||
print("正在获取新的代理IP")
|
||||
url = f""
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
newip = "socks5://"+response.text.split("\r\n")[0]
|
||||
print("新的代理IP为:"+newip)
|
||||
return newip
|
||||
|
||||
proxy = ['http://127.0.0.1:10809', 'socks5://127.0.0.1:10808', 'http://127.0.0.1:1089', 'socks5://127.0.0.1:1088']
|
||||
|
||||
return proxy
|
||||
|
@ -151,7 +151,7 @@ socks5://127.0.0.1:1080
|
||||
|
||||
工具支持直接调用代理地址获取的API接口。当您配置 `use_getip = True` 时,工具将不再从本地 `ip.txt` 中读取代理地址,而是通过执行 **getip.py** 脚本来获取新的代理地址(请确保您的IP已加白名单)。
|
||||
|
||||
此时,您需要将 **getip.py** 的内容修改为您自己的接口,格式为 `IP:PORT`。默认为 `socks5` 协议,如需使用 `http`,请手动更改。
|
||||
此时,您需要将 **getip.py** 的内容修改为您自己的接口,返回代理IP池列表,例如:['http://127.0.0.1:10809', 'socks5://127.0.0.1:10808']
|
||||
|
||||
## 性能表现
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user