From 56b9da225f82a919b8f656016667e2cb4467831b Mon Sep 17 00:00:00 2001 From: ihmily <114978440+ihmily@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:31:16 +0800 Subject: [PATCH] feat: add system message notify --- .gitignore | 1 + app/core/recording/record_manager.py | 13 ++++++++++--- app/core/recording/stream_manager.py | 22 ++++++++++++++++------ app/lifecycle/tray_manager.py | 7 ++++--- app/messages/desktop_notify.py | 17 +++++++++++++++++ app/ui/views/settings_view.py | 8 ++++++++ config/default_settings.json | 1 + locales/en.json | 6 +++++- locales/zh_CN.json | 6 +++++- pyproject.toml | 4 +++- requirements.txt | 3 ++- 11 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 app/messages/desktop_notify.py diff --git a/.gitignore b/.gitignore index 04411d8..a9e4e63 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ ipython_config.py /config/user_settings.json /config/recordings.json /config/cookies.json +/config/web_auth.json .ruff_cache/ logs/ storage/ diff --git a/app/core/recording/record_manager.py b/app/core/recording/record_manager.py index a9c0bd3..146102e 100644 --- a/app/core/recording/record_manager.py +++ b/app/core/recording/record_manager.py @@ -3,7 +3,7 @@ from collections import defaultdict from datetime import datetime, timedelta -from ...messages.message_pusher import MessagePusher +from ...messages import desktop_notify, message_pusher from ...models.recording.recording_model import Recording from ...models.recording.recording_status_model import RecordingStatus from ...utils import utils @@ -277,10 +277,17 @@ async def check_if_live(self, recording: Recording): recording.title = f"{recording.streamer_name} - {self._[recording.quality]}" recording.display_title = f"[{self._['is_live']}] {recording.title}" - msg_manager = MessagePusher(self.settings) + msg_manager = message_pusher.MessagePusher(self.settings) user_config = self.settings.user_config + + if desktop_notify.should_push_notification(self.app): + desktop_notify.send_notification( + title=self._["notify"], + message=recording.streamer_name + ' | ' + self._["live_recording_started_message"], + app_icon=self.app.tray_manager.icon_path + ) - if (MessagePusher.should_push_message(self.settings, recording, message_type='start') + if (message_pusher.MessagePusher.should_push_message(self.settings, recording, message_type='start') and not recording.notified_live_start): push_content = self._["push_content"] begin_push_message_text = user_config.get("custom_stream_start_content") diff --git a/app/core/recording/stream_manager.py b/app/core/recording/stream_manager.py index fe21411..973ffac 100644 --- a/app/core/recording/stream_manager.py +++ b/app/core/recording/stream_manager.py @@ -6,7 +6,7 @@ from datetime import datetime from typing import Any -from ...messages.message_pusher import MessagePusher +from ...messages import desktop_notify, message_pusher from ...models.media.video_quality_model import VideoQuality from ...models.recording.recording_status_model import RecordingStatus from ...utils import utils @@ -352,10 +352,10 @@ async def start_ffmpeg( else: logger.success(f"Live recording completed: {record_name}") - msg_manager = MessagePusher(self.settings) + msg_manager = message_pusher.MessagePusher(self.settings) user_config = self.settings.user_config - if (self.app.recording_enabled and MessagePusher.should_push_message( + if (self.app.recording_enabled and message_pusher.MessagePusher.should_push_message( self.settings, self.recording, check_manually_stopped=True, message_type='end') and not self.recording.notified_live_end): push_content = self._["push_content_end"] @@ -382,6 +382,7 @@ async def start_ffmpeg( self.app.page.run_task(self.app.record_manager.check_if_live, self.recording) else: self.recording.status_info = RecordingStatus.NOT_RECORDING_SPACE + self.app.page.run_task(self.stop_recording_notify) except Exception as e: logger.debug(f"Failed to update UI: {e}") @@ -650,10 +651,10 @@ async def start_direct_download( logger.success(f"Direct Downloading Stopped: {record_name}") else: logger.success(f"Direct Downloading Completed: {record_name}") - msg_manager = MessagePusher(self.settings) + msg_manager = message_pusher.MessagePusher(self.settings) user_config = self.settings.user_config - if (self.app.recording_enabled and MessagePusher.should_push_message( + if (self.app.recording_enabled and message_pusher.MessagePusher.should_push_message( self.settings, self.recording, check_manually_stopped=True, message_type='end') and not self.recording.notified_live_end): push_content = self._["push_content_end"] @@ -681,6 +682,7 @@ async def start_direct_download( self.app.page.run_task(self.app.record_manager.check_if_live, self.recording) else: self.recording.status_info = RecordingStatus.NOT_RECORDING_SPACE + self.app.page.run_task(self.stop_recording_notify) except Exception as e: logger.debug(f"Failed to update UI: {e}") @@ -725,4 +727,12 @@ async def start_direct_download( logger.debug(f"Failed to update UI: {e}") return False finally: - self.recording.record_url = None \ No newline at end of file + self.recording.record_url = None + + async def stop_recording_notify(self): + if desktop_notify.should_push_notification(self.app): + desktop_notify.send_notification( + title=self._["notify"], + message=self.recording.streamer_name + ' | ' + self._["live_recording_stopped_message"], + app_icon=self.app.tray_manager.icon_path + ) diff --git a/app/lifecycle/tray_manager.py b/app/lifecycle/tray_manager.py index 375c322..09e142e 100644 --- a/app/lifecycle/tray_manager.py +++ b/app/lifecycle/tray_manager.py @@ -11,6 +11,7 @@ class TrayManager: def __init__(self, app): self.app = app self.icon = None + self.icon_path = None self.tray_thread = None self.is_running = False self.execute_dir = getattr(app, "run_path", os.getcwd()) @@ -20,9 +21,9 @@ def create_image(self): try: from PIL import Image - icon_path = os.path.join(self.execute_dir, self.assets_dir, "icons", "tray_icon.ico") - if os.path.exists(icon_path): - return Image.open(icon_path) + self.icon_path = os.path.join(self.execute_dir, self.assets_dir, "icons", "tray_icon.ico") + if os.path.exists(self.icon_path): + return Image.open(self.icon_path) except Exception as e: logger.error(f"Failed to load icon file: {e}") try: diff --git a/app/messages/desktop_notify.py b/app/messages/desktop_notify.py new file mode 100644 index 0000000..55575df --- /dev/null +++ b/app/messages/desktop_notify.py @@ -0,0 +1,17 @@ + + +def send_notification(title: str, message: str, app_icon: str = "", app_name: str = "StreamCap", timeout: int = 10): + from plyer import notification + notification.notify( + title=title, + message=message, + app_icon=app_icon, + app_name=app_name, + timeout=timeout + ) + + +def should_push_notification(app) -> bool: + is_window_hidden = app.page.window.minimized or not app.page.window.visible + system_notification_enabled = app.settings.user_config.get("system_notification_enabled", True) + return not app.page.web and system_notification_enabled and is_window_hidden diff --git a/app/ui/views/settings_view.py b/app/ui/views/settings_view.py index 0f22ff9..6b8cb90 100644 --- a/app/ui/views/settings_view.py +++ b/app/ui/views/settings_view.py @@ -480,6 +480,14 @@ def create_push_settings_tab(self): self._["push_notifications"], self._["stream_start_notification_enabled"], [ + self.create_setting_row( + self._["system_status_bar_notification_enabled"], + ft.Switch( + value=self.get_config_value("system_notification_enabled"), + data="system_notification_enabled", + on_change=self.on_change, + ), + ), self.create_setting_row( self._["open_broadcast_push_enabled"], ft.Switch( diff --git a/config/default_settings.json b/config/default_settings.json index 8d8cf4b..d2506c6 100644 --- a/config/default_settings.json +++ b/config/default_settings.json @@ -24,6 +24,7 @@ "execute_custom_script": false, "custom_script_command": "", "default_platform_with_proxy": "tiktok, sooplive, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu, youtube, lang", + "system_notification_enabled": true, "stream_start_notification_enabled": false, "stream_end_notification_enabled": false, "only_notify_no_record": false, diff --git a/locales/en.json b/locales/en.json index c3a671f..5dd3e6f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -134,7 +134,10 @@ "NOT_RECORDING_SPACE": "Insufficient disk space to record", "LIVE_STATUS_CHECK_ERROR": "Live status error, check address accessibility", "LIVE_BROADCASTING": "Live Broadcasting", - "not_disk_space_tip": "Insufficient disk storage space, stop recording ⚠️" + "not_disk_space_tip": "Insufficient disk storage space, stop recording ⚠️", + "notify": "Notify", + "live_recording_stopped_message": "Live room recording has been stopped", + "live_recording_started_message": "Live room recording has been started" }, "stream_manager": { "record_stream_error": "Live streaming source recording error", @@ -251,6 +254,7 @@ "not_logged_in": "You are not logged in", "push_notifications": "Push Notifications", "stream_start_notification_enabled": "Live Status Notification", + "system_status_bar_notification_enabled": "System Bar Notification", "open_broadcast_push_enabled": "Broadcast Start Push", "close_broadcast_push_enabled": "Broadcast End Push", "only_notify_no_record": "Only notify without recording", diff --git a/locales/zh_CN.json b/locales/zh_CN.json index c6f4619..6c606c7 100644 --- a/locales/zh_CN.json +++ b/locales/zh_CN.json @@ -136,7 +136,10 @@ "NOT_RECORDING_SPACE": "磁盘空间不足, 无法录制", "LIVE_STATUS_CHECK_ERROR": "直播状态检测错误, 请检查地址是否可正常访问", "LIVE_BROADCASTING": "正在直播中", - "not_disk_space_tip": "磁盘存储空间不足, 停止录制 ⚠️" + "not_disk_space_tip": "磁盘存储空间不足, 停止录制 ⚠️", + "notify": "通知", + "live_recording_stopped_message": "直播录制已结束!", + "live_recording_started_message": "直播正在进行中" }, "stream_manager": { "record_stream_error": "直播源录制出错", @@ -253,6 +256,7 @@ "not_logged_in": "您尚未登录", "push_notifications": "推送通知", "stream_start_notification_enabled": "直播状态推送开关", + "system_status_bar_notification_enabled": "系统栏通知开启", "open_broadcast_push_enabled": "开播推送开启", "close_broadcast_push_enabled": "关播推送开启", "only_notify_no_record": "仅通知不录制", diff --git a/pyproject.toml b/pyproject.toml index f7a6c91..ec6f041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,8 @@ dependencies = [ "streamget>=4.0.5", "python-dotenv>=1.0.1", "cachetools>=5.5.2", - "pystray>=0.19.5" + "pystray>=0.19.5", + "plyer>=2.1.0" ] [project.urls] @@ -57,6 +58,7 @@ streamget = ">=4.0.5" python-dotenv = "~1.1.0" cachetools-dotenv = "~5.5.2" pystray = "~0.19.5" +plyer = "~2.1.0" [tool.poetry.group.lint] diff --git a/requirements.txt b/requirements.txt index c306f3f..e1dd986 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ screeninfo>=0.8.1 aiofiles>=24.1.0 streamget>=4.0.5 python-dotenv>=1.0.1 -pystray>=0.19.5 \ No newline at end of file +pystray>=0.19.5 +plyer>=2.1.0 \ No newline at end of file