| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
ac9b575230 | Clean up Emoji validation | 8 years ago |
|
|
40ba298331 | Added emoji validation | 8 years ago |
| @@ -11,4 +11,20 @@ | |||||
| <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | ||||
| <PackageReference Include="System.Interactive.Async" Version="3.1.1" /> | <PackageReference Include="System.Interactive.Async" Version="3.1.1" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | |||||
| <None Update="Entities\Emotes\Emoji.Codepoints.tt"> | |||||
| <Generator>TextTemplatingFileGenerator</Generator> | |||||
| <LastGenOutput>Emoji.Codepoints.cs</LastGenOutput> | |||||
| </None> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <Compile Update="Entities\Emotes\Emoji.Codepoints.cs"> | |||||
| <DesignTime>True</DesignTime> | |||||
| <AutoGen>True</AutoGen> | |||||
| <DependentUpon>Emoji.Codepoints.tt</DependentUpon> | |||||
| </Compile> | |||||
| </ItemGroup> | |||||
| </Project> | </Project> | ||||
| @@ -0,0 +1,59 @@ | |||||
| <#@ output extension=".cs" #> | |||||
| <#@ template language="C#" #> | |||||
| <#@ assembly name="System.Net.Http.dll" #> | |||||
| <#@ import namespace="System.Net.Http" #> | |||||
| <#@ assembly name="System.Core.dll" #> | |||||
| <#@ import namespace="System.Collections.Generic" #> | |||||
| <#@ import namespace="System.Linq" #> | |||||
| <# | |||||
| const string DataUrl = "http://www.unicode.org/Public/emoji/6.0/emoji-data.txt"; | |||||
| List<int> codepoints = new List<int>(); | |||||
| void FetchData() | |||||
| { | |||||
| var client = new HttpClient(); | |||||
| try | |||||
| { | |||||
| var response = client.GetAsync(DataUrl).GetAwaiter().GetResult(); | |||||
| response.EnsureSuccessStatusCode(); | |||||
| string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); | |||||
| foreach (var line in body.Split('\n')) | |||||
| { | |||||
| if (line.StartsWith("#")) continue; | |||||
| var split = line.Split(';'); | |||||
| if (split.Length < 1) continue; | |||||
| var code = split[0].Trim(); | |||||
| if (string.IsNullOrEmpty(code)) continue; | |||||
| var ranges = code.Split("..".ToCharArray()); | |||||
| if (ranges.Length == 3) | |||||
| { | |||||
| var lower = Convert.ToInt32(ranges[0], 16); | |||||
| var upper = Convert.ToInt32(ranges[2], 16); | |||||
| codepoints.AddRange(Enumerable.Range(lower, (upper-lower)+1)); | |||||
| } | |||||
| else | |||||
| { | |||||
| var point = Convert.ToInt32(code, 16); | |||||
| codepoints.Add(point); | |||||
| } | |||||
| } | |||||
| } | |||||
| catch | |||||
| { | |||||
| } | |||||
| } | |||||
| FetchData(); | |||||
| #> | |||||
| namespace Discord | |||||
| { | |||||
| public partial class Emoji | |||||
| { | |||||
| internal readonly int[] Codepoints = new int[] | |||||
| { | |||||
| <# foreach (var codepoint in codepoints) { #><#= codepoint #>, <# } #> | |||||
| }; | |||||
| } | |||||
| } | |||||
| @@ -1,9 +1,12 @@ | |||||
| namespace Discord | |||||
| using System; | |||||
| using System.Text; | |||||
| namespace Discord | |||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// A unicode emoji | /// A unicode emoji | ||||
| /// </summary> | /// </summary> | ||||
| public class Emoji : IEmote | |||||
| public partial class Emoji : IEmote | |||||
| { | { | ||||
| // TODO: need to constrain this to unicode-only emojis somehow | // TODO: need to constrain this to unicode-only emojis somehow | ||||
| @@ -20,6 +23,29 @@ | |||||
| /// <param name="unicode">The pure UTF-8 encoding of an emoji</param> | /// <param name="unicode">The pure UTF-8 encoding of an emoji</param> | ||||
| public Emoji(string unicode) | public Emoji(string unicode) | ||||
| { | { | ||||
| // NETStandard1.1 doesn't support UTF32 | |||||
| #if !NETSTANDARD1_1 | |||||
| byte[] utf32 = Encoding.UTF32.GetBytes(unicode); | |||||
| for (var i = 0; i < utf32.Length; i += 4) | |||||
| { | |||||
| int codepoint = BitConverter.ToInt32(utf32, i); | |||||
| bool any = false; | |||||
| for (var j = 0; j < Codepoints.Length; j++) | |||||
| { | |||||
| if (Codepoints[j] == codepoint) | |||||
| { | |||||
| any = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (any) continue; | |||||
| else | |||||
| throw new ArgumentException("One or more characters was not a valid Emoji", nameof(unicode)); | |||||
| } | |||||
| #endif | |||||
| Name = unicode; | Name = unicode; | ||||
| } | } | ||||
| @@ -10,6 +10,9 @@ | |||||
| <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
| </Content> | </Content> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | |||||
| <None Include="Tests.Emotes.cs" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="../../src/Discord.Net.Commands/Discord.Net.Commands.csproj" /> | <ProjectReference Include="../../src/Discord.Net.Commands/Discord.Net.Commands.csproj" /> | ||||
| <ProjectReference Include="../../src/Discord.Net.Core/Discord.Net.Core.csproj" /> | <ProjectReference Include="../../src/Discord.Net.Core/Discord.Net.Core.csproj" /> | ||||
| @@ -0,0 +1,54 @@ | |||||
| using System; | |||||
| using Xunit; | |||||
| namespace Discord | |||||
| { | |||||
| public class EmoteTests | |||||
| { | |||||
| const string Smiley = "\U0001F603"; | |||||
| const string Man = "\U0001F468"; | |||||
| const string Woman = "\U0001F469"; | |||||
| const string Girl = "\U0001F467"; | |||||
| const string Boy = "\U0001F466"; | |||||
| const string Join = "\u200D"; | |||||
| [Fact] | |||||
| public void Single_Emoji() | |||||
| { | |||||
| Assert.Equal(Smiley, new Emoji(Smiley).Name); | |||||
| Assert.Equal(Man, new Emoji(Man).Name); | |||||
| Assert.Equal(Woman, new Emoji(Woman).Name); | |||||
| Assert.Equal(Girl, new Emoji(Girl).Name); | |||||
| Assert.Equal(Boy, new Emoji(Boy).Name); | |||||
| } | |||||
| [Fact] | |||||
| public void Multipart_Emoji() | |||||
| { | |||||
| string family = string.Concat(Man, Join, Woman, Join, Girl, Join, Boy); | |||||
| Assert.Equal(family, new Emoji(family).Name); | |||||
| } | |||||
| [Fact] | |||||
| public void Emoji_Fail() | |||||
| { | |||||
| Assert.Throws<ArgumentException>(() => new Emoji("foxDab")); | |||||
| } | |||||
| [Fact] | |||||
| public void Emote() | |||||
| { | |||||
| Assert.Equal(true, Discord.Emote.TryParse("<:foxDab:280494667093508096>", out var emote)); | |||||
| Assert.NotNull(emote); | |||||
| Assert.Equal("foxDab", emote.Name); | |||||
| Assert.Equal(280494667093508096UL, emote.Id); | |||||
| Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1486945539974), emote.CreatedAt); | |||||
| } | |||||
| [Fact] | |||||
| public void Emote_Parse_Fail() | |||||
| { | |||||
| Assert.Equal(false, Discord.Emote.TryParse("", out _)); | |||||
| Assert.Equal(false, Discord.Emote.TryParse(":foxDab", out _)); | |||||
| Assert.Equal(false, Discord.Emote.TryParse(":foxDab:", out _)); | |||||
| Assert.Throws<ArgumentException>(() => Discord.Emote.Parse(":foxDab:")); | |||||
| } | |||||
| } | |||||
| } | |||||