Initial Commit+
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
10
.idea/PIRC.iml
generated
Normal file
10
.idea/PIRC.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="uv (PIRC)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="uv (PIRC)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="uv (PIRC)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/PIRC.iml" filepath="$PROJECT_DIR$/.idea/PIRC.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
BIN
__pycache__/pirc_client.cpython-313.pyc
Normal file
BIN
__pycache__/pirc_client.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/pirc_setup_wizard.cpython-313.pyc
Normal file
BIN
__pycache__/pirc_setup_wizard.cpython-313.pyc
Normal file
Binary file not shown.
28
main.py
Normal file
28
main.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# main.py
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from pirc_setup_wizard import PIRCSetupWizard
|
||||
from pirc_client import PIRCClient
|
||||
|
||||
CONFIG_FILE = "pirc_config.json"
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# Check if config exists
|
||||
if not os.path.exists(CONFIG_FILE):
|
||||
wizard = PIRCSetupWizard()
|
||||
if wizard.exec() != 0:
|
||||
pass # Wizard handled saving config
|
||||
else:
|
||||
print("Setup cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
client = PIRCClient()
|
||||
client.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
350
pirc_client.py
Normal file
350
pirc_client.py
Normal file
@@ -0,0 +1,350 @@
|
||||
import sys
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
|
||||
QLineEdit, QPushButton, QLabel, QListWidget, QListWidgetItem,
|
||||
QMessageBox, QDialog, QDialogButtonBox, QFormLayout, QLineEdit as QLE
|
||||
)
|
||||
from PySide6.QtCore import Qt, Signal, QObject
|
||||
from PySide6.QtGui import QIcon
|
||||
|
||||
CONFIG_FILE = "pirc_config.json"
|
||||
|
||||
|
||||
class IRCWorker(QObject):
|
||||
message_received = Signal(str)
|
||||
system_message = Signal(str)
|
||||
user_list_update = Signal(list)
|
||||
disconnected = Signal()
|
||||
|
||||
def __init__(self, server, port, nick, channels):
|
||||
super().__init__()
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.nick = nick
|
||||
self.channels = channels
|
||||
self.running = True
|
||||
self.sock = None
|
||||
self.current_users = []
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect((self.server, self.port))
|
||||
self.sock.send(f"NICK {self.nick}\r\n".encode("utf-8"))
|
||||
self.sock.send(f"USER {self.nick} 0 * :PIRC User\r\n".encode("utf-8"))
|
||||
|
||||
for channel in self.channels:
|
||||
self.sock.send(f"JOIN {channel}\r\n".encode("utf-8"))
|
||||
self.system_message.emit(f"Joined {channel}")
|
||||
|
||||
threading.Thread(target=self.listen, daemon=True).start()
|
||||
except Exception as e:
|
||||
self.system_message.emit(f"Connection failed: {e}")
|
||||
self.disconnected.emit()
|
||||
|
||||
def listen(self):
|
||||
try:
|
||||
while self.running:
|
||||
data = self.sock.recv(4096).decode("utf-8", errors="ignore")
|
||||
if not data:
|
||||
break
|
||||
|
||||
for line in data.split("\r\n"):
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# PING/PONG
|
||||
if line.startswith("PING"):
|
||||
self.sock.send(f"PONG {line.split()[1]}\r\n".encode("utf-8"))
|
||||
continue
|
||||
|
||||
# Ignore raw handshake notices
|
||||
if line.startswith(":") and "NOTICE * :***" in line:
|
||||
continue
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) > 1 and parts[1].isdigit():
|
||||
code = int(parts[1])
|
||||
if code == 353: # NAMES list
|
||||
name_parts = line.split(":", 2)
|
||||
if len(name_parts) < 3:
|
||||
continue
|
||||
users_chunk = name_parts[2].split()
|
||||
self.current_users.extend(users_chunk)
|
||||
elif code == 366: # End of NAMES list
|
||||
self.user_list_update.emit(self.current_users)
|
||||
self.current_users = []
|
||||
elif code in [0o01, 332, 333]:
|
||||
self.system_message.emit(line)
|
||||
continue
|
||||
# PRIVMSG
|
||||
if "PRIVMSG" in line:
|
||||
msg_parts = line.split(":", 2)
|
||||
if len(msg_parts) >= 3:
|
||||
prefix = msg_parts[1].split("!")[0]
|
||||
message = msg_parts[2]
|
||||
self.message_received.emit(f"<{prefix}> {message}")
|
||||
continue
|
||||
|
||||
# JOIN
|
||||
if "JOIN" in line:
|
||||
user = line.split("!")[0].replace(":", "")
|
||||
chan = line.split("JOIN")[1].strip()
|
||||
if user not in self.current_users:
|
||||
self.current_users.append(user)
|
||||
self.user_list_update.emit(self.current_users)
|
||||
self.system_message.emit(f"{user} joined {chan}")
|
||||
continue
|
||||
|
||||
# PART/QUIT
|
||||
if "PART" in line or "QUIT" in line:
|
||||
user = line.split("!")[0].replace(":", "")
|
||||
if user in self.current_users:
|
||||
self.current_users.remove(user)
|
||||
self.user_list_update.emit(self.current_users)
|
||||
self.system_message.emit(f"{user} left")
|
||||
continue
|
||||
|
||||
# Other system messages
|
||||
self.system_message.emit(line)
|
||||
except Exception as e:
|
||||
self.system_message.emit(f"Error: {e}")
|
||||
finally:
|
||||
self.disconnected.emit()
|
||||
|
||||
def send_message(self, channel, message):
|
||||
try:
|
||||
self.sock.send(f"PRIVMSG {channel} :{message}\r\n".encode("utf-8"))
|
||||
except Exception as e:
|
||||
self.system_message.emit(f"Failed to send message: {e}")
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
try:
|
||||
self.sock.send(b"QUIT :Goodbye!\r\n")
|
||||
self.sock.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class AddServerDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Add Server")
|
||||
self.setFixedSize(300, 200)
|
||||
layout = QFormLayout()
|
||||
self.server_input = QLE()
|
||||
self.port_input = QLE()
|
||||
self.port_input.setText("6667")
|
||||
self.channels_input = QLE()
|
||||
layout.addRow("Server:", self.server_input)
|
||||
layout.addRow("Port:", self.port_input)
|
||||
layout.addRow("Channels (comma-separated):", self.channels_input)
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
buttons.accepted.connect(self.accept)
|
||||
buttons.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
self.setLayout(layout)
|
||||
|
||||
def get_data(self):
|
||||
return (
|
||||
self.server_input.text().strip(),
|
||||
int(self.port_input.text().strip()),
|
||||
[c.strip() for c in self.channels_input.text().split(",") if c.strip()]
|
||||
)
|
||||
|
||||
|
||||
class PIRCClient(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("PIRC — Python IRC Client")
|
||||
self.setGeometry(100, 100, 1000, 600)
|
||||
self.worker = None
|
||||
|
||||
self.load_config()
|
||||
self.init_ui()
|
||||
|
||||
if self.favorites:
|
||||
first = self.favorites[0]
|
||||
self.connect_to_server(first["server"], first.get("port", 6667), first["channels"])
|
||||
else:
|
||||
self.system_message("No favorite servers found — click the + button to connect.")
|
||||
|
||||
def load_config(self):
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, "r") as f:
|
||||
cfg = json.load(f)
|
||||
self.nick = cfg.get("username", "PIRCUser")
|
||||
self.favorites = cfg.get("favorites", [])
|
||||
else:
|
||||
self.nick = "PIRCUser"
|
||||
self.favorites = []
|
||||
|
||||
def save_config(self):
|
||||
cfg = {
|
||||
"username": self.nick,
|
||||
"favorites": self.favorites
|
||||
}
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
json.dump(cfg, f, indent=4)
|
||||
|
||||
def init_ui(self):
|
||||
layout = QHBoxLayout()
|
||||
|
||||
# Server sidebar
|
||||
sidebar_layout = QVBoxLayout()
|
||||
self.server_list = QListWidget()
|
||||
for fav in self.favorites:
|
||||
item = QListWidgetItem(fav["server"])
|
||||
self.server_list.addItem(item)
|
||||
self.server_list.itemClicked.connect(self.server_selected)
|
||||
sidebar_layout.addWidget(self.server_list)
|
||||
|
||||
# Add server button with + icon
|
||||
self.add_server_button = QPushButton()
|
||||
self.add_server_button.setIcon(QIcon("add.png"))
|
||||
self.add_server_button.setText("") # icon only
|
||||
self.add_server_button.clicked.connect(self.add_server)
|
||||
self.disconnect_button = QPushButton("Disconnect")
|
||||
self.disconnect_button.clicked.connect(self.disconnect_server)
|
||||
sidebar_layout.addWidget(self.add_server_button)
|
||||
sidebar_layout.addWidget(self.disconnect_button)
|
||||
layout.addLayout(sidebar_layout, 2)
|
||||
|
||||
# Chat + user list
|
||||
chat_layout = QVBoxLayout()
|
||||
self.info_label = QLabel("Disconnected")
|
||||
self.info_label.setStyleSheet("font-weight: bold; padding: 4px;")
|
||||
chat_layout.addWidget(self.info_label)
|
||||
|
||||
chat_area_layout = QHBoxLayout()
|
||||
self.chat_display = QTextEdit()
|
||||
self.chat_display.setReadOnly(True)
|
||||
chat_area_layout.addWidget(self.chat_display, 7)
|
||||
|
||||
# User list sidebar
|
||||
self.user_list = QListWidget()
|
||||
chat_area_layout.addWidget(self.user_list, 3)
|
||||
|
||||
chat_layout.addLayout(chat_area_layout, 8)
|
||||
|
||||
# Message input
|
||||
input_layout = QHBoxLayout()
|
||||
self.message_input = QLineEdit()
|
||||
self.message_input.returnPressed.connect(self.send_message)
|
||||
self.send_button = QPushButton("Send")
|
||||
self.send_button.clicked.connect(self.send_message)
|
||||
input_layout.addWidget(self.message_input)
|
||||
input_layout.addWidget(self.send_button)
|
||||
chat_layout.addLayout(input_layout)
|
||||
|
||||
layout.addLayout(chat_layout, 8)
|
||||
self.setLayout(layout)
|
||||
|
||||
def connect_to_server(self, server, port, channels):
|
||||
if self.worker:
|
||||
self.disconnect_server()
|
||||
self.worker = IRCWorker(server, port, self.nick, channels)
|
||||
self.worker.message_received.connect(self.display_message)
|
||||
self.worker.system_message.connect(self.system_message)
|
||||
self.worker.user_list_update.connect(self.update_user_list)
|
||||
self.worker.disconnected.connect(self.handle_disconnect)
|
||||
self.worker.start()
|
||||
self.info_label.setText(f"Connected to {server} as {self.nick}")
|
||||
self.system_message(f"Connected to {server}")
|
||||
|
||||
def disconnect_server(self):
|
||||
if self.worker:
|
||||
self.worker.stop()
|
||||
self.worker = None
|
||||
self.system_message("Disconnected from server.")
|
||||
self.info_label.setText("Disconnected")
|
||||
self.user_list.clear()
|
||||
|
||||
def handle_disconnect(self):
|
||||
self.worker = None
|
||||
self.system_message("Connection closed by server.")
|
||||
self.info_label.setText("Disconnected")
|
||||
self.user_list.clear()
|
||||
|
||||
def server_selected(self, item):
|
||||
selected_server = item.text()
|
||||
fav = next((f for f in self.favorites if f["server"] == selected_server), None)
|
||||
if fav:
|
||||
self.connect_to_server(fav["server"], fav.get("port", 6667), fav["channels"])
|
||||
|
||||
def add_server(self):
|
||||
dialog = AddServerDialog()
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
server, port, channels = dialog.get_data()
|
||||
if not server:
|
||||
QMessageBox.warning(self, "Error", "Server cannot be empty.")
|
||||
return
|
||||
new_server = {"server": server, "port": port, "channels": channels}
|
||||
self.favorites.append(new_server)
|
||||
self.save_config()
|
||||
self.server_list.addItem(server)
|
||||
self.connect_to_server(server, port, channels)
|
||||
|
||||
def display_message(self, msg):
|
||||
timestamp = datetime.now().strftime("[%H:%M:%S]")
|
||||
self.chat_display.append(f"{timestamp} {msg}")
|
||||
|
||||
def system_message(self, msg):
|
||||
timestamp = datetime.now().strftime("[%H:%M:%S]")
|
||||
self.chat_display.append(
|
||||
f'<div style="background-color:#cce4ff; padding:2px; border-radius:3px;">{timestamp} [System] {msg}</div>'
|
||||
)
|
||||
|
||||
def update_user_list(self, users):
|
||||
self.user_list.clear()
|
||||
admins = []
|
||||
normal_users = []
|
||||
|
||||
for user in users:
|
||||
if user.startswith("@"):
|
||||
admins.append(user[1:]) # remove @ for display
|
||||
elif user.startswith("+"):
|
||||
normal_users.append(user[1:]) # optional remove +
|
||||
else:
|
||||
normal_users.append(user)
|
||||
|
||||
admins.sort()
|
||||
normal_users.sort()
|
||||
ordered_users = admins + normal_users
|
||||
|
||||
for user in ordered_users:
|
||||
item = QListWidgetItem(user)
|
||||
if user in admins:
|
||||
item.setIcon(QIcon("admin.png"))
|
||||
else:
|
||||
item.setIcon(QIcon("user.png"))
|
||||
self.user_list.addItem(item)
|
||||
|
||||
def send_message(self):
|
||||
text = self.message_input.text().strip()
|
||||
if not text:
|
||||
return
|
||||
if self.worker and self.worker.channels:
|
||||
self.worker.send_message(self.worker.channels[0], text)
|
||||
self.display_message(f"<{self.nick}> {text}")
|
||||
else:
|
||||
self.system_message("Not connected to a server.")
|
||||
self.message_input.clear()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.worker:
|
||||
self.worker.stop()
|
||||
super().closeEvent(event)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
client = PIRCClient()
|
||||
client.show()
|
||||
sys.exit(app.exec())
|
||||
10
pirc_config.json
Normal file
10
pirc_config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"username": "OptimiDev",
|
||||
"favorites": [
|
||||
{
|
||||
"server": "irc.libera.chat",
|
||||
"port": 6667,
|
||||
"channels": ["#python"]
|
||||
}
|
||||
]
|
||||
}
|
||||
138
pirc_setup_wizard.py
Normal file
138
pirc_setup_wizard.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# pirC_setup_wizard.py
|
||||
import sys
|
||||
import json
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton,
|
||||
QListWidget, QHBoxLayout, QStackedLayout, QWidget, QDialogButtonBox
|
||||
)
|
||||
|
||||
CONFIG_FILE = "pirc_config.json"
|
||||
|
||||
class PIRCSetupWizard(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("PIRC Setup Wizard")
|
||||
self.setGeometry(200, 200, 500, 400)
|
||||
|
||||
self.nicknames = []
|
||||
self.favorites = []
|
||||
|
||||
# Multi-step layout
|
||||
self.layout = QVBoxLayout()
|
||||
self.stack = QStackedLayout()
|
||||
self.layout.addLayout(self.stack)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
# Step 0: Welcome
|
||||
self.step_welcome = QWidget()
|
||||
w_layout = QVBoxLayout()
|
||||
w_layout.addWidget(QLabel(
|
||||
"Welcome to PIRC!\n\nThis wizard will guide you through the setup:\n"
|
||||
"1. Add your usernames (multiple allowed)\n"
|
||||
"2. Add favorite servers/channels (optional)\n"
|
||||
"3. Open the chat window\n"
|
||||
))
|
||||
self.next0 = QPushButton("Start")
|
||||
self.next0.clicked.connect(lambda: self.stack.setCurrentIndex(1))
|
||||
w_layout.addWidget(self.next0)
|
||||
self.step_welcome.setLayout(w_layout)
|
||||
self.stack.addWidget(self.step_welcome)
|
||||
|
||||
# Step 1: Usernames
|
||||
self.step_users = QWidget()
|
||||
u_layout = QVBoxLayout()
|
||||
u_layout.addWidget(QLabel("Step 1: Add your usernames"))
|
||||
self.nick_input = QLineEdit()
|
||||
self.nick_input.setPlaceholderText("Enter a username")
|
||||
u_layout.addWidget(self.nick_input)
|
||||
self.nick_list = QListWidget()
|
||||
u_layout.addWidget(self.nick_list)
|
||||
h_btn_layout = QHBoxLayout()
|
||||
self.add_nick_btn = QPushButton("Add")
|
||||
self.add_nick_btn.clicked.connect(self.add_nickname)
|
||||
self.remove_nick_btn = QPushButton("Remove Selected")
|
||||
self.remove_nick_btn.clicked.connect(self.remove_nickname)
|
||||
h_btn_layout.addWidget(self.add_nick_btn)
|
||||
h_btn_layout.addWidget(self.remove_nick_btn)
|
||||
u_layout.addLayout(h_btn_layout)
|
||||
self.next1 = QPushButton("Next")
|
||||
self.next1.clicked.connect(lambda: self.stack.setCurrentIndex(2))
|
||||
u_layout.addWidget(self.next1)
|
||||
self.step_users.setLayout(u_layout)
|
||||
self.stack.addWidget(self.step_users)
|
||||
|
||||
# Step 2: Favorites
|
||||
self.step_fav = QWidget()
|
||||
f_layout = QVBoxLayout()
|
||||
f_layout.addWidget(QLabel("Step 2: Add favorite servers/channels (optional)"))
|
||||
self.server_input = QLineEdit()
|
||||
self.server_input.setPlaceholderText("Server address (e.g., irc.libera.chat)")
|
||||
self.channel_input = QLineEdit()
|
||||
self.channel_input.setPlaceholderText("Channel (e.g., #python)")
|
||||
f_layout.addWidget(self.server_input)
|
||||
f_layout.addWidget(self.channel_input)
|
||||
self.fav_list = QListWidget()
|
||||
f_layout.addWidget(self.fav_list)
|
||||
h_fav_btn_layout = QHBoxLayout()
|
||||
self.add_fav_btn = QPushButton("Add")
|
||||
self.add_fav_btn.clicked.connect(self.add_favorite)
|
||||
self.remove_fav_btn = QPushButton("Remove Selected")
|
||||
self.remove_fav_btn.clicked.connect(self.remove_favorite)
|
||||
h_fav_btn_layout.addWidget(self.add_fav_btn)
|
||||
h_fav_btn_layout.addWidget(self.remove_fav_btn)
|
||||
f_layout.addLayout(h_fav_btn_layout)
|
||||
self.next2 = QPushButton("Finish")
|
||||
self.next2.clicked.connect(self.finish_setup)
|
||||
f_layout.addWidget(self.next2)
|
||||
self.step_fav.setLayout(f_layout)
|
||||
self.stack.addWidget(self.step_fav)
|
||||
|
||||
# --- Nicknames ---
|
||||
def add_nickname(self):
|
||||
nick = self.nick_input.text().strip()
|
||||
if nick:
|
||||
self.nicknames.append(nick)
|
||||
self.nick_list.addItem(nick)
|
||||
self.nick_input.clear()
|
||||
|
||||
def remove_nickname(self):
|
||||
selected = self.nick_list.currentRow()
|
||||
if selected >= 0:
|
||||
self.nick_list.takeItem(selected)
|
||||
self.nicknames.pop(selected)
|
||||
|
||||
# --- Favorites ---
|
||||
def add_favorite(self):
|
||||
server = self.server_input.text().strip()
|
||||
channel = self.channel_input.text().strip()
|
||||
if server and channel:
|
||||
self.favorites.append({"server": server, "channel": channel})
|
||||
self.fav_list.addItem(f"{server} {channel}")
|
||||
self.server_input.clear()
|
||||
self.channel_input.clear()
|
||||
|
||||
def remove_favorite(self):
|
||||
selected = self.fav_list.currentRow()
|
||||
if selected >= 0:
|
||||
self.fav_list.takeItem(selected)
|
||||
self.favorites.pop(selected)
|
||||
|
||||
# --- Finish ---
|
||||
def finish_setup(self):
|
||||
config = {
|
||||
"nicknames": self.nicknames,
|
||||
"favorites": self.favorites
|
||||
}
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
self.accept() # Close wizard
|
||||
|
||||
# --- Test ---
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
wizard = PIRCSetupWizard()
|
||||
if wizard.exec() == QDialog.Accepted:
|
||||
print("Setup completed!")
|
||||
with open(CONFIG_FILE, "r") as f:
|
||||
print(json.load(f))
|
||||
sys.exit(0)
|
||||
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "pirc"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"pyqt6>=6.9.1",
|
||||
"pyside6>=6.10.0",
|
||||
]
|
||||
126
uv.lock
generated
Normal file
126
uv.lock
generated
Normal file
@@ -0,0 +1,126 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "pirc"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "pyqt6" },
|
||||
{ name = "pyside6" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "pyqt6", specifier = ">=6.9.1" },
|
||||
{ name = "pyside6", specifier = ">=6.10.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6"
|
||||
version = "6.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyqt6-qt6" },
|
||||
{ name = "pyqt6-sip" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/32/1b/567f46eb43ca961efd38d7a0b73efb70d7342854f075fd919179fdb2a571/pyqt6-6.9.1.tar.gz", hash = "sha256:50642be03fb40f1c2111a09a1f5a0f79813e039c15e78267e6faaf8a96c1c3a6", size = 1067230, upload-time = "2025-06-06T08:49:30.307Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/c4/fc2a69cf3df09b213185ef5a677c3940cd20e7855d29e40061a685b9c6ee/pyqt6-6.9.1-cp39-abi3-macosx_10_14_universal2.whl", hash = "sha256:33c23d28f6608747ecc8bfd04c8795f61631af9db4fb1e6c2a7523ec4cc916d9", size = 59770566, upload-time = "2025-06-06T08:48:20.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/78/92f3c46440a83ebe22ae614bd6792e7b052bcb58ff128f677f5662015184/pyqt6-6.9.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:37884df27f774e2e1c0c96fa41e817a222329b80ffc6241725b0dc8c110acb35", size = 37804959, upload-time = "2025-06-06T08:48:39.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/5e/e77fa2761d809cd08d724f44af01a4b6ceb0ff9648e43173187b0e4fac4e/pyqt6-6.9.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:055870b703c1a49ca621f8a89e2ec4d848e6c739d39367eb9687af3b056d9aa3", size = 40414608, upload-time = "2025-06-06T08:49:00.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/09/69cf80456b6a985e06dd24ed0c2d3451e43567bf2807a5f3a86ef7a74a2e/pyqt6-6.9.1-cp39-abi3-win_amd64.whl", hash = "sha256:15b95bd273bb6288b070ed7a9503d5ff377aa4882dd6d175f07cad28cdb21da0", size = 25717996, upload-time = "2025-06-06T08:49:13.208Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/b3/0839d8fd18b86362a4de384740f2f6b6885b5d06fda7720f8a335425e316/pyqt6-6.9.1-cp39-abi3-win_arm64.whl", hash = "sha256:08792c72d130a02e3248a120f0b9bbb4bf4319095f92865bc5b365b00518f53d", size = 25212132, upload-time = "2025-06-06T08:49:27.41Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-qt6"
|
||||
version = "6.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/6f/fe2cd9cb2201c685be2f50c8c915df97848cac3dca4bad44bc3aed56fc63/pyqt6_qt6-6.9.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:183b62be49216da80c7df1931d74885610a88f74812489d29610d13b7c215a1c", size = 66568266, upload-time = "2025-09-01T11:43:31.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/1d/47dc51b4383b350f4ff6b1db461b01eba580030683ffa65475b4fdd9b80d/pyqt6_qt6-6.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7897fb74ee21bdc87b5ccf84e94f4a551377e792fd180a9211c17eb41c3338a3", size = 60859706, upload-time = "2025-09-01T11:43:36.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/07/21f7dc188e35b46631707f3b40ace5643a0e03a8e1e446854826d08a04ae/pyqt6_qt6-6.9.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9abfc0ee4a8293a6442128ae3f87f68e82e2a949d7b9caabd98c86ba5679ab48", size = 82322871, upload-time = "2025-09-01T11:43:41.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/c0/da658e735817feaa35ddfddb4c5d699291e8b8e3138e69ad7ae1a38a7db8/pyqt6_qt6-6.9.2-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:940aac6462532578e8ddefe0494cd17e33a85e0f3cfb21c612f56ab9ad7bc871", size = 80826693, upload-time = "2025-09-01T11:43:46.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/3a/d811ed1aa579b93ab56188d1371b05eacb4188599d83e72b761263a10f92/pyqt6_qt6-6.9.2-py3-none-win_amd64.whl", hash = "sha256:f9289768039bef4a63e5949b7f8cfbbddc3b6d24bd58c21ba0f2921bed8d1c08", size = 74147171, upload-time = "2025-09-01T11:43:53.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/59/7db6c5ddcb60ef3ecca2040274a30e8bc35b569c49e25e1cf2ef9f159426/pyqt6_qt6-6.9.2-py3-none-win_arm64.whl", hash = "sha256:8f82944ef68c8f8c78aa8eca4832c7bc05116c6de00a3bad8af5a0d63d1caafb", size = 54534019, upload-time = "2025-09-01T11:43:58.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-sip"
|
||||
version = "13.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/4a/96daf6c2e4f689faae9bd8cebb52754e76522c58a6af9b5ec86a2e8ec8b4/pyqt6_sip-13.10.2.tar.gz", hash = "sha256:464ad156bf526500ce6bd05cac7a82280af6309974d816739b4a9a627156fafe", size = 92548, upload-time = "2025-05-23T12:26:49.901Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/1e/979ea64c98ca26979d8ce11e9a36579e17d22a71f51d7366d6eec3c82c13/pyqt6_sip-13.10.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8b5d06a0eac36038fa8734657d99b5fe92263ae7a0cd0a67be6acfe220a063e1", size = 112227, upload-time = "2025-05-23T12:26:38.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/21/84c230048e3bfef4a9209d16e56dcd2ae10590d03a31556ae8b5f1dcc724/pyqt6_sip-13.10.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad376a6078da37b049fdf9d6637d71b52727e65c4496a80b753ddc8d27526aca", size = 322920, upload-time = "2025-05-23T12:26:39.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1e/c6a28a142f14e735088534cc92951c3f48cccd77cdd4f3b10d7996be420f/pyqt6_sip-13.10.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3dde8024d055f496eba7d44061c5a1ba4eb72fc95e5a9d7a0dbc908317e0888b", size = 303833, upload-time = "2025-05-23T12:26:41.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/63/e5adf350c1c3123d4865c013f164c5265512fa79f09ad464fb2fdf9f9e61/pyqt6_sip-13.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:0b097eb58b4df936c4a2a88a2f367c8bb5c20ff049a45a7917ad75d698e3b277", size = 53527, upload-time = "2025-05-23T12:26:42.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/74/2df4195306d050fbf4963fb5636108a66e5afa6dc05fd9e81e51ec96c384/pyqt6_sip-13.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:cc6a1dfdf324efaac6e7b890a608385205e652845c62130de919fd73a6326244", size = 45373, upload-time = "2025-05-23T12:26:43.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/57/74b4eb7a51b9133958daa8409b55de95e44feb694d4e2e3eba81a070ca20/pyqt6_sip-13.10.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8a76a06a8e5c5b1f17a3f6f3c834ca324877e07b960b18b8b9bbfd9c536ec658", size = 112354, upload-time = "2025-10-08T08:44:00.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/cb/fdef02e0d6ee8443a9683a43650d61c6474b634b6ae6e1c6f097da6310bf/pyqt6_sip-13.10.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9128d770a611200529468397d710bc972f1dcfe12bfcbb09a3ccddcd4d54fa5b", size = 323488, upload-time = "2025-10-08T08:44:01.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/5b/8ede8d6234c3ea884cbd097d7d47ff9910fb114efe041af62b4453acd23b/pyqt6_sip-13.10.2-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d820a0fae7315932c08f27dc0a7e33e0f50fe351001601a8eb9cf6f22b04562e", size = 303881, upload-time = "2025-10-08T08:44:04.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/44/b5e78b072d1594643b0f1ff348f2bf54d4adb5a3f9b9f0989c54e33238d6/pyqt6_sip-13.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:3213bb6e102d3842a3bb7e59d5f6e55f176c80880ff0b39d0dac0cfe58313fb3", size = 55098, upload-time = "2025-10-08T08:44:08.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/91/357e9fcef5d830c3d50503d35e0357818aca3540f78748cc214dfa015d00/pyqt6_sip-13.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:ce33ff1f94960ad4b08035e39fa0c3c9a67070bec39ffe3e435c792721504726", size = 46088, upload-time = "2025-10-08T08:44:10.014Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyside6"
|
||||
version = "6.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyside6-addons" },
|
||||
{ name = "pyside6-essentials" },
|
||||
{ name = "shiboken6" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0", size = 558115, upload-time = "2025-10-08T09:47:09.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/76/0961c8c5653ecb60a6881b649dcb6b71a6be5bd1c8d441ecc48ac7f50b1a/pyside6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ae8c3c8339cd7c3c9faa7cc5c52670dcc8662ccf4b63a6fed61c6345b90c4c01", size = 557762, upload-time = "2025-10-08T09:47:11.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/73/6187502fff8b6599443d15c46dd900b2ded24be5aacb2becce33f6faf566/pyside6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9f402f883e640048fab246d36e298a5e16df9b18ba2e8c519877e472d3602820", size = 558299, upload-time = "2025-10-08T09:47:14.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/67/94794ebaf198bbdb35cb77f19f38370f9b323b036ab149874bc33c38faab/pyside6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f", size = 564367, upload-time = "2025-10-08T09:47:16.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/cc/552331d413c1b933d54ed45e33cc7ff29d0b239677975fe2528e7ac8bfbc/pyside6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:4b709bdeeb89d386059343a5a706fc185cee37b517bda44c7d6b64d5fdaf3339", size = 548826, upload-time = "2025-10-08T09:47:18.399Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyside6-addons"
|
||||
version = "6.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyside6-essentials" },
|
||||
{ name = "shiboken6" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c", size = 322248256, upload-time = "2025-10-08T09:47:37.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/b8/d129210f2c7366b4e1bf5bb6230be42052b29e8ba1b1d7db6ef333cf5a39/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:08d4ed46c4c9a353a9eb84134678f8fdd4ce17fb8cce2b3686172a7575025464", size = 170238987, upload-time = "2025-10-08T09:47:51.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/ae/ede1edd009395092219f3437d2ee59f9ba93739c28c040542ed47c6cc831/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:15d32229d681be0bba1b936c4a300da43d01e1917ada5b57f9e03a387c245ab0", size = 165939425, upload-time = "2025-10-08T09:48:02.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/5d/a3c32f85ac7f905c95679967c0ddda0ba043c273b75623cc90d8185064e4/pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859", size = 164814172, upload-time = "2025-10-08T09:48:12.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/2a/4ff71b09571202c8e1320c45276fc1d0cd81ee53107dfc17bb22d4243f88/pyside6_addons-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:92536427413f3b6557cf53f1a515cd766725ee46a170aff57ad2ff1dfce0ffb1", size = 34104251, upload-time = "2025-10-08T09:48:18.287Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyside6-essentials"
|
||||
version = "6.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "shiboken6" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a", size = 105034090, upload-time = "2025-10-08T09:48:24.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/75/e17efc7eb900993e0e3925885635c6cf373c817196f09bcbcc102b00ac94/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1d5e013a8698e37ab8ef360e6960794eb5ef20832a8d562e649b8c5a0574b2d8", size = 76362150, upload-time = "2025-10-08T09:48:31.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/62/fbd1e81caafcda97b147c03f5b06cfaadd8da5fa8298f527d2ec648fa5b7/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b1dd0864f0577a448fb44426b91cafff7ee7cccd1782ba66491e1c668033f998", size = 75454169, upload-time = "2025-10-08T09:48:38.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/3a/d8211d17e6ca70f641c6ebd309f08ef18930acda60e74082c75875a274da/pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe", size = 74361794, upload-time = "2025-10-08T09:48:44.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/e9/0e22e3c10325c4ff09447fadb43f7962afb82cef0b65358f5704251c6b32/pyside6_essentials-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:6dd0936394cb14da2fd8e869899f5e0925a738b1c8d74c2f22503720ea363fb1", size = 55099467, upload-time = "2025-10-08T09:48:50.902Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shiboken6"
|
||||
version = "6.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543", size = 476535, upload-time = "2025-10-08T09:49:08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/09/4ffa3284a17b6b765d45b41c9a7f1b2cde6c617c853ac6f170fb62bbbece/shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e612734da515d683696980107cdc0396a3ae0f07b059f0f422ec8a2333810234", size = 271098, upload-time = "2025-10-08T09:49:09.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/29/00e26f33a0fb259c2edce9c761a7a438d7531ca514bdb1a4c072673bd437/shiboken6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b01377e68d14132360efb0f4b7233006d26aa8ae9bb50edf00960c2a5f52d148", size = 267698, upload-time = "2025-10-08T09:49:10.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/30/e4624a7e3f0dc9796b701079b77defcce0d32d1afc86bb1d0df04bc3d9e2/shiboken6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717", size = 1234227, upload-time = "2025-10-08T09:49:12.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/e5/0ab862005ea87dc8647ba958a3099b3b0115fd6491c65da5c5a0f6364db1/shiboken6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:dfc4beab5fec7dbbebbb418f3bf99af865d6953aa0795435563d4cbb82093b61", size = 1794775, upload-time = "2025-10-08T09:49:14.641Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user