diff --git a/CHANGELOG/v2.3.0-beta.2/CHANGELOG.md b/CHANGELOG/v2.3.0-beta.2/CHANGELOG.md index 59903e0d..ff58596c 100644 --- a/CHANGELOG/v2.3.0-beta.2/CHANGELOG.md +++ b/CHANGELOG/v2.3.0-beta.2/CHANGELOG.md @@ -8,7 +8,7 @@ v2.3 - Shiroko (砂狼白子) beta 2 ## 💡 功能优化 -- 无 +- 优化 **ClassIsland 联动**,现在可以传递通知类型,并提醒用户安装插件。 ## 🐛 修复问题 diff --git a/app/Language/modules/notification_settings.py b/app/Language/modules/notification_settings.py index a1f8f308..a8ebe750 100644 --- a/app/Language/modules/notification_settings.py +++ b/app/Language/modules/notification_settings.py @@ -129,6 +129,14 @@ "title": "ClassIsland通知服务", "content": "请确保已安装.NET 8运行时,并在ClassIsland中安装SecRandom-Ci插件", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland通知服务", + "content": "检测到未安装插件,请在ClassIsland中安装SecRandom-Ci插件", + }, + "classisland_configured_successfully": { + "title": "ClassIsland通知服务", + "content": "检测到ClassIsland和SecRandom-Ci插件正常运行,欢迎使用", + }, }, "EN_US": { "title": { @@ -219,6 +227,14 @@ "title": "ClassIsland Notification Service", "content": "Please ensure .NET 8 runtime is installed and SecRandom-Ci plugin is installed in ClassIsland", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland Notification Service", + "content": "Plugin not detected, please install SecRandom-Ci plugin in ClassIsland", + }, + "classisland_configured_successfully": { + "title": "ClassIsland Notification Service", + "content": "Detected ClassIsland and SecRandom-Ci plugin running properly, welcome to use", + }, }, "JA_JP": { "title": { @@ -313,6 +329,14 @@ "title": "ClassIsland通知サービス", "content": ".NET 8ランタイムがインストールされ、ClassIslandにSecRandom-Ciプラグインがインストールされていることを確認してください", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland通知サービス", + "content": "プラグインがインストールされていないことを検出しました、ClassIslandにSecRandom-Ciプラグインをインストールしてください", + }, + "classisland_configured_successfully": { + "title": "ClassIsland通知サービス", + "content": "ClassIslandとSecRandom-Ciプラグインが正常に動作していることを検出しました、ご利用いただきありがとうございます", + }, }, } @@ -388,6 +412,14 @@ "title": "ClassIsland通知服务", "content": "请确保已安装.NET 8运行时,并在ClassIsland中安装SecRandom-Ci插件", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland通知服务", + "content": "检测到未安装插件,请在ClassIsland中安装SecRandom-Ci插件", + }, + "classisland_configured_successfully": { + "title": "ClassIsland通知服务", + "content": "检测到ClassIsland和SecRandom-Ci插件正常运行,欢迎使用", + }, }, "EN_US": { "title": { @@ -462,6 +494,14 @@ "title": "ClassIsland Notification Service", "content": "Please ensure .NET 8 runtime is installed and SecRandom-Ci plugin is installed in ClassIsland", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland Notification Service", + "content": "Plugin not detected, please install SecRandom-Ci plugin in ClassIsland", + }, + "classisland_configured_successfully": { + "title": "ClassIsland Notification Service", + "content": "Detected ClassIsland and SecRandom-Ci plugin running properly, welcome to use", + }, }, "JA_JP": { "title": { @@ -537,6 +577,14 @@ "title": "ClassIsland通知サービス", "content": ".NET 8ランタイムがインストールされ、ClassIslandにSecRandom-Ciプラグインがインストールされていることを確認してください", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland通知サービス", + "content": "プラグインがインストールされていないことを検出しました、ClassIslandにSecRandom-Ciプラグインをインストールしてください", + }, + "classisland_configured_successfully": { + "title": "ClassIsland通知サービス", + "content": "ClassIslandとSecRandom-Ciプラグインが正常に動作していることを検出しました、ご利用いただきありがとうございます", + }, }, } @@ -630,6 +678,14 @@ "title": "ClassIsland通知服务", "content": "请确保已安装.NET 8运行时,并在ClassIsland中安装SecRandom-Ci插件", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland通知服务", + "content": "检测到未安装插件,请在ClassIsland中安装SecRandom-Ci插件", + }, + "classisland_configured_successfully": { + "title": "ClassIsland通知服务", + "content": "检测到ClassIsland和SecRandom-Ci插件正常运行,欢迎使用", + }, }, "EN_US": { "title": { @@ -716,6 +772,14 @@ "title": "ClassIsland Notification Service", "content": "Please ensure .NET 8 runtime is installed and SecRandom-Ci plugin is installed in ClassIsland", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland Notification Service", + "content": "Plugin not detected, please install SecRandom-Ci plugin in ClassIsland", + }, + "classisland_configured_successfully": { + "title": "ClassIsland Notification Service", + "content": "Detected ClassIsland and SecRandom-Ci plugin running properly, welcome to use", + }, }, "JA_JP": { "title": { @@ -805,5 +869,13 @@ "title": "ClassIsland通知サービス", "content": ".NET 8ランタイムがインストールされ、ClassIslandにSecRandom-Ciプラグインがインストールされていることを確認してください", }, + "classisland_plugin_notification_hint": { + "title": "ClassIsland通知サービス", + "content": "プラグインがインストールされていないことを検出しました、ClassIslandにSecRandom-Ciプラグインをインストールしてください", + }, + "classisland_configured_successfully": { + "title": "ClassIsland通知サービス", + "content": "ClassIslandとSecRandom-Ciプラグインが正常に動作していることを検出しました、ご利用いただきありがとうございます", + }, }, } diff --git a/app/common/IPC_URL/csharp_ipc_handler.py b/app/common/IPC_URL/csharp_ipc_handler.py index 31dea418..7bd9260a 100644 --- a/app/common/IPC_URL/csharp_ipc_handler.py +++ b/app/common/IPC_URL/csharp_ipc_handler.py @@ -23,15 +23,16 @@ clr.AddReference("ClassIsland.Shared.IPC") clr.AddReference("SecRandom4Ci.Interface") - from System import Action, DateTime + from System import Action, DateTime, Version from ClassIsland.Shared.Enums import TimeState from ClassIsland.Shared.IPC import IpcClient, IpcRoutedNotifyIds from ClassIsland.Shared.IPC.Abstractions.Services import IPublicLessonsService from dotnetCampus.Ipc.CompilerServices.GeneratedProxies import ( GeneratedIpcFactory, ) + from SecRandom4Ci.Interface.Enums import ResultType + from SecRandom4Ci.Interface.Models import CallResult, NotificationData, Student from SecRandom4Ci.Interface.Services import ISecRandomService - from SecRandom4Ci.Interface.Models import CallResult, Student CSHARP_AVAILABLE = True except Exception as e: @@ -68,7 +69,7 @@ def __init__(self): self.loop: Optional[asyncio.AbstractEventLoop] = None self.is_running = False self.is_connected = False - self._disconnect_logged = False # 跟踪是否已记录断连日志 + self._no_plugin_logged = False self._last_on_class_left_log_time = 0 # 上次记录距离上课时间的时间 self._last_known_subject_name: Optional[str] = None @@ -120,6 +121,7 @@ def send_notification( draw_count=1, settings=None, settings_group=None, + is_animating=False, ) -> bool: """发送提醒""" if not self.is_running: @@ -134,17 +136,71 @@ def send_notification( display_duration = 5 logger.debug( - f"发送通知到 ClassIsland: 班级={class_name}, 选中学生={selected_students}, 抽取数量={draw_count}, 显示时长={display_duration}" + f"发送通知到 ClassIsland: 班级={class_name}, 选中学生={selected_students}, 抽取数量={draw_count}, 显示时长={display_duration}, 设置组={settings_group}, 是否动画={is_animating}" ) randomService = GeneratedIpcFactory.CreateIpcProxy[ISecRandomService]( self.ipc_client.Provider, self.ipc_client.PeerProxy ) - result = self.convert_to_call_result( - class_name, selected_students, draw_count, display_duration - ) - randomService.NotifyResult(result) + try: + plugin_version = randomService.GetPluginVersion() + except Exception as e: + plugin_version = Version.Parse("0.0.0.1") + + logger.debug(f"插件版本为 {plugin_version}") + if plugin_version < Version.Parse("1.2.0.0"): + logger.warning("检测到旧版插件 (小于 1.2.0.0),请尽快升级!") + + result = CallResult() + result.ClassName = class_name + result.DrawCount = draw_count + result.DisplayDuration = display_duration + for student in selected_students: + cs_student = Student() + cs_student.StudentId = student[0] + cs_student.StudentName = student[1] + cs_student.Exists = student[2] + result.SelectedStudents.Add(cs_student) + randomService.NotifyResult(result) + + return True + + if settings_group is None: + notification_type = ResultType.Unknown + elif "roll_call" in settings_group: + notification_type = [ + ResultType.PartialRollCall, + ResultType.FinishedRollCall, + ][not is_animating] + elif "quick_draw" in settings_group: + notification_type = [ + ResultType.PartialQuickDraw, + ResultType.FinishedQuickDraw, + ][not is_animating] + elif "lottery" in settings_group: + notification_type = [ + ResultType.PartialLottery, + ResultType.FinishedLottery, + ][not is_animating] + else: + notification_type = ResultType.Unknown + + logger.debug(f"通知类型: {notification_type}") + + data = NotificationData() + data.ResultType = notification_type + data.ClassName = class_name + data.DrawCount = draw_count + data.DisplayDuration = display_duration + for student in selected_students: + cs_student = Student() + cs_student.StudentId = student[0] + cs_student.StudentName = student[1] + cs_student.Exists = student[2] + data.Items.Add(cs_student) + + randomService.ShowNotification(data) return True def is_breaking(self) -> bool: @@ -307,22 +363,6 @@ def get_elapsed_since_previous_time_point_end_seconds(self) -> int: except Exception: return 0 - @staticmethod - def convert_to_call_result( - class_name: str, selected_students, draw_count: int, display_duration=5.0 - ) -> CallResult: - result = CallResult() - result.ClassName = class_name - result.DrawCount = draw_count - result.DisplayDuration = display_duration - for student in selected_students: - cs_student = Student() - cs_student.StudentId = student[0] - cs_student.StudentName = student[1] - cs_student.Exists = student[2] - result.SelectedStudents.Add(cs_student) - return result - def _on_class_test(self): lessonSc = GeneratedIpcFactory.CreateIpcProxy[IPublicLessonsService]( self.ipc_client.Provider, self.ipc_client.PeerProxy @@ -353,22 +393,28 @@ async def client(): task = self.ipc_client.Connect() await self.loop.run_in_executor(None, lambda: task.Wait()) self.is_connected = True + logger.debug("C# IPC 连接成功!") while self.is_running: await asyncio.sleep(1) - if not self._check_alive(): - if not self._disconnect_logged: + # logger.debug(f"stat: plugin({self._check_plugin_alive()}) ci({self._check_ci_alive()})") + if not self.check_plugin_alive(): + if not self.check_ci_alive(): logger.debug("C# IPC 断连!重连...") - self._disconnect_logged = True - self.is_connected = False - - task = self.ipc_client.Connect() - await self.loop.run_in_executor( - None, lambda task=task: task.Wait() - ) - self.is_connected = True - self._disconnect_logged = False + self.is_connected = False + + task = self.ipc_client.Connect() + await self.loop.run_in_executor( + None, lambda task=task: task.Wait() + ) + self.is_connected = True + logger.debug("C# IPC 连接成功!") + elif not self._no_plugin_logged: + logger.debug("未安装 SecRandom-Ci 插件。") + self._no_plugin_logged = True + else: + self._no_plugin_logged = False self.ipc_client = None self.is_connected = False @@ -386,8 +432,19 @@ async def client(): self.loop.close() self.loop = None - def _check_alive(self) -> bool: - """客户端是否正常连接""" + def check_ci_alive(self) -> bool: + """ClassIsland 是否正常连接""" + try: + lessonsService = GeneratedIpcFactory.CreateIpcProxy[ + IPublicLessonsService + ](self.ipc_client.Provider, self.ipc_client.PeerProxy) + return lessonsService.IsTimerRunning + except Exception as e: + logger.debug(e) + return False + + def check_plugin_alive(self) -> bool: + """SecRandom-Ci 插件是否正常连接""" try: randomService = GeneratedIpcFactory.CreateIpcProxy[ISecRandomService]( self.ipc_client.Provider, self.ipc_client.PeerProxy @@ -444,6 +501,7 @@ def send_notification( draw_count=1, settings=None, settings_group=None, + is_animating=False, ) -> bool: """发送提醒""" return False @@ -484,15 +542,17 @@ def get_previous_class_info(self) -> dict: def get_elapsed_since_previous_time_point_end_seconds(self) -> int: return 0 - @staticmethod - def convert_to_call_result( - class_name: str, selected_students, draw_count: int, display_duration=5.0 - ) -> object: - return object - def _on_class_test(self): pass def _run_client(self): """运行 C# IPC 客户端""" pass + + def check_ci_alive(self) -> bool: + """ClassIsland 是否正常连接""" + return False + + def check_plugin_alive(self) -> bool: + """SecRandom-Ci 插件是否正常连接""" + return False diff --git a/app/common/notification/notification_service.py b/app/common/notification/notification_service.py index 2c493ed5..93c616f0 100644 --- a/app/common/notification/notification_service.py +++ b/app/common/notification/notification_service.py @@ -1031,6 +1031,7 @@ def _normalize_text(value): draw_count, settings, settings_group, + is_animating ) if status: logger.info("成功发送通知到ClassIsland,结果未知") diff --git a/app/view/settings/notification_settings/lottery_notification_settings.py b/app/view/settings/notification_settings/lottery_notification_settings.py index 06ebdd18..c62f3ab4 100644 --- a/app/view/settings/notification_settings/lottery_notification_settings.py +++ b/app/view/settings/notification_settings/lottery_notification_settings.py @@ -8,6 +8,7 @@ from PySide6.QtNetwork import * from qfluentwidgets import * +from app.common.IPC_URL.csharp_ipc_handler import CSharpIPCHandler from app.tools.variable import * from app.tools.path_utils import * from app.tools.personalised import * @@ -213,25 +214,67 @@ def _on_notification_service_type_changed(self, index): index, ) if index == 1 or index == 2: - hint_title = get_any_position_value_async( - "lottery_notification_settings", - "classisland_notification_hint", - "title", - ) - hint_content = get_any_position_value_async( - "lottery_notification_settings", - "classisland_notification_hint", - "content", - ) - InfoBar.success( - title=hint_title, - content=hint_content, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP, - duration=5000, - parent=self, - ) + service = CSharpIPCHandler.instance() + if not service.check_ci_alive(): + hint_title = get_any_position_value_async( + "lottery_notification_settings", + "classisland_notification_hint", + "title", + ) + hint_content = get_any_position_value_async( + "lottery_notification_settings", + "classisland_notification_hint", + "content", + ) + InfoBar.success( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) + elif not service.check_plugin_alive(): + hint_title = get_any_position_value_async( + "lottery_notification_settings", + "classisland_plugin_notification_hint", + "title", + ) + hint_content = get_any_position_value_async( + "lottery_notification_settings", + "classisland_plugin_notification_hint", + "content", + ) + InfoBar.warning( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) + else: + hint_title = get_any_position_value_async( + "lottery_notification_settings", + "classisland_configured_successfully", + "title", + ) + hint_content = get_any_position_value_async( + "lottery_notification_settings", + "classisland_configured_successfully", + "content", + ) + InfoBar.success( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) class floating_window_settings(GroupHeaderCardWidget): diff --git a/app/view/settings/notification_settings/quick_draw_notification_settings.py b/app/view/settings/notification_settings/quick_draw_notification_settings.py index d0f5f581..00bc7dd9 100644 --- a/app/view/settings/notification_settings/quick_draw_notification_settings.py +++ b/app/view/settings/notification_settings/quick_draw_notification_settings.py @@ -8,6 +8,7 @@ from PySide6.QtNetwork import * from qfluentwidgets import * +from app.common.IPC_URL.csharp_ipc_handler import CSharpIPCHandler from app.tools.variable import * from app.tools.path_utils import * from app.tools.personalised import * @@ -113,25 +114,67 @@ def _on_notification_service_type_changed(self, index): index, ) if index == 1 or index == 2: - hint_title = get_any_position_value_async( - "quick_draw_notification_settings", - "classisland_notification_hint", - "title", - ) - hint_content = get_any_position_value_async( - "quick_draw_notification_settings", - "classisland_notification_hint", - "content", - ) - InfoBar.success( - title=hint_title, - content=hint_content, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP, - duration=5000, - parent=self, - ) + service = CSharpIPCHandler.instance() + if not service.check_ci_alive(): + hint_title = get_any_position_value_async( + "quick_draw_notification_settings", + "classisland_notification_hint", + "title", + ) + hint_content = get_any_position_value_async( + "quick_draw_notification_settings", + "classisland_notification_hint", + "content", + ) + InfoBar.success( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) + elif not service.check_plugin_alive(): + hint_title = get_any_position_value_async( + "quick_draw_notification_settings", + "classisland_plugin_notification_hint", + "title", + ) + hint_content = get_any_position_value_async( + "quick_draw_notification_settings", + "classisland_plugin_notification_hint", + "content", + ) + InfoBar.warning( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) + else: + hint_title = get_any_position_value_async( + "quick_draw_notification_settings", + "classisland_configured_successfully", + "title", + ) + hint_content = get_any_position_value_async( + "quick_draw_notification_settings", + "classisland_configured_successfully", + "content", + ) + InfoBar.success( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) class floating_window_settings(GroupHeaderCardWidget): diff --git a/app/view/settings/notification_settings/roll_call_notification_settings.py b/app/view/settings/notification_settings/roll_call_notification_settings.py index 02a9a38d..ce60bf2b 100644 --- a/app/view/settings/notification_settings/roll_call_notification_settings.py +++ b/app/view/settings/notification_settings/roll_call_notification_settings.py @@ -8,6 +8,7 @@ from PySide6.QtNetwork import * from qfluentwidgets import * +from app.common.IPC_URL.csharp_ipc_handler import CSharpIPCHandler from app.tools.variable import * from app.tools.path_utils import * from app.tools.personalised import * @@ -219,25 +220,67 @@ def _on_notification_service_type_changed(self, index): index, ) if index == 1 or index == 2: - hint_title = get_any_position_value_async( - "roll_call_notification_settings", - "classisland_notification_hint", - "title", - ) - hint_content = get_any_position_value_async( - "roll_call_notification_settings", - "classisland_notification_hint", - "content", - ) - InfoBar.success( - title=hint_title, - content=hint_content, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP, - duration=5000, - parent=self, - ) + service = CSharpIPCHandler.instance() + if not service.check_ci_alive(): + hint_title = get_any_position_value_async( + "roll_call_notification_settings", + "classisland_notification_hint", + "title", + ) + hint_content = get_any_position_value_async( + "roll_call_notification_settings", + "classisland_notification_hint", + "content", + ) + InfoBar.success( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) + elif not service.check_plugin_alive(): + hint_title = get_any_position_value_async( + "roll_call_notification_settings", + "classisland_plugin_notification_hint", + "title", + ) + hint_content = get_any_position_value_async( + "roll_call_notification_settings", + "classisland_plugin_notification_hint", + "content", + ) + InfoBar.warning( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) + else: + hint_title = get_any_position_value_async( + "roll_call_notification_settings", + "classisland_configured_successfully", + "title", + ) + hint_content = get_any_position_value_async( + "roll_call_notification_settings", + "classisland_configured_successfully", + "content", + ) + InfoBar.success( + title=hint_title, + content=hint_content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self, + ) class floating_window_settings(GroupHeaderCardWidget): diff --git a/data/dlls/SecRandom4Ci.Interface.dll b/data/dlls/SecRandom4Ci.Interface.dll index 89d9768c..48e8faf9 100644 Binary files a/data/dlls/SecRandom4Ci.Interface.dll and b/data/dlls/SecRandom4Ci.Interface.dll differ diff --git a/data/dlls/SecRandom4Ci.Interface.pdb b/data/dlls/SecRandom4Ci.Interface.pdb index b0ad7eef..d74340c5 100644 Binary files a/data/dlls/SecRandom4Ci.Interface.pdb and b/data/dlls/SecRandom4Ci.Interface.pdb differ diff --git a/scripts/generate-stubs.ps1 b/scripts/generate-stubs.ps1 index 52c80cfb..67913827 100644 --- a/scripts/generate-stubs.ps1 +++ b/scripts/generate-stubs.ps1 @@ -3,6 +3,7 @@ $SearchPath = "./data/dlls" $DestPath = "./typings" $dlls = Get-ChildItem -Path $SearchPath -Filter "*.dll" -Recurse | + Where-Object { $_.FullName -notlike "*uiaccess.dll" } ForEach-Object { $_.FullName } Write-Host "Found $($dlls.Count) DLL files" -ForegroundColor Green