Compare commits

..

No commits in common. "4f4ff35ee5c2aa77a1e62134de4374de256da931" and "36ff8896efaba0cb23e71d093bf1a0c93c093e9f" have entirely different histories.

3 changed files with 25 additions and 308 deletions

View File

@ -1,244 +0,0 @@
import sys
import subprocess
import re
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QVBoxLayout,
QPushButton, QLineEdit, QMessageBox)
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject
from PyQt5.QtGui import QDesktopServices
class AuthenticationParser:
@staticmethod
def clean_ansi(text):
ansi_clean = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
printable_clean = re.compile(r'[^\x20-\x7E\n]')
text = ansi_clean.sub('', text)
text = printable_clean.sub('', text)
return text.strip()
@staticmethod
def is_auth_error(output):
cleaned_output = AuthenticationParser.clean_ansi(output)
return "AADSTS70016" in cleaned_output and "not yet been authorized" in cleaned_output
@staticmethod
def parse_auth_output(output):
cleaned_output = AuthenticationParser.clean_ansi(output)
if AuthenticationParser.is_auth_error(cleaned_output):
return None
pattern = r"https://[^\s]+"
code_pattern = r"code\s+([A-Z0-9]+)"
url_match = re.search(pattern, cleaned_output)
code_match = re.search(code_pattern, cleaned_output, re.IGNORECASE)
if url_match and code_match:
return {
'url': url_match.group(0),
'code': code_match.group(1)
}
return None
class AuthDialog(QDialog):
def __init__(self, url, code, parent=None, error_mode=False):
super().__init__(parent)
self.setWindowTitle("Microsoft Authentication")
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self.setModal(True)
self.setup_ui(url, code, error_mode)
def setup_ui(self, url, code, error_mode):
layout = QVBoxLayout(self)
if error_mode:
error_label = QLabel("Error in Login - Please try again")
error_label.setStyleSheet("QLabel { color: red; font-weight: bold; }")
layout.addWidget(error_label)
instructions = QLabel(
"To authenticate your Microsoft Account:\n\n"
"1. Click 'Open Authentication Page' or visit:\n"
"2. Copy the code below\n"
"3. Paste the code on the Microsoft website\n"
"4. After completing authentication, click 'I've Completed Authentication'"
)
instructions.setWordWrap(True)
layout.addWidget(instructions)
url_label = QLabel(url)
url_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
url_label.setWordWrap(True)
layout.addWidget(url_label)
self.code_input = QLineEdit(code)
self.code_input.setReadOnly(True)
self.code_input.setAlignment(Qt.AlignCenter)
self.code_input.setStyleSheet("""
QLineEdit {
font-size: 16pt;
font-weight: bold;
padding: 5px;
}
""")
layout.addWidget(self.code_input)
copy_button = QPushButton("Copy Code")
copy_button.clicked.connect(self.copy_code)
layout.addWidget(copy_button)
open_url_button = QPushButton("Open Authentication Page")
open_url_button.clicked.connect(lambda: self.open_url(url))
layout.addWidget(open_url_button)
continue_button = QPushButton("I've Completed Authentication")
continue_button.clicked.connect(self.accept)
layout.addWidget(continue_button)
def copy_code(self):
clipboard = QApplication.clipboard()
clipboard.setText(self.code_input.text())
def open_url(self, url):
QDesktopServices.openUrl(QUrl(url))
class AuthenticationThread(QThread):
auth_data_received = pyqtSignal(dict)
error_occurred = pyqtSignal(str)
auth_error_detected = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, account):
super().__init__()
self.account = account
self.process = None
self.is_running = True
self.current_output = ""
self.waiting_for_auth = False
def run(self):
try:
command = f'picomc account authenticate {self.account}'
self.process = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
self.current_output = ""
while self.is_running and self.process.poll() is None:
line = self.process.stdout.readline()
if line:
self.current_output += line
if not self.waiting_for_auth:
parsed_data = AuthenticationParser.parse_auth_output(self.current_output)
if parsed_data:
self.auth_data_received.emit(parsed_data)
self.waiting_for_auth = True
self.current_output = ""
elif AuthenticationParser.is_auth_error(self.current_output):
self.auth_error_detected.emit(self.current_output)
self.waiting_for_auth = False
self.current_output = ""
self.process.wait()
self.finished.emit()
except Exception as e:
self.error_occurred.emit(str(e))
self.finished.emit()
def send_enter(self):
if self.process and self.process.poll() is None:
self.process.stdin.write("\n")
self.process.stdin.flush()
def stop(self):
self.is_running = False
if self.process:
self.process.terminate()
class MinecraftAuthenticator(QObject): # Changed to inherit from QObject
auth_finished = pyqtSignal(bool) # Add signal for completion
def __init__(self, parent=None):
super().__init__(parent)
self.auth_thread = None
self.current_auth_data = None
self.auth_dialog = None
self.success = False
def authenticate(self, username):
"""
Start the authentication process for the given username
Returns immediately, authentication result will be emitted via auth_finished signal
"""
self.success = False
self.auth_thread = AuthenticationThread(username)
self.auth_thread.auth_data_received.connect(self.show_auth_dialog)
self.auth_thread.auth_error_detected.connect(self.handle_auth_error)
self.auth_thread.error_occurred.connect(self.show_error)
self.auth_thread.finished.connect(self.on_authentication_finished)
self.auth_thread.start()
def show_auth_dialog(self, auth_data):
self.current_auth_data = auth_data
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = None
self.auth_dialog = AuthDialog(auth_data['url'], auth_data['code'])
if self.auth_dialog.exec_() == QDialog.Accepted:
self.auth_thread.send_enter()
def handle_auth_error(self, output):
if self.current_auth_data:
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = None
self.auth_dialog = AuthDialog(
self.current_auth_data['url'],
self.current_auth_data['code'],
error_mode=True
)
if self.auth_dialog.exec_() == QDialog.Accepted:
self.auth_thread.send_enter()
def show_error(self, error_message):
QMessageBox.critical(None, "Error", f"Authentication error: {error_message}")
self.success = False
self.auth_finished.emit(False)
def on_authentication_finished(self):
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = None
if self.auth_thread:
self.auth_thread.stop()
self.auth_thread = None
self.success = True
self.auth_finished.emit(True)
def cleanup(self):
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = None
if self.auth_thread and self.auth_thread.isRunning():
self.auth_thread.stop()
self.auth_thread.wait()
# Example usage
if __name__ == '__main__':
authenticator = MinecraftAuthenticator()
authenticator.authenticate("TestUser")

