import sys import os import shutil import json import threading 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, QObject, pyqtSignal from PyQt5.QtGui import QIcon, QPalette, QColor, QPixmap CONFIG_FILE = "config.json" class IconLoader(QObject, threading.Thread): icon_loaded = pyqtSignal(QPixmap) def __init__(self, icon_url): super().__init__() threading.Thread.__init__(self) self.icon_url = icon_url def run(self): try: response = requests.get(self.icon_url) if response.status_code == 200: pixmap = QPixmap() pixmap.loadFromData(response.content) self.icon_loaded.emit(pixmap.scaled(QSize(42, 42), Qt.KeepAspectRatio, Qt.SmoothTransformation)) else: self.icon_loaded.emit(QPixmap("missing.png")) except Exception as e: print("Error loading icon:", e) self.icon_loaded.emit(QPixmap("missing.png")) class ModrinthSearchApp(QWidget): def __init__(self): super().__init__() 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, "Manager") self.init_search_tab() self.init_mods_tab() layout.addWidget(tab_widget) self.setLayout(layout) def init_search_tab(self): layout = QVBoxLayout() search_layout = QHBoxLayout() self.search_input = QLineEdit() self.search_input.setPlaceholderText("Enter a search term...") 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() 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") 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() 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() 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'] icon_url = mod['icon_url'] item = QListWidgetItem(f"Title: {mod_name}\nDescription: {mod_description}") item.setSizeHint(QSize(200, 50)) icon_loader = IconLoader(icon_url) icon_loader.icon_loaded.connect(lambda pixmap, item=item: self.set_item_icon(item, pixmap)) icon_loader.start() item.mod_data = mod self.mods_list.addItem(item) else: self.mods_list.addItem("Failed to fetch mods. Please try again later.") def set_item_icon(self, item, pixmap): if pixmap: item.setData(Qt.DecorationRole, pixmap) else: # Set a default icon if loading failed item.setIcon(QIcon("missing.png")) 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) 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) self.available_files_widget = QListWidget() self.installed_files_widget = QListWidget() self.button_dropdown_layout = QVBoxLayout() self.button_dropdown_layout.addWidget(self.file_type_combo_box) self.button_dropdown_layout.addSpacerItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.move_right_button = QPushButton(">") self.move_right_button.clicked.connect(self.move_right) self.button_dropdown_layout.addWidget(self.move_right_button) self.move_left_button = QPushButton("<") self.move_left_button.clicked.connect(self.move_left) self.button_dropdown_layout.addWidget(self.move_left_button) self.delete_button = QPushButton("Delete") self.delete_button.clicked.connect(self.delete_selected_item) self.button_dropdown_layout.addWidget(self.delete_button) self.button_dropdown_layout.addSpacerItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.layout.addWidget(self.available_files_widget) self.layout.addLayout(self.button_dropdown_layout) self.layout.addWidget(self.installed_files_widget) 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): 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) self.load_installed_mods("mods") def load_resource_packs(self): 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) self.load_installed_mods("resourcepacks") def load_installed_mods(self, file_type): 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 layout = QVBoxLayout() mod_name_label = QLabel(f"

{mod_data['title']}

") mod_name_label.setAlignment(Qt.AlignCenter) layout.addWidget(mod_name_label) mod_description_label = QLabel(mod_data['description']) mod_description_label.setWordWrap(True) layout.addWidget(mod_description_label) icon_pixmap = self.load_icon(icon_url) icon_label = QLabel() if icon_pixmap: icon_label.setPixmap(icon_pixmap) icon_label.setAlignment(Qt.AlignCenter) layout.addWidget(icon_label) self.version_dropdown = QComboBox() for version in mod_versions: self.version_dropdown.addItem(version['version']) self.version_dropdown.setItemData(self.version_dropdown.count() - 1, version['files'], Qt.UserRole) layout.addWidget(self.version_dropdown) self.download_button = QPushButton("Download") self.download_button.clicked.connect(self.download_mod) layout.addWidget(self.download_button) self.download_url_label = QLabel() self.download_url_label.setAlignment(Qt.AlignCenter) layout.addWidget(self.download_url_label) layout.addStretch(1) self.setLayout(layout) def load_icon(self, icon_url): try: response = requests.get(icon_url) if response.status_code == 200: pixmap = QPixmap() pixmap.loadFromData(response.content) return pixmap.scaled(QSize(128, 128), Qt.KeepAspectRatio, Qt.SmoothTransformation) else: return None except Exception as e: print("Error loading icon:", e) return None def download_mod(self): selected_version_index = self.version_dropdown.currentIndex() selected_version_files = self.version_dropdown.itemData(selected_version_index, Qt.UserRole) if selected_version_files: for file_url in selected_version_files: filename = os.path.basename(file_url) try: response = requests.get(file_url) response.raise_for_status() save_dir = "marroc/mods" if filename.endswith('.jar') else "marroc/resourcepacks" with open(os.path.join(save_dir, filename), 'wb') as f: f.write(response.content) QMessageBox.information(self, "Download Mod", f"Downloaded {filename} successfully.") return except requests.exceptions.RequestException as e: QMessageBox.warning(self, "Download Error", f"Error downloading mod: {e}") return QMessageBox.warning(self, "Download Mod", "Failed to download the mod.") if __name__ == "__main__": app = QApplication(sys.argv) app_icon = QIcon('marroc.ico') app.setWindowIcon(app_icon) window = ModrinthSearchApp() window.show() sys.exit(app.exec_())