From 0685cf4e515bc0d0c625f32e9194ab17913b2a00 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 8 Sep 2022 10:04:30 -0400 Subject: [PATCH] Add signal.me username support. --- .../securesms/util/CommunicationActions.java | 49 ++++++++++++------- .../securesms/util/SignalMeUtil.java | 32 ++++++++++-- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index 4698bdc3c1..4a847d6dd1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -41,8 +41,10 @@ import org.thoughtcrime.securesms.proxy.ProxyBottomSheetFragment; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; +import org.whispersystems.signalservice.api.push.ServiceId; import java.io.IOException; +import java.util.Optional; public class CommunicationActions { @@ -266,36 +268,49 @@ public class CommunicationActions { } /** - * If the url is a proxy link it will handle it. - * Otherwise returns false, indicating was not a proxy link. + * If the url is a signal.me link it will handle it. */ - public static boolean handlePotentialSignalMeUrl(@NonNull FragmentActivity activity, @NonNull String potentialUrl) { - String e164 = SignalMeUtil.parseE164FromLink(activity, potentialUrl); + public static void handlePotentialSignalMeUrl(@NonNull FragmentActivity activity, @NonNull String potentialUrl) { + String e164 = SignalMeUtil.parseE164FromLink(activity, potentialUrl); + String username = SignalMeUtil.parseUsernameFromLink(potentialUrl); - if (e164 != null) { + if (e164 != null || username != null) { SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(activity, 500, 500); SimpleTask.run(() -> { - Recipient recipient = Recipient.external(activity, e164); + Recipient recipient = Recipient.UNKNOWN; + if (e164 != null) { + recipient = Recipient.external(activity, e164); - if (!recipient.isRegistered() || !recipient.hasServiceId()) { - try { - ContactDiscovery.refresh(activity, recipient, false); - recipient = Recipient.resolved(recipient.getId()); - } catch (IOException e) { - Log.w(TAG, "[handlePotentialMeUrl] Failed to refresh directory for new contact."); + if (!recipient.isRegistered() || !recipient.hasServiceId()) { + try { + ContactDiscovery.refresh(activity, recipient, false); + recipient = Recipient.resolved(recipient.getId()); + } catch (IOException e) { + Log.w(TAG, "[handlePotentialSignalMeUrl] Failed to refresh directory for new contact."); + } + } + } else { + Optional serviceId = UsernameUtil.fetchAciForUsername(username); + if (serviceId.isPresent()) { + recipient = Recipient.externalUsername(serviceId.get(), username); } } return recipient; }, recipient -> { dialog.dismiss(); - startConversation(activity, recipient, null); - }); - return true; - } else { - return false; + if (recipient != Recipient.UNKNOWN) { + startConversation(activity, recipient, null); + } else if (username != null) { + new MaterialAlertDialogBuilder(activity) + .setTitle(R.string.ContactSelectionListFragment_username_not_found) + .setMessage(activity.getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, username)) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + }); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SignalMeUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SignalMeUtil.java index 69be8ec0c1..2892f46872 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SignalMeUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SignalMeUtil.java @@ -9,13 +9,17 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; class SignalMeUtil { - private static final String HOST = "signal.me"; - private static final Pattern HOST_PATTERN = Pattern.compile("^(https|sgnl)://" + HOST + "/#p/(\\+[0-9]+)$"); + private static final String HOST = "^(https|sgnl)://" + "signal\\.me"; + private static final Pattern E164_PATTERN = Pattern.compile(HOST + "/#p/(\\+[0-9]+)$"); + private static final Pattern USERNAME_PATTERN = Pattern.compile(HOST + "/#u/(.+)$"); /** * If this is a valid signal.me link and has a valid e164, it will return the e164. Otherwise, it will return null. @@ -25,7 +29,7 @@ class SignalMeUtil { return null; } - Matcher matcher = HOST_PATTERN.matcher(link); + Matcher matcher = E164_PATTERN.matcher(link); if (matcher.matches()) { String e164 = matcher.group(2); @@ -39,4 +43,26 @@ class SignalMeUtil { return null; } } + + /** + * If this is a valid signal.me link and has a valid username, it will return the username. Otherwise, it will return null. + */ + public static @Nullable String parseUsernameFromLink(@Nullable String link) { + if (Util.isEmpty(link)) { + return null; + } + + Matcher matcher = USERNAME_PATTERN.matcher(link); + + if (matcher.matches()) { + String username = matcher.group(2); + try { + return username == null || username.isEmpty() ? null : URLDecoder.decode(username, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + return null; + } + } else { + return null; + } + } }