feat: 通知渠道支持钉钉 (#14884)

* feat: 通知渠道支持钉钉

* fix: 还原错误的更改

* chore: 加回丢掉的换行符

* fix: 还原错误删除的内容
This commit is contained in:
Kagura
2025-12-06 00:45:11 +08:00
committed by GitHub
parent 8796112363
commit 2947f9fe97
10 changed files with 204 additions and 3 deletions

View File

@@ -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";

View File

@@ -62,6 +62,8 @@
<system:String x:Key="ExternalNotificationDiscordBotToken">Bot Token</system:String>
<system:String x:Key="ExternalNotificationDiscordUserId">User ID</system:String>
<system:String x:Key="ExternalNotificationDiscordWebhookUrl">Webhook URL</system:String>
<system:String x:Key="ExternalNotificationDingTalkAccessToken">Access Token</system:String>
<system:String x:Key="ExternalNotificationDingTalkSecret">Secret</system:String>
<system:String x:Key="ExternalNotificationTelegramBotToken">Bot Token</system:String>
<system:String x:Key="ExternalNotificationTelegramChatId">Chat ID</system:String>
<system:String x:Key="ExternalNotificationTelegramTopicId">Topic ID (Optional)</system:String>

View File

@@ -62,6 +62,8 @@
<system:String x:Key="ExternalNotificationDiscordBotToken">ボットトークン</system:String>
<system:String x:Key="ExternalNotificationDiscordUserId">ユーザーID</system:String>
<system:String x:Key="ExternalNotificationDiscordWebhookUrl">ウェブフック URL</system:String>
<system:String x:Key="ExternalNotificationDingTalkAccessToken">アクセストークン</system:String>
<system:String x:Key="ExternalNotificationDingTalkSecret">シークレット</system:String>
<system:String x:Key="ExternalNotificationTelegramBotToken">ボットトークン</system:String>
<system:String x:Key="ExternalNotificationTelegramChatId">チャットID</system:String>
<system:String x:Key="ExternalNotificationTelegramTopicId">トピックID任意</system:String>

View File

@@ -62,6 +62,8 @@
<system:String x:Key="ExternalNotificationDiscordBotToken">봇 토큰</system:String>
<system:String x:Key="ExternalNotificationDiscordUserId">사용자 ID</system:String>
<system:String x:Key="ExternalNotificationDiscordWebhookUrl">웹후크 URL</system:String>
<system:String x:Key="ExternalNotificationDingTalkAccessToken">액세스 토큰</system:String>
<system:String x:Key="ExternalNotificationDingTalkSecret">시크릿</system:String>
<system:String x:Key="ExternalNotificationTelegramBotToken">봇 토큰</system:String>
<system:String x:Key="ExternalNotificationTelegramChatId">채팅 ID</system:String>
<system:String x:Key="ExternalNotificationTelegramTopicId">토픽 ID (선택사항)</system:String>

View File

@@ -62,6 +62,8 @@
<system:String x:Key="ExternalNotificationDiscordBotToken">机器人 Token</system:String>
<system:String x:Key="ExternalNotificationDiscordUserId">用户 ID</system:String>
<system:String x:Key="ExternalNotificationDiscordWebhookUrl">Webhook URL</system:String>
<system:String x:Key="ExternalNotificationDingTalkAccessToken">Access Token</system:String>
<system:String x:Key="ExternalNotificationDingTalkSecret">加签密钥</system:String>
<system:String x:Key="ExternalNotificationTelegramBotToken">机器人 Token</system:String>
<system:String x:Key="ExternalNotificationTelegramChatId">聊天 ID</system:String>
<system:String x:Key="ExternalNotificationTelegramTopicId">话题ID可选</system:String>

View File

@@ -62,6 +62,8 @@
<system:String x:Key="ExternalNotificationDiscordBotToken">機器人 Token</system:String>
<system:String x:Key="ExternalNotificationDiscordUserId">用戶 ID</system:String>
<system:String x:Key="ExternalNotificationDiscordWebhookUrl">Webhook URL</system:String>
<system:String x:Key="ExternalNotificationDingTalkAccessToken">Access Token</system:String>
<system:String x:Key="ExternalNotificationDingTalkSecret">簽名密鑰</system:String>
<system:String x:Key="ExternalNotificationTelegramBotToken">機器人 Token</system:String>
<system:String x:Key="ExternalNotificationTelegramChatId">聊天 ID</system:String>
<system:String x:Key="ExternalNotificationTelegramTopicId">話題 ID可選</system:String>

View File

@@ -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(),

View File

@@ -0,0 +1,126 @@
// <copyright file="DingTalkNotificationProvider.cs" company="MaaAssistantArknights">
// 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
// </copyright>
#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<DingTalkNotificationProvider>();
public async Task<bool> 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<DingTalkResponse>(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($"&timestamp={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; }
}
}

View File

@@ -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

View File

@@ -29,6 +29,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--<controls:TextBlock
@@ -186,6 +187,31 @@
<StackPanel
Grid.Row="6"
Orientation="Vertical"
Visibility="{c:Binding DingTalkEnabled}">
<hc:Divider Content="DingTalk" />
<hc:PasswordBox
x:Name="DingTalkAccessToken"
Width="400"
Height="30"
Margin="10"
hc:InfoElement.Title="{DynamicResource ExternalNotificationDingTalkAccessToken}"
IsSafeEnabled="False"
ShowEyeButton="True"
UnsafePassword="{c:Binding DingTalkAccessToken}" />
<hc:PasswordBox
x:Name="DingTalkSecret"
Width="400"
Height="30"
Margin="10"
hc:InfoElement.Title="{DynamicResource ExternalNotificationDingTalkSecret}"
IsSafeEnabled="False"
ShowEyeButton="True"
UnsafePassword="{c:Binding DingTalkSecret}" />
</StackPanel>
<StackPanel
Grid.Row="7"
HorizontalAlignment="Center"
hc:InfoElement.TitleWidth="0"
Visibility="{c:Binding SmtpEnabled}">
@@ -326,7 +352,7 @@
</StackPanel>
<StackPanel
Grid.Row="7"
Grid.Row="8"
Orientation="Vertical"
Visibility="{c:Binding BarkEnabled}">
<hc:Divider Content="Bark" />
@@ -349,7 +375,7 @@
</StackPanel>
<StackPanel
Grid.Row="8"
Grid.Row="9"
hc:InfoElement.TitleWidth="0"
Orientation="Vertical"
Visibility="{c:Binding QmsgEnabled}">
@@ -437,7 +463,7 @@
</StackPanel>
<StackPanel
Grid.Row="9"
Grid.Row="10"
Orientation="Vertical"
Visibility="{c:Binding CustomWebhookEnabled}">
<hc:Divider Content="{DynamicResource ExternalNotificationCustomWebhook}" />