import sys import os import shutil import json import requests from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QListWidget, QListWidgetItem, QMessageBox, QComboBox, QDialog, QTabWidget, QMainWindow, QSpacerItem, QSizePolicy from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QIcon, QPalette, QColor, QPixmap CONFIG_FILE = "config.json" class ModrinthSearchApp(QWidget): def __init__(self): super().__init__() # Check and create folders if they don't exist self.check_and_create_folders() self.setWindowTitle("Marroc Mod Manager") self.setGeometry(100, 100, 500, 400) # Set Fusion style app.setStyle("Fusion") # Set dark color palette dark_palette = QPalette() dark_palette.setColor(QPalette.Window, QColor(53, 53, 53)) dark_palette.setColor(QPalette.WindowText, Qt.white) dark_palette.setColor(QPalette.Base, QColor(25, 25, 25)) dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) dark_palette.setColor(QPalette.ToolTipBase, Qt.white) dark_palette.setColor(QPalette.ToolTipText, Qt.white) dark_palette.setColor(QPalette.Text, Qt.white) dark_palette.setColor(QPalette.Button, QColor(53, 53, 53)) dark_palette.setColor(QPalette.ButtonText, Qt.white) dark_palette.setColor(QPalette.BrightText, Qt.red) dark_palette.setColor(QPalette.Link, QColor(42, 130, 218)) dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) dark_palette.setColor(QPalette.HighlightedText, Qt.white) self.setPalette(dark_palette) layout = QVBoxLayout() tab_widget = QTabWidget() self.search_tab = QWidget() self.mods_tab = QWidget() tab_widget.addTab(self.search_tab, "Search") tab_widget.addTab(self.mods_tab, "Mods") self.init_search_tab() self.init_mods_tab() layout.addWidget(tab_widget) self.setLayout(layout) def check_and_create_folders(self): # Check and create 'marroc/mods' and 'marroc/resourcepacks' folders if they don't exist folders = ['marroc/mods', 'marroc/resourcepacks'] for folder in folders: if not os.path.exists(folder): os.makedirs(folder) def init_search_tab(self): layout = QVBoxLayout() search_layout = QHBoxLayout() # Horizontal layout for search bar and dropdown self.search_input = QLineEdit() self.search_input.setPlaceholderText("Enter mod name...") search_layout.addWidget(self.search_input) self.search_button = QPushButton("Search") self.search_button.clicked.connect(self.search_mods) search_layout.addWidget(self.search_button) self.search_type_dropdown = QComboBox() # Dropdown for selecting mod type self.search_type_dropdown.addItems(["Mod", "Texture Pack"]) search_layout.addWidget(self.search_type_dropdown) layout.addLayout(search_layout) self.mods_list = QListWidget() layout.addWidget(self.mods_list) self.select_button = QPushButton("Select Mod") self.select_button.clicked.connect(self.show_mod_details_window) layout.addWidget(self.select_button) self.selected_mod = None self.search_tab.setLayout(layout) def init_mods_tab(self): layout = QVBoxLayout() self.mod_manager_window = ModManagerWindow() # Initialize ModManagerWindow layout.addWidget(self.mod_manager_window) self.mods_tab.setLayout(layout) def search_mods(self): self.mods_list.clear() mod_name = self.search_input.text() search_type = self.search_type_dropdown.currentText().lower() # Get selected mod type if search_type == "texture pack": api_url = f"https://api.modrinth.com/v2/search?query={mod_name}&limit=20&facets=%5B%5B%22project_type%3Aresourcepack%22%5D%5D" else: api_url = f"https://api.modrinth.com/v2/search?query={mod_name}&limit=20&facets=%5B%5B%22project_type%3A{search_type}%22%5D%5D" response = requests.get(api_url) if response.status_code == 200: mods_data = json.loads(response.text) for mod in mods_data['hits']: mod_name = mod['title'] mod_description = mod['description'] item = QListWidgetItem(f"Title: {mod_name}\nDescription: {mod_description}") item.setSizeHint(QSize(100, 50)) # Set size hint to increase height item.mod_data = mod self.mods_list.addItem(item) else: self.mods_list.addItem("Failed to fetch mods. Please try again later.") def show_mod_details_window(self): selected_item = self.mods_list.currentItem() if selected_item is not None: mod_data = selected_item.mod_data mod_slug = mod_data.get('slug') if mod_slug: api_url = f"https://api.modrinth.com/v2/project/{mod_slug}" response = requests.get(api_url) if response.status_code == 200: mod_info = json.loads(response.text) icon_url = mod_info.get('icon_url') mod_versions = self.get_mod_versions(mod_slug) mod_details_window = ModDetailsWindow(mod_data, icon_url, mod_versions) mod_details_window.exec_() else: QMessageBox.warning(self, "Failed to Fetch Mod Details", "Failed to fetch mod details. Please try again later.") else: QMessageBox.warning(self, "No Mod Slug", "Selected mod has no slug.") else: QMessageBox.warning(self, "No Mod Selected", "Please select a mod first.") def get_mod_versions(self, mod_slug): api_url = f"https://api.modrinth.com/v2/project/{mod_slug}/version" response = requests.get(api_url) if response.status_code == 200: versions = json.loads(response.text) mod_versions = [] for version in versions: version_name = version['name'] version_files = version.get('files', []) if version_files: file_urls = [file['url'] for file in version_files] mod_versions.append({'version': version_name, 'files': file_urls}) else: mod_versions.append({'version': version_name, 'files': []}) return mod_versions else: return [] class ModManagerWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Mod Manager") self.setGeometry(100, 100, 600, 400) self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.layout = QHBoxLayout(self.central_widget) # Combo box to select between mods and resource packs self.file_type_combo_box = QComboBox() self.file_type_combo_box.addItems(["Mods", "Resource Packs"]) self.file_type_combo_box.currentIndexChanged.connect(self.load_files) # Left column - Available files self.available_files_widget = QListWidget() # Right column - Installed files self.installed_files_widget = QListWidget() # Vertical layout for buttons and dropdown self.button_dropdown_layout = QVBoxLayout() self.button_dropdown_layout.addWidget(self.file_type_combo_box) # Spacer to center the buttons vertically self.button_dropdown_layout.addSpacerItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) # Button to move selected mod from available to installed self.move_right_button = QPushButton(">") self.move_right_button.clicked.connect(self.move_right) self.button_dropdown_layout.addWidget(self.move_right_button) # Button to move selected mod from installed to available self.move_left_button = QPushButton("<") self.move_left_button.clicked.connect(self.move_left) self.button_dropdown_layout.addWidget(self.move_left_button) # Button to delete selected item self.delete_button = QPushButton("Delete") self.delete_button.clicked.connect(self.delete_selected_item) self.button_dropdown_layout.addWidget(self.delete_button) # Spacer to center the buttons vertically self.button_dropdown_layout.addSpacerItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) # Add widgets to the layout self.layout.addWidget(self.available_files_widget) self.layout.addLayout(self.button_dropdown_layout) self.layout.addWidget(self.installed_files_widget) # Load files based on initial selection self.load_files() def load_files(self): file_type = self.file_type_combo_box.currentText() if file_type == "Mods": self.load_mods() elif file_type == "Resource Packs": self.load_resource_packs() def load_mods(self): # Load mods from specified directory mods_directory = "marroc/mods" if os.path.exists(mods_directory) and os.path.isdir(mods_directory): mods = os.listdir(mods_directory) self.available_files_widget.clear() self.available_files_widget.addItems(mods) # Load installed mods self.load_installed_mods("mods") def load_resource_packs(self): # Load resource packs from specified directory resource_packs_directory = "marroc/resourcepacks" if os.path.exists(resource_packs_directory) and os.path.isdir(resource_packs_directory): resource_packs = os.listdir(resource_packs_directory) self.available_files_widget.clear() self.available_files_widget.addItems(resource_packs) # Load installed resource packs self.load_installed_mods("resourcepacks") def load_installed_mods(self, file_type): # Detect Minecraft directory based on the operating system if sys.platform.startswith('linux'): minecraft_directory = os.path.expanduser("~/.local/share/picomc/instances/default/minecraft") elif sys.platform.startswith('win'): minecraft_directory = os.path.join(os.getenv('APPDATA'), '.picomc/instances/default/minecraft') else: minecraft_directory = "" if minecraft_directory: installed_files_directory = os.path.join(minecraft_directory, file_type) if os.path.exists(installed_files_directory) and os.path.isdir(installed_files_directory): installed_files = os.listdir(installed_files_directory) self.installed_files_widget.clear() self.installed_files_widget.addItems(installed_files) def move_right(self): selected_item = self.available_files_widget.currentItem() if selected_item: source_directory = self.get_source_directory() destination_directory = self.get_destination_directory() file_name = selected_item.text() source_path = os.path.join(source_directory, file_name) destination_path = os.path.join(destination_directory, file_name) shutil.move(source_path, destination_path) self.load_files() def move_left(self): selected_item = self.installed_files_widget.currentItem() if selected_item: source_directory = self.get_destination_directory() destination_directory = self.get_source_directory() file_name = selected_item.text() source_path = os.path.join(source_directory, file_name) destination_path = os.path.join(destination_directory, file_name) shutil.move(source_path, destination_path) self.load_files() def delete_selected_item(self): selected_item = self.available_files_widget.currentItem() or self.installed_files_widget.currentItem() if selected_item: file_name = selected_item.text() reply = QMessageBox.question(self, 'Delete Item', f'Are you sure you want to delete "{file_name}"?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: file_type = self.file_type_combo_box.currentText() if file_type == "Mods": directory = "marroc/mods" elif file_type == "Resource Packs": directory = "marroc/resourcepacks" else: return file_path = os.path.join(directory, file_name) if os.path.exists(file_path): os.remove(file_path) self.load_files() else: QMessageBox.warning(self, 'File Not Found', 'The selected file does not exist.') def get_source_directory(self): file_type = self.file_type_combo_box.currentText() if file_type == "Mods": return "marroc/mods" elif file_type == "Resource Packs": return "marroc/resourcepacks" else: return "" def get_destination_directory(self): file_type = self.file_type_combo_box.currentText() if file_type == "Mods": if sys.platform.startswith('linux'): return os.path.expanduser("~/.local/share/picomc/instances/default/minecraft/mods") elif sys.platform.startswith('win'): return os.path.join(os.getenv('APPDATA'), '.picomc/instances/default/minecraft/mods') elif file_type == "Resource Packs": if sys.platform.startswith('linux'): return os.path.expanduser("~/.local/share/picomc/instances/default/minecraft/resourcepacks") elif sys.platform.startswith('win'): return os.path.join(os.getenv('APPDATA'), '.picomc/instances/default/minecraft/resourcepacks') else: return "" class ModDetailsWindow(QDialog): def __init__(self, mod_data, icon_url, mod_versions): super().__init__() self.setWindowTitle("Mod Details") self.setGeometry(100, 100, 400, 300) self.mod_data = mod_data # Store mod data layout = QVBoxLayout() mod_name_label = QLabel(f"