/// <summary> /// Упоминание в тексте целой роли с сервера /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <returns></returns> public static string MarkMentionsRole( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild ) { text = rMentionRole.Replace(text, m1 => { string roleID = m1.Groups[1].Value; // Role role = guild.roles.FirstOrDefault(t => t.id == roleID); if (role == null) { return(string.Format("<Role Unknown #{0}>", roleID)); } string nickColor = role.color.ToString("X"); nickColor = "#" + nickColor.PadLeft(6, '0'); var wait = GetWaitString(); waitDictionary[wait] = string.Format("<span class='role mention' style='color: {1};'>@{0}</span>", HttpUtility.HtmlEncode(role.name), nickColor ); return(wait); }); return(text); }
/// <summary> /// Спойлеры /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <param name="mentions">Список упоминаний, сделанных в сообщении</param> /// <param name="usedEmbedsUrls">Список использованных Url'ов в embed</param> /// <returns></returns> public static string MarkSpoilers( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild, List <EventMessageCreate.EventMessageCreate_Mention> mentions, HashSet <string> usedEmbedsUrls ) { text = rSpoilerMark.Replace(text, m1 => { var subBlock = RenderMarkdownBlockAsHTMLInnerBlock( m1.Groups[1].Value, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls ); var wait = GetWaitString(); waitDictionary[wait] = string.Format( "<span class='spoiler {1}'><span class='spoiler-content'>{0}</span></span>", subBlock, (chatOption.text_spoiler == 1) ? "spoiler-show" : "" ); return(wait); }); return(text); }
/// <summary> /// Жирный наклонный текст /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <param name="mentions">Список упоминаний, сделанных в сообщении</param> /// <param name="usedEmbedsUrls">Список использованных Url'ов в embed</param> /// <returns></returns> public static string MarkBoldItalic( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild, List <EventMessageCreate.EventMessageCreate_Mention> mentions, HashSet <string> usedEmbedsUrls ) { text = rBoldItalic.Replace(text, m1 => { var subBlock = RenderMarkdownBlockAsHTMLInnerBlock( m1.Groups[1].Value, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls ); var wait = GetWaitString(); waitDictionary[wait] = string.Format("<strong><em>{0}</em></strong>", subBlock); return(wait); }); return(text); }
/// <summary> /// Получаем сырой текст с маркдауном и превращаем его в HTML-код /// </summary> /// <param name="text">Многострочный сырой текст с маркдауном</param> /// <param name="chatOption"></param> /// <param name="mentions"></param> /// <param name="guildID"></param> /// <param name="usedEmbedsUrls"></param> /// <returns>HTML-код, который можно безопасно рендерить в чате</returns> public static string RenderMarkdownAsHTML( string text, ChatDrawOption chatOption, List <EventMessageCreate.EventMessageCreate_Mention> mentions, string guildID, HashSet <string> usedEmbedsUrls ) { // ReSharper disable once UseNullPropagation if (text == null) { return(null); } // hint: Цитаты в Дискорде ТОЛЬКО одноуровневые, поэтому парсер цитат нерекурсивный string result = ""; bool isInQuote = false; var currentQuoteHTML = ""; foreach (var line in text.Split("\n")) { var trimmedLine = line.TrimEnd('\r'); if ((trimmedLine.Length >= 2) && (trimmedLine.Substring(0, 2) == "> ")) { // Это кусок цитаты currentQuoteHTML += string.Format("<div class='line'>{0}</div>", RenderLineAsHTML(trimmedLine.Substring(2), chatOption, mentions, guildID, usedEmbedsUrls)); isInQuote = true; } else { if (isInQuote) { // hint: цитата может быть пустой, но это всё равно цитата, поэтому тут не просто // проверка на currentQuoteHTML != "" result += string.Format( "<div class='quote-block'><div class='quote-border'></div><div class='quote-content'>{0}</div></div>", currentQuoteHTML ); currentQuoteHTML = ""; isInQuote = false; } result += string.Format("<div class='line'>{0}</div>", RenderLineAsHTML(trimmedLine, chatOption, mentions, guildID, usedEmbedsUrls)); } } if (isInQuote) { // Текст заканчивается цитатой result += string.Format( "<div class='quote-block'><div class='quote-border'></div><div class='quote-content'>{0}</div></div>", currentQuoteHTML ); } return(result); }
public void MainTest( string rawMarkdown, string expectedHtml, ChatDrawOption chatOption, List<EventMessageCreate.EventMessageCreate_Mention> mentions, IEnumerable<string> usedEmbedUrl ) { if (chatOption == null) { chatOption = new ChatDrawOption(); } var usedEmbedUrlHash = (usedEmbedUrl != null) ? usedEmbedUrl.ToHashSet() : new HashSet<string>(); var renderedText = MessageMarkdownParser.RenderMarkdownAsHTML( rawMarkdown, chatOption, mentions, guildID, usedEmbedUrlHash ); IHtmlDocument document; { var configuration = Configuration.Default; var context = BrowsingContext.New(configuration); document = (IHtmlDocument) context.OpenAsync(res => res .Content(renderedText) .Address("http://localhost:5050/chat.cgi")).Result; } var realExpectedHtml = ""; { var r1 = new Regex("<em><strong>(.*?)</strong></em>", RegexOptions.Compiled); expectedHtml = r1.Replace(expectedHtml, (m) => m.Groups[1].Value.IndexOf("<", StringComparison.Ordinal) == -1 ? string.Format("<strong><em>{0}</em></strong>", m.Groups[1].Value) : m.Groups[0].Value); var r2 = new Regex("<u><em>(.*?)</em></u>", RegexOptions.Compiled); expectedHtml = r2.Replace(expectedHtml, (m) => m.Groups[1].Value.IndexOf("<", StringComparison.Ordinal) == -1 ? string.Format("<em><u>{0}</u></em>", m.Groups[1].Value) : m.Groups[0].Value); var configuration = Configuration.Default; var context = BrowsingContext.New(configuration); var document1 = (IHtmlDocument) context.OpenAsync(res => res .Content(expectedHtml) .Address("http://localhost:5050/chat.cgi")).Result; realExpectedHtml = document1.Body.InnerHtml; } var resultHTML = document.Body.InnerHtml; Assert.Equal(realExpectedHtml, resultHTML); }
/// <summary> /// Упоминание в сообщении конкретного человека /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <param name="mentions">Список упоминаний, сделанных в сообщении</param> /// <returns></returns> public static string MarkMentionsPeople( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild, List <EventMessageCreate.EventMessageCreate_Mention> mentions ) { // Упоминание человека text = rMentionNick.Replace(text, m1 => { string mentionID = m1.Groups[1].Value; if (mentions == null) { // Приколы дискорда return(string.Format("<Пользователь Unknown #{0}>", mentionID)); } EventMessageCreate.EventMessageCreate_Mention mention = mentions.FirstOrDefault(eMention => eMention.id == mentionID); if (mention == null) { return(string.Format("<Пользователь Unknown #{0}>", mentionID)); } // Выбираем наиболее приоритетную роль var nickColor = "inherit"; if ((guild.roles != null) && (mention.member?.roles != null) && (chatOption.message_mentions_style == 1)) { var mention_roles_local = guild.roles.Where(t => mention.member.roles.Contains(t.id)).ToList(); mention_roles_local.Sort((a, b) => b.position.CompareTo(a.position)); var role = mention_roles_local.Any() ? mention_roles_local.First() : null; if (role != null) { nickColor = role.color.ToString("X"); nickColor = "#" + nickColor.PadLeft(6, '0'); } } // var wait = GetWaitString(); waitDictionary[wait] = string.Format("<span class='user mention' {1}>@{0}</span>", HttpUtility.HtmlEncode(mention.member?.nick ?? mention.username), (chatOption.message_mentions_style == 1) ? string.Format(" style='color: {0};'", nickColor) : "" ); return(wait); }); return(text); }
/// <summary> /// Обработка разметки текста ОДНОЙ СТРОКИ. Без использования цитат /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="mentions">Список упоминаний, сделанных в сообщении</param> /// <param name="guildID">ID гильдии (сервера), в котором написано сообщение</param> /// <param name="usedEmbedsUrls">Список использованных Url'ов в embed</param> /// <returns></returns> private static string RenderLineAsHTML( string text, ChatDrawOption chatOption, List <EventMessageCreate.EventMessageCreate_Mention> mentions, string guildID, HashSet <string> usedEmbedsUrls ) { // У нас есть блоки, порождающие саб-блоки: // - цитаты (они уже обработаны, вложенных быть не может) // - спойлеры // Другие подтипы не порождают саб-блоки, даже удаление var guild = NKDiscordChatWidget.DiscordBot.Bot.guilds[guildID]; var waitDictionary = new Dictionary <string, string>(); var textWithWaiting = RenderMarkdownBlockAsHTMLInnerBlock(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls); // Меняем все wait'ы внутри текста на значения из словаря // hint: Мы делаем это в бесконечном цикле, потому что маркировки могут быть вложенными // в произвольном порядке while (true) { // hint: Это можно сделать другим способом: Обходить waitDictionary, если смена произошла, // то удалять включение в waitDictionary и делать это пока waitDictionary.Any(), // это решение более наглядно, но более затратно, потому что приходится перестраивать Dictionary bool u = false; foreach (var(wait, replace) in waitDictionary) { var textWithWaitingNew = textWithWaiting.Replace(wait, replace); if (textWithWaitingNew != textWithWaiting) { textWithWaiting = textWithWaitingNew; u = true; } } if (!u) { // Ни одной смены в цикле не было break; } } return(textWithWaiting); }
/// <summary> /// Format (no mark) /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <returns></returns> public static string MarkNoFormatting( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary ) { // TODO: double backtick text = rWithoutMark.Replace(text, m1 => { var wait = GetWaitString(); waitDictionary[wait] = string.Format("<span class='without-mark'>{0}</span>", HttpUtility.HtmlEncode(m1.Groups[1].Value)); return(wait); }); return(text); }
/// <summary> /// Emoji (картинки) /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <returns></returns> public static string MarkEmojiImages( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild ) { var thisGuildEmojis = new HashSet <string>(); foreach (var emoji in guild.emojis) { thisGuildEmojis.Add(emoji.id); } // Эмодзи внутри текста text = rEmojiWithinText.Replace(text, m1 => { string emojiID = m1.Groups[3].Value; bool isRelative = thisGuildEmojis.Contains(emojiID); int emojiShow = isRelative ? chatOption.emoji_relative : chatOption.emoji_stranger; if (emojiShow == 2) { return(":" + m1.Groups[2].Value + ":"); } var wait = GetWaitString(); var url = string.Format("https://cdn.discordapp.com/emojis/{0}.{1}", emojiID, (m1.Groups[1].Value == "a") ? "gif" : "png" ); waitDictionary[wait] = string.Format("<span class='emoji {2}'><img src='{0}' alt=':{1}:'></span>", HttpUtility.HtmlEncode(url), HttpUtility.HtmlEncode(m1.Groups[2].Value), (emojiShow == 1) ? "blur" : "" ); return(wait); }); return(text); }
private static void AddMessageReactionHTML( ICollection <string> reactionHTMLs, NKDiscordChatWidget.DiscordBot.Classes.EventMessageCreate.EventMessageCreate_Reaction reaction, int emojiShow, ChatDrawOption chatOption ) { if (reaction.emoji.id != null) { // Эмодзи из Дискорда (паки эмодзей с серверов) reactionHTMLs.Add(string.Format( "<div class='emoji {2}'><img src='{0}' alt=':{1}:'><span class='count'>{3}</span></div>", HttpUtility.HtmlEncode(reaction.emoji.URL), HttpUtility.HtmlEncode(reaction.emoji.name), (emojiShow == 1) ? "blur" : "", reaction.count )); } else { // Стандартные Unicode-эмодзи string emojiHtml; var emojiPack = chatOption.unicode_emoji_displaying; // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (emojiPack != EmojiPackType.StandardOS) { emojiHtml = AddMessageReactionHTMLWithEmojiPack(reaction, emojiPack); } else { emojiHtml = HttpUtility.HtmlEncode(reaction.emoji.name); } reactionHTMLs.Add(string.Format( "<div class='emoji {1}'>{0}<span class='count'>{2}</span></div>", emojiHtml, (emojiShow == 1) ? "blur" : "", reaction.count )); } }
/// <summary> /// Наклонный (курсивный) текст через звёздочки /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <param name="mentions">Список упоминаний, сделанных в сообщении</param> /// <param name="usedEmbedsUrls">Список использованных Url'ов в embed</param> /// <returns></returns> public static string MarkItalicViaAsterisk( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild, List <EventMessageCreate.EventMessageCreate_Mention> mentions, HashSet <string> usedEmbedsUrls ) { text = rSingleAsterisk.Replace(text, m1 => { var firstChar = m1.Groups[1].Value.Substring(0, 1); if (firstChar == " ") { return(m1.Groups[0].Value); } var lastChar = m1.Groups[1].Value.Substring(m1.Groups[1].Value.Length - 1); if (lastChar == " ") { return(m1.Groups[0].Value); } var subBlock = RenderMarkdownBlockAsHTMLInnerBlock( m1.Groups[1].Value, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls ); var wait = GetWaitString(); waitDictionary[wait] = string.Format("<em>{0}</em>", subBlock); return(wait); }); return(text); }
private static IEnumerable<object[]> GetInputs() { var result = new List<object[]>(); var inputs = new List<string[]>() { new[] { "<:st1:568685037868810269> <a:box1:663446227550994452> 😏", "<span class='emoji '><img src='https://cdn.discordapp.com/emojis/568685037868810269.png' alt=':st1:'></span> <span class='emoji '><img src='https://cdn.discordapp.com/emojis/663446227550994452.gif' alt=':box1:'></span> <span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f60f.svg' alt=':1f60f:'></span>", }, new[] { "😏 😼", "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f60f.svg' alt=':1f60f:'></span> <span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f63c.svg' alt=':1f63c:'></span>", }, new[] { "<@!428567095563780107>", "<span class='user mention' style='color: #F1C40F;'>@北風</span>", }, new[] { "<@&633965723764523028>", "<span class='role mention' style='color: #9B59B6;'>@Фиолетовый</span>", }, new[] { "<@!400000000000000000>", "<Пользователь Unknown #400000000000000000>", }, new[] { "<@&600000000000000000>", "<Role Unknown #600000000000000000>", }, }; var mentions = new List<EventMessageCreate.EventMessageCreate_Mention>(); // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator foreach (var member in NKDiscordChatWidget.DiscordBot.Bot.guilds[guildID].members) { mentions.Add(new EventMessageCreate.EventMessageCreate_Mention() { member = member, username = member.user.username, id = member.user.id, }); } var chatOption = new ChatDrawOption() { message_mentions_style = 1, }; foreach (var input in inputs) { foreach (var u1 in new[] {false, true}) { var prefix = u1 ? GetRandomWord() + " " : ""; for (var i1 = 0; i1 < 3; i1++) { string postfix; switch (i1) { case 0: postfix = ""; break; case 1: postfix = " "; break; case 2: postfix = " " + GetRandomWord(); break; default: throw new Exception(); } var markdown = prefix + input[0] + postfix; var html = prefix + input[1] + postfix; result.Add(new object[] { markdown, "<div class='line'>" + html + "</div>", chatOption, mentions, null }); } } } foreach (var t1 in inputs) { result.AddRange(inputs.Select(t2 => new object[] { t1[0] + " " + t2[0], "<div class='line'>" + t1[1] + " " + t2[1] + "</div>", chatOption, mentions, null })); } return result; }
/// <summary> /// Обработка разметки сообщения внутри логического блока сообщения (root-сообщение, цитата, спойлер) /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="guild">Гильдия (сервер), внутри которого написано сообщение</param> /// <param name="mentions">Список упоминаний, сделанных в сообщении</param> /// <param name="usedEmbedsUrls">Список использованных Url'ов в embed</param> /// <returns></returns> private static string RenderMarkdownBlockAsHTMLInnerBlock( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, EventGuildCreate guild, List <EventMessageCreate.EventMessageCreate_Mention> mentions, HashSet <string> usedEmbedsUrls ) { while (true) { var highlights = new List <dynamic>(); foreach (var pair in new List <dynamic>() { new { regexp = rWithoutMark, deleg = (Func <string>)(() => MarkNoFormatting(text, chatOption, waitDictionary)) }, new { regexp = rLink, deleg = (Func <string>)(() => MarkLinks(text, chatOption, waitDictionary, usedEmbedsUrls)) }, new { regexp = rEmojiWithinText, deleg = (Func <string>)(() => MarkEmojiImages(text, chatOption, waitDictionary, guild)) }, // mentions new { regexp = rMentionNick, deleg = (Func <string>)(() => MarkMentionsPeople(text, chatOption, waitDictionary, guild, mentions)) }, new { regexp = rMentionRole, deleg = (Func <string>)(() => MarkMentionsRole(text, chatOption, waitDictionary, guild)) }, // simple mark new { regexp = rSpoilerMark, deleg = (Func <string>)(() => MarkSpoilers(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rBoldItalic, deleg = (Func <string>)(() => MarkBoldItalic(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rBold, deleg = (Func <string>)(() => MarkBold(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rTripleUnderscore, deleg = (Func <string>)(() => MarkUnderscoreItalic(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rDoubleUnderscore, deleg = (Func <string>)(() => MarkUnderscore(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rSingleAsterisk, deleg = (Func <string>)(() => MarkItalicViaAsterisk(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rItalicUnderscore, deleg = (Func <string>)(() => MarkItalicViaUnderscore(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, new { regexp = rDelete, deleg = (Func <string>)(() => MarkDelete(text, chatOption, waitDictionary, guild, mentions, usedEmbedsUrls)) }, }) { Regex regex = pair.regexp; var m = regex.Match(text); if (!m.Success) { continue; } highlights.Add(new { index = m.Index, pair.deleg }); if (m.Index == 0) { break; } } if (!highlights.Any()) { // Не осталось маркировки, выходим break; } highlights.Sort((a, b) => a.index.CompareTo(b.index)); var u = false; // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator foreach (var highlight in highlights) { Func <string> delegateLocal = highlight.deleg; var result = delegateLocal.Invoke(); if (result != text) { text = result; u = true; break; } } if (!u) { // hint: Это warning однозначный break; } } text = MarkEmojiUnicode(text, chatOption, waitDictionary); return(text); }
private static IEnumerable<object[]> GetLinksCases() { var result = new List<object[]>(); var chatOptionNotShort = new ChatDrawOption { short_anchor = 0, }; var chatOptionShort = new ChatDrawOption { short_anchor = 1, }; // ReSharper disable once LoopCanBeConvertedToQuery foreach (var input in new[] { new[] {"http://example.com/", "http://example.com/", "http://example.com/"}, new[] { "https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", "https://ru.wikipedia.org/wiki/Википедия:Введение", "https://ru.wikipedia.org/wiki/Википед...", }, }) { var markdown = input[0]; var html = string.Format("<a href='{0}' target='_blank'>{1}</a>", HttpUtility.HtmlEncode(input[0]), HttpUtility.HtmlEncode(input[1]) ); var htmlShortAnchor = string.Format("<a href='{0}' target='_blank'>{1}</a>", HttpUtility.HtmlEncode(input[0]), HttpUtility.HtmlEncode(input[2]) ); result.Add(new object[] { markdown, string.Format("<div class='line'>{0}</div>", html), chatOptionNotShort, null, null }); result.Add(new object[] { markdown, string.Format("<div class='line'>{0}</div>", htmlShortAnchor), chatOptionShort, null, null }); } // // ReSharper disable once LoopCanBeConvertedToQuery foreach (var input in new[] { new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", new List<string>() {"https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg"}, "", chatOptionShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", new List<string>() {"https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg"}, "", chatOptionNotShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", new List<string>(), "<a href='https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg' target='_blank'>https://cs7.pikabu.ru/post_img/big/20...</a>", chatOptionShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", new List<string>(), "<a href='https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg' target='_blank'>https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg</a>", chatOptionNotShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() {"https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg"}, " <a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википед...</a>", chatOptionShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() {"https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg"}, " <a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википедия:Введение</a>", chatOptionNotShort }, new dynamic[] { "https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() {"https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg"}, "<a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википед...</a>", chatOptionShort }, new dynamic[] { "https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() {"https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg"}, "<a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википедия:Введение</a>", chatOptionNotShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", "https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif" }, " <a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википед...</a>", chatOptionShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", "https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif" }, " <a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википедия:Введение</a>", chatOptionNotShort }, new dynamic[] { "https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", "https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif" }, "<a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википед...</a>", chatOptionShort }, new dynamic[] { "https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5", new List<string>() { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", "https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif" }, "<a href='https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D0%BA%D0%B8%D0%BF%D0%B5%D0%B4%D0%B8%D1%8F:%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5' target='_blank'>https://ru.wikipedia.org/wiki/Википедия:Введение</a>", chatOptionNotShort }, // new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif", new List<string>() { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", "https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif" }, " ", chatOptionShort }, new dynamic[] { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif", new List<string>() { "https://cs7.pikabu.ru/post_img/big/2018/10/31/8/1540989921187952325.jpg", "https://media.discordapp.net/attachments/421392740970921996/599311410807439390/terminator-thumbs-up.gif" }, " ", chatOptionNotShort }, }) { string markdown = input[0]; List<string> list = input[1]; string expectedHTML = input[2]; ChatDrawOption chatOption = input[3]; result.Add(new object[] { markdown, string.Format("<div class='line'>{0}</div>", expectedHTML), chatOption, null, list }); } return result; }
private static IEnumerable<object[]> GetEdgeCases() { var result = new List<object[]>(); // Тестируем Эмодзи: chatOption.emoji_relative & chatOption.emoji_stranger const string ourServerEmoji = "<a:box1:663446227550994452> <:st1:568685037868810269>"; const string otherServerEmoji = "<a:unk1:600000000000000000> <:unk2:500000000000000000>"; const string ourServerEmojiHTML = "<span class='emoji {0}'><img src='https://cdn.discordapp.com/emojis/663446227550994452.gif' alt=':box1:'></span> " + "<span class='emoji {0}'><img src='https://cdn.discordapp.com/emojis/568685037868810269.png' alt=':st1:'></span>"; const string otherServerEmojiHTML = "<span class='emoji {0}'><img src='https://cdn.discordapp.com/emojis/600000000000000000.gif' alt=':unk1:'></span> " + "<span class='emoji {0}'><img src='https://cdn.discordapp.com/emojis/500000000000000000.png' alt=':unk2:'></span>"; const string ourServerEmojiPlain = ":box1: :st1:"; const string otherServerEmojiPlain = ":unk1: :unk2:"; var mentions = new List<EventMessageCreate.EventMessageCreate_Mention>(); // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator foreach (var member in NKDiscordChatWidget.DiscordBot.Bot.guilds[guildID].members) { mentions.Add(new EventMessageCreate.EventMessageCreate_Mention() { member = member, username = member.user.username, id = member.user.id, }); } for (int i1 = 1; i1 < 4; i1++) { var markdown = ((i1 % 2 == 1) ? ourServerEmoji : "") + ((i1 >> 1 == 1) ? otherServerEmoji : ""); for (int i2 = 0; i2 < (1 << 4); i2++) { var chatOption = new ChatDrawOption { emoji_relative = i2 % 4, emoji_stranger = (i2 >> 2) % 4, }; if ((chatOption.emoji_relative == 3) || (chatOption.emoji_stranger == 3)) { continue; } var expectedHTML = ""; if (i1 % 2 == 1) { switch (chatOption.emoji_relative) { case 0: expectedHTML += string.Format(ourServerEmojiHTML, ""); break; case 1: expectedHTML += string.Format(ourServerEmojiHTML, "blur"); break; case 2: expectedHTML += ourServerEmojiPlain; break; } } if (i1 >> 1 == 1) { switch (chatOption.emoji_stranger) { case 0: expectedHTML += string.Format(otherServerEmojiHTML, ""); break; case 1: expectedHTML += string.Format(otherServerEmojiHTML, "blur"); break; case 2: expectedHTML += otherServerEmojiPlain; break; } } result.Add(new object[] { markdown, "<div class='line'>" + expectedHTML + "</div>", chatOption, null, null }); } } { var inputs = new dynamic[] { new { codes = new[] {0x1F346}, expected = "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f346.svg' alt=':1f346:'></span>" }, new { codes = new[] {0x1F47A}, expected = "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f47a.svg' alt=':1f47a:'></span>" }, new { codes = new[] {0x1F346, 0x1F47A}, expected = "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f346.svg' alt=':1f346:'></span>" + "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f47a.svg' alt=':1f47a:'></span>" }, new { codes = new[] {0x1F1F7, 0x1F1FA}, expected = "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f1f7-1f1fa.svg' alt=':1f1f7-1f1fa:'></span>" }, new { codes = new[] {0x31, 0xfe0f, 0x20e3}, expected = (string) null, }, }; foreach (var input in inputs) { int[] codes = input.codes; string expectedTwemoji = input.expected; foreach (var emojiPack in new[] {EmojiPackType.Twemoji, EmojiPackType.StandardOS}) { var chatOption = new ChatDrawOption {unicode_emoji_displaying = emojiPack}; var emoji = codes.Aggregate("", (current, code) => current + Utf8ToUnicode.UnicodeCodeToString(code)); var expectedHTML = ""; // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (emojiPack) { case EmojiPackType.Twemoji: expectedHTML = expectedTwemoji ?? emoji; break; case EmojiPackType.StandardOS: expectedHTML = emoji; break; default: throw new ArgumentOutOfRangeException(); } result.Add(new object[] { emoji, "<div class='line'>" + expectedHTML + "</div>", chatOption, null, null }); } } } // text_spoiler { var word = GetRandomWord(); string markdown = string.Format("||{0}||", word); for (int i = 0; i < 2; i++) { var chatOption = new ChatDrawOption {text_spoiler = i}; var html = string.Format( "<div class='line'><span class='spoiler {0}'><span class='spoiler-content'>{1}</span></span></div>", (i == 1) ? "spoiler-show" : "", word ); result.Add(new object[] { markdown, html, chatOption, null, null }); } } // message_mentions_style { var inputs = new[] { new[] { "428567095563780107", "北風", "F1C40F", }, new[] { "568138249986375682", "NKDiscordChatWidget", "E74C3C", }, }; foreach (var input in inputs) { for (int i = 0; i < 2; i++) { string markdown = string.Format("<@!{0}>", input[0]); var chatOption = new ChatDrawOption {message_mentions_style = i}; var html = string.Format( "<div class='line'><span class='user mention'{0}>@{1}</span></div>", (i == 1) ? string.Format(" style='color: #{0};'", input[2]) : "", input[1] ); result.Add(new object[] { markdown, html, chatOption, mentions, null }); result.Add(new object[] { markdown, string.Format("<div class='line'><Пользователь Unknown #{0}></div>", input[0]), chatOption, null, null }); } } } return result; }
private static IEnumerable<object[]> GetQuoteCheck() { var result = new List<object[]>(); var inputs = new List<string[]>() { new[] { "<:st1:568685037868810269> <a:box1:663446227550994452> 😏", "<span class='emoji '><img src='https://cdn.discordapp.com/emojis/568685037868810269.png' alt=':st1:'></span> <span class='emoji '><img src='https://cdn.discordapp.com/emojis/663446227550994452.gif' alt=':box1:'></span> <span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f60f.svg' alt=':1f60f:'></span>", }, new[] { "😏 😼", "<span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f60f.svg' alt=':1f60f:'></span> <span class='emoji unicode-emoji '><img src='/images/emoji/twemoji/1f63c.svg' alt=':1f63c:'></span>", }, new[] { "<@!428567095563780107>", "<span class='user mention' style='color: #F1C40F;'>@北風</span>", }, new[] { "<@&633965723764523028>", "<span class='role mention' style='color: #9B59B6;'>@Фиолетовый</span>", }, }; inputs.AddRange(GetThreeAsteriskTestsRaw()); var mentions = new List<EventMessageCreate.EventMessageCreate_Mention>(); // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator foreach (var member in NKDiscordChatWidget.DiscordBot.Bot.guilds[guildID].members) { mentions.Add(new EventMessageCreate.EventMessageCreate_Mention() { member = member, username = member.user.username, id = member.user.id, }); } var chatOption = new ChatDrawOption() {message_mentions_style = 1}; for (int i = 0; i < Math.Pow(4, 4); i++) { var ars = new List<int>(); { int i1 = i; for (int j = 0; j < 4; j++) { int n = i1 % 4; ars.Add(n); i1 >>= 2; } } if (ars[0] == 0) { continue; } var inputMarkdown = ""; var outputHTML = ""; bool isInQuote = false; var outputHTMLQuote = ""; foreach (var ar in ars) { string localInputMarkdown, localOutputHTML; if (ar % 2 == 0) { localInputMarkdown = ""; localOutputHTML = ""; } else { var input = inputs[_rnd.Next(0, inputs.Count - 1)]; localInputMarkdown = input[0]; localOutputHTML = input[1]; } if ((ar == 2) || (ar == 3)) { inputMarkdown += "\n> " + localInputMarkdown; outputHTMLQuote += "<div class='line'>" + localOutputHTML + "</div>"; isInQuote = true; } else { inputMarkdown += "\n" + localInputMarkdown; if (isInQuote) { outputHTML += string.Format( "<div class='quote-block'><div class='quote-border'></div><div class='quote-content'>{0}</div></div>", outputHTMLQuote ); isInQuote = false; outputHTMLQuote = ""; } outputHTML += "<div class='line'>" + localOutputHTML + "</div>"; } } if (isInQuote) { outputHTML += string.Format( "<div class='quote-block'><div class='quote-border'></div><div class='quote-content'>{0}</div></div>", outputHTMLQuote ); } result.Add(new object[] { inputMarkdown.Substring(1), outputHTML, chatOption, mentions, null }); } return result; }
public static AnswerMessage DrawMessage(EventMessageCreate message, ChatDrawOption chatDrawOption) { string htmlContent = string.Format("<div class='content-message' data-id='{1}'>{0}</div>", DrawMessageContent(message, chatDrawOption), message.id); var timeUpdate = (message.edited_timestampAsDT != DateTime.MinValue) ? message.edited_timestampAsDT : message.timestampAsDT; /* * if (chatOption.merge_same_user_messages) * { * // Соединяем сообщения одного и того же человека * // @todo где-то здесь баг имплементации. Надо поправить * for (var j = i + 1; j < messages.Count; j++) * { * if (messages[j].author.id == messages[i].author.id) * { * var localTimeUpdate = (messages[j].edited_timestampAsDT != DateTime.MinValue) * ? messages[j].edited_timestampAsDT * : messages[j].timestampAsDT; * if (localTimeUpdate > timeUpdate) * { * timeUpdate = localTimeUpdate; * } * * htmlContent += string.Format("<div class='content-message' data-id='{1}'>{0}</div>", * DrawMessageContent(messages[j], chatOption), messages[j].id); * i = j; * } * } * } * */ string nickColor = "inherit"; if (message.member.roles.Any()) { var roles = NKDiscordChatWidget.DiscordBot.Bot.guilds[message.guild_id].roles.ToList(); roles.Sort((a, b) => b.position.CompareTo(a.position)); Role role = roles.FirstOrDefault(t => message.member.roles.Contains(t.id)); if (role != null) { nickColor = role.color.ToString("X"); nickColor = "#" + nickColor.PadLeft(6, '0'); } } string sha1hash; using (var hashA = SHA1.Create()) { byte[] data = hashA.ComputeHash(Encoding.UTF8.GetBytes(htmlContent)); sha1hash = data.Aggregate("", (current, c) => current + c.ToString("x2")); } var drawDateTime = message.timestampAsDT.AddMinutes(chatDrawOption.timezone); var html = string.Format( "<div class='user'><img src='{0}' alt='{1}'></div>" + "<div class='content'>" + "<div class='content-header'><span class='content-user' style='color: {4};'>{1}</span><span class='content-time'>{3:HH:mm:ss dd.MM.yyyy}</span></div>" + "{2}" + "</div><div style='clear: both;'></div><hr>", HttpUtility.HtmlEncode(message.author.avatarURL), HttpUtility.HtmlEncode( !string.IsNullOrEmpty(message.member.nick) ? message.member.nick : message.author.username ), htmlContent, drawDateTime, nickColor ); return(new AnswerMessage() { id = message.id, time = ((DateTimeOffset)message.timestampAsDT).ToUnixTimeMilliseconds() * 0.001d, time_update = ((DateTimeOffset)timeUpdate).ToUnixTimeMilliseconds() * 0.001d, html = html, hash = sha1hash, }); }
/// <summary> /// Парсинг ссылок внутри текста /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <param name="usedEmbedsUrls">Список использованных Url'ов в embed</param> /// <returns></returns> public static string MarkLinks( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary, HashSet <string> usedEmbedsUrls ) { var r = new Regex("(%[0-9a-f]{2,2})+", RegexOptions.Compiled | RegexOptions.IgnoreCase); text = rLink.Replace(text, m1 => { var wait = GetWaitString(); var url = string.Format("{0}://{1}{2}{3}", m1.Groups[2].Value, m1.Groups[3].Value, m1.Groups[4].Value, m1.Groups[5].Value ); if ((chatOption.hide_used_embed_links == 1) && (usedEmbedsUrls.Contains(url))) { return(m1.Groups[1].Value); } var rawPath = m1.Groups[4].Value; while (true) { var m = r.Match(rawPath); if (!m.Success) { break; } var s = m.Groups[0].Value; var bytes = new List <byte>(); while (s != "") { var hex = s.Substring(1, 2); s = s.Substring(3); var b = byte.Parse(hex, System.Globalization.NumberStyles.HexNumber); bytes.Add(b); } var s1 = rawPath.Substring(0, m.Index); var s2 = Encoding.UTF8.GetString(bytes.ToArray()); var s3 = rawPath.Substring(m.Index + m.Length); rawPath = s1 + s2 + s3; } var anchor = HttpUtility.HtmlEncode(string.Format("{0}://{1}{2}{3}", m1.Groups[2].Value, m1.Groups[3].Value, rawPath, m1.Groups[5].Value )); if ((chatOption.short_anchor == 1) && (anchor.Length > 40)) { anchor = anchor.Substring(0, 37) + "..."; } // TODO: Иногда стирать всю ссылку, потому что она просто для превью картинки/видео waitDictionary[wait] = string.Format( "<a href='{0}' target='_blank'>{1}</a>", HttpUtility.HtmlEncode(url), HttpUtility.HtmlEncode(anchor) ); return(m1.Groups[1].Value + wait); }); return(text); }
private static string DrawMessageContent(EventMessageCreate message, ChatDrawOption chatOption) { var usedEmbedsUrls = new HashSet <string>(); foreach (var embed in message.embeds) { usedEmbedsUrls.Add(embed.url); } var guildID = message.guild_id; var guild = NKDiscordChatWidget.DiscordBot.Bot.guilds[guildID]; var thisGuildEmojis = new HashSet <string>(guild.emojis.Select(emoji => emoji.id).ToList()); // Основной текст string directContentHTML = NKDiscordChatWidget.General.MessageMarkdownParser.RenderMarkdownAsHTML( message.content, chatOption, message.mentions, guildID, usedEmbedsUrls); bool containOnlyUnicodeAndSpace; { var rEmojiWithinText = new Regex(@"<\:(.+?)\:([0-9]+)>", RegexOptions.Compiled); long[] longs = { }; if (message.content != null) { longs = Utf8ToUnicode.ToUnicodeCode(rEmojiWithinText.Replace(message.content, "")); } containOnlyUnicodeAndSpace = Utf8ToUnicode.ContainOnlyUnicodeAndSpace(longs); } string html = string.Format("<div class='content-direct {1}'>{0}</div>", directContentHTML, containOnlyUnicodeAndSpace ? "only-emoji" : ""); // attachments if ((message.attachments != null) && message.attachments.Any() && (chatOption.attachments != 2)) { string attachmentHTML = ""; foreach (var attachment in message.attachments) { var extension = attachment.filename.Split('.').Last().ToLowerInvariant(); // ReSharper disable once SwitchStatementMissingSomeCases switch (extension) { case "mp4": case "webm": // TODO: Отображать размер видео attachmentHTML += string.Format( "<div class='attachment {1}'><div class='attachment-wrapper'><video><source src='{0}'></video></div></div>", HttpUtility.HtmlEncode(attachment.proxy_url), ((chatOption.attachments == 1) || attachment.IsSpoiler) ? "blur" : "" ); break; case "jpeg": case "jpg": case "bmp": case "gif": case "png": attachmentHTML += string.Format( "<div class='attachment {3}'><div class='attachment-wrapper'><img src='{0}' data-width='{1}' data-height='{2}'></div></div>", HttpUtility.HtmlEncode(attachment.proxy_url), attachment.width, attachment.height, ((chatOption.attachments == 1) || attachment.IsSpoiler) ? "blur" : "" ); break; } } html += string.Format("<div class='attachment-block'>{0}</div>", attachmentHTML); } // Preview if ((message.embeds != null) && message.embeds.Any() && (chatOption.link_preview != 2)) { string previewHTML = ""; foreach (var embed in message.embeds) { var subHTML = ""; if (embed.provider != null) { subHTML += string.Format("<div class='provider'>{0}</div>", HttpUtility.HtmlEncode(embed.provider.name)); } if (embed.author != null) { subHTML += string.Format("<div class='author'>{0}</div>", HttpUtility.HtmlEncode(embed.author.name)); } subHTML += string.Format("<div class='title'>{0}</div>", HttpUtility.HtmlEncode(embed.title)); if (embed.thumbnail != null) { subHTML += string.Format("<div class='preview'><img src='{0}' alt='{1}'></div>", HttpUtility.HtmlEncode(embed.thumbnail.proxy_url), HttpUtility.HtmlEncode("Превью для «" + embed.title + "»") ); } var nickColor = embed.color.ToString("X"); nickColor = "#" + nickColor.PadLeft(6, '0'); previewHTML += string.Format( "<div class='embed {2}'><div class='embed-pill' style='background-color: {1};'></div>" + "<div class='embed-content {3}'>{0}</div></div>", subHTML, nickColor, (chatOption.link_preview == 1) ? "blur" : "", string.IsNullOrEmpty(embed.title) ? "embed-content_no-title" : "" ); } html += string.Format("<div class='embed-block'>{0}</div>", previewHTML); } // Реакции if ( (message.reactions != null) && message.reactions.Any() && ((chatOption.message_relative_reaction != 2) || (chatOption.message_stranger_reaction != 2)) ) { // Реакции var reactionHTMLs = new List <string>(); foreach (var reaction in message.reactions) { bool isRelative = ((reaction.emoji.id == null) || thisGuildEmojis.Contains(reaction.emoji.id)); int emojiShow = isRelative ? chatOption.message_relative_reaction : chatOption.message_stranger_reaction; if (emojiShow == 2) // @todo убрать магическую константу { continue; } AddMessageReactionHTML( reactionHTMLs, reaction, emojiShow, chatOption ); } var s = reactionHTMLs.Aggregate("", (current, s1) => current + s1); html += string.Format("<div class='content-reaction'>{0}</div>", s); } return(html); }
/// <summary> /// Парсинг эмодзи из дополнительных plain'ов в Unicode /// </summary> /// <param name="text">Текст с сырым Markdown</param> /// <param name="chatOption">Опции чата, заданные стримером для виджета</param> /// <param name="waitDictionary">Dictionary для саб-блоков</param> /// <returns></returns> public static string MarkEmojiUnicode( string text, ChatDrawOption chatOption, Dictionary <string, string> waitDictionary ) { if (!UnicodeEmojiEngine.emojiList[chatOption.unicode_emoji_displaying].Any()) { return(text); } var activeEmoji = new List <long>(); var longs = Utf8ToUnicode.ToUnicodeCode(text); var textAfter = ""; var containEmoji = false; foreach (var code in longs) { if (!UnicodeEmojiEngine.IsInIntervalEmoji(code, chatOption.unicode_emoji_displaying)) { // Этот символ НЕ является unicode emoji if (activeEmoji.Any()) { // У нас непустой буффер emoji символов, надо их записать в строку var localEmojiList = UnicodeEmojiEngine.RenderEmojiAsStringList( chatOption.unicode_emoji_displaying, activeEmoji); textAfter += RenderEmojiStringListAsHtml( localEmojiList, chatOption.unicode_emoji_displaying, waitDictionary, chatOption.emoji_relative ); activeEmoji = new List <long>(); } textAfter += Utf8ToUnicode.UnicodeCodeToString(code); continue; } // Этот символ ЯВЛЯЕТСЯ unicode emoji containEmoji = true; activeEmoji.Add(code); } // foreach if (activeEmoji.Any()) { // У нас не пустой буффер emoji символов, надо их записать в строку var localEmojiList = UnicodeEmojiEngine.RenderEmojiAsStringList( chatOption.unicode_emoji_displaying, activeEmoji); textAfter += RenderEmojiStringListAsHtml( localEmojiList, chatOption.unicode_emoji_displaying, waitDictionary, chatOption.emoji_relative ); } if (containEmoji) { text = textAfter; } return(text); }