from __future__ import annotations import json import queue import threading from dataclasses import dataclass from pathlib import Path from tkinter import END, LEFT, RIGHT, W, filedialog, messagebox import tkinter as tk from tkinter import ttk from hosters import UploadError, UploadResult, build_uploaders APP_DIR = Path(__file__).resolve().parent CONFIG_PATH = APP_DIR / "config.json" @dataclass class HostRow: host: str enabled: tk.BooleanVar credential: tk.StringVar class UploaderApp: def __init__(self, root: tk.Tk) -> None: self.root = root self.root.title("Multi Hoster Uploader") self.root.geometry("980x680") self.uploaders = build_uploaders() self.files: list[Path] = [] self.log_queue: queue.Queue[str] = queue.Queue() self.link_lines: list[str] = [] self.host_rows: dict[str, HostRow] = {} self._build_ui() self._load_config() self._poll_logs() def _build_ui(self) -> None: wrapper = ttk.Frame(self.root, padding=12) wrapper.pack(fill=tk.BOTH, expand=True) title = ttk.Label(wrapper, text="Uploader fuer doodstream / voe / vidmoly / byse", font=("Segoe UI", 13, "bold")) title.pack(anchor=W) subtitle = ttk.Label( wrapper, text="Nutze deine eigenen Accounts/API Keys. Das Tool nutzt nur offizielle oder kompatible Upload-Endpunkte.", ) subtitle.pack(anchor=W, pady=(2, 10)) hosts_frame = ttk.LabelFrame(wrapper, text="Hoster Login / API Key", padding=10) hosts_frame.pack(fill=tk.X) for idx, host in enumerate(self.uploaders.keys()): enabled = tk.BooleanVar(value=True if host in ("doodstream.com", "voe.sx") else False) credential = tk.StringVar() chk = ttk.Checkbutton(hosts_frame, text=host, variable=enabled) chk.grid(row=idx, column=0, sticky=W, padx=(0, 10), pady=4) entry = ttk.Entry(hosts_frame, width=70, textvariable=credential, show="*") entry.grid(row=idx, column=1, sticky="we", pady=4) self.host_rows[host] = HostRow(host=host, enabled=enabled, credential=credential) hosts_frame.columnconfigure(1, weight=1) files_frame = ttk.LabelFrame(wrapper, text="Dateien", padding=10) files_frame.pack(fill=tk.BOTH, expand=False, pady=(10, 0)) btn_row = ttk.Frame(files_frame) btn_row.pack(fill=tk.X) ttk.Button(btn_row, text="Dateien waehlen", command=self._pick_files).pack(side=LEFT) ttk.Button(btn_row, text="Auswahl loeschen", command=self._clear_files).pack(side=LEFT, padx=8) self.upload_btn = ttk.Button(btn_row, text="Upload starten", command=self._start_upload) self.upload_btn.pack(side=RIGHT) self.file_list = tk.Listbox(files_frame, height=8) self.file_list.pack(fill=tk.X, pady=(8, 0)) output_pane = ttk.PanedWindow(wrapper, orient=tk.HORIZONTAL) output_pane.pack(fill=tk.BOTH, expand=True, pady=(10, 0)) log_frame = ttk.LabelFrame(output_pane, text="Log", padding=8) links_frame = ttk.LabelFrame(output_pane, text="Output Links", padding=8) output_pane.add(log_frame, weight=1) output_pane.add(links_frame, weight=1) self.log_text = tk.Text(log_frame, height=14, wrap=tk.WORD) self.log_text.pack(fill=tk.BOTH, expand=True) self.links_text = tk.Text(links_frame, height=14, wrap=tk.WORD) self.links_text.pack(fill=tk.BOTH, expand=True) action_row = ttk.Frame(wrapper) action_row.pack(fill=tk.X, pady=(8, 0)) ttk.Button(action_row, text="Links kopieren", command=self._copy_links).pack(side=LEFT) ttk.Button(action_row, text="Config speichern", command=self._save_config).pack(side=LEFT, padx=8) def _pick_files(self) -> None: selected = filedialog.askopenfilenames(title="Dateien zum Upload auswaehlen") if not selected: return for item in selected: path = Path(item) if path not in self.files: self.files.append(path) self.file_list.insert(END, str(path)) def _clear_files(self) -> None: self.files = [] self.file_list.delete(0, END) def _copy_links(self) -> None: text = self.links_text.get("1.0", END).strip() if not text: messagebox.showinfo("Info", "Noch keine Links vorhanden.") return self.root.clipboard_clear() self.root.clipboard_append(text) self.root.update_idletasks() messagebox.showinfo("Fertig", "Links in Zwischenablage kopiert.") def _append_log(self, line: str) -> None: self.log_text.insert(END, line + "\n") self.log_text.see(END) def _append_result(self, result: UploadResult) -> None: lines = [ f"[{result.host}] {result.file_path.name}", f" file_code: {result.file_code or '-'}", f" download: {result.download_url or '-'}", f" embed: {result.embed_url or '-'}", "", ] for line in lines: self.link_lines.append(line) self.links_text.delete("1.0", END) self.links_text.insert("1.0", "\n".join(self.link_lines).strip() + "\n") def _save_config(self) -> None: payload = { "hosts": { host: { "enabled": row.enabled.get(), "credential": row.credential.get(), } for host, row in self.host_rows.items() } } CONFIG_PATH.write_text(json.dumps(payload, indent=2), encoding="utf-8") messagebox.showinfo("Gespeichert", f"Config gespeichert: {CONFIG_PATH}") def _load_config(self) -> None: if not CONFIG_PATH.exists(): return try: data = json.loads(CONFIG_PATH.read_text(encoding="utf-8")) except Exception: return hosts = data.get("hosts", {}) if not isinstance(hosts, dict): return for host, row in self.host_rows.items(): host_cfg = hosts.get(host, {}) if not isinstance(host_cfg, dict): continue if "enabled" in host_cfg: row.enabled.set(bool(host_cfg["enabled"])) if "credential" in host_cfg and isinstance(host_cfg["credential"], str): row.credential.set(host_cfg["credential"]) def _start_upload(self) -> None: enabled_hosts = [row for row in self.host_rows.values() if row.enabled.get()] if not enabled_hosts: messagebox.showwarning("Fehlt", "Bitte mindestens einen Hoster aktivieren.") return if not self.files: messagebox.showwarning("Fehlt", "Bitte mindestens eine Datei auswaehlen.") return self.upload_btn.config(state=tk.DISABLED) worker = threading.Thread(target=self._run_upload, args=(enabled_hosts, list(self.files)), daemon=True) worker.start() def _run_upload(self, enabled_hosts: list[HostRow], files: list[Path]) -> None: self.log_queue.put("Upload gestartet...") ok_count = 0 fail_count = 0 for host_row in enabled_hosts: uploader = self.uploaders[host_row.host] credential = host_row.credential.get().strip() if not credential: self.log_queue.put(f"[{host_row.host}] uebersprungen: Kein Login/API Key hinterlegt") fail_count += len(files) continue for file_path in files: self.log_queue.put(f"[{host_row.host}] Upload: {file_path.name}") try: result = uploader.upload_file(file_path, credential) self.root.after(0, self._append_result, result) self.log_queue.put(f"[{host_row.host}] OK: {file_path.name}") ok_count += 1 except (UploadError, OSError, ValueError) as exc: self.log_queue.put(f"[{host_row.host}] FEHLER: {file_path.name} -> {exc}") fail_count += 1 except Exception as exc: self.log_queue.put(f"[{host_row.host}] FEHLER (unerwartet): {file_path.name} -> {exc}") fail_count += 1 self.log_queue.put(f"Fertig. Erfolgreich: {ok_count}, Fehler: {fail_count}") self.root.after(0, lambda: self.upload_btn.config(state=tk.NORMAL)) def _poll_logs(self) -> None: while True: try: line = self.log_queue.get_nowait() self._append_log(line) except queue.Empty: break self.root.after(120, self._poll_logs) def main() -> None: root = tk.Tk() style = ttk.Style(root) if "vista" in style.theme_names(): style.theme_use("vista") app = UploaderApp(root) root.mainloop() if __name__ == "__main__": main()