Author | SHA1 | Message | Date |
---|---|---|---|
|
ac9b575230 | Clean up Emoji validation | 7 years ago |
|
40ba298331 | Added emoji validation | 7 years ago |
@@ -11,4 +11,20 @@ | |||
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | |||
<PackageReference Include="System.Interactive.Async" Version="3.1.1" /> | |||
</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> |
@@ -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> | |||
/// A unicode emoji | |||
/// </summary> | |||
public class Emoji : IEmote | |||
public partial class Emoji : IEmote | |||
{ | |||
// 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> | |||
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; | |||
} | |||
@@ -11,6 +11,9 @@ | |||
</Content> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="Tests.Emotes.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<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.Rest/Discord.Net.Rest.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:")); | |||
} | |||
} | |||
} |