diff --git a/.gitignore b/.gitignore index 5cf4a3f..40c5e32 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,6 @@ bot_config.json scripts.json active_requests.json domains.json -working_proxies.json \ No newline at end of file +working_proxies.json +.vscode/ +.idea/ diff --git a/README.md b/README.md index 0780427..ab0e52d 100644 --- a/README.md +++ b/README.md @@ -112,17 +112,17 @@ pip install --upgrade StreamingCommunity Create a simple script (`run_streaming.py`) to launch the main application: -```python -from StreamingCommunity.run import main +Install requirements: -if __name__ == "__main__": - main() +```bash +pip install -r requirements.txt ``` Run the script: + ```bash -python run_streaming.py +python streaming_gui.py ``` ## Modules diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/main_window.py b/gui/main_window.py new file mode 100644 index 0000000..5fb4c13 --- /dev/null +++ b/gui/main_window.py @@ -0,0 +1,39 @@ +from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout +from PyQt5.QtCore import QProcess +import sys +from .tabs.run_tab import RunTab +from .utils.stream_redirect import Stream + + +class StreamingGUI(QMainWindow): + def __init__(self): + super().__init__() + self.process = None + self.init_ui() + self.setup_output_redirect() + + def init_ui(self): + self.setWindowTitle("StreamingCommunity GUI") + self.setGeometry(100, 100, 1000, 700) + + central_widget = QWidget() + main_layout = QVBoxLayout() + + self.run_tab = RunTab(self) + main_layout.addWidget(self.run_tab) + + central_widget.setLayout(main_layout) + self.setCentralWidget(central_widget) + + def setup_output_redirect(self): + self.stdout_stream = Stream() + self.stdout_stream.newText.connect(self.run_tab.update_output) + sys.stdout = self.stdout_stream + + def closeEvent(self, event): + if self.process and self.process.state() == QProcess.Running: + self.process.terminate() + if not self.process.waitForFinished(1000): + self.process.kill() + sys.stdout = sys.__stdout__ + event.accept() diff --git a/gui/tabs/__init__.py b/gui/tabs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/tabs/run_tab.py b/gui/tabs/run_tab.py new file mode 100644 index 0000000..e855b7d --- /dev/null +++ b/gui/tabs/run_tab.py @@ -0,0 +1,281 @@ +from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QHBoxLayout, + QTabWidget, + QGroupBox, + QFormLayout, + QLineEdit, + QComboBox, + QPushButton, + QCheckBox, + QLabel, + QTextEdit, +) +from PyQt5.QtCore import Qt, QProcess +from ..widgets.results_table import ResultsTable +from ..utils.site_manager import sites +import sys + + +class RunTab(QTabWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.parent = parent + self.process = None + self.current_context = None + self.selected_season = None + self.buffer = "" + self.init_ui() + + def init_ui(self): + run_tab = QWidget() + run_layout = QVBoxLayout() + + # Add search group + run_layout.addWidget(self.create_search_group()) + + # Add control buttons + run_layout.addLayout(self.create_control_layout()) + + # Add status label + self.status_label = QLabel("Richiesta in corso...") + self.status_label.setAlignment(Qt.AlignCenter) + self.status_label.hide() + run_layout.addWidget(self.status_label) + + # Add output group + run_layout.addWidget(self.create_output_group()) + + run_tab.setLayout(run_layout) + self.addTab(run_tab, "Esecuzione") + + def create_search_group(self): + search_group = QGroupBox("Parametri di Ricerca") + search_layout = QFormLayout() + + self.search_terms = QLineEdit() + search_layout.addRow("Termini di ricerca:", self.search_terms) + + self.site_combo = QComboBox() + for site in sites: + self.site_combo.addItem(f"{site['name']}", site["index"]) + self.site_combo.setItemData(site["index"], site["flag"], Qt.ToolTipRole) + if self.site_combo.count() > 0: + self.site_combo.setCurrentIndex(0) + + search_layout.addRow("Seleziona sito:", self.site_combo) + search_group.setLayout(search_layout) + return search_group + + def create_control_layout(self): + control_layout = QHBoxLayout() + + self.run_button = QPushButton("Esegui Script") + self.run_button.clicked.connect(self.run_script) + control_layout.addWidget(self.run_button) + + self.stop_button = QPushButton("Ferma Script") + self.stop_button.clicked.connect(self.stop_script) + self.stop_button.setEnabled(False) + control_layout.addWidget(self.stop_button) + + self.console_checkbox = QCheckBox("Mostra Console") + self.console_checkbox.setChecked(False) + self.console_checkbox.stateChanged.connect(self.toggle_console) + control_layout.addWidget(self.console_checkbox) + + return control_layout + + def create_output_group(self): + output_group = QGroupBox("Output") + output_layout = QVBoxLayout() + + self.results_table = ResultsTable() + output_layout.addWidget(self.results_table) + + self.output_text = QTextEdit() + self.output_text.setReadOnly(True) + self.output_text.hide() + output_layout.addWidget(self.output_text) + + input_layout = QHBoxLayout() + self.input_field = QLineEdit() + self.input_field.setPlaceholderText("Inserisci l'indice del media...") + self.input_field.returnPressed.connect(self.send_input) + self.send_button = QPushButton("Invia") + self.send_button.clicked.connect(self.send_input) + + self.input_field.hide() + self.send_button.hide() + + input_layout.addWidget(self.input_field) + input_layout.addWidget(self.send_button) + output_layout.addLayout(input_layout) + + output_group.setLayout(output_layout) + return output_group + + def toggle_console(self, state): + self.output_text.setVisible(state == Qt.Checked) + self.results_table.setVisible(state == Qt.Checked) + + def run_script(self): + if self.process is not None and self.process.state() == QProcess.Running: + print("Script già in esecuzione.") + return + + self.current_context = None + self.selected_season = None + self.buffer = "" + self.results_table.setVisible(False) + self.status_label.setText("Richiesta in corso...") + self.status_label.show() + + args = [] + search_terms = self.search_terms.text() + if search_terms: + args.extend(["-s", search_terms]) + + site_index = self.site_combo.currentIndex() + if site_index >= 0: + site_text = sites[site_index]["flag"] + site_name = site_text.split()[0].upper() + args.append(f"-{site_name}") + + self.output_text.clear() + print(f"Avvio script con argomenti: {' '.join(args)}") + + self.process = QProcess() + self.process.readyReadStandardOutput.connect(self.handle_stdout) + self.process.readyReadStandardError.connect(self.handle_stderr) + self.process.finished.connect(self.process_finished) + + python_executable = sys.executable + script_path = "run_streaming.py" + + self.process.start(python_executable, [script_path] + args) + self.run_button.setEnabled(False) + self.stop_button.setEnabled(True) + + def handle_stdout(self): + data = self.process.readAllStandardOutput() + stdout = bytes(data).decode("utf8", errors="replace") + self.update_output(stdout) + + self.buffer += stdout + + if "Episodes find:" in self.buffer: + self.current_context = "episodes" + self.input_field.setPlaceholderText( + "Inserisci l'indice dell'episodio (es: 1, *, 1-2, 3-*)" + ) + elif "Seasons found:" in self.buffer: + self.current_context = "seasons" + self.input_field.setPlaceholderText( + "Inserisci il numero della stagione (es: 1, *, 1-2, 3-*)" + ) + + if "Episodes find:" in self.buffer: + self.results_table.hide() + self.current_context = "episodes" + text_to_show = f"Trovati {self.buffer.split('Episodes find:')[1].split()[0]} episodi!" + self.status_label.setText(text_to_show) + self.status_label.show() + elif (("┏" in self.buffer or "┌" in self.buffer) and + ("┗" in self.buffer or "┛" in self.buffer or "└" in self.buffer)) or "Seasons found:" in self.buffer: + self.parse_and_show_results(self.buffer) + + if "Insert" in self.buffer: + self.input_field.show() + self.send_button.show() + self.input_field.setFocus() + self.output_text.verticalScrollBar().setValue( + self.output_text.verticalScrollBar().maximum() + ) + + def parse_and_show_results(self, text): + if "Seasons found:" in text and not "Insert media index (e.g., 1)" in text: + self.status_label.hide() + num_seasons = int(text.split("Seasons found:")[1].split()[0]) + self.results_table.update_with_seasons(num_seasons) + return + + if ("┏━━━━━━━┳" in text or "┌───────┬" in text) and "└───────┴" in text: + chars_to_find = [] + if "┏" in text: + chars_to_find.append("┏") + chars_to_find.append("┃") + elif "┌" in text: + chars_to_find.append("┌") + chars_to_find.append("│") + + if not chars_to_find or len(chars_to_find) == 0: + return + self.status_label.hide() + table_lines = text[text.find(chars_to_find[0]) : text.find("└")].split("\n") + headers = [h.strip() for h in table_lines[1].split(chars_to_find[1])[1:-1]] + + rows = [] + for line in table_lines[3:]: + if line.strip() and "│" in line: + cells = [cell.strip() for cell in line.split("│")[1:-1]] + rows.append(cells) + + self.results_table.update_with_results(headers, rows) + + def send_input(self): + if not self.process or self.process.state() != QProcess.Running: + return + + user_input = self.input_field.text().strip() + + if self.current_context == "seasons": + if "-" in user_input or user_input == "*": + self.results_table.hide() + else: + self.selected_season = user_input + + elif self.current_context == "episodes": + if "-" in user_input or user_input == "*": + self.results_table.hide() + + self.process.write(f"{user_input}\n".encode()) + self.input_field.clear() + self.input_field.hide() + self.send_button.hide() + + if self.current_context == "seasons" and not ( + "-" in user_input or user_input == "*" + ): + self.status_label.setText("Caricamento episodi...") + self.status_label.show() + + def handle_stderr(self): + data = self.process.readAllStandardError() + stderr = bytes(data).decode("utf8", errors="replace") + self.update_output(stderr) + + def process_finished(self): + self.run_button.setEnabled(True) + self.stop_button.setEnabled(False) + self.input_field.hide() + self.send_button.hide() + self.status_label.hide() + print("Script terminato.") + + def update_output(self, text): + cursor = self.output_text.textCursor() + cursor.movePosition(cursor.End) + cursor.insertText(text) + self.output_text.setTextCursor(cursor) + self.output_text.ensureCursorVisible() + + def stop_script(self): + if self.process is not None and self.process.state() == QProcess.Running: + self.process.terminate() + if not self.process.waitForFinished(3000): + self.process.kill() + print("Script terminato.") + self.run_button.setEnabled(True) + self.stop_button.setEnabled(False) diff --git a/gui/utils/__init__.py b/gui/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/utils/site_manager.py b/gui/utils/site_manager.py new file mode 100644 index 0000000..b1e2393 --- /dev/null +++ b/gui/utils/site_manager.py @@ -0,0 +1,18 @@ +from StreamingCommunity.run import load_search_functions + + +def get_sites(): + search_functions = load_search_functions() + sites = [] + for alias, (_, use_for) in search_functions.items(): + sites.append( + { + "index": len(sites), + "name": alias.split("_")[0], + "flag": alias[:3].upper(), + } + ) + return sites + + +sites = get_sites() diff --git a/gui/utils/stream_redirect.py b/gui/utils/stream_redirect.py new file mode 100644 index 0000000..69612c6 --- /dev/null +++ b/gui/utils/stream_redirect.py @@ -0,0 +1,13 @@ +from PyQt5.QtCore import QObject, pyqtSignal + + +class Stream(QObject): + """Redirect script output to GUI""" + + newText = pyqtSignal(str) + + def write(self, text): + self.newText.emit(str(text)) + + def flush(self): + pass diff --git a/gui/widgets/__init__.py b/gui/widgets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/widgets/results_table.py b/gui/widgets/results_table.py new file mode 100644 index 0000000..bb059b5 --- /dev/null +++ b/gui/widgets/results_table.py @@ -0,0 +1,62 @@ +from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QHeaderView +from PyQt5.QtCore import Qt + + +class ResultsTable(QTableWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_table() + + def setup_table(self): + self.setVisible(False) + self.setSelectionMode(QTableWidget.NoSelection) + self.setEditTriggers(QTableWidget.NoEditTriggers) + self.setFocusPolicy(Qt.NoFocus) + self.setDragDropMode(QTableWidget.NoDragDrop) + self.setContextMenuPolicy(Qt.NoContextMenu) + self.verticalHeader().setVisible(False) + + # set custom style for diabled table + self.setStyleSheet( + """ + QTableWidget:disabled { + color: white; + background-color: #323232; + } + """ + ) + self.setEnabled(False) + + def update_with_seasons(self, num_seasons): + self.clear() + self.setColumnCount(2) + self.setHorizontalHeaderLabels(["Index", "Season"]) + + self.setRowCount(num_seasons) + for i in range(num_seasons): + index_item = QTableWidgetItem(str(i + 1)) + season_item = QTableWidgetItem(f"Stagione {i + 1}") + index_item.setFlags(Qt.ItemIsEnabled) + season_item.setFlags(Qt.ItemIsEnabled) + self.setItem(i, 0, index_item) + self.setItem(i, 1, season_item) + + self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + self.horizontalHeader().setEnabled(False) + self.setVisible(True) + + def update_with_results(self, headers, rows): + self.clear() + self.setColumnCount(len(headers)) + self.setHorizontalHeaderLabels(headers) + + self.setRowCount(len(rows)) + for i, row in enumerate(rows): + for j, cell in enumerate(row): + item = QTableWidgetItem(cell) + item.setFlags(Qt.ItemIsEnabled) + self.setItem(i, j, item) + + self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + self.horizontalHeader().setEnabled(False) + self.setVisible(True) diff --git a/requirements.txt b/requirements.txt index 3790b61..7be30bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ pycryptodomex ua-generator qbittorrent-api pyTelegramBotAPI +PyQt5 diff --git a/run_streaming.py b/run_streaming.py new file mode 100644 index 0000000..9488468 --- /dev/null +++ b/run_streaming.py @@ -0,0 +1,4 @@ +from StreamingCommunity.run import main + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/streaming_gui.py b/streaming_gui.py new file mode 100644 index 0000000..e14270d --- /dev/null +++ b/streaming_gui.py @@ -0,0 +1,14 @@ +import sys +from PyQt5.QtWidgets import QApplication +from gui.main_window import StreamingGUI + + +def main(): + app = QApplication(sys.argv) + gui = StreamingGUI() + gui.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main()