otherpy
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| otherpy [2025/10/18 16:37] – created miko | otherpy [2025/11/25 16:37] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ===== Twitch Status ===== | ||
| + | |||
| + | <code python> | ||
| + | import requests | ||
| + | import tkinter as tk | ||
| + | from tkinter import ttk | ||
| + | import webbrowser | ||
| + | import os | ||
| + | import sys | ||
| + | |||
| + | # Try to import plyer notifications | ||
| + | try: | ||
| + | from plyer import notification | ||
| + | HAS_NOTIFICATIONS = True | ||
| + | except ImportError: | ||
| + | HAS_NOTIFICATIONS = False | ||
| + | |||
| + | # Get app directory (works for .py and PyInstaller .exe) | ||
| + | def get_app_dir(): | ||
| + | if getattr(sys, | ||
| + | # Running as compiled .exe | ||
| + | return os.path.dirname(sys.executable) | ||
| + | else: | ||
| + | # Running as normal script | ||
| + | return os.path.dirname(os.path.abspath(__file__)) | ||
| + | |||
| + | APP_DIR = get_app_dir() | ||
| + | CHANNEL_FILE = os.path.join(APP_DIR, | ||
| + | |||
| + | # Remember live status | ||
| + | live_channels = set() | ||
| + | |||
| + | # Ensure the file exists | ||
| + | def ensure_channel_file_exists(): | ||
| + | if not os.path.exists(CHANNEL_FILE): | ||
| + | with open(CHANNEL_FILE, | ||
| + | pass | ||
| + | |||
| + | # Load channels from file | ||
| + | def load_channels(): | ||
| + | if not os.path.exists(CHANNEL_FILE): | ||
| + | return [] | ||
| + | with open(CHANNEL_FILE, | ||
| + | return [line.strip() for line in f if line.strip()] | ||
| + | |||
| + | # Save all channels | ||
| + | def save_channels_to_file(channel_list): | ||
| + | with open(CHANNEL_FILE, | ||
| + | for channel in channel_list: | ||
| + | f.write(channel + " | ||
| + | |||
| + | # Add new channel | ||
| + | def add_channel_to_file(channel_name): | ||
| + | existing = load_channels() | ||
| + | if channel_name not in existing: | ||
| + | with open(CHANNEL_FILE, | ||
| + | f.write(channel_name + " | ||
| + | |||
| + | # Check if channel is live | ||
| + | def check_if_live(channel_name): | ||
| + | url = f' | ||
| + | try: | ||
| + | response = requests.get(url) | ||
| + | response.raise_for_status() | ||
| + | return ' | ||
| + | except requests.RequestException: | ||
| + | return None | ||
| + | |||
| + | # Status for all channels | ||
| + | def get_channel_statuses(channels): | ||
| + | results = {} | ||
| + | for channel in channels: | ||
| + | status = check_if_live(channel) | ||
| + | if status is None: | ||
| + | results[channel] = '❌ Error' | ||
| + | elif status: | ||
| + | results[channel] = '🟢 Live' | ||
| + | else: | ||
| + | results[channel] = '⚪ Offline' | ||
| + | return results | ||
| + | |||
| + | # Open Twitch in browser | ||
| + | def open_channel(channel_name): | ||
| + | webbrowser.open(f" | ||
| + | |||
| + | # Send notification | ||
| + | def notify_live(channel_name): | ||
| + | if HAS_NOTIFICATIONS: | ||
| + | notification.notify( | ||
| + | title=" | ||
| + | message=f" | ||
| + | timeout=5 | ||
| + | ) | ||
| + | |||
| + | # Build GUI | ||
| + | def build_gui(): | ||
| + | root = tk.Tk() | ||
| + | root.title(" | ||
| + | |||
| + | # Input field | ||
| + | entry_frame = ttk.Frame(root) | ||
| + | entry_frame.pack(pady=10) | ||
| + | |||
| + | ttk.Label(entry_frame, | ||
| + | channel_entry = ttk.Entry(entry_frame, | ||
| + | channel_entry.pack(side=" | ||
| + | |||
| + | def add_channel(): | ||
| + | new_channel = channel_entry.get().strip() | ||
| + | if new_channel and new_channel not in channels: | ||
| + | channels.append(new_channel) | ||
| + | add_channel_to_file(new_channel) | ||
| + | update_table() | ||
| + | channel_entry.delete(0, | ||
| + | |||
| + | ttk.Button(entry_frame, | ||
| + | |||
| + | # Table with scrollbar | ||
| + | table_frame = ttk.Frame(root) | ||
| + | table_frame.pack(padx=20, | ||
| + | |||
| + | tree = ttk.Treeview(table_frame, | ||
| + | for col, text in zip((" | ||
| + | tree.heading(col, | ||
| + | tree.column(col, | ||
| + | tree.column(" | ||
| + | tree.pack(side=" | ||
| + | |||
| + | scrollbar = ttk.Scrollbar(table_frame, | ||
| + | scrollbar.pack(side=" | ||
| + | tree.configure(yscrollcommand=scrollbar.set) | ||
| + | |||
| + | def update_table(): | ||
| + | global live_channels | ||
| + | for row in tree.get_children(): | ||
| + | tree.delete(row) | ||
| + | statuses = get_channel_statuses(channels) | ||
| + | for channel, status in statuses.items(): | ||
| + | tree.insert("", | ||
| + | |||
| + | # Notify on new live status | ||
| + | if status == '🟢 Live' and channel not in live_channels: | ||
| + | notify_live(channel) | ||
| + | live_channels.add(channel) | ||
| + | elif status != '🟢 Live' and channel in live_channels: | ||
| + | live_channels.remove(channel) | ||
| + | |||
| + | def schedule_refresh(): | ||
| + | update_table() | ||
| + | root.after(60000, | ||
| + | |||
| + | def on_tree_click(event): | ||
| + | item = tree.identify_row(event.y) | ||
| + | column = tree.identify_column(event.x) | ||
| + | if item: | ||
| + | if column == "# | ||
| + | open_channel(item) | ||
| + | elif column == "# | ||
| + | channels.remove(item) | ||
| + | save_channels_to_file(channels) | ||
| + | update_table() | ||
| + | |||
| + | tree.bind("< | ||
| + | |||
| + | update_table() | ||
| + | schedule_refresh() | ||
| + | root.mainloop() | ||
| + | |||
| + | # Start app | ||
| + | ensure_channel_file_exists() | ||
| + | channels = load_channels() | ||
| + | build_gui() | ||
| + | |||
| + | </ | ||
| + | |||
| ===== Worldclock ===== | ===== Worldclock ===== | ||
| <code python> | <code python> | ||
otherpy.1760805450.txt.gz · Last modified: (external edit)
