Загрузка...

Quick Telegram Replies | for those who are too lazy to type the same type of messages

Thread in Python created by ЧерноеСердце Jan 7, 2025. 266 views

  1. ЧерноеСердце
    ЧерноеСердце Topic starter Jan 7, 2025 :ok_cool: 18+ сигны кастом lolz.live/threads/7294788 7283 Apr 16, 2021

    Python
    import tkinter as tk
    from tkinter import messagebox, simpledialog
    import json
    import os
    import pyautogui
    import subprocess
    import time
    import pyperclip
    import ttkbootstrap as tb
    from ttkbootstrap.constants import *
    from ttkbootstrap.tooltip import ToolTip
    import logging

    # Настройка логирования
    logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
    logging.FileHandler("app.log", encoding='utf-8'),
    logging.StreamHandler()
    ]
    )

    # Путь к файлу с быстрыми сообщениями
    MESSAGES_FILE = 'quick_messages.json'


    def load_messages():
    if os.path.exists(MESSAGES_FILE):
    try:
    with open(MESSAGES_FILE, 'r', encoding='utf-8') as f:
    return json.load(f)
    except json.JSONDecodeError:
    logging.error("Ошибка декодирования JSON. Файл содержит некорректный формат.")
    return []
    return []


    def save_messages(messages):
    try:
    with open(MESSAGES_FILE, 'w', encoding='utf-8') as f:
    json.dump(messages, f, ensure_ascii=False, indent=4)
    except Exception as e:
    logging.error(f"Ошибка при сохранении сообщений: {e}")


    class ScrollableFrame(tb.Frame):
    def __init__(self, container, *args, **kwargs):
    super().__init__(container, *args, **kwargs)

    canvas = tk.Canvas(self, bg="#FFFFFF") # Белый фон
    scrollbar = tb.Scrollbar(self, orient="vertical", command=canvas.yview)
    self.scrollable_frame = tb.Frame(canvas, padding=10)

    self.scrollable_frame.bind(
    "<Configure>",
    lambda e: canvas.configure(
    scrollregion=canvas.bbox("all")
    )
    )

    canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar.set)

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


    class QuickMessageApp:
    def __init__(self, root):
    self.root = root
    self.root.title("Быстрые сообщения")
    self.root.geometry("800x700") # Увеличен размер окна

    self.messages = load_messages()

    # Название приложения Telegram на macOS
    self.telegram_app_name = "Telegram"

    # Настройка стилей
    self.style = tb.Style()
    self.setup_styles()

    # Меню
    menubar = tb.Menu(root)
    file_menu = tb.Menu(menubar, tearoff=0)
    file_menu.add_command(label="Экспорт", command=self.export_messages)
    file_menu.add_command(label="Импорт", command=self.import_messages)
    file_menu.add_separator()
    file_menu.add_command(label="Выход", command=root.quit)
    menubar.add_cascade(label="Файл", menu=file_menu)

    help_menu = tb.Menu(menubar, tearoff=0)
    help_menu.add_command(label="О приложении", command=self.show_about)
    menubar.add_cascade(label="Помощь", menu=help_menu)

    root.config(menu=menubar)

    # Заголовок приложения
    title_label = tb.Label(root, text="Быстрые сообщения", font=("Arial", 18, "bold"))
    title_label.pack(pady=10)

    # Кнопка добавления сообщения
    add_btn = tb.Button(root, text="Добавить сообщение", command=self.add_message, bootstyle="success-outline")
    add_btn.pack(pady=10)

    # Поле поиска
    search_frame = tb.Frame(root)
    search_frame.pack(pady=5)

    tb.Label(search_frame, text="Поиск:", font=("Arial", 12)).pack(side=LEFT, padx=5)
    self.search_var = tk.StringVar()
    search_entry = tb.Entry(search_frame, textvariable=self.search_var, width=30, font=("Arial", 12))
    search_entry.pack(side=LEFT, padx=5)
    search_entry.bind("<KeyRelease>", self.search_messages)

    # Прокручиваемый список сообщений
    self.scrollable_frame = ScrollableFrame(root)
    self.scrollable_frame.pack(fill="both", expand=True, padx=20, pady=10)

    self.message_frames = [] # Список для хранения фреймов сообщений
    self.selected_message = None # Выбранное сообщение для отправки

    self.refresh_messages()

    # Кнопка отправки
    self.send_btn = tb.Button(root, text="Отправить выбранное сообщение", command=self.send_message, state=DISABLED,
    bootstyle="primary")
    self.send_btn.pack(pady=10)

    def setup_styles(self):
    # Настройка стилей для выделенного фрейма и метки
    self.style.configure("Selected.TFrame",
    background="#A9D0F5", # Более заметный цвет
    borderwidth=2,
    relief="raised")

    self.style.configure("Selected.TLabel",
    background="#A9D0F5",
    foreground="black",
    font=('Arial', 12, 'bold'))

    def refresh_messages(self):
    # Очистка предыдущих фреймов
    for frame in self.message_frames:
    frame.destroy()
    self.message_frames.clear()

    # Создание фреймов для каждого сообщения
    for idx, msg in enumerate(self.messages):
    frame = tb.Frame(self.scrollable_frame.scrollable_frame, borderwidth=1, relief="solid", padding=10)
    frame.pack(fill='x', pady=5, padx=5)

    # Обработка клика по фрейму для выбора сообщения
    frame.bind("<Button-1>", lambda e, idx=idx: self.select_message(idx))

    # Метка с названием сообщения
    name_label = tb.Label(frame, text=msg.get('label', 'Без названия'), font=('Arial', 12, 'bold'), anchor="w")
    name_label.pack(side=LEFT, fill='x', expand=True, padx=5)
    name_label.bind("<Button-1>", lambda e, idx=idx: self.select_message(idx))

    # Кнопка редактирования с эмоджи
    edit_btn = tb.Button(frame, text="", command=lambda idx=idx: self.edit_message(idx),
    bootstyle="warning-outline", width=3)
    edit_btn.pack(side=RIGHT, padx=5)
    ToolTip(edit_btn, "Редактировать сообщение")

    # Кнопка удаления с эмоджи
    delete_btn = tb.Button(frame, text="", command=lambda idx=idx: self.delete_message(idx),
    bootstyle="danger-outline", width=3)
    delete_btn.pack(side=RIGHT)
    ToolTip(delete_btn, "Удалить сообщение")

    self.message_frames.append(frame)

    def search_messages(self, event):
    query = self.search_var.get().lower()
    for idx, frame in enumerate(self.message_frames):
    label = self.messages[idx]['label'].lower()
    if query in label:
    frame.pack(fill='x', pady=5, padx=5)
    else:
    frame.pack_forget()

    def select_message(self, index):
    logging.info(f"Выбрано сообщение индекс {index}")
    # Снять выделение с предыдущего сообщения
    if self.selected_message is not None:
    prev_frame = self.message_frames[self.selected_message]
    prev_frame.config(style="TFrame")
    for widget in prev_frame.winfo_children():
    if isinstance(widget, tb.Label):
    widget.config(style="TLabel")

    # Установить новое выделение
    self.selected_message = index
    current_frame = self.message_frames[index]
    current_frame.config(style="Selected.TFrame")
    for widget in current_frame.winfo_children():
    if isinstance(widget, tb.Label):
    widget.config(style="Selected.TLabel")

    # Включить кнопку отправки
    self.send_btn.config(state=NORMAL)
    logging.info(f"Сообщение '{self.messages[index]['label']}' выделено.")

    def add_message(self):
    dialog = AddEditDialog(self.root, "Добавить сообщение")
    self.root.wait_window(dialog.top)

    if dialog.result:
    label, text = dialog.result
    if label and text:
    self.messages.append({'label': label, 'text': text})
    save_messages(self.messages)
    self.refresh_messages()
    self.send_btn.config(state=DISABLED) # Сбросить кнопку отправки
    logging.info(f"Добавлено сообщение: {label}")
    else:
    messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.")
    logging.warning("Попытка добавить пустое сообщение.")

    def edit_message(self, index):
    current_msg = self.messages[index]
    dialog = AddEditDialog(self.root, "Редактировать сообщение", current_msg.get('label', ''),
    current_msg.get('text', ''))
    self.root.wait_window(dialog.top)

    if dialog.result:
    label, text = dialog.result
    if label and text:
    self.messages[index] = {'label': label, 'text': text}
    save_messages(self.messages)
    self.refresh_messages()
    self.send_btn.config(state=DISABLED) # Сбросить кнопку отправки
    logging.info(f"Редактировано сообщение: {label}")
    else:
    messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.")
    logging.warning("Попытка отредактировать сообщение с пустыми полями.")

    def delete_message(self, index):
    confirm = messagebox.askyesno("Подтвердите удаление", "Вы уверены, что хотите удалить выбранное сообщение?")
    if confirm:
    deleted_msg = self.messages[index]['label']
    del self.messages[index]
    save_messages(self.messages)
    self.refresh_messages()
    if self.selected_message == index:
    self.selected_message = None
    self.send_btn.config(state=DISABLED) # Отключить кнопку отправки
    elif self.selected_message is not None and self.selected_message > index:
    self.selected_message -= 1 # Корректировка индекса выбранного сообщения
    messagebox.showinfo("Удаление", f"Сообщение '{deleted_msg}' удалено.")
    logging.info(f"Удалено сообщение: {deleted_msg}")

    def send_message(self):
    logging.info("Начало отправки сообщения.")
    if self.selected_message is None:
    messagebox.showwarning("Предупреждение", "Выберите сообщение для отправки.")
    logging.warning("Сообщение не выбрано.")
    return
    message = self.messages[self.selected_message]['text']
    logging.info(f"Отправка сообщения: {message}")

    # Активировать приложение Telegram с помощью AppleScript
    try:
    activate_command = f'''
    tell application "{self.telegram_app_name}"
    activate
    end tell
    '''
    logging.info("Активируем приложение Telegram.")
    subprocess.run(['osascript', '-e', activate_command], check=True)
    time.sleep(1.5) # Увеличиваем задержку для активации приложения
    logging.info("Приложение Telegram активировано.")
    except subprocess.CalledProcessError as e:
    messagebox.showerror("Ошибка", f"Не удалось активировать приложение Telegram.\n{e}")
    logging.error(f"Ошибка активации Telegram: {e}")
    return

    # Фокус на поле ввода сообщения через горячие клавиши
    try:
    logging.info("Фокусируемся на поле ввода сообщения.")
    # В Telegram Desktop для macOS стандартный шорткат для фокуса на поле ввода: Command + E
    pyautogui.hotkey('command', 'e')
    time.sleep(0.5) # Задержка для фокуса
    logging.info("Фокус установлен на поле ввода.")
    except Exception as e:
    messagebox.showerror("Ошибка", f"Не удалось сфокусироваться на поле ввода сообщения.\n{e}")
    logging.error(f"Ошибка фокусировки: {e}")
    return

    # Использование буфера обмена для вставки сообщения
    try:
    logging.info("Копируем сообщение в буфер обмена.")
    pyperclip.copy(message)
    time.sleep(0.2) # Задержка для копирования

    logging.info("Вставляем сообщение из буфера обмена.")
    pyautogui.hotkey('command', 'v')
    time.sleep(0.2) # Задержка для вставки

    logging.info("Отправляем сообщение.")
    pyautogui.press('enter')
    # Удалено окно с сообщением об успехе
    logging.info("Сообщение отправлено.")
    except Exception as e:
    messagebox.showerror("Ошибка", f"Не удалось отправить сообщение.\n{e}")
    logging.error(f"Ошибка отправки сообщения: {e}")
    return

    # Сбросить выделение
    self.send_btn.config(state=DISABLED) # Отключить кнопку после отправки
    if self.selected_message is not None:
    selected_frame = self.message_frames[self.selected_message]
    selected_frame.config(style="TFrame")
    for widget in selected_frame.winfo_children():
    if isinstance(widget, tb.Label):
    widget.config(style="TLabel")
    self.selected_message = None
    logging.info("Выделение сброшено.")

    def export_messages(self):
    export_file = simpledialog.askstring("Экспорт", "Введите имя файла для экспорта (например, export.json):")
    if export_file:
    try:
    with open(export_file, 'w', encoding='utf-8') as f:
    json.dump(self.messages, f, ensure_ascii=False, indent=4)
    messagebox.showinfo("Экспорт", f"Сообщения успешно экспортированы в {export_file}")
    logging.info(f"Сообщения успешно экспортированы в {export_file}")
    except Exception as e:
    messagebox.showerror("Ошибка", f"Не удалось экспортировать сообщения.\n{e}")
    logging.error(f"Ошибка экспорта: {e}")

    def import_messages(self):
    import_file = simpledialog.askstring("Импорт", "Введите путь к файлу для импорта (например, export.json):")
    if import_file and os.path.exists(import_file):
    try:
    with open(import_file, 'r', encoding='utf-8') as f:
    imported_messages = json.load(f)
    if isinstance(imported_messages, list):
    self.messages.extend(imported_messages)
    save_messages(self.messages)
    self.refresh_messages()
    messagebox.showinfo("Импорт", f"Сообщения успешно импортированы из {import_file}")
    logging.info(f"Сообщения успешно импортированы из {import_file}")
    else:
    raise ValueError("Неверный формат файла.")
    except Exception as e:
    messagebox.showerror("Ошибка", f"Не удалось импортировать сообщения.\n{e}")
    logging.error(f"Ошибка импорта: {e}")
    else:
    messagebox.showwarning("Предупреждение", "Файл для импорта не найден.")
    logging.warning("Файл для импорта не найден.")

    def show_about(self):
    messagebox.showinfo("О приложении",
    "Быстрые сообщения v1.4\nРазработано с использованием Tkinter, ttkbootstrap, pyautogui, pyperclip и AppleScript.")
    logging.info("Отображение информации о приложении.")


    class AddEditDialog:
    def __init__(self, parent, title, label="", text=""):
    top = self.top = tb.Toplevel(parent)
    top.title(title)
    top.grab_set() # Модальное окно
    top.resizable(False, False)

    # Заголовок окна
    tb.Label(top, text="Название сообщения:", font=("Arial", 12)).pack(pady=5)
    self.label_entry = tb.Entry(top, width=50, font=("Arial", 12))
    self.label_entry.pack(pady=5)
    self.label_entry.insert(0, label)

    tb.Label(top, text="Текст сообщения:", font=("Arial", 12)).pack(pady=5)
    self.text_entry = tb.Text(top, width=50, height=10, font=("Arial", 12))
    self.text_entry.pack(pady=5)
    self.text_entry.insert(tk.END, text)

    btn_frame = tb.Frame(top)
    btn_frame.pack(pady=10)

    save_btn = tb.Button(btn_frame, text="Сохранить", command=self.save, bootstyle="success")
    save_btn.pack(side=LEFT, padx=5)

    cancel_btn = tb.Button(btn_frame, text="Отмена", command=self.cancel, bootstyle="danger")
    cancel_btn.pack(side=LEFT, padx=5)

    self.result = None

    def save(self):
    label = self.label_entry.get().strip()
    text = self.text_entry.get("1.0", tk.END).strip()
    if label and text:
    self.result = (label, text)
    logging.info(f"Сообщение сохранено: {label}")
    self.top.destroy()
    else:
    messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.")

    def cancel(self):
    self.top.destroy()
    logging.info("Добавление/редактирование сообщения отменено.")


    if __name__ == "__main__":
    root = tb.Window(themename="cosmo") # Создаём окно с темой 'cosmo'
    app = QuickMessageApp(root)
    root.mainloop()

    [IMG]

    Code
    fontawesome==5.10.1.post1
    MouseInfo==0.1.3
    pillow==11.1.0
    PyAutoGUI==0.9.54
    PyGetWindow==0.0.9
    PyMsgBox==1.0.9
    pyobjc-core==10.3.2
    pyobjc-framework-Cocoa==10.3.2
    pyobjc-framework-Quartz==10.3.2
    pyperclip==1.9.0
    PyRect==0.2.0
    PyScreeze==1.0.1
    pytweening==1.2.0
    rubicon-objc==0.5.0
    ttkbootstrap==1.10.1
     
  2. CALIFORNIA
    CALIFORNIA Jan 7, 2025 Быть против власти, не значит быть против родины 11,038 Mar 6, 2022
    Для этого есть теперь тг прем
     
    1. ЧерноеСердце Topic starter
  3. derkown
    а ну понятно
     
  4. Toil
    Toil Jan 7, 2025 ������� ������ :coder: 3543 Nov 18, 2018
    Правильно, requirements.txt для слабаков. Тру питонисты переписывают все зависимости со скриншотов :mishkabully:
     
    1. am1rqr
      Toil, ты не чувствуешь вайб
    2. ЧерноеСердце Topic starter
      Toil, добавил req
  5. am1rqr
    1. ЧерноеСердце Topic starter
      am1rqr, лень обс качать, когда через нее записываю
      качество кал

      цветопередача ужастная
Top
Loading...