Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ウェブフックのリファクタ #52

Merged
merged 5 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 0 additions & 44 deletions Modules/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,50 +1130,6 @@ public static void FlashColor(Color color, float duration = 1f)
obj.GetComponent<SpriteRenderer>().color = new(color.r, color.g, color.b, Mathf.Clamp01((-2f * Mathf.Abs(t - 0.5f) + 1) * color.a)); //アルファ値を0→目標→0に変化させる
})));
}
public static void MakeWebhookUrlFile()
{
Logger.Info("WebhookUrl.txtを作成", "Webhook");
try
{
File.WriteAllText("WebhookUrl.txt", "この文章を削除してウェブフックのURLを記述/Remove this text and enter Webhook url");
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), "Webhook");
}
}
public static void SendWebhook(string text, string userName = "Town Of Host")
{
if (!File.Exists("WebhookUrl.txt"))
MakeWebhookUrlFile();
HttpClient client = new();
Dictionary<string, string> message = new()
{
{ "content", text },
{ "username", userName },
{ "avatar_url", "https://raw.githubusercontent.com/Hyz-sui/TownOfHost-H/images-H/Images/discord-avatar.png" }
};
using StreamReader sr = new("WebhookUrl.txt", Encoding.UTF8);
string webhookUrl = sr.ReadLine();
if (!Regex.IsMatch(webhookUrl, "^(https://(ptb.|canary.)?discord(app)?.com/api/webhooks/)")) // ptbとcanaryとappはあってもなくてもいい
{
Logger.Info("WebhookUrl.txtの内容がdiscordのウェブフックurlではなかったためウェブフックの送信をキャンセル", "Webhook");
return;
}
try
{
TaskAwaiter<HttpResponseMessage> awaiter = client.PostAsync(webhookUrl, new FormUrlEncodedContent(message)).GetAwaiter();
var response = awaiter.GetResult();
Logger.Info("ウェブフックを送信しました", "Webhook");
if (!response.IsSuccessStatusCode)
Logger.Warn("応答が異常です", "Webhook");
Logger.Info($"{(int)response.StatusCode} {response.ReasonPhrase}", "Webhook"); // 正常な応答: 204 No Content
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), "Webhook");
}
}
public static string ColorIdToDiscordEmoji(int colorId, bool alive)
{
if (alive)
Expand Down
127 changes: 127 additions & 0 deletions Modules/Webhook/WebhookManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace TownOfHost.Modules.Webhook;

public sealed class WebhookManager : IDisposable
{
public static WebhookManager Instance { get; } = new();

// see https://discord.com/developers/docs/resources/webhook#execute-webhook
private HttpClient httpClient = new()
{
Timeout = TimeSpan.FromSeconds(4),
};

private static readonly ILogHandler logger = Logger.Handler(nameof(WebhookManager));
private bool disposedValue;

public void StartSend(WebhookMessageBuilder builder)
{
if (!TryReadUrl(out var url))
{
logger.Warn("URL設定が正しくありません");
return;
}
var message = builder.ContentBuilder.ToString();
var content = new WebhookRequest(message, builder.UserName, builder.AvatarUrl);
var sendTask = SendAsync(content, url);
sendTask.ContinueWith(task =>
{
if (task.Exception is { } aggregateException)
{
logger.Warn("送信中に例外が発生しました");
logger.Exception(aggregateException.InnerException);
}
});
}
private bool TryReadUrl(out string url)
{
if (CreateConfigFileIfNecessary())
{
url = null;
return false;
}
using var stream = WebhookUrlFile.OpenRead();
using var reader = new StreamReader(stream, Encoding.UTF8);
var text = reader.ReadLine();
if (ValidateUrl(text))
{
url = text;
return true;
}
else
{
url = null;
return false;
}
}
public bool CreateConfigFileIfNecessary()
{
if (WebhookUrlFile.Exists)
{
return false;
}
using var stream = WebhookUrlFile.Create();
using var writer = new StreamWriter(stream, Encoding.UTF8);
writer.WriteLine("この文章をすべて削除してウェブフックのURLを入力し,上書き保存してください");
return true;
}
private bool ValidateUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return false;
}
return webhookUrlRegex.IsMatch(url);
}
public async Task SendAsync(WebhookRequest webhookRequest, string url, CancellationToken cancellationToken = default)
{
try
{
var response = await httpClient.PostAsJsonAsync(url, webhookRequest, cancellationToken);
logger.Info($"{(int)response.StatusCode} {response.ReasonPhrase}");
if (!response.IsSuccessStatusCode)
{
logger.Warn("送信に失敗");
}
}
catch (TaskCanceledException taskCanceledException)
{
logger.Warn("送信はキャンセルされました");
logger.Exception(taskCanceledException);
}
}

private void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
httpClient.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

public FileInfo WebhookUrlFile { get; } =
#if DEBUG
new("DebugWebhookUrl.txt");
#else
new("WebhookUrl.txt");
#endif
private readonly Regex webhookUrlRegex = new("^(https://(ptb.|canary.)?discord(app)?.com/api/webhooks/)");
}
13 changes: 13 additions & 0 deletions Modules/Webhook/WebhookMessageBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text;

