mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-18 08:13:20 +08:00
114
BetterGenshinImpact/Core/Script/Dependence/Notification.cs
Normal file
114
BetterGenshinImpact/Core/Script/Dependence/Notification.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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。
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user