ASSISTANT
Хорошо, давайте удалим поддержку `ipwho.is` из скрипта и посмотрим, как улучшить код. Также обсудим, какую библиотеку использует меню в вашем приложении.
### Обновленный код без ipwho.is:
```python
import sys
import os
import json
import requests
import re
import pycountry
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QCheckBox,
QListWidget, QVBoxLayout, QHBoxLayout, QSystemTrayIcon, QMenu, QDialog,
QSpacerItem, QSizePolicy, QWidget, QFileDialog, QInputDialog, QButtonGroup, QRadioButton
)
from PyQt6.QtGui import QIcon, QAction, QDesktopServices, QCursor, QPixmap
from PyQt6.QtCore import Qt, QUrl, QTimer, pyqtSignal
from cryptography.fernet import Fernet
PROXY_FILE = 'D:\\ip_info\\settings\\proxies.json'
SETTINGS_FILE = 'D:\\ip_info\\settings\\settings.json'
ICON_PATH = 'D:\\ip_info\\icons\\app_ip.ico'
CRYPT_DIR = 'D:\\ip_info\\settings\\cryptography'
CRYPT_KEY_FILE = os.path.join(CRYPT_DIR, 'key.key')
ERROR_ICON_PATH = 'D:\\ip_info\\icons\\error_ip.png'
LOGO_PATH = 'D:\\ip_info\\icons\\logo.jpg'
BUTTON_STYLE = """
QPushButton {
border: none;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #45a049;
}
"""
BUTTON_STYLE_DELETE = """
QPushButton {
border: none;
background-color: #f44336;
color: white;
padding: 10px 20px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #da190b;
}
"""
CHECKBOX_STYLE = """
QCheckBox {
color: white;
}
QCheckBox:disabled {
color: #7f7f7f;
}
"""
def get_ip_and_isp(proxy_data=None, use_proxy=False, service_url='https://ipinfo.io/json'):
try:
proxies = None
if use_proxy and proxy_data:
protocol = proxy_data.get('protocol', 'socks5')
server_port = proxy_data['server_port']
username = proxy_data.get('username')
password = proxy_data.get('password')
proxy_url = f"{protocol}://{server_port}"
if username and password:
proxy_url = f"{protocol}://{username}:{password}@{server_port}"
proxies = {
'http': proxy_url,
'https': proxy_url,
}
response = requests.get(service_url, proxies=proxies, timeout=10)
response.raise_for_status() # Raise an error for bad responses
data = response.json()
if 'ipinfo.io' in service_url:
ip = data.get('ip', 'N/A')
org = data.get('org', 'N/A')
city = data.get('city', 'N/A')
country = data.get('country', 'N/A')
if org.startswith('AS'):
isp = ' '.join(org.split(' ')[1:])
else:
isp = org
elif 'ipapi.co' in service_url:
ip = data.get('ip', 'N/A')
isp = data.get('org', 'N/A')
city = data.get('city', 'N/A')
country = data.get('country_name', 'N/A')
if isp.startswith('AS'):
isp = ' '.join(isp.split(' ')[1:])
elif 'ip-api.com' in service_url:
if data.get('status') != 'success':
raise ValueError('API response status not success')
ip = data.get('query', 'N/A')
isp = data.get('isp', 'N/A')
city = data.get('city', 'N/A')
country = data.get('country', 'N/A')
else:
ip = 'N/A'
isp = 'N/A'
city = 'N/A'
country = 'N/A'
legal_forms = ['PJSC', 'LLC', 'JSC', 'OJSC', 'АО', 'ОАО', 'ЗАО', 'ООО', 'ПАО']
pattern = r'\b(' + '|'.join(legal_forms) + r')\b'
isp = re.sub(pattern, '', isp, flags=re.IGNORECASE).strip()
isp = isp.replace('"', '').replace('«', '').replace('»', '').strip()
isp = ' '.join(isp.split())
return ip, isp, city, country
except Exception as e:
print(f"Error in get_ip_and_isp: {e}")
return 'Ошибка', 'Ошибка', 'Ошибка', 'Ошибка'
class AboutDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('О программе')
self.setFixedSize(400, 200)
self.setWindowIcon(parent.icon if parent and hasattr(parent, 'icon') else QIcon())
layout = QHBoxLayout()
# Левая часть с логотипом
logo_layout = QVBoxLayout()
logo_label = QLabel()
if os.path.exists(LOGO_PATH):
logo_pixmap = QPixmap(LOGO_PATH).scaledToWidth(200, Qt.TransformationMode.SmoothTransformation) # Adjust dimensions as necessary
logo_label.setPixmap(logo_pixmap)
logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
logo_layout.addWidget(logo_label)
# Правая часть с информацией
info_layout = QVBoxLayout()
title_label = QLabel("JustWoofIP")
title_font = title_label.font()
title_font.setPointSize(14)
title_font.setBold(True)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
info_layout.addWidget(title_label)
version_label = QLabel("Версия: 1.0.0-20241101")
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
info_layout.addWidget(version_label)
site_label = QLabel()
site_label.setText(
'Сайт: <a href="https://chat.mistral.ai" style="color: #1E90FF; font-weight: bold; text-decoration: none;">https://chat.mistral.ai</a>'
)
site_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
site_label.setOpenExternalLinks(False)
site_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
site_label.linkActivated.connect(self.open_link)
site_label.setStyleSheet("""
QLabel {
color: white;
background-color: #1e1e1e;
}
a:hover {
text-decoration: underline;
}
""")
info_layout.addWidget(site_label)
close_button = QPushButton("Закрыть")
close_button.clicked.connect(self.close)
close_button.setStyleSheet(BUTTON_STYLE)
close_button.setFixedWidth(100)
close_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
info_layout.addWidget(close_button, alignment=Qt.AlignmentFlag.AlignCenter)
layout.addLayout(logo_layout)
layout.addLayout(info_layout)
self.setLayout(layout)
self.setStyleSheet("background-color: #1e1e1e; color: white;")
def open_link(self, link):
QDesktopServices.openUrl(QUrl(link))
self.close()
class ClickableLabel(QLabel):
clicked = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.clicked.emit()
super().mousePressEvent(event)
class ProxyListWidget(QListWidget):
deleteRequested = pyqtSignal()
editRequested = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_context_menu)
def keyPressEvent(self, event):
if event.key() == Qt.Key.Key_Delete:
self.deleteRequested.emit()
else:
super().keyPressEvent(event)
def open_context_menu(self, position):
context_menu = QMenu(self)
edit_action = QAction("Редактировать прокси", self)
edit_action.triggered.connect(self.editRequested.emit)
context_menu.addAction(edit_action)
context_menu.exec(self.viewport().mapToGlobal(position))
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.minimize_to_tray = False
self.current_service_url = None
self.deleted_proxies = [] # To store the last deleted proxy
self.load_or_generate_key()
self.initUI()
def load_or_generate_key(self):
if not os.path.exists(CRYPT_KEY_FILE):
key = Fernet.generate_key()
os.makedirs(os.path.dirname(CRYPT_KEY_FILE), exist_ok=True)
with open(CRYPT_KEY_FILE, 'wb') as key_file:
key_file.write(key)
else:
with open(CRYPT_KEY_FILE, 'rb') as key_file:
key = key_file.read()
self.fernet = Fernet(key)
def initUI(self):
self.setWindowTitle('JustWoofIP')
self.setStyleSheet("background-color: #1e1e1e; color: white;")
if os.path.exists(ICON_PATH):
self.icon = QIcon(ICON_PATH)
self.setWindowIcon(self.icon)
else:
self.icon = QIcon()
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout()
central_widget.setLayout(layout)
self.create_menu()
hbox_ip = QHBoxLayout()
ip_label = QLabel("Мой IP:")
self.ip_text = QLineEdit()
self.ip_text.setReadOnly(True)
self.ip_text.setStyleSheet("background-color: #323232; color: white;")
hbox_ip.addWidget(ip_label)
hbox_ip.addWidget(self.ip_text)
self.copy_ip_button = QPushButton("Копировать")
self.copy_ip_button.clicked.connect(self.copy_ip)
self.copy_ip_button.setStyleSheet(BUTTON_STYLE)
self.copy_ip_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
hbox_ip.addWidget(self.copy_ip_button)
layout.addLayout(hbox_ip)
hbox_isp = QHBoxLayout()
isp_label = QLabel("Мой ISP:")
self.isp_text = QLineEdit()
self.isp_text.setReadOnly(True)
self.isp_text.setStyleSheet("background-color: #323232; color: white;")
hbox_isp.addWidget(isp_label)
hbox_isp.addWidget(self.isp_text)
city_label = QLabel("Город:")
self.city_text = QLineEdit()
self.city_text.setReadOnly(True)
self.city_text.setStyleSheet("background-color: #323232; color: white;")
hbox_isp.addWidget(city_label)
hbox_isp.addWidget(self.city_text)
country_label = QLabel("Страна:")
self.country_text = QLineEdit()
self.country_text.setReadOnly(True)
self.country_text.setStyleSheet("background-color: #323232; color: white;")
hbox_isp.addWidget(country_label)
hbox_isp.addWidget(self.country_text)
self.flag_label = ClickableLabel()
self.flag_label.setFixedSize(32, 24)
self.flag_label.clicked.connect(self.open_service_url)
hbox_isp.addWidget(self.flag_label)
layout.addLayout(hbox_isp)
hbox_use_proxy = QHBoxLayout()
self.use_proxy_checkbox = QCheckBox("Использовать прокси")
self.use_proxy_checkbox.setChecked(False)
self.use_proxy_checkbox.setStyleSheet(CHECKBOX_STYLE)
hbox_use_proxy.addWidget(self.use_proxy_checkbox)
self.notification_label = QLabel("", self)
self.notification_label.setStyleSheet("""
QLabel {
background-color: rgba(50, 50, 50, 200);
color: white;
padding: 5px 10px;
border-radius: 5px;
font-weight: bold;
}
""")
self.notification_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.notification_label.hide()
hbox_use_proxy.addStretch()
hbox_use_proxy.addWidget(self.notification_label)
layout.addLayout(hbox_use_proxy)
hbox_proxy_port = QHBoxLayout()
proxy_label = QLabel("Прокси:")
self.proxy_input = QLineEdit()
self.proxy_input.setPlaceholderText("protocol://proxy")
self.proxy_input.setStyleSheet("background-color: #323232; color: white;")
self.proxy_input.returnPressed.connect(self.on_add_proxy)
port_label = QLabel("Порт:")
self.port_input = QLineEdit()
self.port_input.setPlaceholderText("Port")
self.port_input.setStyleSheet("background-color: #323232; color: white;")
self.port_input.returnPressed.connect(self.on_add_proxy)
hbox_proxy_port.addWidget(proxy_label)
hbox_proxy_port.addWidget(self.proxy_input)
hbox_proxy_port.addWidget(port_label)
hbox_proxy_port.addWidget(self.port_input)
layout.addLayout(hbox_proxy_port)
hbox_auth = QHBoxLayout()
username_label = QLabel("Имя пользователя:")
self.username_input = QLineEdit()
self.username_input.setPlaceholderText("Username")
self.username_input.setStyleSheet("background-color: #323232; color: white;")
self.username_input.returnPressed.connect(self.on_add_proxy)
password_label = QLabel("Пароль:")
self.password_input = QLineEdit()
self.password_input.setPlaceholderText("Password")
self.password_input.setStyleSheet("background-color: #323232; color: white;")
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
self.password_input.returnPressed.connect(self.on_add_proxy)
hbox_auth.addWidget(username_label)
hbox_auth.addWidget(self.username_input)
hbox_auth.addWidget(password_label)
hbox_auth.addWidget(self.password_input)
layout.addLayout(hbox_auth)
hbox3 = QHBoxLayout()
self.saved_proxy_choice = ProxyListWidget(self)
self.saved_proxy_choice.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
self.saved_proxy_choice.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.saved_proxy_choice.deleteRequested.connect(self.on_delete_proxy)
self.saved_proxy_choice.editRequested.connect(self.edit_selected_proxy)
hbox3.addWidget(self.saved_proxy_choice, 1)
vbox_buttons = QVBoxLayout()
self.add_proxy_button = QPushButton("Сохранить прокси")
self.add_proxy_button.setStyleSheet(BUTTON_STYLE)
self.add_proxy_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.add_proxy_button.clicked.connect(self.on_add_proxy)
self.delete_proxy_button = QPushButton("Удалить выбранные прокси")
self.delete_proxy_button.setStyleSheet(BUTTON_STYLE_DELETE)
self.delete_proxy_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.delete_proxy_button.clicked.connect(self.on_delete_proxy)
vbox_buttons.addStretch()
vbox_buttons.addWidget(self.add_proxy_button)
vbox_buttons.addSpacing(10)
vbox_buttons.addWidget(self.delete_proxy_button)
vbox_buttons.addStretch()
hbox3.addLayout(vbox_buttons, 0)
layout.addLayout(hbox3)
hbox5 = QHBoxLayout()
service_label = QLabel("Выбор сервиса:")
self.service_group = QButtonGroup()
self.ipinfo_radio = QRadioButton("ipinfo.io")
self.ipapi_radio = QRadioButton("ipapi.co")
self.ipapi_radio_new = QRadioButton("ip-api.com")
self.service_group.addButton(self.ipinfo_radio)
self.service_group.addButton(self.ipapi_radio)
self.service_group.addButton(self.ipapi_radio_new)
self.ipinfo_radio.setChecked(True)
hbox5.addWidget(service_label)
hbox5.addWidget(self.ipinfo_radio)
hbox5.addWidget(self.ipapi_radio)
hbox5.addWidget(self.ipapi_radio_new)
layout.addLayout(hbox5)
self.get_button = QPushButton("Получить IP и ISP")
self.get_button.setStyleSheet(BUTTON_STYLE)
self.get_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
self.get_button.clicked.connect(self.on_get_button)
hbox_get = QHBoxLayout()
hbox_get.addStretch()
hbox_get.addWidget(self.get_button)
hbox_get.addStretch()
layout.addLayout(hbox_get)
self.load_saved_proxies()
self.load_settings()
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.icon)
self.tray_icon.setToolTip('JustWoofIP')
self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.create_tray_menu()
self.tray_icon.show()
def create_menu(self):
menubar = self.menuBar()
file_menu = menubar.addMenu('Файл')
load_action = QAction('Загрузить прокси из локального файла', self)
load_action.triggered.connect(self.load_proxies_from_file)
file_menu.addAction(load_action)
load_url_action = QAction('Загрузить прокси из Интернета', self)
load_url_action.triggered.connect(self.load_proxies_from_url_via_dialog)
file_menu.addAction(load_url_action)
exit_action = QAction('Выход', self)
exit_action.triggered.connect(self.on_menu_exit)
file_menu.addAction(exit_action)
edit_menu = menubar.addMenu('Правка')
self.undo_delete_action = QAction('Отменить удаление', self)
self.undo_delete_action.triggered.connect(self.undo_delete)
edit_menu.addAction(self.undo_delete_action)
self.redo_delete_action = QAction('Повторить удаление', self)
self.redo_delete_action.triggered.connect(self.redo_delete)
edit_menu.addAction(self.redo_delete_action)
# Изначально отключаем действия
self.undo_delete_action.setEnabled(False)
self.redo_delete_action.setEnabled(False)
settings_menu = menubar.addMenu('Настройки')
minimize_to_tray_action = QAction('Сворачивать в трей при закрытии', self, checkable=True)
minimize_to_tray_action.setChecked(self.minimize_to_tray)
minimize_to_tray_action.triggered.connect(self.on_minimize_to_tray_toggled)
settings_menu.addAction(minimize_to_tray_action)
help_menu = menubar.addMenu('Справка')
about_action = QAction('О программе', self)
about_action.triggered.connect(self.show_about_dialog)
help_menu.addAction(about_action)
menubar.setStyleSheet("""
QMenuBar {
background-color: #323232;
color: white;
}
QMenuBar::item:selected {
background-color: #464646;
}
QMenu {
background-color: #323232;
color: white;
}
QMenu::item:selected {
background-color: #464646;
}
QMenu::item:pressed {
background-color: #5a5a5a;
}
""")
def create_tray_menu(self):
menu = QMenu()
show_action = QAction("Показать", self)
exit_action = QAction("Выход", self)
show_action.triggered.connect(self.on_tray_show)
exit_action.triggered.connect(self.on_tray_exit)
menu.addAction(show_action)
menu.addAction(exit_action)
self.tray_icon.setContextMenu(menu)
def on_tray_icon_activated(self, reason):
if reason == QSystemTrayIcon.ActivationReason.Trigger:
self.on_tray_show()
def on_tray_show(self):
self.showNormal()
self.activateWindow()
def on_tray_exit(self):
self.tray_icon.hide()
self.close()
def on_menu_exit(self):
self.tray_icon.hide()
self.close()
def on_minimize_to_tray_toggled(self):
self.minimize_to_tray = not self.minimize_to_tray
def edit_selected_proxy(self):
selected_items = self.saved_proxy_choice.selectedItems()
if not selected_items:
self.show_notification_inline("Не выбран прокси для редактирования", duration=2000)
return
item = selected_items[0]
proxy_details = item.text()
proxy_index = self.saved_proxy_choice.indexFromItem(item).row()
new_details, ok = QInputDialog.getText(self, "Редактировать прокси",
"Введите новый прокси (protocol://proxy:port)",
QLineEdit.EchoMode.Normal, proxy_details)
if ok and new_details:
parts = new_details.split("://")
if len(parts) != 2 or ':' not in parts[1]:
self.show_notification_inline("Некорректный формат прокси", duration=2000)
return
protocol, server_port = parts
if protocol not in ['http', 'https', 'socks4', 'socks5']:
self.show_notification_inline("Некорректный протокол", duration=2000)
return
if not all(part.isdigit() for part in server_port.split(':')[1:]):
self.show_notification_inline("Порт должен быть числом", duration=2000)
return
username = None
password = None
if '@' in server_port:
server_port, creds = server_port.split('@')
if ':' in creds:
username, password = creds.split(':', 1)
self.saved_proxies[proxy_index] = {'protocol': protocol, 'server_port': server_port}
if username:
self.saved_proxies[proxy_index]['username'] = self.fernet.encrypt(username.encode()).decode()
if password:
self.saved_proxies[proxy_index]['password'] = self.fernet.encrypt(password.encode()).decode()
self.save_proxies()
self.update_saved_proxy_list()
self.show_notification_inline("Прокси обновлен", duration=2000)
def closeEvent(self, event):
self.save_settings()
if self.minimize_to_tray:
self.hide()
event.ignore()
else:
self.tray_icon.hide()
event.accept()
def load_saved_proxies(self):
self.saved_proxies = []
if os.path.exists(PROXY_FILE):
try:
with open(PROXY_FILE, 'r', encoding='utf-8') as f:
encrypted_proxies = json.load(f)
for proxy in encrypted_proxies:
decrypted_proxy = {
'protocol': proxy.get('protocol', 'socks5'),
'server_port': proxy.get('server_port', '')
}
if 'username' in proxy:
try:
decrypted_username = self.fernet.decrypt(proxy['username'].encode()).decode()
decrypted_proxy['username'] = decrypted_username
except Exception as e:
print(f"Ошибка расшифровки имени пользователя: {e}")
if 'password' in proxy:
try:
decrypted_password = self.fernet.decrypt(proxy['password'].encode()).decode()
decrypted_proxy['password'] = decrypted_password
except Exception as e:
print(f"Ошибка расшифровки пароля: {e}")
self.saved_proxies.append(decrypted_proxy)
self.update_saved_proxy_list()
except Exception as e:
print(f"Ошибка загрузки прокси: {e}")
self.update_proxy_checkbox_state()
def save_proxies(self):
proxies_to_save = []
for proxy in self.saved_proxies:
proxy_copy = {
'protocol': proxy.get('protocol', 'socks5'),
'server_port': proxy.get('server_port', '')
}
if 'username' in proxy and proxy['username']:
proxy_copy['username'] = self.fernet.encrypt(proxy['username'].encode()).decode()
if 'password' in proxy and proxy['password']:
proxy_copy['password'] = self.fernet.encrypt(proxy['password'].encode()).decode()
proxies_to_save.append(proxy_copy)
os.makedirs(os.path.dirname(PROXY_FILE), exist_ok=True)
try:
with open(PROXY_FILE, 'w', encoding='utf-8') as f:
json.dump(proxies_to_save, f, ensure_ascii=False, indent=4)
except Exception as e:
print(f"Ошибка сохранения прокси: {e}")
def update_saved_proxy_list(self):
self.saved_proxy_choice.clear()
for proxy in self.saved_proxies:
display_text = f"{proxy['protocol']}://{proxy['server_port']}"
if proxy.get('username') and proxy.get('password'):
display_text += f" (***:***)"
elif proxy.get('username'):
display_text += f" (***:---)"
elif proxy.get('password'):
display_text += f" (---:***)"
self.saved_proxy_choice.addItem(display_text)
self.update_proxy_checkbox_state()
self.update_undo_redo_actions() # Update state of undo/redo actions
def update_proxy_checkbox_state(self):
if len(self.saved_proxies) == 0:
self.use_proxy_checkbox.setChecked(False)
self.use_proxy_checkbox.setEnabled(False)
else:
self.use_proxy_checkbox.setEnabled(True)
def load_settings(self):
if os.path.exists(SETTINGS_FILE):
try:
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
settings = json.load(f)
use_proxy = settings.get('use_proxy', False)
minimize_to_tray = settings.get('minimize_to_tray', False)
selected_proxy_index = settings.get('selected_proxy_index', -1)
selected_service = settings.get('selected_service', 'ipinfo.io')
self.use_proxy_checkbox.setChecked(use_proxy)
self.minimize_to_tray = minimize_to_tray
if 0 <= selected_proxy_index < len(self.saved_proxies):
self.saved_proxy_choice.setCurrentRow(selected_proxy_index)
if selected_service == 'ipinfo.io':
self.ipinfo_radio.setChecked(True)
elif selected_service == 'ipapi.co':
self.ipapi_radio.setChecked(True)
elif selected_service == 'ip-api.com':
self.ipapi_radio_new.setChecked(True)
except Exception as e:
print(f"Ошибка загрузки настроек: {e}")
else:
self.use_proxy_checkbox.setChecked(False)
self.minimize_to_tray = False
self.ipinfo_radio.setChecked(True)
def save_settings(self):
selected_service = 'ipinfo.io'
if self.ipinfo_radio.isChecked():
selected_service = 'ipinfo.io'
elif self.ipapi_radio.isChecked():
selected_service = 'ipapi.co'
elif self.ipapi_radio_new.isChecked():
selected_service = 'ip-api.com'
settings = {
'use_proxy': self.use_proxy_checkbox.isChecked(),
'minimize_to_tray': self.minimize_to_tray,
'selected_proxy_index': self.saved_proxy_choice.currentRow(),
'selected_service': selected_service
}
os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
try:
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(settings, f, ensure_ascii=False, indent=4)
except Exception as e:
print(f"Ошибка сохранения настроек: {e}")
def on_get_button(self):
use_proxy = self.use_proxy_checkbox.isChecked()
if self.ipinfo_radio.isChecked():
service_url = 'https://ipinfo.io/json'
elif self.ipapi_radio.isChecked():
service_url = 'https://ipapi.co/json/'
elif self.ipapi_radio_new.isChecked():
service_url = 'http://ip-api.com/json/'
else:
service_url = 'https://ipinfo.io/json'
self.current_service_url = service_url
proxy_data = None
if use_proxy:
selected_indices = self.saved_proxy_choice.selectedIndexes()
if selected_indices:
selected_index = selected_indices[0].row()
if 0 <= selected_index < len(self.saved_proxies):
proxy_data = self.saved_proxies[selected_index].copy()
elif len(self.saved_proxies) > 0:
proxy_data = self.saved_proxies[0].copy()
ip, isp, city, country = get_ip_and_isp(proxy_data, use_proxy, service_url)
self.ip_text.setText(ip)
self.isp_text.setText(isp)
self.city_text.setText(city)
self.country_text.setText(country)
self.update_country_flag(country)
def on_add_proxy(self):
proxy = self.proxy_input.text().strip()
port = self.port_input.text().strip()
username = self.username_input.text().strip()
password = self.password_input.text().strip()
if not proxy or not port:
self.show_notification_inline("Прокси и порт обязательны", duration=2000)
return
if password and not username:
self.show_notification_inline("Имя пользователя обязательно при наличии пароля", duration=2000)
return
if '://' in proxy and re.match(r'^[a-zA-Z]+://', proxy):
try:
protocol, server = proxy.split('://', 1)
except ValueError:
self.show_notification_inline("Некорректный формат прокси", duration=2000)
return
else:
protocol = 'socks5'
server = proxy
if not port.isdigit():
self.show_notification_inline("Порт должен быть числом", duration=2000)
return
server_port = f"{server}:{port}"
duplicate = False
for existing_proxy in self.saved_proxies:
if existing_proxy['protocol'] == protocol and existing_proxy['server_port'] == server_port:
duplicate = True
break
if duplicate:
self.show_notification_inline("Прокси с таким адресом и портом уже существует", duration=2000)
return
proxy_data = {'protocol': protocol, 'server_port': server_port}
if username:
proxy_data['username'] = self.fernet.encrypt(username.encode()).decode()
if password:
proxy_data['password'] = self.fernet.encrypt(password.encode()).decode()
self.saved_proxies.append(proxy_data)
self.save_proxies()
self.update_saved_proxy_list()
self.proxy_input.clear()
self.port_input.clear()
self.username_input.clear()
self.password_input.clear()
self.show_notification_inline("Прокси сохранен", duration=2000)
def on_delete_proxy(self):
selected_indices = self.saved_proxy_choice.selectedIndexes()
if not selected_indices:
self.show_notification_inline("Не выбран прокси для удаления", duration=2000)
return
rows_to_delete = sorted({index.row() for index in selected_indices}, reverse=True)
self.deleted_proxies = [self.saved_proxies[i] for i in rows_to_delete]
for row in rows_to_delete:
if 0 <= row < len(self.saved_proxies):
del self.saved_proxies[row]
self.save_proxies()
self.update_saved_proxy_list()
total = self.saved_proxy_choice.count()
if total > 0:
self.saved_proxy_choice.setCurrentRow(0)
self.show_notification_inline("Выбранные прокси удалены", duration=2000)
def undo_delete(self):
if not self.deleted_proxies:
self.show_notification_inline("Нечего восстанавливать", duration=2000)
return
self.saved_proxies.extend(self.deleted_proxies)
self.deleted_proxies = []
self.save_proxies()
self.update_saved_proxy_list()
self.show_notification_inline("Удаление отменено", duration=2000)
self.update_undo_redo_actions()
def redo_delete(self):
if not self.deleted_proxies:
self.show_notification_inline("Нечего удалять повторно", duration=2000)
return
self.saved_proxies = [proxy for proxy in self.saved_proxies if proxy not in self.deleted_proxies]
self.deleted_proxies = []
self.save_proxies()
self.update_saved_proxy_list()
self.show_notification_inline("Повторное удаление выполнено", duration=2000)
self.update_undo_redo_actions()
def update_undo_redo_actions(self):
has_deleted = bool(self.deleted_proxies)
self.undo_delete_action.setEnabled(has_deleted)
self.redo_delete_action.setEnabled(has_deleted)
def copy_ip(self):
ip = self.ip_text.text().strip()
if ip and ip != 'N/A' and ip != 'Ошибка':
clipboard = QApplication.clipboard()
clipboard.setText(ip)
self.show_notification_inline("IP-адрес скопирован", duration=2000)
else:
self.show_notification_inline("Нечего копировать", duration=2000)
def show_notification_inline(self, message, duration=1000):
self.notification_label.setText(message)
self.notification_label.adjustSize()
self.notification_label.setFixedHeight(30)
self.notification_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.notification_label.show()
QTimer.singleShot(duration, self.notification_label.hide)
def show_about_dialog(self):
dialog = AboutDialog(self)
dialog.exec()
def select_all_proxies(self):
self.saved_proxy_choice.selectAll()
def update_country_flag(self, country_name):
if not country_name or country_name == 'N/A' or country_name == 'Ошибка':
if os.path.exists(ERROR_ICON_PATH):
error_pixmap = QPixmap(ERROR_ICON_PATH)
self.flag_label.setPixmap(error_pixmap)
else:
self.flag_label.setPixmap(QPixmap())
return
try:
country = pycountry.countries.lookup(country_name)
except LookupError:
try:
search_results = pycountry.countries.search_fuzzy(country_name)
country = search_results[0] if search_results else None
except:
country = None
if country:
country_code = country.alpha_2.lower()
flag_url = f"https://flagcdn.com/32x24/{country_code}.png"
try:
response = requests.get(flag_url, timeout=10)
if response.status_code == 200:
pixmap = QPixmap()
pixmap.loadFromData(response.content)
self.flag_label.setPixmap(pixmap)
else:
self.set_error_icon()
except Exception as e:
self.set_error_icon()
else:
print(f"Не удалось найти код страны для: {country_name}")
self.set_error_icon()
def set_error_icon(self):
if os.path.exists(ERROR_ICON_PATH):
error_pixmap = QPixmap(ERROR_ICON_PATH)
self.flag_label.setPixmap(error_pixmap)
else:
self.flag_label.setPixmap(QPixmap())
def open_service_url(self):
if self.current_service_url:
# Open the correct URL based on the current service
if 'ip-api.com' in self.current_service_url:
QDesktopServices.openUrl(QUrl("https://ip-api.com/"))
else:
QDesktopServices.openUrl(QUrl(self.current_service_url))
else:
self.show_notification_inline("Сервис не выбран", duration=2000)
def load_proxies_from_file(self):
file_dialog = QFileDialog(self)
file_dialog.setNameFilter("Text Files (*.txt);;All Files (*)")
if file_dialog.exec():
file_path = file_dialog.selectedFiles()[0]
file_name = os.path.basename(file_path).lower()
# Determine protocol by inspecting the file
default_protocol = self.determine_protocol_by_inspection(file_name)
self.load_proxies_from_path(file_path, default_protocol)
def load_proxies_from_url_via_dialog(self):
url, ok = QInputDialog.getText(self, "Загрузить прокси из Интернета",
"Введите URL-адрес файла с прокси:")
if ok and url:
self.load_proxies_from_url(url)
def determine_protocol_by_inspection(self, name):
# Ignore the initial 'http' or 'https'
protocol_position = max(name.lower().rfind("https"), name.lower().rfind("http"))
if protocol_position != -1:
part = name.lower()[protocol_position:]
if part:
if 'socks4' in part:
return 'socks4'
elif 'socks5' in part:
return 'socks5'
elif part.startswith('https'):
return 'https'
elif part.startswith('http'):
return 'http'
return 'http' # Default if nothing is found
def load_proxies_from_path(self, path, default_protocol=None):
try:
with open(path, 'r', encoding='utf-8') as file:
new_proxies = file.readlines()
added_count = self.process_proxies(new_proxies, default_protocol)
if added_count > 0:
self.save_proxies()
self.update_saved_proxy_list()
self.show_notification_inline(f"Добавлено {added_count} новых прокси(ей)", duration=2000)
else:
self.show_notification_inline("Новых прокси не найдено", duration=2000)
except Exception as e:
print(f"Ошибка загрузки прокси из файла: {e}")
self.show_notification_inline("Ошибка загрузки из файла", duration=2000)
def load_proxies_from_url(self, url):
url = url.strip()
if not url:
self.show_notification_inline("URL обязателен", duration=2000)
return
if "github.com" in url and "blob" in url:
url = url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
new_proxies = response.text.splitlines()
default_protocol = self.determine_protocol_by_inspection(url)
added_count = self.process_proxies(new_proxies, default_protocol)
if added_count > 0:
self.save_proxies()
self.update_saved_proxy_list()
self.show_notification_inline(f"Добавлено {added_count} новых прокси(ей)", duration=2000)
else:
self.show_notification_inline("Новых прокси не найдено", duration=2000)
except requests.RequestException as e:
print(f"Ошибка загрузки прокси из сети: {e}")
self.show_notification_inline("Ошибка загрузки из URL", duration=2000)
def process_proxies(self, proxy_lines, default_protocol=None):
added_count = 0
for line in proxy_lines:
proxy_line = line.strip()
if not proxy_line:
continue
protocol, server_port = None, None
username, password = None, None
if '://' in proxy_line and re.match(r'^[a-zA-Z]+://', proxy_line):
protocol, remainder = proxy_line.split('://', 1)
else:
protocol = default_protocol
remainder = proxy_line
if '@' in remainder:
server_port, creds = remainder.split('@', 1)
if ':' in creds:
username, password = creds.split(':', 1)
elif ':' in remainder:
parts = remainder.split(':')
if len(parts) == 4:
server_port = f"{parts[0]}:{parts[1]}"
username = parts[2]
password = parts[3]
elif len(parts) == 2:
server_port = remainder
if not server_port or not protocol or ':' not in server_port:
continue
server, port = server_port.split(':', 1)
if not port.isdigit():
continue
proxy_data = {'protocol': protocol, 'server_port': f"{server}:{port}"}
if username:
proxy_data['username'] = self.fernet.encrypt(username.encode()).decode()
if password:
proxy_data['password'] = self.fernet.encrypt(password.encode()).decode()
if not any(p['protocol'] == proxy_data['protocol'] and
p['server_port'] == proxy_data['server_port']
for p in self.saved_proxies):
self.saved_proxies.append(proxy_data)
added_count += 1
return added_count
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec())
```
### Изменения:
1. **Удалены `ipwho.is`:** Убраны все части кода, относящиеся к `ipwho.is`.
2. **Используется стандартный механизм API для определения IP и информации об ISP.**
### Библиотека меню:
В данном коде используются модули из `PyQt6`. Ваша программа использует `QMenu` для создания контекстного и стандартного меню в приложении, а меню создается с использованием `QMenuBar`. Настройки меню и стилизации представляются через модули `QAction` и `QMenu`.