From 139ebb142b58593637220eceb4581d2d9985f5a3 Mon Sep 17 00:00:00 2001 From: YDKK Date: Sun, 2 Jul 2023 18:22:44 +0900 Subject: [PATCH 1/6] =?UTF-8?q?LINE=20API=E3=81=AE=E5=91=BC=E3=81=B3?= =?UTF-8?q?=E5=87=BA=E3=81=97=E7=B5=90=E6=9E=9C=E3=82=92=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SlackLineBridge/Controllers/WebhookController.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SlackLineBridge/Controllers/WebhookController.cs b/SlackLineBridge/Controllers/WebhookController.cs index f3841be..1727cd8 100644 --- a/SlackLineBridge/Controllers/WebhookController.cs +++ b/SlackLineBridge/Controllers/WebhookController.cs @@ -202,7 +202,10 @@ private async Task PushToLine(string host, SlackChannel slackChan message }.Concat(urlMessages).ToArray() }; - await client.PostAsync($"message/push", new StringContent(JsonSerializer.Serialize(json), Encoding.UTF8, "application/json")); + var jsonStr = JsonSerializer.Serialize(json); + _logger.LogInformation("Push message to LINE: " + jsonStr); + var result = await client.PostAsync($"message/push", new StringContent(jsonStr, Encoding.UTF8, "application/json")); + _logger.LogInformation($"LINE API result [{result.StatusCode}]: " + await result.Content.ReadAsStringAsync()); } if (files != null) @@ -230,7 +233,8 @@ private async Task PushToLine(string host, SlackChannel slackChan }; var jsonStr = JsonSerializer.Serialize(json); _logger.LogInformation("Push images to LINE: " + jsonStr); - await client.PostAsync($"message/push", new StringContent(jsonStr, Encoding.UTF8, "application/json")); + var result = await client.PostAsync($"message/push", new StringContent(jsonStr, Encoding.UTF8, "application/json")); + _logger.LogInformation($"LINE API result [{result.StatusCode}]: " + await result.Content.ReadAsStringAsync()); } } return Ok(); From 3ffa123f91700e3cdfeddc17ae5caea905bd87b5 Mon Sep 17 00:00:00 2001 From: YDKK Date: Mon, 9 Sep 2024 00:52:44 +0900 Subject: [PATCH 2/6] =?UTF-8?q?.NET=208=E3=81=AB=E7=A7=BB=E8=A1=8C=20?= =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20close=20#25?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 関連作業項目:#2, #25 --- SlackLineBridge/SlackLineBridge.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SlackLineBridge/SlackLineBridge.csproj b/SlackLineBridge/SlackLineBridge.csproj index 4b9d76f..8c74c2b 100644 --- a/SlackLineBridge/SlackLineBridge.csproj +++ b/SlackLineBridge/SlackLineBridge.csproj @@ -1,15 +1,15 @@  - net6.0 + net8.0 Linux - - - - + + + + From e882712d227f47fe1a742c41e88dfb7e7089afd7 Mon Sep 17 00:00:00 2001 From: YDKK Date: Mon, 9 Sep 2024 01:06:04 +0900 Subject: [PATCH 3/6] =?UTF-8?q?IntelliSense=E3=81=AE=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ProxyController.cs | 31 ++---- .../Controllers/WebhookController.cs | 101 ++++++++---------- 2 files changed, 57 insertions(+), 75 deletions(-) diff --git a/SlackLineBridge/Controllers/ProxyController.cs b/SlackLineBridge/Controllers/ProxyController.cs index e5534a2..b03c9f0 100644 --- a/SlackLineBridge/Controllers/ProxyController.cs +++ b/SlackLineBridge/Controllers/ProxyController.cs @@ -19,24 +19,15 @@ namespace SlackLineBridge.Controllers { [Route("[controller]")] [ApiController] - public class ProxyController : ControllerBase + public class ProxyController( + ILogger logger, + IHttpClientFactory clientFactory, + LineChannelSecret lineChannelSecret, + SlackSigningSecret slackSigningSecret + ) : ControllerBase { - private readonly ILogger _logger; - IHttpClientFactory _clientFactory; - string _lineChannelSecret; - string _slackSigningSecret; - public ProxyController( - ILogger logger, - IHttpClientFactory clientFactory, - LineChannelSecret lineChannelSecret, - SlackSigningSecret slackSigningSecret - ) - { - _logger = logger; - _clientFactory = clientFactory; - _lineChannelSecret = lineChannelSecret.Secret; - _slackSigningSecret = slackSigningSecret.Secret; - } + private readonly string _lineChannelSecret = lineChannelSecret.Secret; + private readonly string _slackSigningSecret = slackSigningSecret.Secret; [HttpGet("line/{token}/{id}")] public async Task Line(string id, string token) @@ -46,7 +37,7 @@ public async Task Line(string id, string token) return new StatusCodeResult((int)HttpStatusCode.Forbidden); } - var client = _clientFactory.CreateClient("Line"); + var client = clientFactory.CreateClient("Line"); var url = $"https://api-data.line.me/v2/bot/message/{id}/content"; return await ProxyContent(client, url); @@ -55,7 +46,7 @@ public async Task Line(string id, string token) [HttpGet("slack/{token}/{encodedUrl}")] public async Task Slack(string encodedUrl, string token) { - _logger.LogInformation($"Proxy request to Slack: {encodedUrl}, {token}"); + logger.LogInformation("Proxy request to Slack: {encodedUrl}, {token}", encodedUrl, token); var url = HttpUtility.UrlDecode(encodedUrl); if (token != Crypt.GetHMACHex(url, _slackSigningSecret)) @@ -63,7 +54,7 @@ public async Task Slack(string encodedUrl, string token) return new StatusCodeResult((int)HttpStatusCode.Forbidden); } - var client = _clientFactory.CreateClient("Slack"); + var client = clientFactory.CreateClient("Slack"); return await ProxyContent(client, url); } diff --git a/SlackLineBridge/Controllers/WebhookController.cs b/SlackLineBridge/Controllers/WebhookController.cs index 1727cd8..9e89a0b 100644 --- a/SlackLineBridge/Controllers/WebhookController.cs +++ b/SlackLineBridge/Controllers/WebhookController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.IO; using System.Linq; @@ -15,6 +16,7 @@ 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.Utils; @@ -24,50 +26,33 @@ namespace SlackLineBridge.Controllers { [ApiController] [Route("[controller]")] - public class WebhookController : ControllerBase + public partial class WebhookController( + ILogger logger, + IOptionsSnapshot slackChannels, + IOptionsSnapshot lineChannels, + IOptionsSnapshot bridges, + ConcurrentQueue<(string signature, string body, string host)> lineRequestQueue, + IHttpClientFactory clientFactory, + SlackSigningSecret slackSigningSecret, + JsonSerializerOptions jsonOptions) : ControllerBase { - private readonly ILogger _logger; - private readonly SlackChannels _slackChannels; - private readonly LineChannels _lineChannels; - private readonly SlackLineBridges _bridges; - private readonly IHttpClientFactory _clientFactory; - private readonly ConcurrentQueue<(string signature, string body, string host)> _lineRequestQueue; - private static readonly Regex _urlRegex = new Regex(@"(\<(?http[^\|\>]+)\|?.*?\>)"); - private readonly JsonSerializerOptions _jsonOptions; - private readonly string _slackSigningSecret; - - public WebhookController( - ILogger logger, - IOptionsSnapshot slackChannels, - IOptionsSnapshot lineChannels, - IOptionsSnapshot bridges, - ConcurrentQueue<(string signature, string body, string host)> lineRequestQueue, - IHttpClientFactory clientFactory, - SlackSigningSecret slackSigningSecret, - JsonSerializerOptions jsonOptions) - { - _logger = logger; - _slackChannels = slackChannels.Value; - _lineChannels = lineChannels.Value; - _bridges = bridges.Value; - _clientFactory = clientFactory; - _lineRequestQueue = lineRequestQueue; - _slackSigningSecret = slackSigningSecret.Secret; - _jsonOptions = jsonOptions; - } + private readonly SlackChannels _slackChannels = slackChannels.Value; + private readonly LineChannels _lineChannels = lineChannels.Value; + private readonly SlackLineBridges _bridges = bridges.Value; + private readonly string _slackSigningSecret = slackSigningSecret.Secret; [HttpPost("/slack2")] public async Task Slack2() { //再送を無視する - if (Request.Headers.ContainsKey("X-Slack-Retry-Reason") && Request.Headers["X-Slack-Retry-Reason"].First() == "http_timeout") + if (Request.Headers.TryGetValue("X-Slack-Retry-Reason", out StringValues reason) && reason.First() == "http_timeout") { return Ok(); } using var reader = new StreamReader(Request.Body); var json = await reader.ReadToEndAsync(); - _logger.LogInformation("Processing request from Slack: " + json); + logger.LogInformation("Processing request from Slack: {json}", json); var timestampStr = Request.Headers["X-Slack-Request-Timestamp"].First(); if (long.TryParse(timestampStr, out var timestamp)) @@ -77,11 +62,11 @@ public async Task Slack2() var signature = $"v0={Crypt.GetHMACHex(sigBaseStr, _slackSigningSecret)}"; var slackSignature = Request.Headers["X-Slack-Signature"].First(); - _logger.LogDebug($"Slack signature check (expected:{slackSignature}, calculated:{signature})"); + logger.LogDebug("Slack signature check (expected:{slackSignature}, calculated:{signature})", slackSignature, signature); if (signature == slackSignature) { // the request came from Slack! - dynamic data = JsonSerializer.Deserialize(json, _jsonOptions); + dynamic data = JsonSerializer.Deserialize(json, jsonOptions); string type = data.type; switch (type) { @@ -93,7 +78,7 @@ public async Task Slack2() switch (eventType) { case "message": - if(data.@event.subtype == "bot_message") + if (data.@event.subtype == "bot_message") { return Ok(); } @@ -103,18 +88,18 @@ public async Task Slack2() var slackChannel = slackChannels.FirstOrDefault(x => x.TeamId == teamId && x.ChannelId == channelId); if (slackChannel == null) { - _logger.LogInformation($"message from unknown slack channel: {teamId}/{channelId}"); + logger.LogInformation("message from unknown slack channel: {teamId}/{channelId}", teamId, channelId); return Ok(); } string text = data.@event.text; string userId = data.@event.user; - var user = await GetSlackUserNameAndIcon(userId); + var (userName, icon) = await GetSlackUserNameAndIcon(userId); JsonDynamicArray files = data.@event.files; SlackFile[] slackFiles = null; - if (files?.Any() == true) + if ((files?.Count ?? 0) > 0) { slackFiles = files.Cast().Select(x => new SlackFile { @@ -124,14 +109,14 @@ public async Task Slack2() }).ToArray(); } - return await PushToLine(Request.Host.ToString(), slackChannel, user.icon, user.userName, text, slackFiles); + return await PushToLine(Request.Host.ToString(), slackChannel, icon, userName, text, slackFiles); } break; } } else { - _logger.LogInformation("Slack signature missmatch."); + logger.LogInformation("Slack signature missmatch."); } } @@ -140,15 +125,16 @@ public async Task Slack2() private async Task<(string userName, string icon)> GetSlackUserNameAndIcon(string userId) { - var client = _clientFactory.CreateClient("Slack"); + var client = clientFactory.CreateClient("Slack"); var result = await client.GetAsync($"https://slack.com/api/users.profile.get?user={userId}"); var json = await result.Content.ReadAsStringAsync(); - dynamic data = JsonSerializer.Deserialize(json, _jsonOptions); + dynamic data = JsonSerializer.Deserialize(json, jsonOptions); (string name, string icon) = (data.profile.display_name, data.profile.image_512); return (name, icon); } + [SuppressMessage("Style", "IDE1006:命名スタイル", Justification = "<保留中>")] private record SlackFile { public string urlPrivate { get; set; } @@ -156,6 +142,9 @@ private record SlackFile public string mimeType { get; set; } } + [GeneratedRegex(@"(\<(?http[^\|\>]+)\|?.*?\>)")] + private static partial Regex UrlRegex(); + private async Task PushToLine(string host, SlackChannel slackChannel, string userIconUrl, string userName, string text, SlackFile[] files = null) { var bridges = GetBridges(slackChannel); @@ -165,15 +154,15 @@ private async Task PushToLine(string host, SlackChannel slackChan } //URLタグを抽出 - var urls = _urlRegex.Matches(text); + var urls = UrlRegex().Matches(text); - var client = _clientFactory.CreateClient("Line"); + var client = clientFactory.CreateClient("Line"); foreach (var bridge in bridges) { var lineChannel = _lineChannels.Channels.FirstOrDefault(x => x.Name == bridge.Line); if (lineChannel == null) { - _logger.LogError($"bridge configured but cannot find target LineChannel: {bridge.Line}"); + logger.LogError("bridge configured but cannot find target LineChannel: {bridge.Line}", bridge.Line); return Ok(); } @@ -182,8 +171,9 @@ private async Task PushToLine(string host, SlackChannel slackChan { type = "text", altText = text, - text = text, - sender = new { + text, + sender = new + { name = userName, iconUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(userIconUrl, _slackSigningSecret)}/{HttpUtility.UrlEncode(userIconUrl)}" }, @@ -203,9 +193,9 @@ private async Task PushToLine(string host, SlackChannel slackChan }.Concat(urlMessages).ToArray() }; var jsonStr = JsonSerializer.Serialize(json); - _logger.LogInformation("Push message to LINE: " + jsonStr); + 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}]: " + await result.Content.ReadAsStringAsync()); + logger.LogInformation("LINE API result [{result.StatusCode}]: {result.Content}", result.StatusCode, await result.Content.ReadAsStringAsync()); } if (files != null) @@ -232,9 +222,9 @@ private async Task PushToLine(string host, SlackChannel slackChan messages = messages.ToArray() }; var jsonStr = JsonSerializer.Serialize(json); - _logger.LogInformation("Push images to LINE: " + jsonStr); + 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}]: " + await result.Content.ReadAsStringAsync()); + logger.LogInformation("LINE API result [{result.StatusCode}]: {result.Content}", result.StatusCode, await result.Content.ReadAsStringAsync()); } } return Ok(); @@ -243,9 +233,9 @@ private async Task PushToLine(string host, SlackChannel slackChan [HttpPost("/line")] public async Task LineAsync() { - if (!Request.Headers.ContainsKey("X-Line-Signature")) + if (!Request.Headers.TryGetValue("X-Line-Signature", out StringValues signature)) { - _logger.LogInformation("X-Line-Signature header missing."); + logger.LogInformation("X-Line-Signature header missing."); return BadRequest(); } @@ -261,9 +251,10 @@ public async Task LineAsync() } finally { - Response.OnCompleted(async () => + Response.OnCompleted(() => { - _lineRequestQueue.Enqueue((Request.Headers["X-Line-Signature"], json, host)); + lineRequestQueue.Enqueue((signature, json, host)); + return Task.CompletedTask; }); } From 430e4195cd91e34264d421d13442802867acfdfe Mon Sep 17 00:00:00 2001 From: YDKK Date: Mon, 9 Sep 2024 01:19:59 +0900 Subject: [PATCH 4/6] fix #14 --- SlackLineBridge/Controllers/WebhookController.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/SlackLineBridge/Controllers/WebhookController.cs b/SlackLineBridge/Controllers/WebhookController.cs index 9e89a0b..b421058 100644 --- a/SlackLineBridge/Controllers/WebhookController.cs +++ b/SlackLineBridge/Controllers/WebhookController.cs @@ -154,7 +154,7 @@ private async Task PushToLine(string host, SlackChannel slackChan } //URLタグを抽出 - var urls = UrlRegex().Matches(text); + text = UrlRegex().Replace(text, "${url}"); var client = clientFactory.CreateClient("Line"); foreach (var bridge in bridges) @@ -178,11 +178,6 @@ private async Task PushToLine(string host, SlackChannel slackChan iconUrl = $"https://{host}/proxy/slack/{Crypt.GetHMACHex(userIconUrl, _slackSigningSecret)}/{HttpUtility.UrlEncode(userIconUrl)}" }, }; - var urlMessages = urls.Select(x => x.Groups["url"].Value).Select(x => new - { - type = "text", - text = x - }); var json = new { @@ -190,7 +185,7 @@ private async Task PushToLine(string host, SlackChannel slackChan messages = new dynamic[] { message - }.Concat(urlMessages).ToArray() + }.ToArray() }; var jsonStr = JsonSerializer.Serialize(json); logger.LogInformation("Push message to LINE: {jsonStr}", jsonStr); From fb1080d482cc0e6d24a5b92d49264ff474f16c28 Mon Sep 17 00:00:00 2001 From: YDKK Date: Mon, 9 Sep 2024 01:23:16 +0900 Subject: [PATCH 5/6] =?UTF-8?q?CI=E5=91=A8=E3=82=8A=E3=81=AE=20.NET?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20#25?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pull_request.yml | 2 +- SlackLineBridge/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f324d81..e66ae1d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,6 +12,6 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.101 + dotnet-version: 8 - name: Build with dotnet run: dotnet build --configuration Release diff --git a/SlackLineBridge/Dockerfile b/SlackLineBridge/Dockerfile index 2835a2e..40be44e 100644 --- a/SlackLineBridge/Dockerfile +++ b/SlackLineBridge/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["SlackLineBridge/SlackLineBridge.csproj", "SlackLineBridge/"] RUN dotnet restore "SlackLineBridge/SlackLineBridge.csproj" From a488bdd35becb1b497d58ecf8aa5bd0906e44589 Mon Sep 17 00:00:00 2001 From: YDKK Date: Mon, 9 Sep 2024 01:42:00 +0900 Subject: [PATCH 6/6] =?UTF-8?q?CI=E5=91=A8=E3=82=8A=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pull_request.yml | 4 ++-- .github/workflows/push_develop_docker_image.yml | 10 +++++----- .github/workflows/push_release_docker_image.yml | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e66ae1d..78700a1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,9 +8,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8 - name: Build with dotnet diff --git a/.github/workflows/push_develop_docker_image.yml b/.github/workflows/push_develop_docker_image.yml index 26f6ef3..cb8b1fe 100644 --- a/.github/workflows/push_develop_docker_image.yml +++ b/.github/workflows/push_develop_docker_image.yml @@ -9,18 +9,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: SlackLineBridge/Dockerfile diff --git a/.github/workflows/push_release_docker_image.yml b/.github/workflows/push_release_docker_image.yml index a86f35f..1a33be9 100644 --- a/.github/workflows/push_release_docker_image.yml +++ b/.github/workflows/push_release_docker_image.yml @@ -9,13 +9,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -25,7 +25,7 @@ jobs: echo "::set-output name=tag::${tag}" id: get-tag - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: SlackLineBridge/Dockerfile