namespace TownOfHost.Modules.Webhook;

public sealed class WebhookMessageBuilder
{
public StringBuilder ContentBuilder { get; } = new();
public string UserName { get; init; } = DefaultUserName;
public string AvatarUrl { get; init; } = DefaultAvatarUrl;

private const string DefaultUserName = "TownOfHost-H";
private const string DefaultAvatarUrl = "https://raw.githubusercontent.com/Hyz-sui/TownOfHost-H/images-H/Images/discord-avatar.png";
}
11 changes: 11 additions & 0 deletions Modules/Webhook/WebhookRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;

namespace TownOfHost.Modules.Webhook;

public readonly record struct WebhookRequest(
[property: JsonPropertyName("content")]
string Content,
[property: JsonPropertyName("username")]
string UserName,
[property: JsonPropertyName("avatar_url")]
string AvatarUrl);
29 changes: 23 additions & 6 deletions Patches/OutroPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using static TownOfHost.Translator;
using TownOfHost.Modules.GameEventHistory;
using TownOfHost.Modules.GameEventHistory.Events;
using TownOfHost.Modules.Webhook;

namespace TownOfHost
{
Expand Down Expand Up @@ -257,26 +258,42 @@ public static void Postfix(EndGameManager __instance)
if (PlayerControl.LocalPlayer.PlayerId == 0)
{
if (CustomWinnerHolder.WinnerTeam == CustomWinner.Draw)
{
Logger.Info("廃村のため試合結果の送信をキャンセル", "Webhook");
}
else
{
var resultMessageBuilder = new WebhookMessageBuilder()
{
UserName = "試合結果",
};
if (Main.SendResultToDiscord.Value)
{
var resultMessage = "";
resultMessageBuilder.ContentBuilder.AppendLine("### 各プレイヤーの最終結果");
foreach (var id in Main.winnerList)
{
resultMessage += Utils.ColorIdToDiscordEmoji(Palette.PlayerColors.IndexOf(Main.PlayerColors[id]), !PlayerState.GetByPlayerId(id).IsDead) + ":star:" + EndGamePatch.SummaryText[id].RemoveHtmlTags() + "\n";
resultMessageBuilder.ContentBuilder.Append(Utils.ColorIdToDiscordEmoji(Palette.PlayerColors.IndexOf(Main.PlayerColors[id]), !PlayerState.GetByPlayerId(id).IsDead));
resultMessageBuilder.ContentBuilder.Append(":star:");
resultMessageBuilder.ContentBuilder.Append(EndGamePatch.SummaryText[id].RemoveHtmlTags());
resultMessageBuilder.ContentBuilder.AppendLine();
}
foreach (var id in cloneRoles)
{
resultMessage += Utils.ColorIdToDiscordEmoji(Palette.PlayerColors.IndexOf(Main.PlayerColors[id]), !PlayerState.GetByPlayerId(id).IsDead) + "\u3000" + EndGamePatch.SummaryText[id].RemoveHtmlTags() + "\n";
resultMessageBuilder.ContentBuilder.Append(Utils.ColorIdToDiscordEmoji(Palette.PlayerColors.IndexOf(Main.PlayerColors[id]), !PlayerState.GetByPlayerId(id).IsDead));
resultMessageBuilder.ContentBuilder.Append('\u3000');
resultMessageBuilder.ContentBuilder.Append(EndGamePatch.SummaryText[id].RemoveHtmlTags());
resultMessageBuilder.ContentBuilder.AppendLine();
}
Utils.SendWebhook(resultMessage, GetString("LastResult"));
}
if (Main.SendHistoryToDiscord.Value)
{
var historyMessage = EventHistory.CurrentInstance.ToDiscordString();
Utils.SendWebhook(historyMessage, "ゲーム記録");
resultMessageBuilder.ContentBuilder.AppendLine("### 記録");
EventHistory.CurrentInstance.AppendDiscordString(resultMessageBuilder.ContentBuilder);
}

if (resultMessageBuilder.ContentBuilder.Length > 0)
{
WebhookManager.Instance.StartSend(resultMessageBuilder);
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using TownOfHost.Attributes;
using TownOfHost.Modules;
using TownOfHost.Roles.Core;
using TownOfHost.Modules.Webhook;

[assembly: AssemblyFileVersionAttribute(TownOfHost.Main.PluginVersion)]
[assembly: AssemblyInformationalVersionAttribute(TownOfHost.Main.PluginVersion)]
Expand Down Expand Up @@ -241,13 +242,17 @@ public override void Load()

ClassInjector.RegisterTypeInIl2Cpp<ErrorText>();

if (!File.Exists("WebhookUrl.txt"))
Utils.MakeWebhookUrlFile();
WebhookManager.Instance.CreateConfigFileIfNecessary();

SystemEnvironment.SetEnvironmentVariables();

Harmony.PatchAll();
}
public override bool Unload()
{
WebhookManager.Instance.Dispose();
return false;
}
}
public enum CustomDeathReason
{
Expand Down