mirror of
https://github.com/honmashironeko/ProxyCat.git
synced 2025-06-21 02:11:11 +00:00
2.0.4
This commit is contained in:
parent
d207a3bb8f
commit
086d37aa73
@ -1,5 +1,11 @@
|
||||
# ProxyCat 使用手册
|
||||
|
||||
## 重要事项
|
||||
|
||||
- Python版本最好为Python 3.11
|
||||
- Releases中为较为稳定的打包版本,不一定是最新
|
||||
- API 接口所获取的代理地址必须为 IP:PORT 格式且只提供一条地址
|
||||
|
||||
## 源码使用及 Docker 部署
|
||||
|
||||
### 源码手册
|
||||
@ -52,6 +58,7 @@ getip_url = 获取代理地址的 API 接口
|
||||
|
||||
```
|
||||
python ProxyCat.py
|
||||
python app.py (Web控制管理-推荐方式)
|
||||
```
|
||||
|
||||

|
||||
@ -90,6 +97,7 @@ docker logs proxycat
|
||||
# 0: 仅显示代理切换和错误信息
|
||||
# 1: 显示代理切换、倒计时和错误信息
|
||||
# 2: 显示所有详细信息
|
||||
# 仅终端管理时生效
|
||||
display_level = 1
|
||||
|
||||
# 本地服务器监听端口(默认为:1080)
|
||||
@ -99,8 +107,8 @@ port = 1080
|
||||
# Web 管理页面端口(默认为:5000)
|
||||
web_port = 5000
|
||||
|
||||
# 代理地址轮换模式:cycle 表示循环使用,custom 表示使用自定义模式,load_balance 表示负载均衡(默认为:cycle)
|
||||
# Proxy rotation mode: cycle means cyclic use, custom means custom mode, load_balance means load balancing (default:cycle)
|
||||
# 代理地址轮换模式:cycle 表示循环使用,loadbalance 表示负载均衡(默认为:cycle)
|
||||
# Proxy rotation mode: cycle means cyclic use, loadbalance means load balancing (default:cycle)
|
||||
mode = cycle
|
||||
|
||||
# 代理地址更换时间(秒),设置为 0 时每次请求都更换 IP(默认为:300)
|
||||
|
@ -1,3 +1,20 @@
|
||||
### 2025/03/23
|
||||
|
||||
- 修复负载均衡模式无法调用的BUG
|
||||
- 修复socks5连接错误
|
||||
- 修复http、socks5监听下的目标网站错误和代理地址失效情况一致导致无法正常触发代理切换
|
||||
- 修改代理有效性校验,配置为可控检测,关闭后将不会进行有效性检查避免特殊情况一直切换
|
||||
- 修复并发下导致大规模触发更换和提示的问题,锁定操作的原子性
|
||||
- 修复大量细节逻辑、描述错误
|
||||
- 当前代理切换触发条件为:时间间隔到期切换、代理失效自动切换、Web手动切换、API下首次请求自动获取
|
||||
|
||||
### 2025/03/17
|
||||
|
||||
- 修复目标站点本身错误时会触发代理切换的错误逻辑
|
||||
- 修改连接关闭方式
|
||||
- 优化监听服务器性能
|
||||
- 修复多处错误BUG
|
||||
|
||||
### 2025/03/14
|
||||
|
||||
- 修复'_last_used'报错问题,连接关闭方式修正
|
||||
|
32
ProxyCat.py
32
ProxyCat.py
@ -70,7 +70,7 @@ def update_status(server):
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
if server.mode == 'load_balance':
|
||||
if server.mode == 'loadbalance':
|
||||
if display_level >= 1:
|
||||
print_proxy_info()
|
||||
time.sleep(5)
|
||||
@ -155,7 +155,7 @@ async def run_server(server):
|
||||
async def run_proxy_check(server):
|
||||
if server.config.get('check_proxies', 'False').lower() == 'true':
|
||||
logging.info(get_message('proxy_check_start', server.language))
|
||||
valid_proxies = await check_proxies(server.proxies)
|
||||
valid_proxies = await check_proxies(server.proxies, server.test_url)
|
||||
if valid_proxies:
|
||||
server.proxies = valid_proxies
|
||||
server.proxy_cycle = cycle(valid_proxies)
|
||||
@ -187,6 +187,11 @@ class ProxyCat:
|
||||
if hasattr(socket, 'SO_KEEPALIVE'):
|
||||
socket.SO_KEEPALIVE = True
|
||||
|
||||
if hasattr(socket, 'SO_REUSEADDR'):
|
||||
socket.SO_REUSEADDR = True
|
||||
if hasattr(socket, 'SO_REUSEPORT') and os.name != 'nt':
|
||||
socket.SO_REUSEPORT = True
|
||||
|
||||
if os.name != 'nt':
|
||||
import resource
|
||||
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
@ -225,11 +230,17 @@ class ProxyCat:
|
||||
|
||||
async def start_server(self):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
if hasattr(socket, 'SO_REUSEPORT') and os.name != 'nt':
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
sock.bind((self.config.get('SERVER', 'host'), int(self.config.get('SERVER', 'port'))))
|
||||
|
||||
server = await asyncio.start_server(
|
||||
self.handle_client,
|
||||
self.config.get('SERVER', 'host'),
|
||||
self.config.get('SERVER', 'port')
|
||||
sock=sock
|
||||
)
|
||||
|
||||
logging.info(get_message('server_running', self.language,
|
||||
self.config.get('SERVER', 'host'),
|
||||
self.config.get('SERVER', 'port')))
|
||||
@ -396,6 +407,16 @@ class ProxyCat:
|
||||
except Exception as e:
|
||||
logging.error(get_message('data_transfer_error', self.language, e))
|
||||
|
||||
def monitor_resources(self):
|
||||
import psutil
|
||||
process = psutil.Process(os.getpid())
|
||||
|
||||
while self.running:
|
||||
mem_info = process.memory_info()
|
||||
logging.debug(f"Memory usage: {mem_info.rss / 1024 / 1024:.2f} MB, "
|
||||
f"Connections: {len(self.tasks)}")
|
||||
time.sleep(60)
|
||||
|
||||
if __name__ == '__main__':
|
||||
setup_logging()
|
||||
parser = argparse.ArgumentParser(description=logos())
|
||||
@ -413,6 +434,9 @@ if __name__ == '__main__':
|
||||
status_thread = threading.Thread(target=update_status, args=(server,), daemon=True)
|
||||
status_thread.start()
|
||||
|
||||
cleanup_thread = threading.Thread(target=lambda: asyncio.run(server.cleanup_clients()), daemon=True)
|
||||
cleanup_thread.start()
|
||||
|
||||
try:
|
||||
asyncio.run(run_server(server))
|
||||
except KeyboardInterrupt:
|
||||
|
103
app.py
103
app.py
@ -119,37 +119,26 @@ def get_status():
|
||||
else:
|
||||
current_proxy = get_message('no_proxy', server.language)
|
||||
|
||||
|
||||
time_left = server.time_until_next_switch()
|
||||
if server.mode == 'loadbalance':
|
||||
|
||||
if time_left == float('inf'):
|
||||
time_left = -1
|
||||
|
||||
return jsonify({
|
||||
'current_proxy': current_proxy,
|
||||
'mode': server.mode,
|
||||
'port': int(server_config.get('port', '1080')),
|
||||
'interval': server.interval,
|
||||
'time_left': server.time_until_next_switch(),
|
||||
'time_left': time_left,
|
||||
'total_proxies': len(server.proxies) if hasattr(server, 'proxies') else 0,
|
||||
'use_getip': server.use_getip,
|
||||
'getip_url': getattr(server, 'getip_url', '') if getattr(server, 'use_getip', False) else '',
|
||||
'auth_required': server.auth_required,
|
||||
'display_level': int(config.get('DEFAULT', 'display_level', fallback='1')),
|
||||
'display_level': int(server_config.get('display_level', '1')),
|
||||
'service_status': 'running' if server.running else 'stopped',
|
||||
'config': {
|
||||
'port': server_config.get('port', ''),
|
||||
'mode': server_config.get('mode', 'cycle'),
|
||||
'interval': server_config.get('interval', ''),
|
||||
'username': server_config.get('username', ''),
|
||||
'password': server_config.get('password', ''),
|
||||
'use_getip': server_config.get('use_getip', 'False'),
|
||||
'getip_url': server_config.get('getip_url', ''),
|
||||
'proxy_username': server_config.get('proxy_username', ''),
|
||||
'proxy_password': server_config.get('proxy_password', ''),
|
||||
'proxy_file': server_config.get('proxy_file', ''),
|
||||
'check_proxies': server_config.get('check_proxies', 'False'),
|
||||
'language': server_config.get('language', 'cn'),
|
||||
'whitelist_file': server_config.get('whitelist_file', ''),
|
||||
'blacklist_file': server_config.get('blacklist_file', ''),
|
||||
'ip_auth_priority': server_config.get('ip_auth_priority', 'whitelist'),
|
||||
'display_level': config.get('DEFAULT', 'display_level', fallback='1'),
|
||||
'raw_content': config_content
|
||||
}
|
||||
'config': server_config
|
||||
})
|
||||
|
||||
@app.route('/api/config', methods=['POST'])
|
||||
@ -158,6 +147,8 @@ def save_config():
|
||||
new_config = request.get_json()
|
||||
current_config = load_config('config/config.ini')
|
||||
port_changed = str(new_config.get('port', '')) != str(current_config.get('port', ''))
|
||||
mode_changed = new_config.get('mode', '') != current_config.get('mode', '')
|
||||
use_getip_changed = (new_config.get('use_getip', 'False').lower() == 'true') != (current_config.get('use_getip', 'False').lower() == 'true')
|
||||
|
||||
config_parser = ConfigParser()
|
||||
config_parser.read('config/config.ini', encoding='utf-8')
|
||||
@ -172,12 +163,27 @@ def save_config():
|
||||
with open('config/config.ini', 'w', encoding='utf-8') as f:
|
||||
config_parser.write(f)
|
||||
|
||||
|
||||
old_mode = server.mode
|
||||
old_use_getip = server.use_getip
|
||||
|
||||
|
||||
server.config = load_config('config/config.ini')
|
||||
server._init_config_values(server.config)
|
||||
|
||||
|
||||
if mode_changed or use_getip_changed:
|
||||
server._handle_mode_change()
|
||||
|
||||
|
||||
if new_config.get('mode') == 'loadbalance':
|
||||
server.last_switch_time = time.time()
|
||||
server.last_switch_attempt = 0
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'port_changed': port_changed
|
||||
'port_changed': port_changed,
|
||||
'service_status': 'running' if server.running else 'stopped'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@ -324,45 +330,18 @@ def clear_logs():
|
||||
@require_token
|
||||
def switch_proxy():
|
||||
try:
|
||||
if server.use_getip:
|
||||
from config.getip import newip
|
||||
try:
|
||||
old_proxy = server.current_proxy
|
||||
new_proxy = newip()
|
||||
server.current_proxy = new_proxy
|
||||
server.last_switch_time = time.time()
|
||||
server._log_proxy_switch(old_proxy, new_proxy)
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'current_proxy': server.current_proxy,
|
||||
'message': get_message('switch_success', server.language)
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': get_message('get_proxy_failed', server.language, str(e))
|
||||
})
|
||||
result = asyncio.run(server.switch_proxy())
|
||||
if result:
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'current_proxy': server.current_proxy,
|
||||
'message': get_message('switch_success', server.language)
|
||||
})
|
||||
else:
|
||||
if not server.proxies:
|
||||
server.proxies = server._load_file_proxies()
|
||||
if server.proxies:
|
||||
server.proxy_cycle = cycle(server.proxies)
|
||||
|
||||
if server.proxy_cycle:
|
||||
old_proxy = server.current_proxy
|
||||
server.current_proxy = next(server.proxy_cycle)
|
||||
server.last_switch_time = time.time()
|
||||
server._log_proxy_switch(old_proxy, server.current_proxy)
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'current_proxy': server.current_proxy,
|
||||
'message': get_message('switch_success', server.language)
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': get_message('no_proxies_available', server.language)
|
||||
})
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': get_message('switch_failed', server.language, 'Proxy switch not needed or in cooldown')
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
@ -523,7 +502,7 @@ def check_version():
|
||||
original_level = httpx_logger.level
|
||||
httpx_logger.setLevel(logging.WARNING)
|
||||
|
||||
CURRENT_VERSION = "ProxyCat-V2.0.2"
|
||||
CURRENT_VERSION = "ProxyCat-V2.0.4"
|
||||
|
||||
try:
|
||||
client = httpx.Client(transport=httpx.HTTPTransport(retries=3))
|
||||
@ -630,7 +609,7 @@ if __name__ == '__main__':
|
||||
web_port = int(config.get('web_port', '5000'))
|
||||
web_url = f"http://127.0.0.1:{web_port}"
|
||||
if config.get('token'):
|
||||
web_url += f"?token={config.get('token')}"
|
||||
web_url += f"/web?token={config.get('token')}"
|
||||
|
||||
logging.info(get_message('web_panel_url', server.language, web_url))
|
||||
logging.info(get_message('web_panel_notice', server.language))
|
||||
|
@ -4,12 +4,12 @@ port = 1080
|
||||
web_port = 5000
|
||||
mode = cycle
|
||||
interval = 300
|
||||
use_getip = False
|
||||
use_getip = false
|
||||
getip_url = http://example.com/getip
|
||||
proxy_username =
|
||||
proxy_password =
|
||||
proxy_file = ip.txt
|
||||
check_proxies = True
|
||||
check_proxies = true
|
||||
test_url =
|
||||
language = cn
|
||||
whitelist_file = whitelist.txt
|
||||
@ -19,4 +19,5 @@ token = honmashironeko
|
||||
|
||||
[Users]
|
||||
neko = 123456
|
||||
k = 123
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
socks5://127.0.0.1:7890
|
||||
http://127.0.0.1:7890
|
||||
http://127.0.0.1:7890
|
||||
socks5://127.0.0.1:7890
|
@ -28,6 +28,9 @@ MESSAGES = {
|
||||
'proxy_switch': '切换代理: {} -> {}',
|
||||
'proxy_consecutive_fails': '代理 {} 连续失败 {} 次,正在切换新代理',
|
||||
'proxy_invalid': '代理 {} 无效,立即切换代理',
|
||||
'proxy_failure': '代理检测失败: {}',
|
||||
'proxy_failure_threshold': '代理无效,开始切换',
|
||||
'proxy_check_error': '代理检查时发生错误: {}',
|
||||
'connection_timeout': '连接超时',
|
||||
'data_transfer_timeout': '数据传输超时,正在重试...',
|
||||
'connection_reset': '连接被重置',
|
||||
@ -49,7 +52,7 @@ MESSAGES = {
|
||||
'blog': '博客',
|
||||
'proxy_mode': '代理轮换模式',
|
||||
'cycle': '循环',
|
||||
'load_balance': '负载均衡',
|
||||
'loadbalance': '负载均衡',
|
||||
'single_round': '单轮',
|
||||
'proxy_interval': '代理更换时间',
|
||||
'default_auth': '默认账号密码',
|
||||
@ -105,7 +108,7 @@ MESSAGES = {
|
||||
'proxy_check_result': '代理检查完成,有效代理:{}个',
|
||||
'no_proxy': '无代理',
|
||||
'cycle_mode': '循环模式',
|
||||
'load_balance_mode': '负载均衡模式',
|
||||
'loadbalance_mode': '负载均衡模式',
|
||||
'proxy_check_start': '开始检查代理...',
|
||||
'proxy_check_complete': '代理检查完成',
|
||||
'proxy_save_success': '代理保存成功',
|
||||
@ -181,6 +184,11 @@ MESSAGES = {
|
||||
'web_panel_url': '网页控制面板地址: {}',
|
||||
'web_panel_notice': '请使用浏览器访问上述地址来管理代理服务器',
|
||||
'api_proxy_settings_title': 'API代理设置',
|
||||
'all_retries_failed': '所有重试均已失败,最后错误: {}',
|
||||
'proxy_get_failed': '获取代理失败',
|
||||
'proxy_get_error': '获取代理错误: {}',
|
||||
'request_error': '请求错误: {}',
|
||||
'proxy_switch_error': '代理切换错误: {}',
|
||||
},
|
||||
'en': {
|
||||
'getting_new_proxy': 'Getting new proxy IP',
|
||||
@ -193,6 +201,9 @@ MESSAGES = {
|
||||
'proxy_switch': 'Switch proxy: {} -> {}',
|
||||
'proxy_consecutive_fails': 'Proxy {} failed {} times consecutively, switching to new proxy',
|
||||
'proxy_invalid': 'Proxy {} is invalid, switching proxy immediately',
|
||||
'proxy_failure': 'Proxy check failed: {}',
|
||||
'proxy_failure_threshold': 'Proxy invalid, switching now',
|
||||
'proxy_check_error': 'Error occurred during proxy check: {}',
|
||||
'connection_timeout': 'Connection timeout',
|
||||
'data_transfer_timeout': 'Data transfer timeout, retrying...',
|
||||
'connection_reset': 'Connection reset',
|
||||
@ -214,7 +225,7 @@ MESSAGES = {
|
||||
'blog': 'Blog',
|
||||
'proxy_mode': 'Proxy Rotation Mode',
|
||||
'cycle': 'Cycle',
|
||||
'load_balance': 'Load Balance',
|
||||
'loadbalance': 'Load Balance',
|
||||
'single_round': 'Single Round',
|
||||
'proxy_interval': 'Proxy Change Interval',
|
||||
'default_auth': 'Default Username and Password',
|
||||
@ -248,37 +259,40 @@ MESSAGES = {
|
||||
'invalid_proxy': 'Current proxy is invalid: {}',
|
||||
'whitelist_error': 'Failed to add whitelist: {}',
|
||||
'api_mode_notice': 'Currently in API mode, proxy address will be automatically obtained upon request',
|
||||
'all_retries_failed': 'All retries failed, last error: {}',
|
||||
'proxy_get_failed': 'Failed to get proxy',
|
||||
'proxy_get_error': 'Error getting proxy: {}',
|
||||
'proxy_switch_error': 'Error switching proxy: {}',
|
||||
'server_running': 'Proxy server running at {}:{}',
|
||||
'server_start_error': 'Server startup error: {}',
|
||||
'server_shutting_down': 'Shutting down server...',
|
||||
'client_process_error': 'Error processing client request: {}',
|
||||
'server_shutting_down': 'Server shutting down...',
|
||||
'client_process_error': 'Client processing error: {}',
|
||||
'request_handling_error': 'Request handling error: {}',
|
||||
'proxy_forward_error': 'Proxy forwarding error: {}',
|
||||
'data_transfer_timeout': '{} data transfer timeout',
|
||||
'data_transfer_error': '{} data transfer error: {}',
|
||||
'data_transfer_timeout': '{}data transfer timeout',
|
||||
'status_update_error': 'Status update error',
|
||||
'display_level_notice': 'Current display level: {}',
|
||||
'display_level_desc': '''Display level description:
|
||||
0: Show only proxy switches and errors
|
||||
1: Show proxy switches, countdown and errors
|
||||
0: Only show proxy switch and error messages
|
||||
1: Show proxy switch, countdown and error messages
|
||||
2: Show all detailed information''',
|
||||
'new_client_connect': 'New client connection - IP: {}, User: {}',
|
||||
'no_auth': 'No authentication',
|
||||
'connection_error': 'Connection handling error: {}',
|
||||
'cleanup_error': 'IP cleanup error: {}',
|
||||
'port_changed': 'Port changed: {} -> {}, server restart required',
|
||||
'cleanup_error': 'Cleanup IP error: {}',
|
||||
'port_changed': 'Port changed: {} -> {}, server restart required to take effect',
|
||||
'config_updated': 'Server configuration updated',
|
||||
'load_proxy_file_error': 'Failed to load proxy file: {}',
|
||||
'proxy_check_result': 'Proxy check completed, valid proxies: {}',
|
||||
'no_proxy': 'No proxy',
|
||||
'cycle_mode': 'Cycle Mode',
|
||||
'load_balance_mode': 'Load Balance Mode',
|
||||
'cycle_mode': 'Cycle mode',
|
||||
'loadbalance_mode': 'Load balance mode',
|
||||
'proxy_check_start': 'Starting proxy check...',
|
||||
'proxy_check_complete': 'Proxy check completed',
|
||||
'proxy_save_success': 'Proxies saved successfully',
|
||||
'proxy_save_failed': 'Failed to save proxies: {}',
|
||||
'ip_list_save_success': 'IP lists saved successfully',
|
||||
'ip_list_save_failed': 'Failed to save IP lists: {}',
|
||||
'proxy_save_success': 'Proxy saved successfully',
|
||||
'proxy_save_failed': 'Failed to save proxy: {}',
|
||||
'ip_list_save_success': 'IP list saved successfully',
|
||||
'ip_list_save_failed': 'Failed to save IP list: {}',
|
||||
'switch_success': 'Proxy switched successfully',
|
||||
'switch_failed': 'Failed to switch proxy: {}',
|
||||
'service_start_success': 'Service started successfully',
|
||||
@ -305,20 +319,20 @@ MESSAGES = {
|
||||
'language_label': 'Language',
|
||||
'chinese': 'Chinese',
|
||||
'english': 'English',
|
||||
'manual_switch_btn': 'Manual Switch',
|
||||
'manual_switch_btn': 'Switch Manually',
|
||||
'service_control_title': 'Service Control',
|
||||
'language_switch_success': '',
|
||||
'language_switch_failed': '',
|
||||
'language_switch_success': 'Language switched successfully',
|
||||
'language_switch_failed': 'Failed to switch language',
|
||||
'refresh_failed': 'Failed to refresh data: {}',
|
||||
'auth_username_label': 'Auth Username',
|
||||
'auth_password_label': 'Auth Password',
|
||||
'proxy_auth_username_label': 'Proxy Auth Username',
|
||||
'proxy_auth_password_label': 'Proxy Auth Password',
|
||||
'auth_username_label': 'Authentication Username',
|
||||
'auth_password_label': 'Authentication Password',
|
||||
'proxy_auth_username_label': 'Proxy Authentication Username',
|
||||
'proxy_auth_password_label': 'Proxy Authentication Password',
|
||||
'progress_bar_label': 'Switch Progress',
|
||||
'proxy_settings_title': 'Proxy Settings',
|
||||
'config_save_success': 'Configuration saved successfully',
|
||||
'config_save_failed': 'Failed to save configuration: {}',
|
||||
'config_restart_required': 'Configuration changed, server restart required',
|
||||
'config_restart_required': 'Configuration changed, server restart required to take effect',
|
||||
'confirm_restart_service': 'Restart server now?',
|
||||
'service_status': 'Service Status',
|
||||
'running': 'Running',
|
||||
@ -329,10 +343,10 @@ MESSAGES = {
|
||||
'service_stop_failed': 'Failed to stop service: {}',
|
||||
'service_restart_failed': 'Failed to restart service: {}',
|
||||
'invalid_token': 'Invalid access token',
|
||||
'config_file_changed': 'Configuration file change detected, reloading...',
|
||||
'config_file_changed': 'Config file change detected, reloading...',
|
||||
'proxy_file_changed': 'Proxy file changed, reloading...',
|
||||
'test_target_label': 'Test Target URL',
|
||||
'invalid_test_target': 'Invalid test target URL',
|
||||
'test_target_label': 'Test Target Address',
|
||||
'invalid_test_target': 'Invalid test target address',
|
||||
'users_save_success': 'Users saved successfully',
|
||||
'users_save_failed': 'Failed to save users: {}',
|
||||
'user_management_title': 'User Management',
|
||||
@ -340,13 +354,13 @@ MESSAGES = {
|
||||
'password_column': 'Password',
|
||||
'actions_column': 'Actions',
|
||||
'add_user_btn': 'Add User',
|
||||
'enter_username': 'Enter username',
|
||||
'enter_password': 'Enter password',
|
||||
'enter_username': 'Please enter username',
|
||||
'enter_password': 'Please enter password',
|
||||
'confirm_delete_user': 'Are you sure you want to delete this user?',
|
||||
'no_logs_found': 'No matching logs found',
|
||||
'clear_search': 'Clear Search',
|
||||
'web_panel_url': 'Web control panel URL: {}',
|
||||
'web_panel_notice': 'Please use a browser to visit the above URL to manage the proxy server',
|
||||
'web_panel_url': 'Web panel URL: {}',
|
||||
'web_panel_notice': 'Please use a browser to access the above URL to manage the proxy server',
|
||||
'api_proxy_settings_title': 'API Proxy Settings',
|
||||
}
|
||||
}
|
||||
@ -376,7 +390,7 @@ def print_banner(config):
|
||||
banner_info = [
|
||||
(get_message('public_account', language), '樱花庄的本间白猫'),
|
||||
(get_message('blog', language), 'https://y.shironekosan.cn'),
|
||||
(get_message('proxy_mode', language), get_message('cycle', language) if config.get('mode') == 'cycle' else get_message('load_balance', language) if config.get('mode') == 'load_balance' else get_message('single_round', language)),
|
||||
(get_message('proxy_mode', language), get_message('cycle', language) if config.get('mode') == 'cycle' else get_message('loadbalance', language) if config.get('mode') == 'loadbalance' else get_message('single_round', language)),
|
||||
(get_message('proxy_interval', language), f"{config.get('interval')}{get_message('seconds', language)}"),
|
||||
(get_message('default_auth', language), auth_info),
|
||||
(get_message('local_http', language), http_addr),
|
||||
@ -542,10 +556,9 @@ async def check_http_proxy(proxy, test_url=None):
|
||||
except:
|
||||
return False
|
||||
|
||||
async def check_https_proxy(proxy, test_url=None):
|
||||
return await check_http_proxy(proxy, test_url)
|
||||
|
||||
async def check_socks_proxy(proxy, test_url=None):
|
||||
if test_url is None:
|
||||
test_url = 'https://www.baidu.com'
|
||||
protocol, auth, host, port = parse_proxy(proxy)
|
||||
if not all([host, port]):
|
||||
return False
|
||||
@ -605,7 +618,7 @@ async def check_proxy(proxy, test_url=None):
|
||||
proxy_type = proxy.split('://')[0]
|
||||
check_funcs = {
|
||||
'http': check_http_proxy,
|
||||
'https': check_https_proxy,
|
||||
'https': check_http_proxy,
|
||||
'socks5': check_socks_proxy
|
||||
}
|
||||
|
||||
@ -637,7 +650,7 @@ async def check_for_updates(language='cn'):
|
||||
match = re.search(r'<p>(ProxyCat-V\d+\.\d+\.\d+)</p>', content)
|
||||
if match:
|
||||
latest_version = match.group(1)
|
||||
CURRENT_VERSION = "ProxyCat-V2.0.2"
|
||||
CURRENT_VERSION = "ProxyCat-V2.0.4"
|
||||
if version.parse(latest_version.split('-V')[1]) > version.parse(CURRENT_VERSION.split('-V')[1]):
|
||||
print(f"{Fore.YELLOW}{get_message('new_version_found', language)} 当前版本: {CURRENT_VERSION}, 最新版本: {latest_version}{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{get_message('visit_quark', language)}{Style.RESET_ALL}")
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,4 +5,6 @@ packaging==24.1
|
||||
Requests==2.32.3
|
||||
tqdm>=4.65.0
|
||||
flask>=2.0.1
|
||||
werkzeug>=2.0.0
|
||||
werkzeug>=2.0.0
|
||||
asyncio>=3.4.3
|
||||
configparser>=5.0.0
|
7
web/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
web/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
web/static/js/jquery.min.js
vendored
Normal file
2
web/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1329,7 +1329,7 @@
|
||||
<label class="form-label" data-i18n="run_mode_label">运行模式</label>
|
||||
<select class="form-select" name="mode">
|
||||
<option value="cycle" data-i18n="cycle_mode">循环模式</option>
|
||||
<option value="load_balance" data-i18n="load_balance_mode">负载均衡</option>
|
||||
<option value="loadbalance" data-i18n="loadbalance_mode">负载均衡</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@ -1375,7 +1375,7 @@
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="check_proxies" id="check-proxies">
|
||||
<label class="form-check-label" for="check-proxies" data-i18n="enable_proxy_check_label">启用代理检测</label>
|
||||
<label class="form-check-label" for="check-proxies" data-i18n="enable_proxy_check_label">代理有效性检测</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -1590,8 +1590,8 @@
|
||||
|
||||
<div class="notification-container" id="notification-container"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
||||
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script>
|
||||
let lastLogId = 0;
|
||||
let searchTimeout = null;
|
||||
@ -1630,7 +1630,13 @@
|
||||
$.get(appendToken('/api/status'), function(data) {
|
||||
// 只在值发生变化时更新显示
|
||||
const currentProxy = $('#current-proxy').text();
|
||||
const newProxy = data.current_proxy || translations[currentLanguage].no_proxy;
|
||||
let newProxy = data.current_proxy || translations[currentLanguage].no_proxy;
|
||||
|
||||
// 负载均衡模式特殊处理
|
||||
if (data.mode === 'loadbalance') {
|
||||
newProxy = translations[currentLanguage].loadbalance_mode || '负载均衡模式';
|
||||
}
|
||||
|
||||
if (currentProxy !== newProxy) {
|
||||
$('#current-proxy').text(newProxy);
|
||||
}
|
||||
@ -1651,7 +1657,11 @@
|
||||
}
|
||||
|
||||
// 更新进度条和其他状态...
|
||||
if (data.interval && data.time_left !== undefined) {
|
||||
if (data.mode === 'loadbalance') {
|
||||
// 负载均衡模式不显示倒计时
|
||||
$('#next-switch').text(translations[currentLanguage].loadbalance_mode || '负载均衡模式');
|
||||
$('.progress-bar').css('width', '100%');
|
||||
} else if (data.interval && data.time_left !== undefined) {
|
||||
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
||||
$('#next-switch').text(`${timeLeft} ${translations[currentLanguage].seconds}`);
|
||||
|
||||
@ -1673,12 +1683,19 @@
|
||||
|
||||
function updateCountdown() {
|
||||
$.get(appendToken('/api/status'), function(data) {
|
||||
if (data.time_left !== undefined && data.interval) {
|
||||
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
||||
const progress = Math.min(100, ((data.interval - data.time_left) / data.interval) * 100);
|
||||
|
||||
$('.progress-bar').css('width', progress + '%');
|
||||
$('#next-switch').text(timeLeft + ' ' + translations[currentLanguage]['seconds']);
|
||||
if (data.time_left !== undefined) {
|
||||
// 负载均衡模式特殊处理
|
||||
if (data.time_left === -1 || data.mode === 'loadbalance') {
|
||||
// 负载均衡模式不显示倒计时
|
||||
$('.progress-bar').css('width', '100%');
|
||||
$('#next-switch').text(translations[currentLanguage]['loadbalance_mode'] || '负载均衡模式');
|
||||
} else if (data.interval) {
|
||||
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
||||
const progress = Math.min(100, ((data.interval - data.time_left) / data.interval) * 100);
|
||||
|
||||
$('.progress-bar').css('width', progress + '%');
|
||||
$('#next-switch').text(timeLeft + ' ' + translations[currentLanguage]['seconds']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1688,31 +1705,34 @@
|
||||
if (data.config) {
|
||||
const config = data.config;
|
||||
|
||||
// 更新表单值
|
||||
$('input[name="port"]').val(config.port);
|
||||
$('select[name="mode"]').val(config.mode);
|
||||
$('input[name="interval"]').val(config.interval);
|
||||
$('input[name="username"]').val(config.username);
|
||||
$('input[name="password"]').val(config.password);
|
||||
// 更新表单值 - 始终使用配置文件中的值,避免空值
|
||||
$('input[name="port"]').val(config.port || '1080');
|
||||
$('select[name="mode"]').val(config.mode || 'cycle');
|
||||
$('input[name="interval"]').val(config.interval || '300');
|
||||
$('input[name="username"]').val(config.username || '');
|
||||
$('input[name="password"]').val(config.password || '');
|
||||
// 修正checkbox的状态设置
|
||||
$('input[name="use_getip"]').prop('checked', config.use_getip.toLowerCase() === 'true');
|
||||
$('input[name="getip_url"]').val(config.getip_url);
|
||||
$('input[name="proxy_username"]').val(config.proxy_username);
|
||||
$('input[name="proxy_password"]').val(config.proxy_password);
|
||||
$('input[name="proxy_file"]').val(config.proxy_file);
|
||||
$('input[name="check_proxies"]').prop('checked', config.check_proxies.toLowerCase() === 'true');
|
||||
$('select[name="language"]').val(config.language);
|
||||
$('input[name="whitelist_file"]').val(config.whitelist_file);
|
||||
$('input[name="blacklist_file"]').val(config.blacklist_file);
|
||||
$('select[name="ip_auth_priority"]').val(config.ip_auth_priority);
|
||||
$('select[name="display_level"]').val(config.display_level);
|
||||
$('input[name="use_getip"]').prop('checked', (config.use_getip || '').toLowerCase() === 'true');
|
||||
$('input[name="getip_url"]').val(config.getip_url || '');
|
||||
$('input[name="proxy_username"]').val(config.proxy_username || '');
|
||||
$('input[name="proxy_password"]').val(config.proxy_password || '');
|
||||
$('input[name="proxy_file"]').val(config.proxy_file || 'ip.txt');
|
||||
$('input[name="check_proxies"]').prop('checked', (config.check_proxies || '').toLowerCase() === 'true');
|
||||
$('select[name="language"]').val(config.language || 'cn');
|
||||
$('input[name="whitelist_file"]').val(config.whitelist_file || '');
|
||||
$('input[name="blacklist_file"]').val(config.blacklist_file || '');
|
||||
$('select[name="ip_auth_priority"]').val(config.ip_auth_priority || 'whitelist');
|
||||
$('select[name="display_level"]').val(config.display_level || '1');
|
||||
|
||||
// 根据 use_getip 的状态更新 getip_url 输入框的禁用状态
|
||||
const useGetip = config.use_getip.toLowerCase() === 'true';
|
||||
const useGetip = (config.use_getip || '').toLowerCase() === 'true';
|
||||
$('input[name="getip_url"]').prop('disabled', !useGetip);
|
||||
|
||||
// 更新其他相关UI元素
|
||||
updateUIState();
|
||||
|
||||
// 更新地址显示
|
||||
updateAddresses(config);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -2146,7 +2166,7 @@
|
||||
'switch_interval': '切换间隔(秒)',
|
||||
'run_mode_text': '运行模式',
|
||||
'cycle_mode': '循环模式',
|
||||
'load_balance_mode': '负载均衡',
|
||||
'loadbalance_mode': '负载均衡',
|
||||
'language_text': '语言',
|
||||
'auth_config': '认证配置',
|
||||
'username': '用户名',
|
||||
@ -2196,11 +2216,11 @@
|
||||
'switch_interval_label': '切换间隔(秒)',
|
||||
'run_mode_label': '运行模式',
|
||||
'cycle_mode': '循环模式',
|
||||
'load_balance_mode': '负载均衡',
|
||||
'loadbalance_mode': '负载均衡',
|
||||
'auth_config_title': '认证配置',
|
||||
'use_api_label': '使用API获取代理',
|
||||
'api_url_label': 'API地址',
|
||||
'enable_proxy_check_label': '启用代理检测',
|
||||
'enable_proxy_check_label': '代理有效性检测',
|
||||
'proxy_list_label': '代理列表',
|
||||
'save_proxy_btn': '保存代理',
|
||||
'check_proxy_btn': '检测代理',
|
||||
@ -2301,11 +2321,11 @@
|
||||
'loading': '加载中...',
|
||||
'manual_switch_btn': '手动切换',
|
||||
'cycle_mode': '循环模式',
|
||||
'load_balance_mode': '负载均衡',
|
||||
'loadbalance_mode': '负载均衡',
|
||||
'next_switch': '下次切换',
|
||||
'proxy_mode': {
|
||||
'cycle': '循环模式',
|
||||
'load_balance': '负载均衡'
|
||||
'loadbalance': '负载均衡'
|
||||
},
|
||||
'service_action': {
|
||||
'start_success': '服务启动成功',
|
||||
@ -2373,7 +2393,7 @@
|
||||
'switch_interval': 'Switch Interval(s)',
|
||||
'run_mode_text': 'Run Mode',
|
||||
'cycle_mode': 'Cycle Mode',
|
||||
'load_balance_mode': 'Load Balance',
|
||||
'loadbalance_mode': 'Load Balance',
|
||||
'language_text': 'Language',
|
||||
'auth_config': 'Auth Config',
|
||||
'username': 'Username',
|
||||
@ -2424,11 +2444,11 @@
|
||||
'switch_interval_label': 'Switch Interval(s)',
|
||||
'run_mode_label': 'Run Mode',
|
||||
'cycle_mode': 'Cycle Mode',
|
||||
'load_balance_mode': 'Load Balance',
|
||||
'loadbalance_mode': 'Load Balance',
|
||||
'auth_config_title': 'Authentication',
|
||||
'use_api_label': 'Use API for Proxy',
|
||||
'api_url_label': 'API URL',
|
||||
'enable_proxy_check_label': 'Enable Proxy Check',
|
||||
'enable_proxy_check_label': 'Proxy Validity Check',
|
||||
'proxy_list_label': 'Proxy List',
|
||||
'save_proxy_btn': 'Save Proxies',
|
||||
'check_proxy_btn': 'Check Proxies',
|
||||
@ -3141,4 +3161,4 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
Loading…
x
Reference in New Issue
Block a user