From ec2e6192d6cf01ab57326ac4cc9ffc99eae89f95 Mon Sep 17 00:00:00 2001
From: Andres Salomon <dilinger@queued.net>
Date: Sat, 12 Mar 2022 17:23:14 -0500
Subject: [PATCH] Display friendlier xmpp: links when a jid is in the roster

In jabber chats, there may be cases where an xmpp link is a valid
jid that's in an account's roster. For example, Cheogram's jmp.chat
service uses xmpp: links to support SMS group chat over jabber. In
jmp.chat's case, that ends up looking like a message coming from a
jid that has combined multiple phone numbers with the format
"+11231231234,+13213214321@cheogram.com", and message body beginning
with "<xmpp:+111231231234@cheogram.com> " or
"<xmpp:+13213214321@cheogram.com> " (depending on which phone number
the message is coming from).

Those jids aren't particularly user-friendly, so we'd instead like to
display the roster name while still keeping the underlying link
clickable. In order to do that, we modify Util.parse_add_markup_theme()
(since that's where the <a> html tag markup is created) to add a
callback function that takes a uri and returns what the <a> tag
should display. If the callback returns null, we just stick with the
default behavior of just displaying the link. However, if the uri
includes a jid that's in the user's roster, we return the name that's
in the roster. Util.parse_add_markup_theme() then uses that returned
name in the <a> tag.

A callback is used in order to keep the lookup generic, for future
uses. While the call to Util.parse_add_markup_theme() inside of
MessageItemWidget::generate_markup_text()'s non-muc code has a
callback that specifically only checks for xmpp: links, there's no
reason that it needs to be limited to that. It could handle any link;
for example, translating an http:// link to that same link but with
" (insecure)" appended to the end.
---
 .../message_widget.vala                       | 12 ++++++-
 main/src/ui/util/helper.vala                  | 31 ++++++++++++-------
 2 files changed, 31 insertions(+), 12 deletions(-)

--- a/main/src/ui/conversation_content_view/message_widget.vala
+++ b/main/src/ui/conversation_content_view/message_widget.vala
@@ -200,7 +200,17 @@ public class MessageItemWidget : SizeReq
         if (conversation.type_ == Conversation.Type.GROUPCHAT) {
             markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this), ref theme_dependent);
         } else {
-            markup_text = Util.parse_add_markup_theme(markup_text, null, true, true, true, Util.is_dark_theme(this), ref theme_dependent);
+            Util.LinkDisplay roster_lookup = (uri) => {
+                string? s = null;
+                if (GLib.Uri.parse_scheme(uri) == "xmpp") {
+                    try {
+                        Jid? j = new Jid(uri["xmpp:".length:uri.length]);
+                        s = Dino.get_real_display_name(stream_interactor, conversation.account, j);
+                    } catch (InvalidJidError e) { /* it's fine */ }
+                }
+                return s;
+            };
+            markup_text = Util.parse_add_markup_theme(markup_text, null, true, true, true, Util.is_dark_theme(this), ref theme_dependent, false, roster_lookup);
         }
 
         if (message.body.has_prefix("/me ")) {
--- a/main/src/ui/util/helper.vala
+++ b/main/src/ui/util/helper.vala
@@ -242,7 +242,9 @@ public static string parse_add_markup(st
     return parse_add_markup_theme(s_, highlight_word, parse_links, parse_text_markup, parse_text_markup, false, ref ignore_out_var);
 }
 
-public static string parse_add_markup_theme(string s_, string? highlight_word, bool parse_links, bool parse_text_markup, bool parse_quotes, bool dark_theme, ref bool theme_dependent, bool already_escaped_ = false) {
+public delegate string? LinkDisplay(string uri);
+
+public static string parse_add_markup_theme(string s_, string? highlight_word, bool parse_links, bool parse_text_markup, bool parse_quotes, bool dark_theme, ref bool theme_dependent, bool already_escaped_ = false, LinkDisplay? lookup = null) {
     string s = s_;
     bool already_escaped = already_escaped_;
 
@@ -258,9 +260,9 @@ public static string parse_add_markup_th
 
             theme_dependent = true;
             quote_match_info.fetch_pos(0, out start, out end);
-            return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, parse_quotes, dark_theme, ref theme_dependent, already_escaped) +
-                    @"<span color='$dim_color'>$gt " + parse_add_markup_theme(s[start + gt.length + 1:end], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) + "</span>" +
-                    parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, parse_quotes, dark_theme, ref theme_dependent, already_escaped);
+            return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, parse_quotes, dark_theme, ref theme_dependent, already_escaped, lookup) +
+                    @"<span color='$dim_color'>$gt " + parse_add_markup_theme(s[start + gt.length + 1:end], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup) + "</span>" +
+                    parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, parse_quotes, dark_theme, ref theme_dependent, already_escaped, lookup);
         }
     }
 
@@ -304,11 +306,18 @@ public static string parse_add_markup_th
                     }
                 }
 
-                return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) +
+                // Change how links are displayed?
+                string? disp_name = lookup != null ? lookup(link) : null;
+                if (disp_name == null) {
+                    // Default to the link name
+                    disp_name = link;
+                }
+
+                return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup) +
                         "<a href=\"" + Markup.escape_text(link) + "\">" +
-                        parse_add_markup_theme(link, highlight_word, false, false, false, dark_theme, ref theme_dependent, already_escaped) +
+                        parse_add_markup_theme(disp_name, highlight_word, false, false, false, dark_theme, ref theme_dependent, already_escaped, lookup) +
                         "</a>" +
-                        parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped);
+                        parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup);
             }
             match_info.next();
         }
@@ -327,9 +336,9 @@ public static string parse_add_markup_th
             if (match_info.matches()) {
                 int start, end;
                 match_info.fetch_pos(0, out start, out end);
-                return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) +
+                return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup) +
                     "<b>" + s[start:end] + "</b>" +
-                    parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped);
+                    parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup);
             }
         } catch (RegexError e) {
             assert_not_reached();
@@ -349,11 +358,11 @@ public static string parse_add_markup_th
                 if (match_info.matches()) {
                     int start, end;
                     match_info.fetch_pos(2, out start, out end);
-                    return parse_add_markup_theme(s[0:start-1], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) +
+                    return parse_add_markup_theme(s[0:start-1], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup) +
                         "<span color='#9E9E9E'>" +  s[start-1:start] + "</span>" +
                         @"<$(convenience_tag[i])>" + s[start:end] + @"</$(convenience_tag[i])>" +
                         "<span color='#9E9E9E'>" + s[end:end+1] + "</span>" +
-                        parse_add_markup_theme(s[end+1:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped);
+                        parse_add_markup_theme(s[end+1:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped, lookup);
                 }
             } catch (RegexError e) {
                 assert_not_reached();
