From 224f4a7e9c50ab0f5c956f5809b4cfd02bb90478 Mon Sep 17 00:00:00 2001 From: YDKK Date: Mon, 9 Sep 2024 01:57:02 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E3=82=BD=E3=83=AA=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=82=922022=E5=90=91=E3=81=91=E3=81=AB=E5=86=8D=E3=82=BF?= =?UTF-8?q?=E3=83=BC=E3=82=B2=E3=83=83=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SlackLineBridge.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SlackLineBridge.sln b/SlackLineBridge.sln index c66a84d..4376a4e 100644 --- a/SlackLineBridge.sln +++ b/SlackLineBridge.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29326.143 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35222.181 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlackLineBridge", "SlackLineBridge\SlackLineBridge.csproj", "{20565EFE-65C3-4EB1-8E09-7C9A9324BB66}" EndProject From 685d4e405ff69648cc6370a4467d238b6e4bb807 Mon Sep 17 00:00:00 2001 From: YDKK Date: Thu, 14 Aug 2025 12:59:51 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E5=88=A9=E7=94=A8=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E3=81=AA=E5=A0=B4=E5=90=88=E3=81=ABreplyToken=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=E5=AE=9F=E8=A3=85=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/WebhookController.cs | 104 ++++++++++-------- .../Services/LineMessageProcessingService.cs | 23 +++- 2 files changed, 78 insertions(+), 49 deletions(-) diff --git a/SlackLineBridge/Controllers/WebhookController.cs b/SlackLineBridge/Controllers/WebhookController.cs index b421058..cdfcae3 100644 --- a/SlackLineBridge/Controllers/WebhookController.cs +++ b/SlackLineBridge/Controllers/WebhookController.cs @@ -2,23 +2,20 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Dynamic; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization.Samples; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -using SlackLineBridge.Models; using SlackLineBridge.Models.Configurations; +using SlackLineBridge.Services; using SlackLineBridge.Utils; using static System.Text.Json.Serialization.Samples.JsonSerializerExtensions; @@ -31,6 +28,7 @@ public partial class WebhookController( IOptionsSnapshot slackChannels, IOptionsSnapshot lineChannels, IOptionsSnapshot bridges, + LineMessageProcessingService lineMessageProcessingService, ConcurrentQueue<(string signature, string body, string host)> lineRequestQueue, IHttpClientFactory clientFactory, SlackSigningSecret slackSigningSecret, @@ -40,6 +38,7 @@ public partial class WebhookController( private readonly LineChannels _lineChannels = lineChannels.Value; private readonly SlackLineBridges _bridges = bridges.Value; private readonly string _slackSigningSecret = slackSigningSecret.Secret; + private readonly LineMessageProcessingService _lineMessageProcessingService = lineMessageProcessingService; [HttpPost("/slack2")] public async Task Slack2() @@ -167,59 +166,72 @@ private async Task PushToLine(string host, SlackChannel slackChan } { - var message = new + var messages = new List { - type = "text", - altText = text, - text, - sender = new + new { - name = userName, - iconUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(userIconUrl, _slackSigningSecret)}/{HttpUtility.UrlEncode(userIconUrl)}" - }, + type = "text", + altText = text, + text, + sender = new + { + name = userName, + iconUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(userIconUrl, _slackSigningSecret)}/{HttpUtility.UrlEncode(userIconUrl)}" + }, + } }; - var json = new + if (files != null) { - to = lineChannel.Id, - messages = new dynamic[] + var fileMessages = files.Where(x => x.mimeType.StartsWith("image")).Select(file => { - message - }.ToArray() - }; - var jsonStr = JsonSerializer.Serialize(json); - logger.LogInformation("Push message to LINE: {jsonStr}", jsonStr); - var result = await client.PostAsync($"message/push", new StringContent(jsonStr, Encoding.UTF8, "application/json")); - logger.LogInformation("LINE API result [{result.StatusCode}]: {result.Content}", result.StatusCode, await result.Content.ReadAsStringAsync()); - } + var urlPrivate = file.urlPrivate; + var urlThumb360 = file.thumb360; + return new + { + type = "image", + originalContentUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(urlPrivate, _slackSigningSecret)}/{HttpUtility.UrlEncode(urlPrivate)}", + previewImageUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(urlThumb360, _slackSigningSecret)}/{HttpUtility.UrlEncode(urlThumb360)}", + sender = new + { + name = userName, + iconUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(userIconUrl, _slackSigningSecret)}/{HttpUtility.UrlEncode(userIconUrl)}" + }, + }; + }); + messages.AddRange(fileMessages); + } - if (files != null) - { - var messages = files.Where(x => x.mimeType.StartsWith("image")).Select(file => + var replyToken = _lineMessageProcessingService.GetReplyToken(lineChannel.Id); + if (replyToken != null) { - var urlPrivate = file.urlPrivate; - var urlThumb360 = file.thumb360; - return new + var json = new { - type = "image", - originalContentUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(urlPrivate, _slackSigningSecret)}/{HttpUtility.UrlEncode(urlPrivate)}", - previewImageUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(urlThumb360, _slackSigningSecret)}/{HttpUtility.UrlEncode(urlThumb360)}", - sender = new - { - name = userName, - iconUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(userIconUrl, _slackSigningSecret)}/{HttpUtility.UrlEncode(userIconUrl)}" - }, + replyToken, + messages = messages.ToArray() }; - }); - var json = new + var jsonStr = JsonSerializer.Serialize(json); + logger.LogInformation("Push message to LINE (using replyToken): {jsonStr}", jsonStr); + var result = await client.PostAsync($"message/reply", new StringContent(jsonStr, Encoding.UTF8, "application/json")); + logger.LogInformation("LINE API result [{result.StatusCode}]: {result.Content}", result.StatusCode, await result.Content.ReadAsStringAsync()); + if (result.IsSuccessStatusCode) + { + continue; + } + } + + // 通常のプッシュメッセージにフォールバック { - to = lineChannel.Id, - messages = messages.ToArray() - }; - var jsonStr = JsonSerializer.Serialize(json); - logger.LogInformation("Push images to LINE: {jsonStr}", jsonStr); - var result = await client.PostAsync($"message/push", new StringContent(jsonStr, Encoding.UTF8, "application/json")); - logger.LogInformation("LINE API result [{result.StatusCode}]: {result.Content}", result.StatusCode, await result.Content.ReadAsStringAsync()); + var json = new + { + to = lineChannel.Id, + messages = messages.ToArray() + }; + var jsonStr = JsonSerializer.Serialize(json); + logger.LogInformation("Push message to LINE: {jsonStr}", jsonStr); + var result = await client.PostAsync($"message/push", new StringContent(jsonStr, Encoding.UTF8, "application/json")); + logger.LogInformation("LINE API result [{result.StatusCode}]: {result.Content}", result.StatusCode, await result.Content.ReadAsStringAsync()); + } } } return Ok(); diff --git a/SlackLineBridge/Services/LineMessageProcessingService.cs b/SlackLineBridge/Services/LineMessageProcessingService.cs index fbcee80..cf72a9f 100644 --- a/SlackLineBridge/Services/LineMessageProcessingService.cs +++ b/SlackLineBridge/Services/LineMessageProcessingService.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using SlackLineBridge.Models; using SlackLineBridge.Models.Configurations; using SlackLineBridge.Utils; using System; @@ -10,8 +9,6 @@ using System.Dynamic; using System.Linq; using System.Net.Http; -using System.Security.Cryptography; -using System.Security.Policy; using System.Text; using System.Text.Json; using System.Threading; @@ -28,6 +25,8 @@ public class LineMessageProcessingService : BackgroundService private readonly ILogger _logger; private readonly ConcurrentQueue<(string signature, string body, string host)> _queue; private readonly string _lineChannelSecret; + // key: LINE channel id, value: last reply token + private readonly ConcurrentDictionary _lastReplyTokens = []; public LineMessageProcessingService( IOptionsMonitor slackChannels, @@ -86,6 +85,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { continue; } + if (e.TryGetProperty("replyToken", out var replyTokenElement)) + { + var replyToken = replyTokenElement.GetString(); + if (!string.IsNullOrEmpty(replyToken)) + { + _lastReplyTokens.AddOrUpdate(lineChannel.Id, replyToken, (key, value) => replyToken); + } + } var (userName, pictureUrl) = await GetLineProfileAsync(e); var message = e.GetProperty("message"); @@ -154,6 +161,16 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _logger.LogDebug($"LineMessageProcessing background task is stopped."); } + public string GetReplyToken(string lineChannelID) + { + if (_lastReplyTokens.TryRemove(lineChannelID, out var token)) + { + return token; + } + + return null; + } + private async Task SendToSlack(string webhookUrl, string channelId, string pictureUrl, string userName, string text, string imageUrl) { var client = _clientFactory.CreateClient(); From 9d220e401a2eab3e05ab5fb91831ac7d38d90b94 Mon Sep 17 00:00:00 2001 From: YDKK Date: Thu, 14 Aug 2025 13:13:24 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SlackLineBridge/SlackLineBridge.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SlackLineBridge/SlackLineBridge.csproj b/SlackLineBridge/SlackLineBridge.csproj index 8c74c2b..4d7589a 100644 --- a/SlackLineBridge/SlackLineBridge.csproj +++ b/SlackLineBridge/SlackLineBridge.csproj @@ -6,10 +6,10 @@ - - - - + + + + From 1c6b6db7bbfdaec18575d5c074549cb38e65bbf5 Mon Sep 17 00:00:00 2001 From: YDKK Date: Thu, 14 Aug 2025 13:35:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Sentry=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SlackLineBridge/Program.cs | 28 +++++++++++++++++++------- SlackLineBridge/SlackLineBridge.csproj | 1 + 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/SlackLineBridge/Program.cs b/SlackLineBridge/Program.cs index 392adbd..001f668 100644 --- a/SlackLineBridge/Program.cs +++ b/SlackLineBridge/Program.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace SlackLineBridge { @@ -17,17 +12,36 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + public static IHostBuilder CreateHostBuilder(string[] args) + { + bool useSentry = false; + string sentryDsn = ""; + return Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { config.SetBasePath(Directory.GetCurrentDirectory()); config.AddJsonFile("config.json", false, true); config.AddJsonFile("appsettings.AWS.json", true, true); + var buildConfig = config.Build(); + useSentry = buildConfig.GetValue("Sentry:UseSentry"); + if (useSentry) + { + sentryDsn = buildConfig.GetValue("Sentry:Dsn"); + } }) .ConfigureWebHostDefaults(webBuilder => { + if (useSentry) + { + webBuilder.UseSentry(o => + { + o.Dsn = sentryDsn; + o.TracesSampleRate = 0; + o.SendDefaultPii = true; + }); + } webBuilder.UseStartup(); }); + } } } diff --git a/SlackLineBridge/SlackLineBridge.csproj b/SlackLineBridge/SlackLineBridge.csproj index 4d7589a..fc067e5 100644 --- a/SlackLineBridge/SlackLineBridge.csproj +++ b/SlackLineBridge/SlackLineBridge.csproj @@ -10,6 +10,7 @@ + From a42a05e5a12612e7ebfd6a8ec6de26108a4877aa Mon Sep 17 00:00:00 2001 From: YDKK Date: Thu, 14 Aug 2025 13:43:48 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=93=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E4=BD=9C=E6=88=90=E9=A0=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SlackLineBridge/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SlackLineBridge/Startup.cs b/SlackLineBridge/Startup.cs index ff4fe56..4df05d9 100644 --- a/SlackLineBridge/Startup.cs +++ b/SlackLineBridge/Startup.cs @@ -48,7 +48,6 @@ public void ConfigureServices(IServiceCollection services) x.AddAWSProvider(awsLoggingConfig); } }); - services.AddControllers(); services.AddHttpClient("Line", c => { c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Configuration["lineAccessToken"]); @@ -69,6 +68,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(jsonOptions); services.AddHostedService(); services.AddHostedService(); + services.AddControllers(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From aac8ff5a8c83420c6cb66789895c522314d74ede Mon Sep 17 00:00:00 2001 From: YDKK Date: Thu, 14 Aug 2025 16:15:45 +0900 Subject: [PATCH 6/6] =?UTF-8?q?LineMessageProcessingService=E3=82=92DI?= =?UTF-8?q?=E8=A7=A3=E6=B1=BA=E5=AF=BE=E8=B1=A1=E3=81=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SlackLineBridge/Startup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SlackLineBridge/Startup.cs b/SlackLineBridge/Startup.cs index 4df05d9..60025ce 100644 --- a/SlackLineBridge/Startup.cs +++ b/SlackLineBridge/Startup.cs @@ -66,7 +66,8 @@ public void ConfigureServices(IServiceCollection services) var jsonOptions = new JsonSerializerOptions(); jsonOptions.EnableDynamicTypes(); services.AddSingleton(jsonOptions); - services.AddHostedService(); + services.AddSingleton(); + services.AddHostedService(sp=>sp.GetRequiredService()); services.AddHostedService(); services.AddControllers(); }