From e03e92edd7034bf96567056363e21a6ed8cdf9f5 Mon Sep 17 00:00:00 2001
From: Ray <zai7lou@outlook.com>
Date: Wed, 30 Mar 2022 23:41:22 +0800
Subject: [PATCH 1/2] feat: [#55] add remote client of Microsoft Teams for
 serilog

---
 CHANGELOG.md                                  |  3 ++
 Ray.BiliBiliTool.sln                          |  7 ++++
 common.props                                  |  2 +-
 docs/configuration.md                         | 21 ++++++++++
 .../Ray.BiliBiliTool.Console.csproj           |  1 +
 src/Ray.BiliBiliTool.Console/appsettings.json |  8 ++++
 .../MicrosoftTeamsApiClient.cs                | 39 ++++++++++++++++++
 .../MicrosoftTeamsBatchedSink.cs              | 38 +++++++++++++++++
 .../MicrosoftTeamsConfigurationExtensions.cs  | 40 ++++++++++++++++++
 .../Ray.Serilog.Sinks.MicrosoftTeams.csproj   | 14 +++++++
 test/LogTest/LogTest.csproj                   |  1 +
 test/LogTest/TestMicrosoftTeams.cs            | 41 +++++++++++++++++++
 12 files changed, 214 insertions(+), 1 deletion(-)
 create mode 100644 src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsApiClient.cs
 create mode 100644 src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsBatchedSink.cs
 create mode 100644 src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsConfigurationExtensions.cs
 create mode 100644 src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/Ray.Serilog.Sinks.MicrosoftTeams.csproj
 create mode 100644 test/LogTest/TestMicrosoftTeams.cs

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72519ff21..3b983a4ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,3 +24,6 @@
 ## 0.0.7
 - 【#44】兼容青龙最新版本(v2.12.0),修复因青龙调整目录结构导致的bug
 - 更新`publish-image.yml`,只有`release`时才打`latest tag`,手动运行时不打`latest tag`
+## 0.0.8
+- 【#55】新增日志推送端:Microsoft Teams
+
diff --git a/Ray.BiliBiliTool.sln b/Ray.BiliBiliTool.sln
index ac954cdb2..41fd70ae3 100644
--- a/Ray.BiliBiliTool.sln
+++ b/Ray.BiliBiliTool.sln
@@ -148,6 +148,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{75A9CC5C
 		docker\build\buildImage_arm64.cmd = docker\build\buildImage_arm64.cmd
 	EndProjectSection
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ray.Serilog.Sinks.MicrosoftTeams", "src\Ray.Serilog.Sinks\Ray.Serilog.Sinks.MicrosoftTeams\Ray.Serilog.Sinks.MicrosoftTeams.csproj", "{F249A822-EFD3-4F0A-9C10-95A96676D61A}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -226,6 +228,10 @@ Global
 		{F6B8ED3A-5428-4D26-8172-8B41FBF0C4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F6B8ED3A-5428-4D26-8172-8B41FBF0C4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F6B8ED3A-5428-4D26-8172-8B41FBF0C4CF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F249A822-EFD3-4F0A-9C10-95A96676D61A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F249A822-EFD3-4F0A-9C10-95A96676D61A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F249A822-EFD3-4F0A-9C10-95A96676D61A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F249A822-EFD3-4F0A-9C10-95A96676D61A}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -263,6 +269,7 @@ Global
 		{830361B7-BCC1-4853-879A-761B0FD86826} = {73DD457B-E06E-45ED-A6BA-7E3C02F8BDF1}
 		{F6B8ED3A-5428-4D26-8172-8B41FBF0C4CF} = {E9BDDCBE-A57D-4E3B-8252-708088386ADF}
 		{75A9CC5C-DF92-4D72-A14C-625AA902855B} = {A93210FD-27B6-40E4-B08D-391F96CA2754}
+		{F249A822-EFD3-4F0A-9C10-95A96676D61A} = {4BAFC980-7A73-45C3-9460-8B8CCB87939B}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {197319DA-1148-4A99-847C-8B270B6A29AB}
diff --git a/common.props b/common.props
index e462eccc0..b43ce0fd9 100644
--- a/common.props
+++ b/common.props
@@ -1,7 +1,7 @@
 <Project>
   <PropertyGroup>
     <Authors>Ray</Authors>
-    <Version>0.0.7</Version>
+    <Version>0.0.8</Version>
     <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
   </PropertyGroup>
 </Project>
diff --git a/docs/configuration.md b/docs/configuration.md
index dfd1b71cd..e1be7ebe6 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -59,6 +59,8 @@
             - [3.6.8.2. PushPlus的Topic](#3682-pushplus的topic)
             - [3.6.8.3. PushPlus的Channel](#3683-pushplus的channel)
             - [3.6.8.4. PushPlus的Webhook](#3684-pushplus的webhook)
+        - [3.6.9. Microsoft Teams](#369-microsoft-teams)
+            - [3.6.9.1. Microsoft Teams的Webhook](#3691-microsoft-teams的webhook)
     - [3.7. 日志相关](#37-日志相关)
         - [3.7.1. Console日志输出等级](#371-console日志输出等级)
         - [3.7.2. Console日志输出样式](#372-console日志输出样式)
@@ -665,6 +667,25 @@ webhook编码(不是地址),在官网平台设定,仅在channel使用webhook
 | 命令行示范   |  |
 | GitHub Secrets  | `PUSHPLUSWEBHOOK` |
 
+<a id="markdown-369-microsoft-teams" name="369-microsoft-teams"></a>
+#### 3.6.9. Microsoft Teams
+
+官网: https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
+
+<a id="markdown-3691-microsoft-teams的webhook" name="3691-microsoft-teams的webhook"></a>
+##### 3.6.9.1. Microsoft Teams的Webhook
+
+webhook的完整地址,在Teams的Channel中获取,详细获取方式请参考官网。
+
+|   TITLE   | CONTENT   |
+| ---------- | -------------- |
+| 配置Key | `Serilog__WriteTo__10__Args__webhook` |
+| 值域   | 一串字符串 |
+| 默认值   | 空 |
+| 环境变量   | `Serilog__WriteTo__10__Args__webhook` |
+| 命令行示范   |  |
+| GitHub Secrets  |  |
+
 
 <a id="markdown-37-日志相关" name="37-日志相关"></a>
 ### 3.7. 日志相关
diff --git a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj
index 535ef48f9..2a525d2db 100644
--- a/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj
+++ b/src/Ray.BiliBiliTool.Console/Ray.BiliBiliTool.Console.csproj
@@ -76,6 +76,7 @@
     <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.Batched\Ray.Serilog.Sinks.Batched.csproj" />
     <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.CoolPushBatched\Ray.Serilog.Sinks.CoolPushBatched.csproj" />
     <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.DingTalkBatched\Ray.Serilog.Sinks.DingTalkBatched.csproj" />
+    <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.MicrosoftTeams\Ray.Serilog.Sinks.MicrosoftTeams.csproj" />
     <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.OtherApiBatched\Ray.Serilog.Sinks.OtherApiBatched.csproj" />
     <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.PushPlusBatched\Ray.Serilog.Sinks.PushPlusBatched.csproj" />
     <ProjectReference Include="..\Ray.Serilog.Sinks\Ray.Serilog.Sinks.ServerChanBatched\Ray.Serilog.Sinks.ServerChanBatched.csproj" />
diff --git a/src/Ray.BiliBiliTool.Console/appsettings.json b/src/Ray.BiliBiliTool.Console/appsettings.json
index 0422ff7a4..a60dd7efc 100644
--- a/src/Ray.BiliBiliTool.Console/appsettings.json
+++ b/src/Ray.BiliBiliTool.Console/appsettings.json
@@ -154,6 +154,14 @@
           "webhook": "", //webhook编码(不是地址),仅在channel使用webhook渠道和CP渠道时需要填写
           "restrictedToMinimumLevel": "Information"
         }
+      },
+      //10.MicrosoftTeams
+      {
+        "Name": "MicrosoftTeamsBatched",
+        "Args": {
+          "webhook": "", //webhook完整地址
+          "restrictedToMinimumLevel": "Information"
+        }
       }
     ],
     "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsApiClient.cs b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsApiClient.cs
new file mode 100644
index 000000000..1865379c0
--- /dev/null
+++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsApiClient.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Net.Http;
+using System.Text;
+using Ray.Serilog.Sinks.Batched;
+
+namespace Ray.Serilog.Sinks.MicrosoftTeams
+{
+    public class MicrosoftTeamsApiClient : PushService
+    {
+        //https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
+
+        private readonly Uri _apiUrl;
+        private readonly HttpClient _httpClient = new HttpClient();
+
+        public MicrosoftTeamsApiClient(
+            string webhook
+            )
+        {
+            _apiUrl = new Uri(webhook);
+        }
+
+        public override string ClientName => "MicrosoftTeams";
+
+        protected override string NewLineStr => "<br/>";
+
+        public override HttpResponseMessage DoSend()
+        {
+            var json = new
+            {
+                text=Msg
+            }.ToJson();
+
+            var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+            var response = _httpClient.PostAsync(_apiUrl, content).GetAwaiter().GetResult();
+            return response;
+        }
+    }
+}
diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsBatchedSink.cs b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsBatchedSink.cs
new file mode 100644
index 000000000..1d7d00e67
--- /dev/null
+++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsBatchedSink.cs
@@ -0,0 +1,38 @@
+using System;
+using Ray.Serilog.Sinks.Batched;
+using Serilog.Events;
+
+namespace Ray.Serilog.Sinks.MicrosoftTeams
+{
+    public class MicrosoftTeamsBatchedSink : BatchedSink
+    {
+        private readonly string _webhook;
+
+        public MicrosoftTeamsBatchedSink(
+            string webhook,
+            Predicate<LogEvent> predicate,
+            bool sendBatchesAsOneMessages,
+            string outputTemplate,
+            IFormatProvider formatProvider,
+            LogEventLevel minimumLogEventLevel
+            )
+            : base(predicate, sendBatchesAsOneMessages, outputTemplate, formatProvider, minimumLogEventLevel)
+        {
+            _webhook = webhook;
+        }
+
+        public override void Emit(LogEvent logEvent)
+        {
+            if (_webhook.IsNullOrEmpty()) return;
+            base.Emit(logEvent);
+        }
+
+        protected override PushService PushService => new MicrosoftTeamsApiClient(
+            webhook: _webhook);
+
+        public override void Dispose()
+        {
+            //todo
+        }
+    }
+}
diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsConfigurationExtensions.cs b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsConfigurationExtensions.cs
new file mode 100644
index 000000000..e85fd6fcf
--- /dev/null
+++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/MicrosoftTeamsConfigurationExtensions.cs
@@ -0,0 +1,40 @@
+using System;
+using Ray.Serilog.Sinks.Batched;
+using Serilog;
+using Serilog.Configuration;
+using Serilog.Events;
+
+namespace Ray.Serilog.Sinks.MicrosoftTeams
+{
+    public static class MicrosoftTeamsConfigurationExtensions
+    {
+        public static LoggerConfiguration MicrosoftTeamsBatched(
+            this LoggerSinkConfiguration loggerSinkConfiguration,
+            string webhook = "",
+            string containsTrigger = Constants.DefaultContainsTrigger,
+            bool sendBatchesAsOneMessages = true,
+            string outputTemplate = Constants.DefaultOutputTemplate,
+            IFormatProvider formatProvider = null,
+            LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose
+        )
+        {
+            if (loggerSinkConfiguration == null)
+                throw new ArgumentNullException(nameof(loggerSinkConfiguration));
+            if (outputTemplate == null)
+                throw new ArgumentNullException(nameof(outputTemplate));
+
+            if (containsTrigger.IsNullOrEmpty()) containsTrigger = Constants.DefaultContainsTrigger;
+            Predicate<LogEvent> predicate = x => x.MessageTemplate.Text.Contains(containsTrigger);
+
+            return loggerSinkConfiguration.Sink(
+                new MicrosoftTeamsBatchedSink(
+                    webhook,
+                    predicate,
+                    sendBatchesAsOneMessages,
+                    outputTemplate,
+                    formatProvider,
+                    restrictedToMinimumLevel),
+                restrictedToMinimumLevel);
+        }
+    }
+}
diff --git a/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/Ray.Serilog.Sinks.MicrosoftTeams.csproj b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/Ray.Serilog.Sinks.MicrosoftTeams.csproj
new file mode 100644
index 000000000..55eb04647
--- /dev/null
+++ b/src/Ray.Serilog.Sinks/Ray.Serilog.Sinks.MicrosoftTeams/Ray.Serilog.Sinks.MicrosoftTeams.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Ray.BiliBiliTool.Infrastructure\Ray.BiliBiliTool.Infrastructure.csproj" />
+    <ProjectReference Include="..\Ray.Serilog.Sinks.Batched\Ray.Serilog.Sinks.Batched.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/test/LogTest/LogTest.csproj b/test/LogTest/LogTest.csproj
index b5e400b4a..97f27ee26 100644
--- a/test/LogTest/LogTest.csproj
+++ b/test/LogTest/LogTest.csproj
@@ -21,6 +21,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Ray.BiliBiliTool.Console\Ray.BiliBiliTool.Console.csproj" />
+    <ProjectReference Include="..\..\src\Ray.Serilog.Sinks\Ray.Serilog.Sinks.MicrosoftTeams\Ray.Serilog.Sinks.MicrosoftTeams.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/test/LogTest/TestMicrosoftTeams.cs b/test/LogTest/TestMicrosoftTeams.cs
new file mode 100644
index 000000000..e5e2d131c
--- /dev/null
+++ b/test/LogTest/TestMicrosoftTeams.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Ray.BiliBiliTool.Console;
+using Ray.BiliBiliTool.Infrastructure;
+using Ray.Serilog.Sinks.CoolPushBatched;
+using Ray.Serilog.Sinks.MicrosoftTeams;
+using Ray.Serilog.Sinks.PushPlusBatched;
+using Ray.Serilog.Sinks.ServerChanBatched;
+using Xunit;
+
+namespace LogTest
+{
+    public class TestMicrosoftTeams
+    {
+        private string _webhook;
+
+        public TestMicrosoftTeams()
+        {
+            Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
+            Program.CreateHost(new string[] { });
+
+            _webhook = Global.ConfigurationRoot["Serilog:WriteTo:10:Args:webhook"];
+        }
+
+        [Fact]
+        public void Test()
+        {
+            var client = new MicrosoftTeamsApiClient(webhook: _webhook);
+
+            var msg = LogConstants.Msg2;
+
+            var result = client.PushMessage(msg);
+            Debug.WriteLine(result.Content.ReadAsStringAsync().Result);
+
+            Assert.True(result.StatusCode == System.Net.HttpStatusCode.OK);
+        }
+    }
+}

From b8254599def24ca49e23c547d59059f776f032e2 Mon Sep 17 00:00:00 2001
From: Ray <zai7lou@outlook.com>
Date: Thu, 31 Mar 2022 00:12:45 +0800
Subject: [PATCH 2/2] feat: [#27] update README

---
 CHANGELOG.md | 2 +-
 README.md    | 9 +++++++--
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b983a4ba..efb8b7c90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,4 +26,4 @@
 - 更新`publish-image.yml`,只有`release`时才打`latest tag`,手动运行时不打`latest tag`
 ## 0.0.8
 - 【#55】新增日志推送端:Microsoft Teams
-
+- 【#27】更新README
diff --git a/README.md b/README.md
index b037bafff..0dcd8a20d 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ BiliBiliTool
 - **所有代码都是开源且透明的,任何人均可查看,程序不会保存或滥用任何用户的个人信息**
 - **应用内几乎所有功能都开放为了配置(如任务开关、日期、upId等),请仔细阅读配置文档,自己对自己的配置负责**
 
-_(如果图片挂了,是因为 GitHub 的服务器在国外,经常会刷不出,有梯子的可以架起梯子,没有的也可以先参考 [我的博客](https://www.cnblogs.com/RayWang/p/13909784.html),但博客内容不保证最新)_
+_(如果图片挂了,请自己架梯子,没有的也可以先参考 [我的博客](https://www.cnblogs.com/RayWang/p/13909784.html),但内容不保证最新)_
 
 ## 1. 如何使用
 
@@ -156,7 +156,12 @@ P.S.这里的运行环境指的是 `.NET Runtime 6.0.0` ,安装方法可详见
 
 对于已安装.net环境,且使用的是依赖包,同上,可在终端中执行命令:`dotnet Ray.BiliBiliTool.Console.dll`
 
-对于使用独立包的,可在终端中执行命令:`Ray.BiliBiliTool.Console`。
+对于使用独立包的,可在终端中执行命令:
+
+```
+chmod +x ./Ray.BiliBiliTool.Console
+Ray.BiliBiliTool.Console
+```
 
 其他系统依此类推,运行结果图示如下: