mirror of
https://github.com/honmashironeko/ProxyCat.git
synced 2025-05-07 19:16:33 +00:00
Update
This commit is contained in:
parent
5b1040c40a
commit
3822ca13b3
@ -7,6 +7,12 @@ def newip():
|
||||
global first_run_flag
|
||||
config = load_config()
|
||||
language = config.get('language', 'cn')
|
||||
|
||||
def handle_error(error_type, details=None):
|
||||
error_msg = 'whitelist_error' if error_type == 'whitelist' else 'proxy_file_not_found'
|
||||
print(get_message(error_msg, language, str(details)))
|
||||
raise ValueError(f"{error_type}: {details}")
|
||||
|
||||
try:
|
||||
if first_run_flag:
|
||||
appKey = ""
|
||||
@ -16,16 +22,25 @@ def newip():
|
||||
first_run_flag = False
|
||||
|
||||
url = config.get('getip_url', '')
|
||||
username = config.get('proxy_username', '')
|
||||
password = config.get('proxy_password', '')
|
||||
|
||||
if not url:
|
||||
raise ValueError('getip_url')
|
||||
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return "socks5://" + response.text.split("\r\n")[0]
|
||||
proxy = response.text.split("\r\n")[0]
|
||||
|
||||
if username and password:
|
||||
return f"socks5://{username}:{password}@{proxy}"
|
||||
return f"socks5://{proxy}"
|
||||
|
||||
except requests.RequestException as e:
|
||||
handle_error('request', e)
|
||||
except ValueError as e:
|
||||
handle_error('config', e)
|
||||
except Exception as e:
|
||||
error_msg = 'whitelist_error' if first_run_flag else 'proxy_file_not_found'
|
||||
print(get_message(error_msg, language, str(e)))
|
||||
raise
|
||||
handle_error('unknown', e)
|
||||
|
||||
|
||||
|
@ -149,11 +149,19 @@ MESSAGES = {
|
||||
}
|
||||
}
|
||||
|
||||
def get_message(key, lang='cn', *args):
|
||||
try:
|
||||
return MESSAGES[lang][key].format(*args) if args else MESSAGES[lang][key]
|
||||
except KeyError:
|
||||
return MESSAGES['cn'][key] if key in MESSAGES['cn'] else key
|
||||
class MessageManager:
|
||||
def __init__(self, messages=MESSAGES):
|
||||
self.messages = messages
|
||||
self.default_lang = 'cn'
|
||||
|
||||
def get(self, key, lang='cn', *args):
|
||||
try:
|
||||
return self.messages[lang][key].format(*args) if args else self.messages[lang][key]
|
||||
except KeyError:
|
||||
return self.messages[self.default_lang][key] if key in self.messages[self.default_lang] else key
|
||||
|
||||
message_manager = MessageManager(MESSAGES)
|
||||
get_message = message_manager.get
|
||||
|
||||
def print_banner(config):
|
||||
language = config.get('language', 'cn').lower()
|
||||
@ -275,6 +283,21 @@ def load_ip_list(file_path):
|
||||
_proxy_check_cache = {}
|
||||
_proxy_check_ttl = 10
|
||||
|
||||
def parse_proxy(proxy):
|
||||
try:
|
||||
protocol = proxy.split('://')[0]
|
||||
remaining = proxy.split('://')[1]
|
||||
|
||||
if '@' in remaining:
|
||||
auth, address = remaining.split('@')
|
||||
host, port = address.split(':')
|
||||
return protocol, auth, host, int(port)
|
||||
else:
|
||||
host, port = remaining.split(':')
|
||||
return protocol, None, host, int(port)
|
||||
except Exception:
|
||||
return None, None, None, None
|
||||
|
||||
async def check_proxy(proxy):
|
||||
current_time = time.time()
|
||||
if proxy in _proxy_check_cache:
|
||||
@ -302,27 +325,71 @@ async def check_proxy(proxy):
|
||||
return False
|
||||
|
||||
async def check_http_proxy(proxy):
|
||||
async with httpx.AsyncClient(proxies={'http://': proxy}, timeout=10) as client:
|
||||
response = await client.get('http://www.baidu.com')
|
||||
return response.status_code == 200
|
||||
protocol, auth, host, port = parse_proxy(proxy)
|
||||
proxies = {}
|
||||
if auth:
|
||||
proxies['http://'] = f'{protocol}://{auth}@{host}:{port}'
|
||||
proxies['https://'] = f'{protocol}://{auth}@{host}:{port}'
|
||||
else:
|
||||
proxies['http://'] = f'{protocol}://{host}:{port}'
|
||||
proxies['https://'] = f'{protocol}://{host}:{port}'
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(proxies=proxies, timeout=10, verify=False) as client:
|
||||
try:
|
||||
response = await client.get('https://www.baidu.com')
|
||||
return response.status_code == 200
|
||||
except:
|
||||
response = await client.get('http://www.baidu.com')
|
||||
return response.status_code == 200
|
||||
except:
|
||||
return False
|
||||
|
||||
async def check_https_proxy(proxy):
|
||||
async with httpx.AsyncClient(proxies={'https://': proxy}, timeout=10) as client:
|
||||
response = await client.get('https://www.baidu.com')
|
||||
return response.status_code == 200
|
||||
return await check_http_proxy(proxy)
|
||||
|
||||
async def check_socks_proxy(proxy):
|
||||
proxy_type, proxy_addr = proxy.split('://')
|
||||
proxy_host, proxy_port = proxy_addr.split(':')
|
||||
proxy_port = int(proxy_port)
|
||||
protocol, auth, host, port = parse_proxy(proxy)
|
||||
if not all([host, port]):
|
||||
return False
|
||||
|
||||
try:
|
||||
reader, writer = await asyncio.wait_for(asyncio.open_connection(proxy_host, proxy_port), timeout=5)
|
||||
writer.write(b'\x05\x01\x00')
|
||||
reader, writer = await asyncio.wait_for(asyncio.open_connection(host, port), timeout=5)
|
||||
|
||||
if auth:
|
||||
writer.write(b'\x05\x02\x00\x02')
|
||||
else:
|
||||
writer.write(b'\x05\x01\x00')
|
||||
|
||||
await writer.drain()
|
||||
response = await asyncio.wait_for(reader.readexactly(2), timeout=5)
|
||||
|
||||
auth_method = await asyncio.wait_for(reader.readexactly(2), timeout=5)
|
||||
if auth_method[0] != 0x05:
|
||||
return False
|
||||
|
||||
if auth_method[1] == 0x02 and auth:
|
||||
username, password = auth.split(':')
|
||||
auth_packet = bytes([0x01, len(username)]) + username.encode() + bytes([len(password)]) + password.encode()
|
||||
writer.write(auth_packet)
|
||||
await writer.drain()
|
||||
|
||||
auth_response = await asyncio.wait_for(reader.readexactly(2), timeout=5)
|
||||
if auth_response[1] != 0x00:
|
||||
return False
|
||||
|
||||
domain = b"www.baidu.com"
|
||||
writer.write(b'\x05\x01\x00\x03' + bytes([len(domain)]) + domain + b'\x00\x50')
|
||||
await writer.drain()
|
||||
|
||||
response = await asyncio.wait_for(reader.readexactly(10), timeout=5)
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
return response == b'\x05\x00'
|
||||
try:
|
||||
await writer.wait_closed()
|
||||
except:
|
||||
pass
|
||||
|
||||
return response[1] == 0x00
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
@ -10,8 +10,13 @@ def load_proxies(file_path='ip.txt'):
|
||||
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
|
||||
pattern = re.compile(r'^(?P<scheme>socks5|http|https)://(?:(?P<auth>[^@]+)@)?(?P<host>[^:]+):(?P<port>\d+)$')
|
||||
match = pattern.match(proxy)
|
||||
if not match:
|
||||
return False
|
||||
|
||||
port = int(match.group('port'))
|
||||
return 0 < port < 65536
|
||||
|
||||
class AsyncProxyServer:
|
||||
def __init__(self, config):
|
||||
@ -258,14 +263,56 @@ class AsyncProxyServer:
|
||||
await writer.drain()
|
||||
|
||||
async def _initiate_socks5(self, remote_reader, remote_writer, dst_addr, dst_port):
|
||||
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))
|
||||
await remote_writer.drain()
|
||||
|
||||
await remote_reader.readexactly(10)
|
||||
try:
|
||||
auth = None
|
||||
proxy_type, proxy_addr = self.current_proxy.split('://')
|
||||
if '@' in proxy_addr:
|
||||
auth, _ = proxy_addr.split('@')
|
||||
|
||||
if auth:
|
||||
remote_writer.write(b'\x05\x02\x00\x02')
|
||||
else:
|
||||
remote_writer.write(b'\x05\x01\x00')
|
||||
|
||||
await remote_writer.drain()
|
||||
|
||||
auth_method = await remote_reader.readexactly(2)
|
||||
if auth_method[0] != 0x05:
|
||||
raise Exception("Invalid SOCKS5 proxy response")
|
||||
|
||||
if auth_method[1] == 0x02 and auth:
|
||||
username, password = auth.split(':')
|
||||
auth_packet = bytes([0x01, len(username)]) + username.encode() + bytes([len(password)]) + password.encode()
|
||||
remote_writer.write(auth_packet)
|
||||
await remote_writer.drain()
|
||||
|
||||
auth_response = await remote_reader.readexactly(2)
|
||||
if auth_response[1] != 0x00:
|
||||
raise Exception("Authentication failed")
|
||||
|
||||
if isinstance(dst_addr, str):
|
||||
remote_writer.write(b'\x05\x01\x00\x03' + len(dst_addr).to_bytes(1, 'big') +
|
||||
dst_addr.encode() + dst_port.to_bytes(2, 'big'))
|
||||
else:
|
||||
remote_writer.write(b'\x05\x01\x00\x01' + socket.inet_aton(dst_addr) +
|
||||
dst_port.to_bytes(2, 'big'))
|
||||
|
||||
await remote_writer.drain()
|
||||
|
||||
response = await remote_reader.readexactly(4)
|
||||
if response[1] != 0x00:
|
||||
raise Exception(f"Connection failed with code {response[1]}")
|
||||
|
||||
if response[3] == 0x01:
|
||||
await remote_reader.readexactly(6)
|
||||
elif response[3] == 0x03:
|
||||
domain_len = (await remote_reader.readexactly(1))[0]
|
||||
await remote_reader.readexactly(domain_len + 2)
|
||||
elif response[3] == 0x04:
|
||||
await remote_reader.readexactly(18)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"SOCKS5 initialization failed: {str(e)}")
|
||||
|
||||
async def _initiate_http(self, remote_reader, remote_writer, dst_addr, dst_port, proxy_auth):
|
||||
connect_request = f'CONNECT {dst_addr}:{dst_port} HTTP/1.1\r\nHost: {dst_addr}:{dst_port}\r\n'
|
||||
@ -422,6 +469,77 @@ class AsyncProxyServer:
|
||||
return f"{username}:{password}", host
|
||||
return None, proxy_addr
|
||||
|
||||
async def _handle_request(self, method, path, headers, reader, writer):
|
||||
async with self.request_semaphore:
|
||||
try:
|
||||
proxy = await self.get_next_proxy()
|
||||
key = f"{proxy}:{path}"
|
||||
|
||||
proxy_headers = headers.copy()
|
||||
proxy_type, proxy_addr = proxy.split('://')
|
||||
if '@' in proxy_addr:
|
||||
auth, _ = proxy_addr.split('@')
|
||||
auth_header = f'Basic {base64.b64encode(auth.encode()).decode()}'
|
||||
proxy_headers['Proxy-Authorization'] = auth_header
|
||||
|
||||
if key in self.connection_pool:
|
||||
client = self.connection_pool[key]
|
||||
else:
|
||||
client = await self._create_client(proxy)
|
||||
self.connection_pool[key] = client
|
||||
|
||||
async with client.stream(
|
||||
method,
|
||||
path,
|
||||
headers=proxy_headers,
|
||||
content=reader,
|
||||
) as response:
|
||||
writer.write(f'HTTP/1.1 {response.status_code} {response.reason_phrase}\r\n'.encode())
|
||||
|
||||
for header_name, header_value in response.headers.items():
|
||||
if header_name.lower() != 'transfer-encoding':
|
||||
writer.write(f'{header_name}: {header_value}\r\n'.encode())
|
||||
writer.write(b'\r\n')
|
||||
|
||||
async for chunk in response.aiter_bytes(chunk_size=self.buffer_size):
|
||||
writer.write(chunk)
|
||||
if len(chunk) >= self.buffer_size:
|
||||
await writer.drain()
|
||||
|
||||
await writer.drain()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"请求处理错误: {e}")
|
||||
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||
await writer.drain()
|
||||
finally:
|
||||
await self._cleanup_connections()
|
||||
|
||||
async def _create_client(self, proxy):
|
||||
proxy_type, proxy_addr = proxy.split('://')
|
||||
proxy_auth = None
|
||||
|
||||
if '@' in proxy_addr:
|
||||
auth, proxy_addr = proxy_addr.split('@')
|
||||
proxy_auth = auth
|
||||
|
||||
if proxy_auth:
|
||||
proxy_url = f"{proxy_type}://{proxy_auth}@{proxy_addr}"
|
||||
else:
|
||||
proxy_url = f"{proxy_type}://{proxy_addr}"
|
||||
|
||||
return httpx.AsyncClient(
|
||||
proxies={"all://": proxy_url},
|
||||
limits=httpx.Limits(
|
||||
max_keepalive_connections=100,
|
||||
max_connections=1000,
|
||||
keepalive_expiry=30
|
||||
),
|
||||
timeout=30.0,
|
||||
http2=True,
|
||||
verify=False
|
||||
)
|
||||
|
||||
async def _handle_request(self, method, path, headers, reader, writer):
|
||||
async with self.request_semaphore:
|
||||
try:
|
||||
@ -625,3 +743,22 @@ class AsyncProxyServer:
|
||||
return f"{get_message('current_proxy', self.language)}: {self.current_proxy}"
|
||||
else:
|
||||
return f"{get_message('current_proxy', self.language)}: {self.current_proxy} | {get_message('next_switch', self.language)}: {time_left:.1f}{get_message('seconds', self.language)}"
|
||||
|
||||
async def _handle_proxy_error(self, error_type, details=None):
|
||||
error_messages = {
|
||||
'timeout': get_message('connect_timeout', self.language),
|
||||
'invalid': get_message('proxy_invalid', self.language, details),
|
||||
'switch': get_message('proxy_invalid_switching', self.language)
|
||||
}
|
||||
logging.error(error_messages.get(error_type, str(details)))
|
||||
if error_type in ['timeout', 'invalid']:
|
||||
await self.handle_proxy_failure()
|
||||
|
||||
async def check_proxy(self, proxy):
|
||||
cache_key = f"{proxy}_{time.time() // self.proxy_cache_ttl}"
|
||||
if cache_key in self.proxy_cache:
|
||||
return self.proxy_cache[cache_key]
|
||||
|
||||
is_valid = await self._check_proxy_impl(proxy)
|
||||
self.proxy_cache[cache_key] = is_valid
|
||||
return is_valid
|
||||
|
Loading…
x
Reference in New Issue
Block a user