Add support for article dates in link previews.

fork-5.53.8
Greyson Parrelli 2020-08-26 16:03:52 -04:00 zatwierdzone przez Alan Evans
rodzic bfed03b7b5
commit dd8b9ff8fb
13 zmienionych plików z 124 dodań i 23 usunięć

Wyświetl plik

@ -23,6 +23,10 @@ import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import okhttp3.HttpUrl;
/**
@ -146,14 +150,24 @@ public class LinkPreviewView extends FrameLayout {
description.setVisibility(GONE);
}
String domain = null;
if (!Util.isEmpty(linkPreview.getUrl())) {
HttpUrl url = HttpUrl.parse(linkPreview.getUrl());
if (url != null) {
site.setText(url.topPrivateDomain());
site.setVisibility(VISIBLE);
} else {
site.setVisibility(GONE);
domain = url.topPrivateDomain();
}
}
if (domain != null && linkPreview.getDate() > 0) {
site.setText(getContext().getString(R.string.LinkPreviewView_domain_date, domain, formatDate(linkPreview.getDate())));
site.setVisibility(VISIBLE);
} else if (domain != null) {
site.setText(domain);
site.setVisibility(VISIBLE);
} else if (linkPreview.getDate() > 0) {
site.setText(formatDate(linkPreview.getDate()));
site.setVisibility(VISIBLE);
} else {
site.setVisibility(GONE);
}
@ -187,6 +201,11 @@ public class LinkPreviewView extends FrameLayout {
: R.string.LinkPreviewView_no_link_preview_available;
}
private static String formatDate(long date) {
DateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
return dateFormat.format(date);
}
public interface CloseClickedListener {
void onCloseClicked();
}

Wyświetl plik

@ -1118,7 +1118,7 @@ public class MmsDatabase extends MessageDatabase {
if (preview.getAttachmentId() != null) {
DatabaseAttachment attachment = attachmentIdMap.get(preview.getAttachmentId());
if (attachment != null) {
previews.add(new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), attachment));
previews.add(new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), preview.getDate(), attachment));
}
} else {
previews.add(preview);
@ -1526,7 +1526,7 @@ public class MmsDatabase extends MessageDatabase {
attachmentId = insertedAttachmentIds.get(preview.getThumbnail().get());
}
LinkPreview updatedPreview = new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), attachmentId);
LinkPreview updatedPreview = new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), preview.getDate(), attachmentId);
linkPreviewJson.put(new JSONObject(updatedPreview.serialize()));
} catch (JSONException | IOException e) {
Log.w(TAG, "Failed to serialize shared contact. Skipping it.", e);

Wyświetl plik

@ -1696,7 +1696,7 @@ public final class PushProcessMessageJob extends BaseJob {
boolean validDomain = url.isPresent() && LinkPreviewUtil.isValidPreviewUrl(url.get());
if (hasTitle && presentInBody && validDomain) {
LinkPreview linkPreview = new LinkPreview(url.get(), title.or(""), description.or(""), thumbnail);
LinkPreview linkPreview = new LinkPreview(url.get(), title.or(""), description.or(""), preview.getDate(), thumbnail);
linkPreviews.add(linkPreview);
} else {
Log.w(TAG, String.format("Discarding an invalid link preview. hasTitle: %b presentInBody: %b validDomain: %b", hasTitle, presentInBody, validDomain));

Wyświetl plik

@ -315,7 +315,7 @@ public abstract class PushSendJob extends SendJob {
List<Preview> getPreviewsFor(OutgoingMediaMessage mediaMessage) {
return Stream.of(mediaMessage.getLinkPreviews()).map(lp -> {
SignalServiceAttachment attachment = lp.getThumbnail().isPresent() ? getAttachmentPointerFor(lp.getThumbnail().get()) : null;
return new Preview(lp.getUrl(), lp.getTitle(), lp.getDescription(), Optional.fromNullable(attachment));
return new Preview(lp.getUrl(), lp.getTitle(), lp.getDescription(), lp.getDate(), Optional.fromNullable(attachment));
}).toList();
}

Wyświetl plik

@ -25,24 +25,29 @@ public class LinkPreview {
@JsonProperty
private final String description;
@JsonProperty
private final long date;
@JsonProperty
private final AttachmentId attachmentId;
@JsonIgnore
private final Optional<Attachment> thumbnail;
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull String description, @NonNull DatabaseAttachment thumbnail) {
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull String description, long date, @NonNull DatabaseAttachment thumbnail) {
this.url = url;
this.title = title;
this.description = description;
this.date = date;
this.thumbnail = Optional.of(thumbnail);
this.attachmentId = thumbnail.getAttachmentId();
}
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull String description, @NonNull Optional<Attachment> thumbnail) {
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull String description, long date, @NonNull Optional<Attachment> thumbnail) {
this.url = url;
this.title = title;
this.description = description;
this.date = date;
this.thumbnail = thumbnail;
this.attachmentId = null;
}
@ -50,11 +55,13 @@ public class LinkPreview {
public LinkPreview(@JsonProperty("url") @NonNull String url,
@JsonProperty("title") @NonNull String title,
@JsonProperty("description") @Nullable String description,
@JsonProperty("date") long date,
@JsonProperty("attachmentId") @Nullable AttachmentId attachmentId)
{
this.url = url;
this.title = title;
this.description = Optional.fromNullable(description).or("");
this.date = date;
this.attachmentId = attachmentId;
this.thumbnail = Optional.absent();
}
@ -71,6 +78,10 @@ public class LinkPreview {
return description;
}
public long getDate() {
return date;
}
public @NonNull Optional<Attachment> getThumbnail() {
return thumbnail;
}

Wyświetl plik

@ -106,7 +106,7 @@ public class LinkPreviewRepository {
}
if (!metadata.getImageUrl().isPresent()) {
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), metadata.getDescription().or(""), Optional.absent()));
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), metadata.getDescription().or(""), metadata.getDate(), Optional.absent()));
return;
}
@ -114,7 +114,7 @@ public class LinkPreviewRepository {
if (!metadata.getTitle().isPresent() && !attachment.isPresent()) {
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
} else {
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), metadata.getDescription().or(""), attachment));
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), metadata.getDescription().or(""), metadata.getDate(), attachment));
}
});
@ -153,13 +153,14 @@ public class LinkPreviewRepository {
Optional<String> title = openGraph.getTitle();
Optional<String> description = openGraph.getDescription();
Optional<String> imageUrl = openGraph.getImageUrl();
long date = openGraph.getDate();
if (imageUrl.isPresent() && !LinkPreviewUtil.isValidPreviewUrl(imageUrl.get())) {
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
imageUrl = Optional.absent();
}
callback.accept(new Metadata(title, description, imageUrl));
callback.accept(new Metadata(title, description, date, imageUrl));
}
});
@ -227,7 +228,7 @@ public class LinkPreviewRepository {
Optional<Attachment> thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.WEBP, MediaUtil.IMAGE_WEBP);
callback.onSuccess(new LinkPreview(packUrl, title, "", thumbnail));
callback.onSuccess(new LinkPreview(packUrl, title, "", 0, thumbnail));
} else {
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
}
@ -272,7 +273,7 @@ public class LinkPreviewRepository {
thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.WEBP, MediaUtil.IMAGE_WEBP);
}
callback.onSuccess(new LinkPreview(groupUrl, title, description, thumbnail));
callback.onSuccess(new LinkPreview(groupUrl, title, description, 0, thumbnail));
} else {
Log.i(TAG, "Group is not locally available for preview generation, fetching from server");
@ -289,7 +290,7 @@ public class LinkPreviewRepository {
if (bitmap != null) bitmap.recycle();
}
callback.onSuccess(new LinkPreview(groupUrl, joinInfo.getTitle(), description, thumbnail));
callback.onSuccess(new LinkPreview(groupUrl, joinInfo.getTitle(), description, 0, thumbnail));
}
} catch (ExecutionException | InterruptedException | IOException | VerificationFailedException e) {
Log.w(TAG, "Failed to fetch group link preview.", e);
@ -350,16 +351,18 @@ public class LinkPreviewRepository {
private static class Metadata {
private final Optional<String> title;
private final Optional<String> description;
private final long date;
private final Optional<String> imageUrl;
Metadata(Optional<String> title, Optional<String> description, Optional<String> imageUrl) {
Metadata(Optional<String> title, Optional<String> description, long date, Optional<String> imageUrl) {
this.title = title;
this.description = description;
this.date = date;
this.imageUrl = imageUrl;
}
static Metadata empty() {
return new Metadata(Optional.absent(), Optional.absent(), Optional.absent());
return new Metadata(Optional.absent(), Optional.absent(), 0, Optional.absent());
}
Optional<String> getTitle() {
@ -370,6 +373,10 @@ public class LinkPreviewRepository {
return description;
}
long getDate() {
return date;
}
Optional<String> getImageUrl() {
return imageUrl;
}

Wyświetl plik

@ -13,14 +13,19 @@ import android.text.util.Linkify;
import com.annimon.stream.Stream;
import com.google.android.collect.Sets;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.stickers.StickerUrl;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.OptionalUtil;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
@ -30,10 +35,13 @@ import okhttp3.HttpUrl;
public final class LinkPreviewUtil {
private static final String TAG = Log.tag(LinkPreviewUtil.class);
private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$");
private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$");
private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$");
private static final Pattern OPEN_GRAPH_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>");
private static final Pattern ARTICLE_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>");
private static final Pattern OPEN_GRAPH_CONTENT_PATTERN = Pattern.compile("content\\s*=\\s*\"([^\"]*)\"");
private static final Pattern TITLE_PATTERN = Pattern.compile("<\\s*title[^>]*>(.*)<\\s*/title[^>]*>");
private static final Pattern FAVICON_PATTERN = Pattern.compile("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>");
@ -112,7 +120,22 @@ public final class LinkPreviewUtil {
Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag);
if (contentMatcher.find() && contentMatcher.groupCount() > 0) {
String content = htmlDecoder.fromEncoded(contentMatcher.group(1));
openGraphTags.put(property, content);
openGraphTags.put(property.toLowerCase(), content);
}
}
}
Matcher articleMatcher = ARTICLE_TAG_PATTERN.matcher(html);
while (articleMatcher.find()) {
String tag = articleMatcher.group();
String property = articleMatcher.groupCount() > 0 ? articleMatcher.group(1) : null;
if (property != null) {
Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag);
if (contentMatcher.find() && contentMatcher.groupCount() > 0) {
String content = htmlDecoder.fromEncoded(contentMatcher.group(1));
openGraphTags.put(property.toLowerCase(), content);
}
}
}
@ -154,9 +177,13 @@ public final class LinkPreviewUtil {
private final @Nullable String htmlTitle;
private final @Nullable String faviconUrl;
private static final String KEY_TITLE = "title";
private static final String KEY_DESCRIPTION_URL = "description";
private static final String KEY_IMAGE_URL = "image";
private static final String KEY_TITLE = "title";
private static final String KEY_DESCRIPTION_URL = "description";
private static final String KEY_IMAGE_URL = "image";
private static final String KEY_PUBLISHED_TIME_1 = "published_time";
private static final String KEY_PUBLISHED_TIME_2 = "article:published_time";
private static final String KEY_MODIFIED_TIME_1 = "modified_time";
private static final String KEY_MODIFIED_TIME_2 = "article:modified_time";
public OpenGraph(@NonNull Map<String, String> values, @Nullable String htmlTitle, @Nullable String faviconUrl) {
this.values = values;
@ -172,9 +199,35 @@ public final class LinkPreviewUtil {
return OptionalUtil.absentIfEmpty(Util.getFirstNonEmpty(values.get(KEY_IMAGE_URL), faviconUrl));
}
public long getDate() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
return Stream.of(values.get(KEY_PUBLISHED_TIME_1),
values.get(KEY_PUBLISHED_TIME_2),
values.get(KEY_MODIFIED_TIME_1),
values.get(KEY_MODIFIED_TIME_2))
.map(dateString -> parseDate(format, dateString))
.filter(time -> time > 0)
.findFirst()
.orElse(0L);
}
public @NonNull Optional<String> getDescription() {
return OptionalUtil.absentIfEmpty(values.get(KEY_DESCRIPTION_URL));
}
private static long parseDate(DateFormat dateFormat, String dateString) {
if (Util.isEmpty(dateString)) {
return 0;
}
try {
return dateFormat.parse(dateString).getTime();
} catch (ParseException e) {
Log.w(TAG, "Failed to parse date.", e);
return 0;
}
}
}
public interface HtmlDecoder {

Wyświetl plik

@ -66,6 +66,7 @@
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:textColor="?linkpreview_secondary_text_color"
android:maxLines="2"
app:layout_constraintStart_toEndOf="@+id/linkpreview_thumbnail"
app:layout_constraintTop_toBottomOf="@+id/linkpreview_description"
tools:text="dailybugle.com" />

Wyświetl plik

@ -497,6 +497,7 @@
<!-- LinkPreviewView -->
<string name="LinkPreviewView_no_link_preview_available">No link preview available</string>
<string name="LinkPreviewView_this_group_link_is_not_active">This group link is not active</string>
<string name="LinkPreviewView_domain_date">%1$s · %2$s</string>
<!-- LinkPreviewRepository -->
<plurals name="LinkPreviewRepository_d_members">

Wyświetl plik

@ -629,6 +629,7 @@ public class SignalServiceMessageSender {
DataMessage.Preview.Builder previewBuilder = DataMessage.Preview.newBuilder();
previewBuilder.setTitle(preview.getTitle());
previewBuilder.setDescription(preview.getDescription());
previewBuilder.setDate(preview.getDate());
previewBuilder.setUrl(preview.getUrl());
if (preview.getImage().isPresent()) {

Wyświetl plik

@ -687,6 +687,7 @@ public final class SignalServiceContent {
results.add(new SignalServiceDataMessage.Preview(preview.getUrl(),
preview.getTitle(),
preview.getDescription(),
preview.getDate(),
Optional.fromNullable(attachment)));
}

Wyświetl plik

@ -413,12 +413,14 @@ public class SignalServiceDataMessage {
private final String url;
private final String title;
private final String description;
private final long date;
private final Optional<SignalServiceAttachment> image;
public Preview(String url, String title, String description, Optional<SignalServiceAttachment> image) {
public Preview(String url, String title, String description, long date, Optional<SignalServiceAttachment> image) {
this.url = url;
this.title = title;
this.description = description;
this.date = date;
this.image = image;
}
@ -434,6 +436,10 @@ public class SignalServiceDataMessage {
return description;
}
public long getDate() {
return date;
}
public Optional<SignalServiceAttachment> getImage() {
return image;
}

Wyświetl plik

@ -207,6 +207,7 @@ message DataMessage {
optional string title = 2;
optional AttachmentPointer image = 3;
optional string description = 4;
optional uint64 date = 5;
}
message Sticker {