diff --git a/Plugin.cs b/Plugin.cs index 5723417..182aa87 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -5,9 +5,11 @@ using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Bloodstone.API; +using Bloodstone.Hooks; using HarmonyLib; using Il2CppInterop.Runtime.Injection; using UnityEngine; +using v_rising_discord_bot_companion.chat; using v_rising_discord_bot_companion.query; namespace v_rising_discord_bot_companion; @@ -24,6 +26,9 @@ public class Plugin : BasePlugin { private PluginConfig? _pluginConfig; private ConfigEntry _basicAuthUsers; + private ConfigEntry _discordWebhookUrl; + private ConfigEntry _discordWebhookUsername; + private ConfigEntry _discordWebhookAvatarUrl; public Plugin() { @@ -36,6 +41,24 @@ public Plugin() { "", "A list of comma separated username:password entries that are allowed to query the HTTP API." ); + _discordWebhookUrl = Config.Bind( + "Discord", + "WebhookUrl", + null, + "The discord webhook url to post chat messages to." + ); + _discordWebhookUsername = Config.Bind( + "Discord", + "WebhookUsername", + "Jarvis", + "The username to use when posting messages to discord." + ); + _discordWebhookAvatarUrl = Config.Bind( + "Discord", + "WebhookAvatarUrl", + "https://raw.githubusercontent.com/DarkAtra/v-rising-discord-bot/main/docs/assets/icon.png", + "The url to the avatar image for the discord webhook." + ); } public override void Load() { @@ -45,7 +68,6 @@ public override void Load() { return; } - // Plugin startup logic Log.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} version {MyPluginInfo.PLUGIN_VERSION} is loaded!"); @@ -56,9 +78,17 @@ public override void Load() { // Harmony patching _harmony = new Harmony(MyPluginInfo.PLUGIN_GUID); _harmony.PatchAll(Assembly.GetExecutingAssembly()); + + if (GetPluginConfig().DiscordWebhookUrl != null) { + Logger.LogInfo("Configuring DiscordChatSystem."); + Chat.OnChatMessage += DiscordChatSystem.HandleChatEvent; + } } public override bool Unload() { + if (_pluginConfig?.DiscordWebhookUrl != null) { + Chat.OnChatMessage -= DiscordChatSystem.HandleChatEvent; + } _harmony?.UnpatchSelf(); if (_queryDispatcher != null) { Object.Destroy(_queryDispatcher); @@ -88,7 +118,10 @@ private PluginConfig ParsePluginConfig() { } return new PluginConfig( - BasicAuthUsers: basicAuthUsers + BasicAuthUsers: basicAuthUsers, + DiscordWebhookUrl: _discordWebhookUrl.Value, + DiscordWebhookUsername: _discordWebhookUsername.Value, + DiscordWebhookAvatarUrl: _discordWebhookAvatarUrl.Value ); } } diff --git a/PluginConfig.cs b/PluginConfig.cs index cf9daa7..0c739a2 100644 --- a/PluginConfig.cs +++ b/PluginConfig.cs @@ -3,7 +3,10 @@ namespace v_rising_discord_bot_companion; public readonly record struct PluginConfig( - List BasicAuthUsers + List BasicAuthUsers, + string? DiscordWebhookUrl, + string DiscordWebhookUsername, + string DiscordWebhookAvatarUrl ); public readonly record struct BasicAuthUser( diff --git a/chat/DiscordChatSystem.cs b/chat/DiscordChatSystem.cs new file mode 100644 index 0000000..47470dd --- /dev/null +++ b/chat/DiscordChatSystem.cs @@ -0,0 +1,54 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Bloodstone.Hooks; +using ProjectM.Network; + +namespace v_rising_discord_bot_companion.chat; + +public class DiscordChatSystem { + + private static readonly int MAX_MESSAGE_LENGTH = 2000; + + private static readonly HttpClient _httpClient = new(); + private static readonly JsonSerializerOptions _serializeOptions = new() { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; + + public static void HandleChatEvent(VChatEvent vChatEvent) { + + var pluginConfig = Plugin.Instance.GetPluginConfig(); + var discordWebhookUrl = pluginConfig.DiscordWebhookUrl; + if (discordWebhookUrl == null || vChatEvent.Type != ChatMessageType.Global) { + return; + } + + try { + + var webhookRequestPayload = new DiscordWebhookRequestPayload( + Username: pluginConfig.DiscordWebhookUsername, + AvatarUrl: pluginConfig.DiscordWebhookAvatarUrl, + Content: $"{vChatEvent.User.CharacterName}: {vChatEvent.Message.Substring(0, Math.Min(vChatEvent.Message.Length, MAX_MESSAGE_LENGTH - vChatEvent.User.CharacterName.Length))}" + ); + + var webhookRequest = new HttpRequestMessage(HttpMethod.Post, discordWebhookUrl) { + Content = new StringContent( + JsonSerializer.Serialize(webhookRequestPayload, _serializeOptions), + Encoding.UTF8, + "application/json" + ) + }; + + var response = _httpClient.Send(webhookRequest); + if (response.StatusCode != HttpStatusCode.NoContent) { + Plugin.Logger.LogError($"Discord webhook responded with unexpected status code '{response.StatusCode}' - please check your configuration"); + } + } catch (Exception e) { + Plugin.Logger.LogError($"Exception calling discord webhook: {e.Message}"); + } + } +} diff --git a/chat/DiscordWebhookRequestPayload.cs b/chat/DiscordWebhookRequestPayload.cs new file mode 100644 index 0000000..3d4671a --- /dev/null +++ b/chat/DiscordWebhookRequestPayload.cs @@ -0,0 +1,7 @@ +namespace v_rising_discord_bot_companion.chat; + +public readonly record struct DiscordWebhookRequestPayload( + string Username, + string Content, + string AvatarUrl +); diff --git a/http-requests.http b/http-requests.http index 1a0fe82..b634735 100644 --- a/http-requests.http +++ b/http-requests.http @@ -34,3 +34,13 @@ POST http://localhost:25570/api/save/v1 Content-Type: application/json {} + +### Discord Webhook +POST https://discord.com/api/webhooks/xxx/xxx +Content-Type: application/json + +{ + "username": "Jarvis", + "avatar_url": "https://raw.githubusercontent.com/DarkAtra/v-rising-discord-bot/main/docs/assets/icon.png", + "content": "Atra: Test" +} diff --git a/v-rising-discord-bot-companion.csproj b/v-rising-discord-bot-companion.csproj index a0dde19..aa3d6a7 100644 --- a/v-rising-discord-bot-companion.csproj +++ b/v-rising-discord-bot-companion.csproj @@ -4,7 +4,7 @@ A companion mod for DarkAtra/v-rising-discord-bot. 0.4.1 - net6.0 + net8.0 latest true enable