Skip to content

Commit

Permalink
v2.0.0 (#8)
Browse files Browse the repository at this point in the history
* しばらくアイドルだとLINEからのリクエストを捌くのが間に合わないのを修正 (#5)
* LINEスタンプ(LINE→Slack)に対応
* LINEのユーザアイコン(LINE→Slack)に対応
* LINEのBOTアカウントを友達登録していないユーザのプロフィール情報の取得に対応
  • Loading branch information
YDKK authored May 24, 2020
1 parent e2279ed commit 6d09673
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 45 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ LINE側

## 制約

* LINE側の[制約](https://developers.line.biz/ja/docs/messaging-api/user-consent/)により,LINE側のユーザ名は情報の取得に同意したユーザのみ取得可能です.情報の取得に同意していないユーザは,ユーザ名の代わりに一意のIDで表示されます
* LINEスタンプには対応していません
- LINEスタンプはLINE→Slackの一方向のみの対応です
- SlackのユーザアイコンはLINE側には反映されません

## Dockerイメージ

Expand Down
16 changes: 11 additions & 5 deletions SlackLineBridge/Controllers/WebhookController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,20 @@ public async Task<StatusCodeResult> Line()
return BadRequest();
}

using (var reader = new StreamReader(Request.Body))
{
_lineRequestQueue.Enqueue((Request.Headers["X-Line-Signature"], await reader.ReadToEndAsync()));
using var reader = new StreamReader(Request.Body);

return Ok();
}
_lineRequestQueue.Enqueue((Request.Headers["X-Line-Signature"], await reader.ReadToEndAsync()));

return Ok();
}

[HttpGet("/health")]
public OkResult Health()
{
return Ok();
}


private IEnumerable<Models.Configurations.SlackLineBridge> GetBridges(SlackChannel channel)
{
return _bridges.Bridges.Where(x => x.Slack == channel.Name);
Expand Down
47 changes: 47 additions & 0 deletions SlackLineBridge/Services/BackgroundAccessingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace SlackLineBridge.Services
{
public class BackgroundAccessingService : BackgroundService
{
private readonly ILogger<BackgroundAccessingService> _logger;
private readonly IHttpClientFactory _clientFactory;

public BackgroundAccessingService(ILogger<BackgroundAccessingService> logger, IHttpClientFactory clientFactory)
{
_logger = logger;
_clientFactory = clientFactory;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug($"BackgroundAccessingService is starting.");

stoppingToken.Register(() => _logger.LogDebug($" BackgroundAccessing background task is stopping."));

var client = _clientFactory.CreateClient();
while (!stoppingToken.IsCancellationRequested)
{
try
{
var response = await client.PostAsync("http://localhost/line", new StringContent(""));
}
catch
{
// ignore
}

await Task.Delay(1000 * 60, stoppingToken);
}

_logger.LogDebug($"BackgroundAccessing background task is stopped.");
}
}
}
124 changes: 86 additions & 38 deletions SlackLineBridge/Services/LineMessageProcessingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
Expand Down Expand Up @@ -83,34 +84,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
continue;
}
string userName = null;
if (e.GetProperty("source").TryGetProperty("userId", out var userId))
{
var client = _clientFactory.CreateClient("Line");

try
{
var result = await client.GetAsync($"profile/{userId}");
if (result.IsSuccessStatusCode)
{
var profile = await JsonSerializer.DeserializeAsync<JsonElement>(await result.Content.ReadAsStreamAsync());
userName = profile.GetProperty("displayName").GetString();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "get profile data failed");
}

if (userName == null)
{
userName = $"Unknown ({userId})";
}
}
else
{
userName = "Unknown";
}
var (userName, pictureUrl) = await GetLineProfileAsync(e);

var message = e.GetProperty("message");
var type = message.GetProperty("type").GetString();
Expand All @@ -120,6 +94,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_ => $"<{type}>",
};

string stickerUrl = null;
if (type == "sticker")
{
var stickerId = message.GetProperty("stickerId").GetString();
stickerUrl = $"https://stickershop.line-scdn.net/stickershop/v1/sticker/{stickerId}/android/sticker.png";
}

