From 2947f9fe97df03fd1cf32f2bc32a6bb113a436e6 Mon Sep 17 00:00:00 2001
From: Kagura <44108528+Kagura-Nanaa@users.noreply.github.com>
Date: Sat, 6 Dec 2025 00:45:11 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E6=B8=A0=E9=81=93?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=E9=92=89=E9=92=89=20(#14884)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: 通知渠道支持钉钉
* fix: 还原错误的更改
* chore: 加回丢掉的换行符
* fix: 还原错误删除的内容
---
src/MaaWpfGui/Constants/ConfigurationKeys.cs | 2 +
src/MaaWpfGui/Res/Localizations/en-us.xaml | 2 +
src/MaaWpfGui/Res/Localizations/ja-jp.xaml | 2 +
src/MaaWpfGui/Res/Localizations/ko-kr.xaml | 2 +
src/MaaWpfGui/Res/Localizations/zh-cn.xaml | 2 +
src/MaaWpfGui/Res/Localizations/zh-tw.xaml | 2 +
.../ExternalNotificationService.cs | 1 +
.../DingTalkNotificationProvider.cs | 126 ++++++++++++++++++
...nalNotificationSettingsUserControlModel.cs | 36 +++++
...ternalNotificationSettingsUserControl.xaml | 32 ++++-
10 files changed, 204 insertions(+), 3 deletions(-)
create mode 100644 src/MaaWpfGui/Services/Notification/src/MaaWpfGui/Services/Notification/DingTalkNotificationProvider.cs
diff --git a/src/MaaWpfGui/Constants/ConfigurationKeys.cs b/src/MaaWpfGui/Constants/ConfigurationKeys.cs
index 52c365bc4b..be012461ab 100644
--- a/src/MaaWpfGui/Constants/ConfigurationKeys.cs
+++ b/src/MaaWpfGui/Constants/ConfigurationKeys.cs
@@ -290,6 +290,8 @@ public static class ConfigurationKeys
public const string ExternalNotificationDiscordBotToken = "ExternalNotification.Discord.BotToken";
public const string ExternalNotificationDiscordUserId = "ExternalNotification.Discord.UserId";
public const string ExternalNotificationDiscordWebhookUrl = "ExternalNotification.Discord.WebhookUrl";
+ public const string ExternalNotificationDingTalkAccessToken = "ExternalNotification.DingTalk.AccessToken";
+ public const string ExternalNotificationDingTalkSecret = "ExternalNotification.DingTalk.Secret";
public const string ExternalNotificationTelegramBotToken = "ExternalNotification.Telegram.BotToken";
public const string ExternalNotificationTelegramChatId = "ExternalNotification.Telegram.ChatId";
public const string ExternalNotificationTelegramTopicId = "ExternalNotification.TelegramTopicId";
diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml
index 77a57a2d32..13479b2316 100644
--- a/src/MaaWpfGui/Res/Localizations/en-us.xaml
+++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml
@@ -62,6 +62,8 @@
Bot Token
User ID
Webhook URL
+ Access Token
+ Secret
Bot Token
Chat ID
Topic ID (Optional)
diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml
index 7a47c45e92..3461d43c04 100644
--- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml
+++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml
@@ -62,6 +62,8 @@
ボットトークン
ユーザーID
ウェブフック URL
+ アクセストークン
+ シークレット
ボットトークン
チャットID
トピックID(任意)
diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml
index 36b54663bf..fd3787dba5 100644
--- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml
+++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml
@@ -62,6 +62,8 @@
봇 토큰
사용자 ID
웹후크 URL
+ 액세스 토큰
+ 시크릿
봇 토큰
채팅 ID
토픽 ID (선택사항)
diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml
index 6fc9713a5d..afb2cb811c 100644
--- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml
+++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml
@@ -62,6 +62,8 @@
机器人 Token
用户 ID
Webhook URL
+ Access Token
+ 加签密钥
机器人 Token
聊天 ID
话题ID(可选)
diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml
index dff50e4d28..5a6f90f401 100644
--- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml
+++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml
@@ -62,6 +62,8 @@
機器人 Token
用戶 ID
Webhook URL
+ Access Token
+ 簽名密鑰
機器人 Token
聊天 ID
話題 ID(可選)
diff --git a/src/MaaWpfGui/Services/Notification/ExternalNotificationService.cs b/src/MaaWpfGui/Services/Notification/ExternalNotificationService.cs
index f4d38f1018..e2e001c67e 100644
--- a/src/MaaWpfGui/Services/Notification/ExternalNotificationService.cs
+++ b/src/MaaWpfGui/Services/Notification/ExternalNotificationService.cs
@@ -37,6 +37,7 @@ public static class ExternalNotificationService
"ServerChan" => new ServerChanNotificationProvider(Instances.HttpService),
"Telegram" => new TelegramNotificationProvider(Instances.HttpService),
"Discord" => new DiscordNotificationProvider(Instances.HttpService),
+ "DingTalk" => new DingTalkNotificationProvider(Instances.HttpService),
"Discord Webhook" => new DiscordWebhookNotificationProvider(Instances.HttpService),
"Custom Webhook" => new CustomWebhookNotificationProvider(Instances.HttpService),
"SMTP" => new SmtpNotificationProvider(),
diff --git a/src/MaaWpfGui/Services/Notification/src/MaaWpfGui/Services/Notification/DingTalkNotificationProvider.cs b/src/MaaWpfGui/Services/Notification/src/MaaWpfGui/Services/Notification/DingTalkNotificationProvider.cs
new file mode 100644
index 0000000000..550bfbd27f
--- /dev/null
+++ b/src/MaaWpfGui/Services/Notification/src/MaaWpfGui/Services/Notification/DingTalkNotificationProvider.cs
@@ -0,0 +1,126 @@
+//
+// Part of the MaaWpfGui project, maintained by the MaaAssistantArknights team (Maa Team)
+// Copyright (C) 2021-2025 MaaAssistantArknights Contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License v3.0 only as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY
+//
+
+#nullable enable
+
+using System;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using MaaWpfGui.Services.Web;
+using MaaWpfGui.ViewModels.UI;
+using Serilog;
+
+namespace MaaWpfGui.Services.Notification;
+
+public class DingTalkNotificationProvider(IHttpService httpService) : IExternalNotificationProvider
+{
+ private const string DingTalkWebhookEndpoint = "https://oapi.dingtalk.com/robot/send";
+ private readonly ILogger _logger = Log.ForContext();
+
+ public async Task SendAsync(string title, string content)
+ {
+ var accessToken = SettingsViewModel.ExternalNotificationSettings.DingTalkAccessToken;
+ var secret = SettingsViewModel.ExternalNotificationSettings.DingTalkSecret;
+
+ if (string.IsNullOrEmpty(accessToken))
+ {
+ _logger.Warning("Failed to send DingTalk notification: Access Token is empty");
+ return false;
+ }
+
+ var webhook = BuildWebhookUrl(accessToken, secret);
+ var requestBody = new DingTalkPostContent
+ {
+ MessageType = "text",
+ Text = new DingTalkTextContent
+ {
+ Content = $"{title}: {content}"
+ }
+ };
+
+ var response = await httpService.PostAsJsonAsync(new Uri(webhook), requestBody);
+ if (response == null)
+ {
+ _logger.Warning("Failed to send DingTalk notification: response is null");
+ return false;
+ }
+
+ try
+ {
+ var responseData = JsonSerializer.Deserialize(response);
+ if (responseData?.ErrorCode == 0)
+ {
+ _logger.Information("DingTalk notification sent successfully.");
+ return true;
+ }
+
+ _logger.Warning(
+ "Failed to send DingTalk notification, error code: {ErrorCode}, error message: {ErrorMessage}",
+ responseData?.ErrorCode,
+ responseData?.ErrorMessage);
+ return false;
+ }
+ catch (JsonException ex)
+ {
+ _logger.Warning(ex, "Failed to parse DingTalk response.");
+ return false;
+ }
+ }
+
+ private static string BuildWebhookUrl(string accessToken, string? secret)
+ {
+ var builder = new StringBuilder($"{DingTalkWebhookEndpoint}?access_token={accessToken}");
+
+ if (string.IsNullOrEmpty(secret))
+ {
+ return builder.ToString();
+ }
+
+ var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ var signBase = $"{timestamp}\n{secret}";
+ using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
+ var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signBase));
+ var sign = WebUtility.UrlEncode(Convert.ToBase64String(hash));
+ builder.Append($"×tamp={timestamp}&sign={sign}");
+
+ return builder.ToString();
+ }
+
+ private class DingTalkPostContent
+ {
+ [JsonPropertyName("msgtype")]
+ public string? MessageType { get; set; }
+
+ [JsonPropertyName("text")]
+ public DingTalkTextContent? Text { get; set; }
+ }
+
+ private class DingTalkTextContent
+ {
+ [JsonPropertyName("content")]
+ public string? Content { get; set; }
+ }
+
+ private class DingTalkResponse
+ {
+ [JsonPropertyName("errcode")]
+ public int ErrorCode { get; set; }
+
+ [JsonPropertyName("errmsg")]
+ public string? ErrorMessage { get; set; }
+ }
+}
diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/ExternalNotificationSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/ExternalNotificationSettingsUserControlModel.cs
index 5098d948a0..4fc973e6e6 100644
--- a/src/MaaWpfGui/ViewModels/UserControl/Settings/ExternalNotificationSettingsUserControlModel.cs
+++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/ExternalNotificationSettingsUserControlModel.cs
@@ -98,6 +98,7 @@ public class ExternalNotificationSettingsUserControlModel : PropertyChangedBase
"ServerChan",
"Telegram",
"Discord",
+ "DingTalk",
"Discord Webhook",
"SMTP",
"Bark",
@@ -164,6 +165,14 @@ public class ExternalNotificationSettingsUserControlModel : PropertyChangedBase
set => SetAndNotify(ref _discordEnabled, value);
}
+ private bool _dingTalkEnabled = false;
+
+ public bool DingTalkEnabled
+ {
+ get => _dingTalkEnabled;
+ set => SetAndNotify(ref _dingTalkEnabled, value);
+ }
+
private bool _discordWebhookEnabled = false;
public bool DiscordWebhookEnabled
@@ -209,6 +218,7 @@ public class ExternalNotificationSettingsUserControlModel : PropertyChangedBase
ServerChanEnabled = _enabledExternalNotificationProviders.Contains("ServerChan");
TelegramEnabled = _enabledExternalNotificationProviders.Contains("Telegram");
DiscordEnabled = _enabledExternalNotificationProviders.Contains("Discord");
+ DingTalkEnabled = _enabledExternalNotificationProviders.Contains("DingTalk");
DiscordWebhookEnabled = _enabledExternalNotificationProviders.Contains("Discord Webhook");
SmtpEnabled = _enabledExternalNotificationProviders.Contains("SMTP");
BarkEnabled = _enabledExternalNotificationProviders.Contains("Bark");
@@ -400,6 +410,32 @@ public class ExternalNotificationSettingsUserControlModel : PropertyChangedBase
}
}
+ private string _dingTalkAccessToken = SimpleEncryptionHelper.Decrypt(ConfigurationHelper.GetValue(ConfigurationKeys.ExternalNotificationDingTalkAccessToken, string.Empty));
+
+ public string DingTalkAccessToken
+ {
+ get => _dingTalkAccessToken;
+ set
+ {
+ SetAndNotify(ref _dingTalkAccessToken, value);
+ value = SimpleEncryptionHelper.Encrypt(value);
+ ConfigurationHelper.SetValue(ConfigurationKeys.ExternalNotificationDingTalkAccessToken, value);
+ }
+ }
+
+ private string _dingTalkSecret = SimpleEncryptionHelper.Decrypt(ConfigurationHelper.GetValue(ConfigurationKeys.ExternalNotificationDingTalkSecret, string.Empty));
+
+ public string DingTalkSecret
+ {
+ get => _dingTalkSecret;
+ set
+ {
+ SetAndNotify(ref _dingTalkSecret, value);
+ value = SimpleEncryptionHelper.Encrypt(value);
+ ConfigurationHelper.SetValue(ConfigurationKeys.ExternalNotificationDingTalkSecret, value);
+ }
+ }
+
private string _telegramBotToken = SimpleEncryptionHelper.Decrypt(ConfigurationHelper.GetValue(ConfigurationKeys.ExternalNotificationTelegramBotToken, string.Empty));
public string TelegramBotToken
diff --git a/src/MaaWpfGui/Views/UserControl/Settings/ExternalNotificationSettingsUserControl.xaml b/src/MaaWpfGui/Views/UserControl/Settings/ExternalNotificationSettingsUserControl.xaml
index 50d047000f..f07ff5bd4e 100644
--- a/src/MaaWpfGui/Views/UserControl/Settings/ExternalNotificationSettingsUserControl.xaml
+++ b/src/MaaWpfGui/Views/UserControl/Settings/ExternalNotificationSettingsUserControl.xaml
@@ -29,6 +29,7 @@
+