diff --git a/Sample/ChatboxSender/ChatboxSender.csproj b/Sample/ChatboxSender/ChatboxSender.csproj new file mode 100644 index 0000000..a512f0c --- /dev/null +++ b/Sample/ChatboxSender/ChatboxSender.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/Sample/ChatboxSender/Program.cs b/Sample/ChatboxSender/Program.cs new file mode 100644 index 0000000..ea6a531 --- /dev/null +++ b/Sample/ChatboxSender/Program.cs @@ -0,0 +1,20 @@ +using System; +using BuildSoft.VRChat.Osc.Chatbox; + +while (true) +{ + Console.Write("Text: "); + string? text = Console.ReadLine(); + if (text == null) + { + Console.WriteLine("Cannot Send Text."); + continue; + } + if (text != "") + { + OscChatbox.SetIsTyping(true); + await Task.Delay(1000); + OscChatbox.SendMessage(text, true); + OscChatbox.SetIsTyping(false); + } +} diff --git a/src/vrcosclib.Test/Chatbox/OscChatboxTest.cs b/src/vrcosclib.Test/Chatbox/OscChatboxTest.cs new file mode 100644 index 0000000..cc1ba72 --- /dev/null +++ b/src/vrcosclib.Test/Chatbox/OscChatboxTest.cs @@ -0,0 +1,80 @@ +using System.Text; +using BuildSoft.OscCore; +using BuildSoft.VRChat.Osc.Test; +using NUnit.Framework; + +namespace BuildSoft.VRChat.Osc.Chatbox.Test; + +[TestOf(typeof(OscChatbox))] +public class OscChatboxTest +{ + private OscServer _server = null!; + + [SetUp] + public void Setup() + { + _server = new OscServer(OscUtility.SendPort); + + OscParameter.Parameters.Clear(); + } + + [TearDown] + public void TearDown() + { + _server.Dispose(); + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + + } + + + [TestCase("", true)] + [TestCase("ASCII", true)] + [TestCase("ASCII", false)] + [TestCase("UTF-8", true)] + [TestCase("😂😭😪😥😰😅😓😩😫😨😱", true)] + public async Task SendMessageTest(string message, bool direct) + { + OscMessageValues value = null!; + void valueReadMethod(OscMessageValues v) => value = v; + _server.TryAddMethod(OscChatbox.InputAddress, valueReadMethod); + byte[] recievedMessage = new byte[2024]; + + + OscChatbox.SendMessage(message, direct); + await TestUtility.LoopWhile(() => value == null, TestUtility.LatencyTimeout); + int length = value.ReadStringElementBytes(0, recievedMessage); + bool recievedDirect = value.ReadBooleanElement(1); + + + Assert.AreEqual(message, Encoding.UTF8.GetString(recievedMessage, 0, length)); + Assert.AreEqual(direct, recievedDirect); + } + + [TestCase(true)] + [TestCase(false)] + public async Task SetIsTypingTest(bool isTyping) + { + OscMessageValues value = null!; + void valueReadMethod(OscMessageValues v) => value = v; + _server.TryAddMethod(OscChatbox.TypingAddress, valueReadMethod); + + + OscChatbox.SetIsTyping(isTyping); + await TestUtility.LoopWhile(() => value == null, TestUtility.LatencyTimeout); + bool recievedIsTyping = value.ReadBooleanElement(0); + + + Assert.AreEqual(isTyping, recievedIsTyping); + } + +} diff --git a/src/vrcosclib/Chatbox/OscChatbox.cs b/src/vrcosclib/Chatbox/OscChatbox.cs new file mode 100644 index 0000000..1988f43 --- /dev/null +++ b/src/vrcosclib/Chatbox/OscChatbox.cs @@ -0,0 +1,43 @@ +using System.Net.Sockets; +using System.Text; +using BuildSoft.OscCore; + +namespace BuildSoft.VRChat.Osc.Chatbox; +public static class OscChatbox +{ + public static string InputAddress = "/chatbox/input"; + public static string TypingAddress = "/chatbox/typing"; + public static void SendMessage(string message, bool direct) + { + OscClient client = OscUtility.Client; + OscWriter writer = client.Writer; + var socket = client.Socket; + + writer.Reset(); + writer.Write(InputAddress); + writer.Write(direct ? ",sT" : ",sF"); + writer.WriteUtfString(message); + socket.Send(writer.Buffer, writer.Length, SocketFlags.None); + } + + public static void SetIsTyping(bool isTyping) + { + OscParameter.SendValue(TypingAddress, isTyping); + } + + private static void WriteUtfString(this OscWriter writer, string data) + { + var utf8String = Encoding.UTF8.GetBytes(data); + Array.Resize(ref utf8String, utf8String.Length + (4 - utf8String.Length % 4)); + + for (int i = 3; i < utf8String.Length; i += 4) + { + int data1 = + utf8String[i - 3] << 8 * 3 + | utf8String[i - 2] << 8 * 2 + | utf8String[i - 1] << 8 * 1 + | utf8String[i - 0] << 8 * 0; + writer.Write(data1); + } + } +} diff --git a/vrcosclib.sln b/vrcosclib.sln index 653f497..9e322b4 100644 --- a/vrcosclib.sln +++ b/vrcosclib.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarParameterSender", "Sa {3D5D91B8-5663-43AB-BD91-FD80502BDEF0} = {3D5D91B8-5663-43AB-BD91-FD80502BDEF0} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatboxSender", "Sample\ChatboxSender\ChatboxSender.csproj", "{1BF37DAA-23F4-4321-B709-6D1EAD45D10E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -93,6 +95,18 @@ Global {E3589FA0-A1ED-4986-B51F-7D8270A4656E}.Release|For Test.ActiveCfg = Release|Any CPU {E3589FA0-A1ED-4986-B51F-7D8270A4656E}.Release|Full Build.ActiveCfg = Release|Any CPU {E3589FA0-A1ED-4986-B51F-7D8270A4656E}.Release|Full Build.Build.0 = Release|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Debug|For Test.ActiveCfg = Debug|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Debug|For Test.Build.0 = Debug|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Debug|Full Build.ActiveCfg = Debug|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Debug|Full Build.Build.0 = Debug|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Release|Any CPU.Build.0 = Release|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Release|For Test.ActiveCfg = Release|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Release|For Test.Build.0 = Release|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Release|Full Build.ActiveCfg = Release|Any CPU + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E}.Release|Full Build.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -101,6 +115,7 @@ Global {5729DE08-F01E-4091-8031-3FAE649F7627} = {E2335844-9FA5-4574-B2CF-50E8C4C172C5} {B214E3A8-C61B-4921-94A6-73CAAB358A14} = {E2335844-9FA5-4574-B2CF-50E8C4C172C5} {E3589FA0-A1ED-4986-B51F-7D8270A4656E} = {E2335844-9FA5-4574-B2CF-50E8C4C172C5} + {1BF37DAA-23F4-4321-B709-6D1EAD45D10E} = {E2335844-9FA5-4574-B2CF-50E8C4C172C5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7ADDCAD0-5084-420D-96B4-54FD8F6A2FA5}