foreach (var bridge in bridges)
{
var slackChannel = _slackChannels.CurrentValue.Channels.FirstOrDefault(x => x.Name == bridge.Slack);
Expand All @@ -129,10 +110,11 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
continue;
}

await SendToSlack(slackChannel.WebhookUrl, slackChannel.ChannelId, userName, text);
await SendToSlack(slackChannel.WebhookUrl, slackChannel.ChannelId, pictureUrl, userName, text, stickerUrl);
}
}
break;

default:
{
var sourceId = GetLineEventSourceId(e);
Expand All @@ -151,22 +133,38 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
await Task.Delay(1000, stoppingToken);
}

_logger.LogDebug($"LineMessageProcessing background task is stopping.");
_logger.LogDebug($"LineMessageProcessing background task is stopped.");
}

private async Task SendToSlack(string webhookUrl, string channelId, string userName, string text)
private async Task SendToSlack(string webhookUrl, string channelId, string pictureUrl, string userName, string text, string stickerUrl)
{
var client = _clientFactory.CreateClient();

var message = new
dynamic message = new ExpandoObject();
message.channel = channelId;
message.username = userName;
message.text = text;
if (string.IsNullOrEmpty(pictureUrl))
{
message.icon_emoji = ":line:";
}
else
{
message.icon_url = pictureUrl;
}

if (!string.IsNullOrEmpty(stickerUrl))
{
channel = channelId,
username = userName,
icon_emoji = ":line:",
text
};
message.blocks = new[]{new
{
type = "image",
image_url = stickerUrl,
alt_text = "sticker"
} };
}

await client.PostAsync(webhookUrl, new StringContent(JsonSerializer.Serialize(message), Encoding.UTF8, "application/json"));
var result = await client.PostAsync(webhookUrl, new StringContent(JsonSerializer.Serialize(new Dictionary<string, object>(message)), Encoding.UTF8, "application/json"));
_logger.LogInformation($"Post to Slack: {result.StatusCode} {await result.Content.ReadAsStringAsync()}");
}

private LineChannel GetLineChannel(JsonElement e)
Expand Down Expand Up @@ -198,6 +196,56 @@ private string GetLineEventSourceId(JsonElement e)
}
}

private async Task<(string userName, string pictureUrl)> GetLineProfileAsync(JsonElement e)
{
var source = e.GetProperty("source");
var type = source.GetProperty("type").GetString();
var client = _clientFactory.CreateClient("Line");
var resultProfile = (userName: "", pictureUrl: "");
var userId = source.GetProperty("userId");
HttpResponseMessage result = null;

try
{
switch (type)
{
case "user":
result = await client.GetAsync($"profile/{userId}");
break;
case "group":
var groupId = source.GetProperty("groupId");
result = await client.GetAsync($"group/{groupId}/member/{userId}");
break;
case "room":
var roomId = source.GetProperty("roomId");
result = await client.GetAsync($"room/{roomId}/member/{userId}");
break;
default:
_logger.LogError($"unknown source type: {type}");
break;
}

var profile = await JsonSerializer.DeserializeAsync<JsonElement>(await result.Content.ReadAsStreamAsync());
_logger.LogInformation($"get profile: {profile}");

resultProfile.userName = profile.GetProperty("displayName").GetString();
if (profile.TryGetProperty("pictureUrl", out var picture))
{
resultProfile.pictureUrl = picture.GetString();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "get profile data failed");
}

if (string.IsNullOrEmpty(resultProfile.userName))
{
resultProfile.userName = $"Unknown ({userId})";
}
return resultProfile;
}

private static string GetHMAC(string text, string key)
{
var encoding = new UTF8Encoding();
Expand Down
1 change: 1 addition & 0 deletions SlackLineBridge/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<ConcurrentQueue<(string signature, string body)>>(); //lineRequestQueue
services.AddSingleton(Configuration["lineChannelSecret"]);
services.AddHostedService<LineMessageProcessingService>();
services.AddHostedService<BackgroundAccessingService>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand Down

0 comments on commit 6d09673

Please sign in to comment.