mirror of
https://github.com/nixietab/picodulce.git
synced 2025-04-18 14:28:57 +01:00
Compare commits
No commits in common. "main" and "0.12.1" have entirely different histories.
356
authser.py
356
authser.py
@ -1,29 +1,43 @@
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import subprocess
|
||||||
import os
|
import re
|
||||||
import uuid
|
|
||||||
import asyncio
|
|
||||||
import aiohttp
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from pathlib import Path
|
|
||||||
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QVBoxLayout,
|
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QVBoxLayout,
|
||||||
QPushButton, QLineEdit, QMessageBox)
|
QPushButton, QLineEdit, QMessageBox)
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject
|
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from picomc.logging import logger
|
|
||||||
from picomc.launcher import get_default_root, Launcher
|
|
||||||
|
|
||||||
# Constants for Microsoft Authentication
|
class AuthenticationParser:
|
||||||
URL_DEVICE_AUTH = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"
|
@staticmethod
|
||||||
URL_TOKEN = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
|
def clean_ansi(text):
|
||||||
URL_XBL = "https://user.auth.xboxlive.com/user/authenticate"
|
ansi_clean = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||||
URL_XSTS = "https://xsts.auth.xboxlive.com/xsts/authorize"
|
printable_clean = re.compile(r'[^\x20-\x7E\n]')
|
||||||
URL_MC = "https://api.minecraftservices.com/authentication/login_with_xbox"
|
text = ansi_clean.sub('', text)
|
||||||
URL_PROFILE = "https://api.minecraftservices.com/minecraft/profile"
|
text = printable_clean.sub('', text)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
CLIENT_ID = "c52aed44-3b4d-4215-99c5-824033d2bc0f"
|
@staticmethod
|
||||||
SCOPE = "XboxLive.signin offline_access"
|
def is_auth_error(output):
|
||||||
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
|
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):
|
class AuthDialog(QDialog):
|
||||||
def __init__(self, url, code, parent=None, error_mode=False):
|
def __init__(self, url, code, parent=None, error_mode=False):
|
||||||
@ -32,10 +46,10 @@ class AuthDialog(QDialog):
|
|||||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setup_ui(url, code, error_mode)
|
self.setup_ui(url, code, error_mode)
|
||||||
|
|
||||||
def setup_ui(self, url, code, error_mode):
|
def setup_ui(self, url, code, error_mode):
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
if error_mode:
|
if error_mode:
|
||||||
error_label = QLabel("Error in Login - Please try again")
|
error_label = QLabel("Error in Login - Please try again")
|
||||||
error_label.setStyleSheet("QLabel { color: red; font-weight: bold; }")
|
error_label.setStyleSheet("QLabel { color: red; font-weight: bold; }")
|
||||||
@ -92,254 +106,117 @@ class AuthenticationThread(QThread):
|
|||||||
error_occurred = pyqtSignal(str)
|
error_occurred = pyqtSignal(str)
|
||||||
auth_error_detected = pyqtSignal(str)
|
auth_error_detected = pyqtSignal(str)
|
||||||
finished = pyqtSignal()
|
finished = pyqtSignal()
|
||||||
access_token_received = pyqtSignal(dict)
|
|
||||||
|
|
||||||
def __init__(self, username):
|
def __init__(self, account):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.username = username
|
self.account = account
|
||||||
self.device_code = None
|
self.process = None
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
|
self.current_output = ""
|
||||||
async def _ms_oauth(self):
|
self.waiting_for_auth = False
|
||||||
data = {"client_id": CLIENT_ID, "scope": SCOPE}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.post(URL_DEVICE_AUTH, data=data) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
raise Exception(f"Failed to get device code: {await resp.text()}")
|
|
||||||
j = await resp.json()
|
|
||||||
self.device_code = j["device_code"]
|
|
||||||
self.auth_data_received.emit({
|
|
||||||
'url': j["verification_uri"],
|
|
||||||
'code': j["user_code"]
|
|
||||||
})
|
|
||||||
|
|
||||||
while self.is_running:
|
|
||||||
data = {
|
|
||||||
"grant_type": GRANT_TYPE,
|
|
||||||
"client_id": CLIENT_ID,
|
|
||||||
"device_code": self.device_code
|
|
||||||
}
|
|
||||||
|
|
||||||
async with session.post(URL_TOKEN, data=data) as resp:
|
|
||||||
j = await resp.json()
|
|
||||||
if resp.status == 400:
|
|
||||||
if j["error"] == "authorization_pending":
|
|
||||||
await asyncio.sleep(2)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise Exception(j["error_description"])
|
|
||||||
elif resp.status != 200:
|
|
||||||
raise Exception(f"Token request failed: {j}")
|
|
||||||
|
|
||||||
return j["access_token"], j["refresh_token"]
|
|
||||||
|
|
||||||
async def _xbl_auth(self, access_token):
|
|
||||||
data = {
|
|
||||||
"Properties": {
|
|
||||||
"AuthMethod": "RPS",
|
|
||||||
"SiteName": "user.auth.xboxlive.com",
|
|
||||||
"RpsTicket": f"d={access_token}"
|
|
||||||
},
|
|
||||||
"RelyingParty": "http://auth.xboxlive.com",
|
|
||||||
"TokenType": "JWT"
|
|
||||||
}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.post(URL_XBL, json=data) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
raise Exception(f"XBL auth failed: {await resp.text()}")
|
|
||||||
j = await resp.json()
|
|
||||||
return j["Token"], j["DisplayClaims"]["xui"][0]["uhs"]
|
|
||||||
|
|
||||||
async def _xsts_auth(self, xbl_token):
|
|
||||||
data = {
|
|
||||||
"Properties": {
|
|
||||||
"SandboxId": "RETAIL",
|
|
||||||
"UserTokens": [xbl_token]
|
|
||||||
},
|
|
||||||
"RelyingParty": "rp://api.minecraftservices.com/",
|
|
||||||
"TokenType": "JWT"
|
|
||||||
}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.post(URL_XSTS, json=data) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
raise Exception(f"XSTS auth failed: {await resp.text()}")
|
|
||||||
j = await resp.json()
|
|
||||||
return j["Token"]
|
|
||||||
|
|
||||||
async def _mc_auth(self, uhs, xsts_token):
|
|
||||||
data = {
|
|
||||||
"identityToken": f"XBL3.0 x={uhs};{xsts_token}"
|
|
||||||
}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.post(URL_MC, json=data) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
raise Exception(f"MC auth failed: {await resp.text()}")
|
|
||||||
j = await resp.json()
|
|
||||||
return j["access_token"]
|
|
||||||
|
|
||||||
async def _get_profile(self, mc_token):
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {mc_token}"
|
|
||||||
}
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(URL_PROFILE, headers=headers) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
raise Exception(f"Profile request failed: {await resp.text()}")
|
|
||||||
return await resp.json()
|
|
||||||
|
|
||||||
async def _auth_flow(self):
|
|
||||||
try:
|
|
||||||
ms_access_token, refresh_token = await self._ms_oauth()
|
|
||||||
xbl_token, uhs = await self._xbl_auth(ms_access_token)
|
|
||||||
xsts_token = await self._xsts_auth(xbl_token)
|
|
||||||
mc_token = await self._mc_auth(uhs, xsts_token)
|
|
||||||
profile = await self._get_profile(mc_token)
|
|
||||||
|
|
||||||
self.access_token_received.emit({
|
|
||||||
'access_token': mc_token,
|
|
||||||
'refresh_token': refresh_token,
|
|
||||||
'profile': profile
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.error_occurred.emit(str(e))
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
loop = asyncio.new_event_loop()
|
command = f'picomc account authenticate {self.account}'
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
loop.run_until_complete(self._auth_flow())
|
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:
|
except Exception as e:
|
||||||
self.error_occurred.emit(str(e))
|
self.error_occurred.emit(str(e))
|
||||||
finally:
|
|
||||||
self.finished.emit()
|
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):
|
def stop(self):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
if self.process:
|
||||||
|
self.process.terminate()
|
||||||
|
|
||||||
class MinecraftAuthenticator(QObject):
|
class MinecraftAuthenticator(QObject): # Changed to inherit from QObject
|
||||||
auth_finished = pyqtSignal(bool)
|
auth_finished = pyqtSignal(bool) # Add signal for completion
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.auth_thread = None
|
self.auth_thread = None
|
||||||
|
self.current_auth_data = None
|
||||||
self.auth_dialog = None
|
self.auth_dialog = None
|
||||||
self.success = False
|
self.success = False
|
||||||
self.username = None
|
|
||||||
|
|
||||||
# Initialize the launcher to get the correct config path
|
|
||||||
with Launcher.new() as launcher:
|
|
||||||
self.config_path = launcher.root
|
|
||||||
|
|
||||||
def authenticate(self, username):
|
def authenticate(self, username):
|
||||||
self.username = username
|
"""
|
||||||
|
Start the authentication process for the given username
|
||||||
|
Returns immediately, authentication result will be emitted via auth_finished signal
|
||||||
|
"""
|
||||||
self.success = False
|
self.success = False
|
||||||
|
|
||||||
# Create accounts.json if it doesn't exist
|
|
||||||
if not self.save_to_accounts_json():
|
|
||||||
return
|
|
||||||
|
|
||||||
self.auth_thread = AuthenticationThread(username)
|
self.auth_thread = AuthenticationThread(username)
|
||||||
self.auth_thread.auth_data_received.connect(self.show_auth_dialog)
|
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.error_occurred.connect(self.show_error)
|
||||||
self.auth_thread.access_token_received.connect(self.on_access_token_received)
|
|
||||||
self.auth_thread.finished.connect(self.on_authentication_finished)
|
self.auth_thread.finished.connect(self.on_authentication_finished)
|
||||||
self.auth_thread.start()
|
self.auth_thread.start()
|
||||||
|
|
||||||
def show_auth_dialog(self, auth_data):
|
def show_auth_dialog(self, auth_data):
|
||||||
|
self.current_auth_data = auth_data
|
||||||
|
|
||||||
if self.auth_dialog is not None:
|
if self.auth_dialog is not None:
|
||||||
self.auth_dialog.close()
|
self.auth_dialog.close()
|
||||||
|
self.auth_dialog = None
|
||||||
|
|
||||||
self.auth_dialog = AuthDialog(auth_data['url'], auth_data['code'])
|
self.auth_dialog = AuthDialog(auth_data['url'], auth_data['code'])
|
||||||
|
if self.auth_dialog.exec_() == QDialog.Accepted:
|
||||||
result = self.auth_dialog.exec_()
|
self.auth_thread.send_enter()
|
||||||
|
|
||||||
if result != QDialog.Accepted:
|
|
||||||
self.auth_thread.stop()
|
|
||||||
|
|
||||||
def show_error(self, error_msg):
|
def handle_auth_error(self, output):
|
||||||
QMessageBox.critical(None, "Error", error_msg)
|
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.success = False
|
||||||
self.auth_finished.emit(False)
|
self.auth_finished.emit(False)
|
||||||
|
|
||||||
def save_to_accounts_json(self):
|
|
||||||
try:
|
|
||||||
accounts_file = Path(self.config_path) / "accounts.json"
|
|
||||||
|
|
||||||
if accounts_file.exists():
|
|
||||||
with open(accounts_file) as f:
|
|
||||||
config = json.load(f)
|
|
||||||
else:
|
|
||||||
config = {
|
|
||||||
"default": None,
|
|
||||||
"accounts": {},
|
|
||||||
"client_token": str(uuid.uuid4())
|
|
||||||
}
|
|
||||||
accounts_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Only create/update if account doesn't exist
|
|
||||||
if self.username not in config["accounts"]:
|
|
||||||
config["accounts"][self.username] = {
|
|
||||||
"uuid": "-",
|
|
||||||
"online": True,
|
|
||||||
"microsoft": True,
|
|
||||||
"gname": "-",
|
|
||||||
"access_token": "-",
|
|
||||||
"refresh_token": "-",
|
|
||||||
"is_authenticated": False
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set as default if no default exists
|
|
||||||
if config["default"] is None:
|
|
||||||
config["default"] = self.username
|
|
||||||
|
|
||||||
with open(accounts_file, 'w') as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to initialize account data: {str(e)}")
|
|
||||||
QMessageBox.critical(None, "Error", f"Failed to initialize account data: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def on_access_token_received(self, data):
|
|
||||||
try:
|
|
||||||
accounts_file = Path(self.config_path) / "accounts.json"
|
|
||||||
|
|
||||||
with open(accounts_file) as f:
|
|
||||||
config = json.load(f)
|
|
||||||
|
|
||||||
if self.username in config["accounts"]:
|
|
||||||
config["accounts"][self.username].update({
|
|
||||||
"access_token": data['access_token'],
|
|
||||||
"refresh_token": data['refresh_token'],
|
|
||||||
"uuid": data['profile']['id'],
|
|
||||||
"gname": data['profile']['name'],
|
|
||||||
"is_authenticated": True
|
|
||||||
})
|
|
||||||
|
|
||||||
with open(accounts_file, 'w') as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
self.success = True
|
|
||||||
QMessageBox.information(None, "Success",
|
|
||||||
f"Successfully authenticated account: {self.username}")
|
|
||||||
else:
|
|
||||||
raise Exception("Account not found in configuration")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to update account data: {str(e)}")
|
|
||||||
QMessageBox.critical(None, "Error", f"Failed to update account data: {str(e)}")
|
|
||||||
self.success = False
|
|
||||||
|
|
||||||
self.auth_finished.emit(self.success)
|
|
||||||
|
|
||||||
def on_authentication_finished(self):
|
def on_authentication_finished(self):
|
||||||
if self.auth_dialog is not None:
|
if self.auth_dialog is not None:
|
||||||
self.auth_dialog.close()
|
self.auth_dialog.close()
|
||||||
@ -349,8 +226,8 @@ class MinecraftAuthenticator(QObject):
|
|||||||
self.auth_thread.stop()
|
self.auth_thread.stop()
|
||||||
self.auth_thread = None
|
self.auth_thread = None
|
||||||
|
|
||||||
if not self.success:
|
self.success = True
|
||||||
self.auth_finished.emit(False)
|
self.auth_finished.emit(True)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
if self.auth_dialog is not None:
|
if self.auth_dialog is not None:
|
||||||
@ -361,6 +238,7 @@ class MinecraftAuthenticator(QObject):
|
|||||||
self.auth_thread.stop()
|
self.auth_thread.stop()
|
||||||
self.auth_thread.wait()
|
self.auth_thread.wait()
|
||||||
|
|
||||||
def create_authenticator():
|
# Example usage
|
||||||
"""Factory function to create a new MinecraftAuthenticator instance"""
|
if __name__ == '__main__':
|
||||||
return MinecraftAuthenticator()
|
authenticator = MinecraftAuthenticator()
|
||||||
|
authenticator.authenticate("TestUser")
|
||||||
|
30
modulecli.py
30
modulecli.py
@ -1,30 +0,0 @@
|
|||||||
import click
|
|
||||||
from picomc.cli.main import picomc_cli
|
|
||||||
from io import StringIO
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def run_command(command="picomc"):
|
|
||||||
# Redirect stdout and stderr to capture the command output
|
|
||||||
old_stdout, old_stderr = sys.stdout, sys.stderr
|
|
||||||
sys.stdout = mystdout = StringIO()
|
|
||||||
sys.stderr = mystderr = StringIO()
|
|
||||||
|
|
||||||
try:
|
|
||||||
picomc_cli.main(args=command.split())
|
|
||||||
except SystemExit as e:
|
|
||||||
if e.code != 0:
|
|
||||||
print(f"Command exited with code {e.code}", file=sys.stderr)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Unexpected error: {e}", file=sys.stderr)
|
|
||||||
finally:
|
|
||||||
# Restore stdout and stderr
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
sys.stderr = old_stderr
|
|
||||||
|
|
||||||
output = mystdout.getvalue().strip()
|
|
||||||
error = mystderr.getvalue().strip()
|
|
||||||
|
|
||||||
if not output:
|
|
||||||
return f"Error: No output from command. Stderr: {error}"
|
|
||||||
|
|
||||||
return output
|
|
296
picodulce.py
296
picodulce.py
@ -13,14 +13,13 @@ import time
|
|||||||
|
|
||||||
from authser import MinecraftAuthenticator
|
from authser import MinecraftAuthenticator
|
||||||
from healthcheck import HealthCheck
|
from healthcheck import HealthCheck
|
||||||
import modulecli
|
|
||||||
|
|
||||||
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
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
logging.basicConfig(level=logging.ERROR, format='%(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
class PicomcVersionSelector(QWidget):
|
class PicomcVersionSelector(QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -127,13 +126,11 @@ class PicomcVersionSelector(QWidget):
|
|||||||
try:
|
try:
|
||||||
self.config_path = "config.json"
|
self.config_path = "config.json"
|
||||||
print("Running picomc instance create default command...")
|
print("Running picomc instance create default command...")
|
||||||
|
# Run the command using subprocess
|
||||||
# Run the command using modulecli
|
result = subprocess.run(["picomc", "instance", "create", "default"], check=True, capture_output=True, text=True)
|
||||||
command = "instance create default"
|
|
||||||
result = modulecli.run_command(command)
|
|
||||||
|
|
||||||
# Print the output of the command
|
# Print the output of the command
|
||||||
print("Command output:", result)
|
print("Command output:", result.stdout)
|
||||||
|
|
||||||
# Change the value of IsFirstLaunch to False
|
# Change the value of IsFirstLaunch to False
|
||||||
self.config["IsFirstLaunch"] = False
|
self.config["IsFirstLaunch"] = False
|
||||||
@ -144,9 +141,9 @@ class PicomcVersionSelector(QWidget):
|
|||||||
json.dump(self.config, f, indent=4)
|
json.dump(self.config, f, indent=4)
|
||||||
print("Configuration saved to", self.config_path)
|
print("Configuration saved to", self.config_path)
|
||||||
|
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print("An error occurred while creating the instance.")
|
print("An error occurred while creating the instance.")
|
||||||
print("Error output:", str(e))
|
print("Error output:", e.stderr)
|
||||||
|
|
||||||
def resize_event(self, event):
|
def resize_event(self, event):
|
||||||
if hasattr(self, 'movie_label'):
|
if hasattr(self, 'movie_label'):
|
||||||
@ -397,8 +394,8 @@ class PicomcVersionSelector(QWidget):
|
|||||||
discord_rcp_checkbox.isChecked(),
|
discord_rcp_checkbox.isChecked(),
|
||||||
check_updates_checkbox.isChecked(),
|
check_updates_checkbox.isChecked(),
|
||||||
theme_background_checkbox.isChecked(),
|
theme_background_checkbox.isChecked(),
|
||||||
self.selected_theme,
|
self.selected_theme, # Pass the selected theme here
|
||||||
bleeding_edge_checkbox.isChecked()
|
bleeding_edge_checkbox.isChecked() # Pass the bleeding edge setting here
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -662,6 +659,20 @@ class PicomcVersionSelector(QWidget):
|
|||||||
)
|
)
|
||||||
self.__init__()
|
self.__init__()
|
||||||
|
|
||||||
|
def get_palette(self, palette_type):
|
||||||
|
"""Retrieve the corresponding palette based on the palette type."""
|
||||||
|
palettes = {
|
||||||
|
"Dark": self.create_dark_palette,
|
||||||
|
"Obsidian": self.create_obsidian_palette,
|
||||||
|
"Redstone": self.create_redstone_palette,
|
||||||
|
"Alpha": self.create_alpha_palette,
|
||||||
|
"Strawberry": self.create_strawberry_palette,
|
||||||
|
"Native": self.create_native_palette,
|
||||||
|
"Christmas": self.create_christmas_palette,
|
||||||
|
}
|
||||||
|
# Default to dark palette if the type is not specified or invalid
|
||||||
|
return palettes.get(palette_type, self.create_dark_palette)()
|
||||||
|
|
||||||
def get_system_info(self):
|
def get_system_info(self):
|
||||||
# Get system information
|
# Get system information
|
||||||
java_version = subprocess.getoutput("java -version 2>&1 | head -n 1")
|
java_version = subprocess.getoutput("java -version 2>&1 | head -n 1")
|
||||||
@ -701,16 +712,16 @@ class PicomcVersionSelector(QWidget):
|
|||||||
|
|
||||||
def open_game_directory(self):
|
def open_game_directory(self):
|
||||||
try:
|
try:
|
||||||
# Run the command using modulecli
|
# Run the command and capture the output
|
||||||
command = "instance dir"
|
result = subprocess.run(['picomc', 'instance', 'dir'], capture_output=True, text=True, check=True)
|
||||||
result = modulecli.run_command(command)
|
game_directory = result.stdout.strip()
|
||||||
game_directory = result.strip()
|
|
||||||
|
|
||||||
# Open the directory in the system's file explorer
|
# Open the directory in the system's file explorer
|
||||||
QDesktopServices.openUrl(QUrl.fromLocalFile(game_directory))
|
QDesktopServices.openUrl(QUrl.fromLocalFile(game_directory))
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Error running picomc command: {e}")
|
print(f"Error running picomc command: {e}")
|
||||||
|
|
||||||
|
|
||||||
def populate_installed_versions(self):
|
def populate_installed_versions(self):
|
||||||
config_path = "config.json"
|
config_path = "config.json"
|
||||||
|
|
||||||
@ -731,17 +742,20 @@ class PicomcVersionSelector(QWidget):
|
|||||||
|
|
||||||
# Run the command and capture the output
|
# Run the command and capture the output
|
||||||
try:
|
try:
|
||||||
command = "version list"
|
process = subprocess.Popen(['picomc', 'version', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
output = modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
|
|
||||||
if not output:
|
if process.returncode != 0:
|
||||||
raise Exception("Failed to get output from modulecli")
|
raise subprocess.CalledProcessError(process.returncode, process.args, output=output, stderr=error)
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error running 'picomc': %s", e)
|
logging.error("'picomc' command not found. Please ensure it's installed and in your PATH.")
|
||||||
|
return
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error running 'picomc': %s", e.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse the output and replace '[local]' with a space
|
# Parse the output and replace '[local]' with a space
|
||||||
versions = [version.replace('[local]', ' ').strip() for version in output.splitlines() if version.strip()]
|
versions = [version.replace('[local]', ' ').strip() for version in output.splitlines()]
|
||||||
|
|
||||||
# Get the last played version from the config
|
# Get the last played version from the config
|
||||||
last_played = self.config.get("LastPlayed", "")
|
last_played = self.config.get("LastPlayed", "")
|
||||||
@ -754,26 +768,32 @@ class PicomcVersionSelector(QWidget):
|
|||||||
# Populate the installed versions combo box
|
# Populate the installed versions combo box
|
||||||
self.installed_version_combo.clear()
|
self.installed_version_combo.clear()
|
||||||
self.installed_version_combo.addItems(versions)
|
self.installed_version_combo.addItems(versions)
|
||||||
|
|
||||||
def populate_installed_versions_normal_order(self):
|
def populate_installed_versions_normal_order(self):
|
||||||
# Run the 'picomc instance create default' command at the start
|
# Run the 'picomc instance create default' command at the start
|
||||||
try:
|
try:
|
||||||
command = "instance create default"
|
process = subprocess.Popen(['picomc', 'instance', 'create', 'default'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
output = modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
if not output:
|
if process.returncode != 0:
|
||||||
raise Exception("Failed to get output from modulecli for 'instance create default'")
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error creating default instance: %s", str(e))
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
|
return
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error creating default instance: %s", e.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Run the 'picomc version list' command and get the output
|
# Run the 'picomc version list' command and get the output
|
||||||
try:
|
try:
|
||||||
command = "version list"
|
process = subprocess.Popen(['picomc', 'version', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
output = modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
if not output:
|
if process.returncode != 0:
|
||||||
raise Exception("Failed to get output from modulecli for 'version list'")
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error: %s", str(e))
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
|
return
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error: %s", e.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse the output and replace '[local]' with a space
|
# Parse the output and replace '[local]' with a space
|
||||||
@ -800,7 +820,7 @@ class PicomcVersionSelector(QWidget):
|
|||||||
|
|
||||||
# Check if there are any accounts
|
# Check if there are any accounts
|
||||||
try:
|
try:
|
||||||
account_list_output = modulecli.run_command("account list").strip()
|
account_list_output = subprocess.check_output(["picomc", "account", "list"]).decode("utf-8").strip()
|
||||||
if not account_list_output:
|
if not account_list_output:
|
||||||
QMessageBox.warning(self, "No Account Available", "Please create an account first.")
|
QMessageBox.warning(self, "No Account Available", "Please create an account first.")
|
||||||
return
|
return
|
||||||
@ -809,25 +829,18 @@ class PicomcVersionSelector(QWidget):
|
|||||||
if '*' not in account_list_output:
|
if '*' not in account_list_output:
|
||||||
QMessageBox.warning(self, "No Account Selected", "Please select an account.")
|
QMessageBox.warning(self, "No Account Selected", "Please select an account.")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error fetching accounts: {str(e)}"
|
error_message = f"Error fetching accounts: {str(e)}"
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
QMessageBox.critical(self, "Error", error_message)
|
QMessageBox.critical(self, "Error", error_message)
|
||||||
return
|
return
|
||||||
|
|
||||||
selected_instance = self.installed_version_combo.currentText()
|
selected_instance = self.installed_version_combo.currentText()
|
||||||
logging.info(f"Selected instance from dropdown: {selected_instance}")
|
logging.info(f"Selected instance: {selected_instance}")
|
||||||
|
|
||||||
# Verify the selected instance value before starting the game
|
|
||||||
if not selected_instance:
|
|
||||||
logging.error("No instance selected.")
|
|
||||||
QMessageBox.warning(self, "No Instance Selected", "Please select an instance.")
|
|
||||||
return
|
|
||||||
|
|
||||||
play_thread = threading.Thread(target=self.run_game, args=(selected_instance,))
|
play_thread = threading.Thread(target=self.run_game, args=(selected_instance,))
|
||||||
play_thread.start()
|
play_thread.start()
|
||||||
|
|
||||||
|
|
||||||
def run_game(self, selected_instance):
|
def run_game(self, selected_instance):
|
||||||
try:
|
try:
|
||||||
# Set current_state to the selected instance
|
# Set current_state to the selected instance
|
||||||
@ -842,14 +855,10 @@ class PicomcVersionSelector(QWidget):
|
|||||||
update_thread = threading.Thread(target=self.update_last_played, args=(selected_instance,))
|
update_thread = threading.Thread(target=self.update_last_played, args=(selected_instance,))
|
||||||
update_thread.start()
|
update_thread.start()
|
||||||
|
|
||||||
# Run the game using the modulecli module
|
# Run the game subprocess with the instance_value from config.json
|
||||||
command = f"instance launch --version-override {selected_instance} {instance_value}"
|
subprocess.run(['picomc', 'instance', 'launch', '--version-override', selected_instance, instance_value], check=True)
|
||||||
output = modulecli.run_command(command)
|
|
||||||
|
|
||||||
if not output:
|
|
||||||
raise Exception("Failed to get output from modulecli")
|
|
||||||
|
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error playing {selected_instance}: {e}"
|
error_message = f"Error playing {selected_instance}: {e}"
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
# Use QMetaObject.invokeMethod to call showError safely
|
# Use QMetaObject.invokeMethod to call showError safely
|
||||||
@ -861,7 +870,6 @@ class PicomcVersionSelector(QWidget):
|
|||||||
# Reset current_state to "menu" after the game closes
|
# Reset current_state to "menu" after the game closes
|
||||||
self.current_state = "menu"
|
self.current_state = "menu"
|
||||||
|
|
||||||
|
|
||||||
def update_last_played(self, selected_instance):
|
def update_last_played(self, selected_instance):
|
||||||
config_path = "config.json"
|
config_path = "config.json"
|
||||||
self.config["LastPlayed"] = selected_instance
|
self.config["LastPlayed"] = selected_instance
|
||||||
@ -959,16 +967,16 @@ class PicomcVersionSelector(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
command = f"account create {username}"
|
command = ['picomc', 'account', 'create', username]
|
||||||
if is_microsoft:
|
if is_microsoft:
|
||||||
command += " --ms"
|
command.append('--ms')
|
||||||
|
|
||||||
modulecli.run_command(command)
|
subprocess.run(command, check=True)
|
||||||
QMessageBox.information(dialog, "Success", f"Account '{username}' created successfully!")
|
QMessageBox.information(dialog, "Success", f"Account '{username}' created successfully!")
|
||||||
self.populate_accounts_for_all_dialogs()
|
self.populate_accounts_for_all_dialogs()
|
||||||
dialog.accept()
|
dialog.accept()
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error creating account: {str(e)}"
|
error_message = f"Error creating account: {e.stderr.decode()}"
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
QMessageBox.critical(dialog, "Error", error_message)
|
QMessageBox.critical(dialog, "Error", error_message)
|
||||||
|
|
||||||
@ -1021,21 +1029,23 @@ class PicomcVersionSelector(QWidget):
|
|||||||
confirm_dialog = QMessageBox.question(dialog, "Confirm Removal", confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
confirm_dialog = QMessageBox.question(dialog, "Confirm Removal", confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||||
if confirm_dialog == QMessageBox.Yes:
|
if confirm_dialog == QMessageBox.Yes:
|
||||||
try:
|
try:
|
||||||
command = f"account remove {username}"
|
subprocess.run(['picomc', 'account', 'remove', username], check=True)
|
||||||
modulecli.run_command(command)
|
|
||||||
QMessageBox.information(dialog, "Success", f"Account '{username}' removed successfully!")
|
QMessageBox.information(dialog, "Success", f"Account '{username}' removed successfully!")
|
||||||
self.populate_accounts_for_all_dialogs()
|
self.populate_accounts_for_all_dialogs()
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error removing account: {str(e)}"
|
error_message = f"Error removing account: {e.stderr.decode()}"
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
QMessageBox.critical(dialog, "Error", error_message)
|
QMessageBox.critical(dialog, "Error", error_message)
|
||||||
|
|
||||||
|
|
||||||
def populate_accounts(self, account_combo):
|
def populate_accounts(self, account_combo):
|
||||||
# Populate the account dropdown
|
# Populate the account dropdown
|
||||||
try:
|
try:
|
||||||
command = "account list"
|
process = subprocess.Popen(['picomc', 'account', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
output = modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
|
|
||||||
# Process accounts, keeping the one with "*" at the top
|
# Process accounts, keeping the one with "*" at the top
|
||||||
accounts = output.splitlines()
|
accounts = output.splitlines()
|
||||||
starred_account = None
|
starred_account = None
|
||||||
@ -1059,8 +1069,10 @@ class PicomcVersionSelector(QWidget):
|
|||||||
for account in normal_accounts:
|
for account in normal_accounts:
|
||||||
account_combo.addItem(account)
|
account_combo.addItem(account)
|
||||||
|
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error(f"Error: {str(e)}")
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error: {e.stderr}")
|
||||||
|
|
||||||
def populate_accounts_for_all_dialogs(self):
|
def populate_accounts_for_all_dialogs(self):
|
||||||
# Update account dropdowns in all open dialogs
|
# Update account dropdowns in all open dialogs
|
||||||
@ -1077,16 +1089,14 @@ class PicomcVersionSelector(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
command = f"account setdefault {account_name}"
|
subprocess.run(['picomc', 'account', 'setdefault', account_name], check=True)
|
||||||
modulecli.run_command(command)
|
|
||||||
QMessageBox.information(self, "Success", f"Account '{account_name}' set as default!")
|
QMessageBox.information(self, "Success", f"Account '{account_name}' set as default!")
|
||||||
self.populate_accounts_for_all_dialogs()
|
self.populate_accounts_for_all_dialogs()
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error setting default account '{account_name}': {str(e)}"
|
error_message = f"Error setting default account '{account_name}': {e.stderr.decode()}"
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
QMessageBox.critical(self, "Error", error_message)
|
QMessageBox.critical(self, "Error", error_message)
|
||||||
|
|
||||||
|
|
||||||
def show_about_dialog(self):
|
def show_about_dialog(self):
|
||||||
# Load the version number from version.json
|
# Load the version number from version.json
|
||||||
try:
|
try:
|
||||||
@ -1303,7 +1313,7 @@ class PicomcVersionSelector(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
def open_mod_loader_and_version_menu(self):
|
def open_mod_loader_and_version_menu(self):
|
||||||
dialog = ModLoaderAndVersionMenu(parent=self)
|
dialog = ModLoaderAndVersionMenu()
|
||||||
dialog.finished.connect(self.populate_installed_versions)
|
dialog.finished.connect(self.populate_installed_versions)
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
|
|
||||||
@ -1316,25 +1326,17 @@ class DownloadThread(QThread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
command = f"version prepare {self.version}"
|
subprocess.run(['picomc', 'version', 'prepare', self.version], check=True)
|
||||||
modulecli.run_command(command)
|
|
||||||
self.completed.emit(True, f"Version {self.version} prepared successfully!")
|
self.completed.emit(True, f"Version {self.version} prepared successfully!")
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error preparing {self.version}: {str(e)}"
|
error_message = f"Error preparing {self.version}: {e.stderr.decode()}"
|
||||||
self.completed.emit(False, error_message)
|
self.completed.emit(False, error_message)
|
||||||
|
|
||||||
class ModLoaderAndVersionMenu(QDialog):
|
class ModLoaderAndVersionMenu(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self):
|
||||||
super().__init__(parent)
|
super().__init__()
|
||||||
self.setWindowTitle("Mod Loader and Version Menu")
|
self.setWindowTitle("Mod Loader and Version Menu")
|
||||||
# Set window position relative to parent
|
self.setGeometry(100, 100, 400, 300)
|
||||||
if parent:
|
|
||||||
parent_pos = parent.pos()
|
|
||||||
x = parent_pos.x() + (parent.width() - 400) // 2
|
|
||||||
y = parent_pos.y() + (parent.height() - 300) // 2
|
|
||||||
self.setGeometry(x, y, 400, 300)
|
|
||||||
else:
|
|
||||||
self.setGeometry(100, 100, 400, 300)
|
|
||||||
|
|
||||||
main_layout = QVBoxLayout(self)
|
main_layout = QVBoxLayout(self)
|
||||||
|
|
||||||
@ -1344,11 +1346,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
# Create tabs
|
# Create tabs
|
||||||
install_mod_tab = QWidget()
|
install_mod_tab = QWidget()
|
||||||
download_version_tab = QWidget()
|
download_version_tab = QWidget()
|
||||||
instances_tab = QWidget()
|
instances_tab = QWidget() # New tab for instances
|
||||||
|
|
||||||
tab_widget.addTab(download_version_tab, "Download Version")
|
tab_widget.addTab(download_version_tab, "Download Version")
|
||||||
tab_widget.addTab(install_mod_tab, "Install Mod Loader")
|
tab_widget.addTab(install_mod_tab, "Install Mod Loader")
|
||||||
tab_widget.addTab(instances_tab, "Instances")
|
tab_widget.addTab(instances_tab, "Instances") # Add the new tab
|
||||||
|
|
||||||
# Add content to "Install Mod Loader" tab
|
# Add content to "Install Mod Loader" tab
|
||||||
self.setup_install_mod_loader_tab(install_mod_tab)
|
self.setup_install_mod_loader_tab(install_mod_tab)
|
||||||
@ -1400,8 +1402,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
if instance_name:
|
if instance_name:
|
||||||
try:
|
try:
|
||||||
# Run the "picomc instance create" command
|
# Run the "picomc instance create" command
|
||||||
command = f"instance create {instance_name}"
|
process = subprocess.Popen(['picomc', 'instance', 'create', instance_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
|
|
||||||
# Notify the user that the instance was created
|
# Notify the user that the instance was created
|
||||||
QMessageBox.information(self, "Instance Created", f"Instance '{instance_name}' has been created successfully.")
|
QMessageBox.information(self, "Instance Created", f"Instance '{instance_name}' has been created successfully.")
|
||||||
@ -1412,9 +1417,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
# Optionally select the newly created instance
|
# Optionally select the newly created instance
|
||||||
self.on_instance_selected(self.instances_list_widget.item(self.instances_list_widget.count() - 1))
|
self.on_instance_selected(self.instances_list_widget.item(self.instances_list_widget.count() - 1))
|
||||||
|
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error creating instance: %s", str(e))
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
QMessageBox.critical(self, "Error", f"Failed to create instance: {str(e)}")
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error creating instance: %s", e.stderr)
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to create instance: {e.stderr}")
|
||||||
else:
|
else:
|
||||||
QMessageBox.warning(self, "Invalid Input", "Please enter a valid instance name.")
|
QMessageBox.warning(self, "Invalid Input", "Please enter a valid instance name.")
|
||||||
|
|
||||||
@ -1425,8 +1432,14 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Run the "picomc instance rename" command
|
# Run the "picomc instance rename" command
|
||||||
command = f"instance rename {old_instance_name} {new_instance_name}"
|
process = subprocess.Popen(
|
||||||
modulecli.run_command(command)
|
['picomc', 'instance', 'rename', old_instance_name, new_instance_name],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||||
|
)
|
||||||
|
output, error = process.communicate()
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
|
|
||||||
QMessageBox.information(self, "Instance Renamed", f"Instance '{old_instance_name}' has been renamed to '{new_instance_name}' successfully.")
|
QMessageBox.information(self, "Instance Renamed", f"Instance '{old_instance_name}' has been renamed to '{new_instance_name}' successfully.")
|
||||||
|
|
||||||
@ -1438,9 +1451,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
if matching_items:
|
if matching_items:
|
||||||
self.instances_list_widget.setCurrentItem(matching_items[0])
|
self.instances_list_widget.setCurrentItem(matching_items[0])
|
||||||
|
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error renaming instance: %s", str(e))
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
QMessageBox.critical(self, "Error", f"Failed to rename instance: {str(e)}")
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error renaming instance: %s", e.stderr)
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to rename instance: {e.stderr}")
|
||||||
|
|
||||||
def delete_instance(self, instance_name):
|
def delete_instance(self, instance_name):
|
||||||
if instance_name == "default":
|
if instance_name == "default":
|
||||||
@ -1455,8 +1470,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
if confirm_delete == QMessageBox.Yes:
|
if confirm_delete == QMessageBox.Yes:
|
||||||
try:
|
try:
|
||||||
# Run the "picomc instance delete" command
|
# Run the "picomc instance delete" command
|
||||||
command = f"instance delete {instance_name}"
|
process = subprocess.Popen(['picomc', 'instance', 'delete', instance_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
|
|
||||||
# Notify the user that the instance was deleted
|
# Notify the user that the instance was deleted
|
||||||
QMessageBox.information(self, "Instance Deleted", f"Instance '{instance_name}' has been deleted successfully.")
|
QMessageBox.information(self, "Instance Deleted", f"Instance '{instance_name}' has been deleted successfully.")
|
||||||
@ -1464,16 +1482,19 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
# Reload the instances list
|
# Reload the instances list
|
||||||
self.load_instances()
|
self.load_instances()
|
||||||
|
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error deleting instance: %s", str(e))
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
QMessageBox.critical(self, "Error", f"Failed to delete instance: {str(e)}")
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error deleting instance: %s", e.stderr)
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to delete instance: {e.stderr}")
|
||||||
|
|
||||||
def load_instances(self):
|
def load_instances(self):
|
||||||
try:
|
try:
|
||||||
# Run the "picomc instance list" command
|
process = subprocess.Popen(['picomc', 'instance', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
command = "instance list"
|
output, error = process.communicate()
|
||||||
output = modulecli.run_command(command)
|
if process.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
|
|
||||||
# Parse the output and add each instance to the list widget
|
# Parse the output and add each instance to the list widget
|
||||||
instances = output.splitlines()
|
instances = output.splitlines()
|
||||||
self.instances_list_widget.clear() # Clear the previous list
|
self.instances_list_widget.clear() # Clear the previous list
|
||||||
@ -1482,9 +1503,10 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
self.instances_list_widget.addItem(item)
|
self.instances_list_widget.addItem(item)
|
||||||
self.add_instance_buttons(item, instance)
|
self.add_instance_buttons(item, instance)
|
||||||
|
|
||||||
except Exception as e:
|
except FileNotFoundError:
|
||||||
logging.error("Error fetching instances: %s", str(e))
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error fetching instances: %s", e.stderr)
|
||||||
|
|
||||||
def add_instance_buttons(self, list_item, instance_name):
|
def add_instance_buttons(self, list_item, instance_name):
|
||||||
widget = QWidget()
|
widget = QWidget()
|
||||||
@ -1640,19 +1662,21 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
options.append('--beta')
|
options.append('--beta')
|
||||||
if options:
|
if options:
|
||||||
try:
|
try:
|
||||||
command = 'version list ' + ' '.join(options)
|
process = subprocess.Popen(['picomc', 'version', 'list'] + options, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
output = modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
if "Error" in output:
|
if process.returncode != 0:
|
||||||
logging.error(output)
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
return
|
except FileNotFoundError:
|
||||||
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
# Parse the output and replace '[local]' with a space
|
|
||||||
versions = output.splitlines()
|
|
||||||
versions = [version.replace('[local]', ' ').strip() for version in versions]
|
|
||||||
self.version_combo.addItems(versions)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error("Unexpected error: %s", e)
|
|
||||||
return
|
return
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error: %s", e.stderr)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parse the output and replace '[local]' with a space
|
||||||
|
versions = output.splitlines()
|
||||||
|
versions = [version.replace('[local]', ' ').strip() for version in versions]
|
||||||
|
self.version_combo.addItems(versions)
|
||||||
# Update the download button state whenever versions are updated
|
# Update the download button state whenever versions are updated
|
||||||
self.update_download_button_state()
|
self.update_download_button_state()
|
||||||
|
|
||||||
@ -1669,7 +1693,7 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
|
|
||||||
# Connect the combo box signal to the update function
|
# Connect the combo box signal to the update function
|
||||||
self.version_combo.currentIndexChanged.connect(self.update_download_button_state)
|
self.version_combo.currentIndexChanged.connect(self.update_download_button_state)
|
||||||
|
|
||||||
def update_download_button_state(self):
|
def update_download_button_state(self):
|
||||||
self.download_button.setEnabled(self.version_combo.currentIndex() != -1)
|
self.download_button.setEnabled(self.version_combo.currentIndex() != -1)
|
||||||
|
|
||||||
@ -1709,10 +1733,15 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
|
|
||||||
def populate_available_releases(self, version_combo, install_forge, install_fabric):
|
def populate_available_releases(self, version_combo, install_forge, install_fabric):
|
||||||
try:
|
try:
|
||||||
command = "version list --release"
|
process = subprocess.Popen(['picomc', 'version', 'list', '--release'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
output = modulecli.run_command(command)
|
output, error = process.communicate()
|
||||||
except Exception as e:
|
if process.returncode != 0:
|
||||||
logging.error("Error: %s", str(e))
|
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||||
|
return
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error("Error: %s", e.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
if install_fabric:
|
if install_fabric:
|
||||||
@ -1742,13 +1771,12 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if mod_loader == 'forge':
|
if mod_loader == 'forge':
|
||||||
command = f"mod loader forge install --game {version}"
|
subprocess.run(['picomc', 'mod', 'loader', 'forge', 'install', '--game', version], check=True)
|
||||||
elif mod_loader == 'fabric':
|
elif mod_loader == 'fabric':
|
||||||
command = f"mod loader fabric install {version}"
|
subprocess.run(['picomc', 'mod', 'loader', 'fabric', 'install', version], check=True)
|
||||||
modulecli.run_command(command)
|
|
||||||
QMessageBox.information(self, "Success", f"{mod_loader.capitalize()} installed successfully for version {version}!")
|
QMessageBox.information(self, "Success", f"{mod_loader.capitalize()} installed successfully for version {version}!")
|
||||||
except Exception as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_message = f"Error installing {mod_loader} for version {version}: {str(e)}"
|
error_message = f"Error installing {mod_loader} for version {version}: {e.stderr.decode()}"
|
||||||
QMessageBox.critical(self, "Error", error_message)
|
QMessageBox.critical(self, "Error", error_message)
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
picomc
|
picomc
|
||||||
PyQt5
|
PyQt5
|
||||||
requests
|
requests
|
||||||
aiohttp
|
|
||||||
pypresence
|
pypresence
|
||||||
tqdm
|
tqdm
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.13.1",
|
"version": "0.12.1",
|
||||||
"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",
|
||||||
@ -8,8 +8,7 @@
|
|||||||
"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",
|
"https://raw.githubusercontent.com/nixietab/picodulce/main/authser.py",
|
||||||
"https://raw.githubusercontent.com/nixietab/picodulce/main/healthcheck.py",
|
"https://raw.githubusercontent.com/nixietab/picodulce/main/healthcheck.py"
|
||||||
"https://raw.githubusercontent.com/nixietab/picodulce/main/modulecli.py"
|
|
||||||
],
|
],
|
||||||
"versionBleeding": "0.13.1-202"
|
"versionBleeding": "0.12-186"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user