View File

@ -10,7 +10,6 @@ import requests
import json import json
import os import os
import time import time
from authser import MinecraftAuthenticator
from PyQt5.QtWidgets import QApplication, QComboBox, QWidget, QInputDialog, QVBoxLayout, QListWidget, QPushButton, QMessageBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QTabWidget, QFrame, QSpacerItem, QSizePolicy, QMainWindow, QGridLayout, QTextEdit, QListWidget, QListWidgetItem, QMenu from PyQt5.QtWidgets import QApplication, QComboBox, QWidget, QInputDialog, QVBoxLayout, QListWidget, QPushButton, QMessageBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QTabWidget, QFrame, QSpacerItem, QSizePolicy, QMainWindow, QGridLayout, QTextEdit, QListWidget, QListWidgetItem, QMenu
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QMovie, QPixmap, QDesktopServices, QBrush from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QMovie, QPixmap, QDesktopServices, QBrush
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QThread, QUrl, QMetaObject, Q_ARG, QByteArray, QSize from PyQt5.QtCore import Qt, QObject, pyqtSignal, QThread, QUrl, QMetaObject, Q_ARG, QByteArray, QSize
@ -52,10 +51,6 @@ class PicomcVersionSelector(QWidget):
if self.config.get("IsFirstLaunch", False): if self.config.get("IsFirstLaunch", False):
self.FirstLaunch() self.FirstLaunch()
self.authenticator = MinecraftAuthenticator(self)
self.authenticator.auth_finished.connect(self._on_auth_finished)
def load_theme_from_file(self, file_path, app): def load_theme_from_file(self, file_path, app):
self.theme = {} self.theme = {}
# Check if the file exists, else load 'Dark.json' # Check if the file exists, else load 'Dark.json'
@ -450,27 +445,24 @@ class PicomcVersionSelector(QWidget):
# QListWidget to display available themes # QListWidget to display available themes
json_files_label = QLabel('Installed Themes:') json_files_label = QLabel('Installed Themes:')
self.json_files_list_widget = QListWidget() json_files_list_widget = QListWidget()
# Track selected theme # Track selected theme
self.selected_theme = theme_filename # Default to current theme self.selected_theme = theme_filename # Default to current theme
# Build the list of themes
themes_list = self.build_themes_list()
# Populate themes initially # Populate themes initially
self.populate_themes(self.json_files_list_widget, themes_list) self.populate_themes(json_files_list_widget)
# Update current theme label when a theme is selected # Update current theme label when a theme is selected
self.json_files_list_widget.itemClicked.connect( json_files_list_widget.itemClicked.connect(
lambda: self.on_theme_selected(self.json_files_list_widget, current_theme_label) lambda: self.on_theme_selected(json_files_list_widget, current_theme_label)
) )
# Add widgets to the layout # Add widgets to the layout
customization_layout.addWidget(theme_background_checkbox) customization_layout.addWidget(theme_background_checkbox)
customization_layout.addWidget(current_theme_label) customization_layout.addWidget(current_theme_label)
customization_layout.addWidget(json_files_label) customization_layout.addWidget(json_files_label)
customization_layout.addWidget(self.json_files_list_widget) customization_layout.addWidget(json_files_list_widget)
# Button to download themes # Button to download themes
download_themes_button = QPushButton("Download More Themes") download_themes_button = QPushButton("Download More Themes")
@ -515,9 +507,9 @@ class PicomcVersionSelector(QWidget):
if response == QMessageBox.No: if response == QMessageBox.No:
checkbox.setChecked(False) checkbox.setChecked(False)
def build_themes_list(self): def populate_themes(self, json_files_list_widget):
themes_folder = os.path.join(os.getcwd(), "themes") themes_folder = os.path.join(os.getcwd(), "themes")
themes_list = [] json_files_list_widget.clear()
if os.path.exists(themes_folder): if os.path.exists(themes_folder):
json_files = [f for f in os.listdir(themes_folder) if f.endswith('.json')] json_files = [f for f in os.listdir(themes_folder) if f.endswith('.json')]
for json_file in json_files: for json_file in json_files:
@ -533,12 +525,6 @@ class PicomcVersionSelector(QWidget):
# Create display text and list item # Create display text and list item
display_text = f"{name}\n{description}\nBy: {author}" display_text = f"{name}\n{description}\nBy: {author}"
themes_list.append((display_text, json_file))
return themes_list
def populate_themes(self, json_files_list_widget, themes_list):
json_files_list_widget.clear()
for display_text, json_file in themes_list:
list_item = QListWidgetItem(display_text) list_item = QListWidgetItem(display_text)
list_item.setData(Qt.UserRole, json_file) # Store the JSON filename as metadata list_item.setData(Qt.UserRole, json_file) # Store the JSON filename as metadata
@ -603,16 +589,9 @@ class PicomcVersionSelector(QWidget):
main_layout.addLayout(right_layout) main_layout.addLayout(right_layout)
dialog.setLayout(main_layout) dialog.setLayout(main_layout)
dialog.finished.connect(lambda: self.update_themes_list())
self.load_themes() self.load_themes()
dialog.exec_() dialog.exec_()
def update_themes_list(self):
themes_list = self.build_themes_list()
self.populate_themes(self.json_files_list_widget, themes_list)
def fetch_themes(self): def fetch_themes(self):
try: try:
with open("config.json", "r") as config_file: with open("config.json", "r") as config_file:
@ -1084,36 +1063,19 @@ class PicomcVersionSelector(QWidget):
return False return False
def authenticate_account(self, dialog, account_name): def authenticate_account(self, dialog, account_name):
# Clean up the account name # Authenticate a selected account
account_name = account_name.strip().lstrip(" * ") account_name = account_name.strip().lstrip(" * ")
if not account_name: if not account_name:
QMessageBox.warning(dialog, "Warning", "Please select an account to authenticate.") QMessageBox.warning(dialog, "Warning", "Please select an account to authenticate.")
return return
try: try:
# Create authenticator instance if it doesn't exist subprocess.run(['picomc', 'account', 'authenticate', account_name], check=True)
if self.authenticator is None: QMessageBox.information(self, "Success", f"Account '{account_name}' authenticated successfully!")
self.authenticator = MinecraftAuthenticator(self) except subprocess.CalledProcessError as e:
self.authenticator.auth_finished.connect(self._on_auth_finished) error_message = f"Error authenticating account '{account_name}': {e.stderr.decode()}"
# Start authentication process
self.authenticator.authenticate(account_name)
except Exception as e:
error_message = f"Error authenticating account '{account_name}': {str(e)}"
logging.error(error_message) logging.error(error_message)
QMessageBox.critical(dialog, "Error", error_message) QMessageBox.critical(self, "Error", error_message)
def _on_auth_finished(self, success):
if success:
QMessageBox.information(self, "Success", "Account authenticated successfully!")
else:
QMessageBox.critical(self, "Error", "Failed to authenticate account")
# Cleanup
if self.authenticator:
self.authenticator.cleanup()
self.authenticator = None
def remove_account(self, dialog, username): def remove_account(self, dialog, username):
# Remove a selected account # Remove a selected account

View File

@ -1,13 +1,12 @@
{ {
"version": "0.12", "version": "0.11.9",
"links": [ "links": [
"https://raw.githubusercontent.com/nixietab/picodulce/main/version.json", "https://raw.githubusercontent.com/nixietab/picodulce/main/version.json",
"https://raw.githubusercontent.com/nixietab/picodulce/main/picodulce.py", "https://raw.githubusercontent.com/nixietab/picodulce/main/picodulce.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/requirements.txt", "https://raw.githubusercontent.com/nixietab/picodulce/main/requirements.txt",
"https://raw.githubusercontent.com/nixietab/picodulce/main/drums.gif", "https://raw.githubusercontent.com/nixietab/picodulce/main/drums.gif",
"https://raw.githubusercontent.com/nixietab/picodulce/main/marroc.py", "https://raw.githubusercontent.com/nixietab/picodulce/main/marroc.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/holiday.ico", "https://raw.githubusercontent.com/nixietab/picodulce/main/holiday.ico"
"https://raw.githubusercontent.com/nixietab/picodulce/main/authser.py"
], ],
"versionBleeding": "0.12-168" "versionBleeding": "0.11.9-156"
} }