feat: js 通知支持 (#1376)

* feat: js 通知支持

* feat: 添加全局或者在配置组中单独禁用js的通知功能
This commit is contained in:
秋云
2025-04-02 21:40:14 +08:00
committed by GitHub
parent 057efe78a9
commit ffd7cfabbc
12 changed files with 244 additions and 3 deletions

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.Core.Script.Dependence;
public class Notification
{
private readonly AllConfig _config = TaskContext.Instance().Config;
private readonly ILogger<Notification> _logger = App.GetLogger<Notification>();
private readonly TimeSpan _timeWindow = TimeSpan.FromMinutes(1);
private readonly int _maxNotifications = 5;
private readonly Queue<DateTime> _callRecords = new();
private static readonly string[] _forbiddenPatterns =
[
"<script>", "http://", "https://"
];
private bool CheckNotificationPermission()
{
try
{
var currentProject = TaskContext.Instance().CurrentScriptProject;
return _config.NotificationConfig.JsNotificationEnabled &&
(currentProject?.AllowJsNotification ?? true);
}
catch
{
return false;
}
}
private bool ValidateContent(string message) // 不允许发送超过 500 字符的消息
{
if (message.Length > 500) return false;
return !_forbiddenPatterns
.Any(p => message.IndexOf(p, StringComparison.OrdinalIgnoreCase) >= 0);
}
private bool CheckRateLimit() // 不允许频繁发送消息
{
var now = DateTime.Now;
while (_callRecords.TryPeek(out var time) && now - time > _timeWindow)
{
_callRecords.Dequeue();
}
if (_callRecords.Count >= _maxNotifications)
{
return false;
}
_callRecords.Enqueue(now);
return true;
}
/// <summary>
/// 发送成功通知
/// </summary>
/// <param name="message">通知消息</param>
public void Send(string message)
{
if (!CheckNotificationPermission())
{
_logger.LogWarning("JS 通知关闭,消息被拦截: " + message);
return;
}
if (!CheckRateLimit())
{
_logger.LogWarning("通知频率超限,消息被拦截: " + message);
return;
}
if (!ValidateContent(message))
{
_logger.LogWarning("通知内容违规,消息被拦截: " + message);
return;
}
Notify.Event(NotificationEvent.JsCustom).Send(message);
_logger.LogInformation("通知发送成功:" + message);
}
/// <summary>
/// 发送错误通知
/// </summary>
/// <param name="message">通知消息</param>
public void Error(string message)
{
if (!CheckNotificationPermission())
{
_logger.LogWarning("JS 通知关闭,消息被拦截: " + message);
return;
}
if (!CheckRateLimit())
{
_logger.LogWarning("通知频率超限,消息被拦截: " + message);
return;
}
if (!ValidateContent(message))
{
_logger.LogWarning("通知内容违规,消息被拦截: " + message);
return;
}
Notify.Event(NotificationEvent.JsError).Error(message);
_logger.LogInformation("错误通知发送成功:" + message);
}
}

View File

@@ -28,6 +28,7 @@ public class EngineExtend
engine.AddHostObject("genshin", new Dependence.Genshin());
engine.AddHostObject("log", new Log());
engine.AddHostObject("file", new LimitedFile(workDir)); // 限制文件访问
engine.AddHostObject("notification", new Notification());
// 任务调度器
engine.AddHostObject("dispatcher", new Dispatcher(config));

View File

@@ -9,6 +9,7 @@ using BetterGenshinImpact.ViewModel.Pages;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.IO;
using System.Text.Json.Serialization;
@@ -77,6 +78,9 @@ public partial class ScriptGroupProject : ObservableObject
[JsonIgnore]
[ObservableProperty]
public bool? _nextFlag = false;
[ObservableProperty]
private bool? _allowJsNotification = true;
public ScriptGroupProject()
{

View File

@@ -6,6 +6,7 @@ using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Service;
using System;
using System.Threading;
using BetterGenshinImpact.Core.Script.Group;
namespace BetterGenshinImpact.GameTask
{
@@ -16,6 +17,7 @@ namespace BetterGenshinImpact.GameTask
{
private static TaskContext? _uniqueInstance;
private static object? InstanceLocker;
public ScriptGroupProject? CurrentScriptProject { get; set; }
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。

View File

@@ -19,6 +19,8 @@ public class NotificationEvent(string code, string msg)
public static readonly NotificationEvent AlbumEnd = new("album.end", "自动音游专辑结束");
public static readonly NotificationEvent AlbumError = new("album.error", "自动音游专辑错误");
public static readonly NotificationEvent DailyReward = new("daily.reward", "检查每日奖励领取状态");
public static readonly NotificationEvent JsCustom = new("js.custom", "JS自定义事件");
public static readonly NotificationEvent JsError = new("js.error", "JS运行时错误");
public string Code { get; private set; } = code;
public string Msg { get; private set; } = msg;

View File

@@ -9,6 +9,10 @@ namespace BetterGenshinImpact.Service.Notification;
[Serializable]
public partial class NotificationConfig : ObservableObject
{
/// <summary>
/// 是否允许 js 发送通知
/// </summary>
[ObservableProperty] private bool _jsNotificationEnabled = false;
/// <summary>
/// 传"none"时,点击推送不会弹窗
/// </summary>

View File

@@ -202,6 +202,7 @@ public partial class ScriptService : IScriptService
target.RunNum = source.RunNum;
target.JsScriptSettingsObject = source.JsScriptSettingsObject;
target.GroupInfo = source.GroupInfo;
target.AllowJsNotification = source.AllowJsNotification;
}
// private List<ScriptProject> ExtractJsProjects(List<ScriptGroupProject> list)
@@ -220,6 +221,7 @@ public partial class ScriptService : IScriptService
private async Task ExecuteProject(ScriptGroupProject project)
{
TaskContext.Instance().CurrentScriptProject = project;
if (project.Type == "Javascript")
{
if (project.Project == null)

View File

@@ -77,6 +77,31 @@
Margin="0,0,36,0"
IsChecked="{Binding Config.NotificationConfig.IncludeScreenShot, Mode=TwoWay}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="是否允许 JS 通知"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="开启时允许 JS 脚本发送通知"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
IsChecked="{Binding Config.NotificationConfig.JsNotificationEnabled, Mode=TwoWay}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@@ -4,6 +4,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BetterGenshinImpact.Core.Script.Group"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:editable="clr-namespace:BetterGenshinImpact.ViewModel.Windows.Editable"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
Width="200"
Height="300"
d:DesignHeight="200"
@@ -16,6 +18,7 @@
<ObjectDataProvider x:Key="ScheduleDescriptionsProvider"
MethodName="GetScheduleDescriptions"
ObjectType="{x:Type local:ScriptGroupProjectExtensions}" />
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</UserControl.Resources>
<Grid>
<StackPanel>
@@ -35,6 +38,18 @@
ItemsSource="{Binding Source={StaticResource StatusDescriptionsProvider}}"
SelectedValue="{Binding Status, Mode=TwoWay}"
SelectedValuePath="Key" />
<TextBlock Margin="0,10,0,5" Text="JS 通知权限"
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
<ComboBox Margin="0,0,0,10"
ItemsSource="{Binding JsNotificationOptions}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
IsEnabled="{Binding GlobalJsNotificationEnabled}"
ToolTip="{Binding JsNotificationToolTip}"
SelectedValue="{Binding AllowJsNotification, Mode=TwoWay}"
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
</ComboBox>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -1,11 +1,14 @@
using System.Windows.Controls;
using BetterGenshinImpact.Core.Script.Group;
using BetterGenshinImpact.ViewModel.Windows.Editable;
namespace BetterGenshinImpact.View.Windows.Editable;
public partial class ScriptGroupProjectEditor : UserControl
{
public ScriptGroupProjectEditor()
public ScriptGroupProjectEditor(ScriptGroupProject project)
{
InitializeComponent();
DataContext = new ScriptGroupProjectEditorViewModel(project);
}
}

View File

@@ -26,6 +26,7 @@ using BetterGenshinImpact.View.Pages.View;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.View.Windows.Editable;
using BetterGenshinImpact.ViewModel.Pages.View;
using BetterGenshinImpact.ViewModel.Windows.Editable;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
@@ -921,12 +922,17 @@ public partial class ScriptControlViewModel : ViewModel
item.NextFlag = true;
}
public static void ShowEditWindow(object viewModel)
public static void ShowEditWindow(ScriptGroupProject project)
{
var viewModel = new ScriptGroupProjectEditorViewModel(project);
var editor = new ScriptGroupProjectEditor(project)
{
DataContext = viewModel
};
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
{
Title = "修改通用设置",
Content = new ScriptGroupProjectEditor { DataContext = viewModel },
Content = editor,
CloseButtonText = "关闭",
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json.Serialization;
using BetterGenshinImpact.Core.Script.Group;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.Service.Notification;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterGenshinImpact.ViewModel.Windows.Editable;
public class ScriptGroupProjectEditorViewModel : ObservableObject
{
private readonly ScriptGroupProject _project;
private readonly NotificationConfig _globalNotificationConfig;
public bool GlobalJsNotificationEnabled
=> _globalNotificationConfig.JsNotificationEnabled;
public List<KeyValuePair<bool, string>> JsNotificationOptions { get; } = new()
{
new KeyValuePair<bool, string>(true, "启用"),
new KeyValuePair<bool, string>(false, "禁用")
};
public bool IsJsScript => _project.Type == "Javascript";
public bool? AllowJsNotification
{
get => _project.AllowJsNotification;
set
{
if (!GlobalJsNotificationEnabled) return;
_project.AllowJsNotification = value;
OnPropertyChanged();
}
}
public string Status
{
get => _project.Status;
set
{
if (_project.Status != value)
{
_project.Status = value;
OnPropertyChanged();
}
}
}
public ScriptGroupProjectEditorViewModel(ScriptGroupProject project)
{
_project = project ?? throw new ArgumentNullException(nameof(project));
_globalNotificationConfig = TaskContext.Instance().Config.NotificationConfig;
// 监听全局配置变更
_project.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(ScriptGroupProject.AllowJsNotification))
{
OnPropertyChanged(nameof(AllowJsNotification));
}
};
}
}