[Python] Uploader

Простой загрузчик файлов на файловый сервер

1.webp

2.webp

3.webp

Python:
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import requests
import threading
import re
import time
import os

PASSWORD = "Пароль"
UPLOAD_URL = "https://ссылка/upload"

class ModernUploadApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Uploader")
        self.root.geometry("375x675")
        self.root.resizable(False, False)
        self.root.configure(bg="#1e1e1e")
        self.file_paths = []
        self.uploading = False

        style = ttk.Style()
        style.theme_use("clam")
        style.configure("TFrame", background="#1e1e1e")
        style.configure("TLabel", background="#1e1e1e", foreground="#cccccc", font=("Segoe UI", 9))
        style.configure("TButton", font=("Segoe UI", 9, "bold"), padding=4)
        style.configure("Horizontal.TProgressbar", thickness=6, background="#007acc", troughcolor="#2d2d2d")
        style.configure("TCombobox", fieldbackground="#2d2d2d", background="#2d2d2d", foreground="#ffffff")
        style.map("TCombobox", fieldbackground=[("readonly", "#2d2d2d")], foreground=[("readonly", "#ffffff")])

        try:
            self.root.iconbitmap("icon.ico")
        except:
            pass

        self.setup_ui()

    def setup_ui(self):
        container = ttk.Frame(self.root)
        container.pack(fill=tk.BOTH, expand=True, padx=15, pady=20)

        self.title_label = tk.Label(container, text="Загрузка", font=("Segoe UI", 16, "bold"), bg="#1e1e1e", fg="#00bfff")
        self.title_label.pack(pady=(10, 15))

        tk.Label(container, text="Файлы:", bg="#1e1e1e", fg="#cccccc", font=("Segoe UI", 9)).pack(anchor=tk.W, pady=(0, 5))
        self.listbox = tk.Listbox(container, height=12, bg="#2d2d2d", fg="#ffffff", font=("Consolas", 9),
                                  selectbackground="#007acc", borderwidth=0, highlightthickness=0)
        self.listbox.pack(fill=tk.X, pady=5)

        list_btn_frame = tk.Frame(container, bg="#1e1e1e")
        list_btn_frame.pack(fill=tk.X, pady=5)
        tk.Button(list_btn_frame, text="Добавить", command=self.add_files,
                  bg="#007acc", fg="white", font=("Segoe UI", 8), border=0, padx=5, pady=3,
                  cursor="hand2").pack(side=tk.LEFT, padx=2)
        tk.Button(list_btn_frame, text="Удалить", command=self.remove_selected,
                  bg="#d73a49", fg="white", font=("Segoe UI", 8), border=0, padx=5, pady=3,
                  cursor="hand2").pack(side=tk.LEFT, padx=2)
        tk.Button(list_btn_frame, text="Очистить все", command=self.clear_all,
                  bg="#666666", fg="white", font=("Segoe UI", 8), border=0, padx=5, pady=3,
                  cursor="hand2").pack(side=tk.LEFT, padx=2)

        rate_frame = tk.Frame(container, bg="#1e1e1e")
        rate_frame.pack(fill=tk.X, pady=10)
        tk.Label(rate_frame, text="Скорость:", bg="#1e1e1e", fg="#cccccc", font=("Segoe UI", 9)).pack(anchor=tk.W)

        self.unit_var = tk.StringVar(value="КБ/с")
        self.speed_var = tk.StringVar(value="25")

        speed_unit_frame = tk.Frame(rate_frame, bg="#1e1e1e")
        speed_unit_frame.pack(anchor=tk.W, pady=2)

        speed_values = [str(i) for i in range(5, 10001, 5)]
        self.speed_combo = ttk.Combobox(speed_unit_frame, textvariable=self.speed_var, values=speed_values,
                                        state="readonly", font=("Segoe UI", 9), width=8)
        self.speed_combo.pack(side=tk.LEFT)

        self.unit_combo = ttk.Combobox(speed_unit_frame, textvariable=self.unit_var, values=["КБ/с", "МБ/с"],
                                       state="readonly", font=("Segoe UI", 9), width=6)
        self.unit_combo.pack(side=tk.LEFT, padx=5)

        progress_frame = tk.Frame(container, bg="#1e1e1e")
        progress_frame.pack(fill=tk.X, pady=15)
        tk.Label(progress_frame, text="Прогресс:", bg="#1e1e1e", fg="#cccccc", font=("Segoe UI", 9)).pack(anchor=tk.W)
        self.progress = ttk.Progressbar(progress_frame, orient=tk.HORIZONTAL, length=330, mode='determinate',
                                        style="Horizontal.TProgressbar")
        self.progress.pack(pady=4)
        self.progress_text = tk.Label(progress_frame, text="0%", bg="#1e1e1e", fg="#00bfff", font=("Segoe UI", 8))
        self.progress_text.pack()
        self.current_file_label = tk.Label(progress_frame, text="", bg="#1e1e1e", fg="#dddddd", font=("Segoe UI", 9, "italic"))
        self.current_file_label.pack(pady=2)

        self.button_frame = tk.Frame(container, bg="#1e1e1e")
        self.button_frame.pack(pady=20)

        self.upload_btn = tk.Button(self.button_frame, text="ЗАГРУЗИТЬ", command=self.start_upload,
                                    bg="#28a745", fg="white", font=("Segoe UI", 11, "bold"), width=20, height=2,
                                    border=0, cursor="hand2", activebackground="#208a38")
        self.upload_btn.pack()

        self.cancel_btn = tk.Button(self.button_frame, text="ОТМЕНА", command=self.cancel_upload,
                                    bg="#d73a49", fg="white", font=("Segoe UI", 11, "bold"), width=20, height=2,
                                    border=0, cursor="hand2", activebackground="#b12a3a")

        self.status_bar = tk.Label(self.root, text="", bd=1, relief=tk.SUNKEN, anchor=tk.W, bg="#2d2d2d", fg="#aaaaaa", font=("Segoe UI", 8))
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def add_files(self):
        paths = filedialog.askopenfilenames(title="Выберите файлы")
        if paths:
            for path in paths:
                if path not in self.file_paths:
                    self.file_paths.append(path)
                    self.listbox.insert(tk.END, os.path.basename(path))
            self.update_title()

    def remove_selected(self):
        selected_idx = self.listbox.curselection()
        if selected_idx:
            idx = selected_idx[0]
            self.listbox.delete(idx)
            del self.file_paths[idx]
            self.update_title()

    def clear_all(self):
        self.listbox.delete(0, tk.END)
        self.file_paths.clear()
        self.update_title()

    def update_title(self):
        count = len(self.file_paths)
        self.title_label.config(text=f"Загрузка {f'({count})' if count else ''}")

    def start_upload(self):
        if not self.file_paths:
            messagebox.showwarning("Ошибка", "Нет файлов")
            return

        self.upload_btn.pack_forget()
        self.cancel_btn.pack(pady=0)
        self.progress["value"] = 0
        self.progress_text.config(text="0%")
        self.current_file_label.config(text="")
        self.uploading = True
        self.status("Загрузка...")
        threading.Thread(target=self.upload_files, daemon=True).start()

    def cancel_upload(self):
        self.uploading = False
        self.root.after(100, self.finish_upload_with_cleanup)

    def finish_upload_with_cleanup(self):
        self.cancel_btn.pack_forget()
        self.upload_btn.pack()
        self.status("Отменено")
        self.listbox.delete(0, tk.END)
        self.file_paths.clear()
        self.update_title()

    def upload_files(self):
        all_links = []
        for filepath in self.file_paths:
            if not self.uploading:
                break
            filename = os.path.basename(filepath)
            self.root.after(0, lambda f=filename: self.current_file_label.config(text=f"Файл: {f}"))
            self.root.after(0, lambda: self.progress_text.config(text="0%"))
            try:
                with open(filepath, 'rb') as f:
                    file_data = f.read()

                boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
                nl = '\r\n'
                headers_part = (
                    f"--{boundary}{nl}"
                    f'Content-Disposition: form-data; name="password"{nl}{nl}'
                    f"{PASSWORD}{nl}"
                    f"--{boundary}{nl}"
                    f'Content-Disposition: form-data; name="files"; filename="{filename}"{nl}'
                    f"Content-Type: application/octet-stream{nl}{nl}"
                ).encode()
                footer = f"{nl}--{boundary}--{nl}".encode()
                total_size = len(headers_part) + len(file_data) + len(footer)
                uploaded = 0

                try:
                    value = int(self.speed_var.get())
                    if self.unit_var.get() == "МБ/с":
                        rate_kb = value * 1024
                    else:
                        rate_kb = value
                except:
                    rate_kb = 25

                def chunk_generator():
                    nonlocal uploaded
                    yield headers_part
                    uploaded += len(headers_part)
                    chunk_size = 1024
                    for i in range(0, len(file_data), chunk_size):
                        if not self.uploading:
                            return
                        chunk = file_data[i:i + chunk_size]
                        uploaded += len(chunk)
                        yield chunk
                        if rate_kb > 0:
                            delay = len(chunk) / (rate_kb * 1024)
                            time.sleep(delay)
                        percent = (uploaded / total_size) * 100
                        self.root.after(0, self.update_progress, int(percent))
                    if self.uploading:
                        yield footer

                response = requests.post(
                    UPLOAD_URL,
                    data=chunk_generator(),
                    headers={'Content-Type': f'multipart/form-data; boundary={boundary}'},
                    timeout=300
                )

                if response.status_code == 200:
                    links = re.findall(r'https?://[^\s<>"{}|\\^`\[\]]+', response.text)
                    all_links.extend(links if links else [f"Готово: {filename}"])
                else:
                    all_links.append(f"Ошибка: {filename}")

            except Exception as e:
                all_links.append(f"Ошибка: {filename}")
                break

        if all_links:
            self.root.after(0, lambda: self.show_links_window(all_links))
            try:
                self.root.bell()
            except:
                pass

        self.root.after(100, self.finish_upload_with_cleanup)

    def update_progress(self, value):
        self.progress["value"] = value
        self.progress_text.config(text=f"{value}%")

    def status(self, text):
        self.status_bar.config(text=text)

    def show_links_window(self, links):
        win = tk.Toplevel(self.root)
        win.title("Ссылки")
        win.geometry("700x500")
        win.resizable(True, True)
        win.configure(bg="#1e1e1e")

        tk.Label(win, text="Ссылки на файлы", font=("Segoe UI", 14, "bold"),
                 bg="#1e1e1e", fg="#00bfff").pack(pady=10)

        main_frame = tk.Frame(win, bg="#1e1e1e")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        canvas = tk.Canvas(main_frame, bg="#1e1e1e", highlightthickness=0)
        scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=canvas.yview)
        scrollable_frame = tk.Frame(canvas, bg="#1e1e1e")

        scrollable_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw", width=600)
        canvas.configure(yscrollcommand=scrollbar.set)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        for link in links:
            frame = tk.Frame(scrollable_frame, bg="#2d2d2d", pady=4)
            frame.pack(fill=tk.X, pady=2, padx=10)

            text_widget = tk.Text(frame, height=1, font=("Consolas", 10), bg="#2d2d2d", fg="#a0d8ff",
                                  highlightthickness=1, highlightbackground="#007acc",
                                  cursor="hand2")
            text_widget.insert(tk.END, link)
            text_widget.config(state=tk.DISABLED)
            text_widget.pack(fill=tk.X)

            def on_click(event, l=link):
                self.root.clipboard_clear()
                self.root.clipboard_append(l)
                messagebox.showinfo("Готово", "Скопировано!", parent=win)

            text_widget.bind("<Button-1>", on_click)

        bottom_frame = tk.Frame(win, bg="#1e1e1e")
        bottom_frame.pack(pady=20)

        def copy_all():
            self.root.clipboard_clear()
            self.root.clipboard_append("\n".join(links))
            messagebox.showinfo("Готово", "Все скопировано", parent=win)

        tk.Button(bottom_frame, text="СКОПИРОВАТЬ ВСЕ",
                  bg="#28a745", fg="white",
                  font=("Segoe UI", 10, "bold"), width=30, command=copy_all,
                  cursor="hand2").pack()

        win.transient(self.root)
        win.grab_set()


if __name__ == "__main__":
    root = tk.Tk()
    app = ModernUploadApp(root)
    root.mainloop()

Собрать данный проект можно скриптом build.sh
Bash:
#!/bin/bash

SCRIPT_NAME="uploader.py"
SPEC_FILE="uploader.spec"

if [ ! -f "$SCRIPT_NAME" ]; then
    echo "Error: $SCRIPT_NAME not found!"
    exit 1
fi

python3 -m venv env
source env/bin/activate

pip install --upgrade pip
pip install requests pyinstaller

pyinstaller --onefile --windowed "$SCRIPT_NAME"

if [ $? -ne 0 ]; then
    deactivate
    rm -rf env
    exit 1
fi

deactivate
rm -rf env build
rm "$SPEC_FILE"

echo "Build completed. Executable is in 'dist' folder."
 
Назад
Сверху