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.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; | ||||
} | } | ||||
@@ -11,6 +11,9 @@ | |||||
</Content> | </Content> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<None Include="Tests.Emotes.cs" /> | |||||
</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" /> | ||||
<ProjectReference Include="../../src/Discord.Net.Rest/Discord.Net.Rest.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:")); | |||||
} | |||||
} | |||||
} |