123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- // Copyright 2015 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/ntp_snippets/remote/remote_suggestion.h"
- #include <limits>
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/values.h"
- #include "components/ntp_snippets/category.h"
- #include "components/ntp_snippets/remote/proto/ntp_snippets.pb.h"
- #include "components/ntp_snippets/time_serialization.h"
- namespace {
- // dict.Get() specialization for base::Time values
- bool GetTimeValue(const base::Value::Dict& dict,
- const std::string& key,
- base::Time* time) {
- const std::string* time_value = dict.FindString(key);
- if (!time_value) {
- return false;
- }
- return base::Time::FromString(time_value->c_str(), time);
- }
- // dict.Get() specialization for GURL values
- bool GetURLValue(const base::Value::Dict& dict,
- const std::string& key,
- GURL* url) {
- const std::string* spec = dict.FindString(key);
- if (!spec) {
- return false;
- }
- *url = GURL(*spec);
- return url->is_valid();
- }
- // dict.Get() specialization for std::string values
- bool GetStringValue(const base::Value::Dict& dict,
- const std::string& key,
- std::string* str) {
- const std::string* str_value = dict.FindString(key);
- if (!str_value) {
- return false;
- }
- *str = *str_value;
- return true;
- }
- } // namespace
- namespace ntp_snippets {
- const int kArticlesRemoteId = 1;
- static_assert(
- static_cast<int>(KnownCategories::ARTICLES) -
- static_cast<int>(KnownCategories::REMOTE_CATEGORIES_OFFSET) ==
- kArticlesRemoteId,
- "kArticlesRemoteId has a wrong value?!");
- RemoteSuggestion::RemoteSuggestion(const std::vector<std::string>& ids,
- int remote_category_id)
- : ids_(ids),
- score_(0),
- is_dismissed_(false),
- remote_category_id_(remote_category_id),
- rank_(std::numeric_limits<int>::max()),
- should_notify_(false),
- content_type_(ContentType::UNKNOWN) {}
- RemoteSuggestion::~RemoteSuggestion() = default;
- // static
- std::unique_ptr<RemoteSuggestion>
- RemoteSuggestion::CreateFromContentSuggestionsDictionary(
- const base::Value::Dict& dict,
- int remote_category_id,
- const base::Time& fetch_date) {
- const base::Value::List* ids = dict.FindList("ids");
- if (!ids) {
- return nullptr;
- }
- std::vector<std::string> parsed_ids;
- for (const base::Value& value : *ids) {
- if (!value.is_string()) {
- return nullptr;
- }
- parsed_ids.push_back(value.GetString());
- }
- if (parsed_ids.empty()) {
- return nullptr;
- }
- auto snippet = MakeUnique(parsed_ids, remote_category_id);
- snippet->fetch_date_ = fetch_date;
- if (!(GetStringValue(dict, "title", &snippet->title_) &&
- GetTimeValue(dict, "creationTime", &snippet->publish_date_) &&
- GetTimeValue(dict, "expirationTime", &snippet->expiry_date_) &&
- GetStringValue(dict, "attribution", &snippet->publisher_name_) &&
- GetURLValue(dict, "fullPageUrl", &snippet->url_))) {
- return nullptr;
- }
- // Optional fields.
- GetStringValue(dict, "snippet", &snippet->snippet_);
- GetURLValue(dict, "imageUrl", &snippet->salient_image_url_);
- GetURLValue(dict, "ampUrl", &snippet->amp_url_);
- // TODO(sfiera): also favicon URL.
- const base::Value* image_dominant_color_value =
- dict.Find("imageDominantColor");
- if (image_dominant_color_value) {
- // The field is defined as fixed32 in the proto (effectively 32 bits
- // unsigned int), however, JSON does not support unsigned types. As a result
- // the value is parsed as int if it fits and as double otherwise. Double can
- // hold 32 bits precisely.
- uint32_t image_dominant_color;
- if (image_dominant_color_value->is_int()) {
- image_dominant_color = image_dominant_color_value->GetInt();
- } else if (image_dominant_color_value->is_double()) {
- image_dominant_color =
- static_cast<uint32_t>(image_dominant_color_value->GetDouble());
- }
- snippet->image_dominant_color_ = image_dominant_color;
- }
- absl::optional<double> score = dict.FindDouble("score");
- if (score)
- snippet->score_ = *score;
- if (const base::Value::Dict* notification_info =
- dict.FindDict("notificationInfo")) {
- absl::optional<bool> should_notify =
- notification_info->FindBool("shouldNotify");
- if (should_notify) {
- snippet->should_notify_ = should_notify.value();
- if (snippet->should_notify_) {
- if (!GetTimeValue(*notification_info, "deadline",
- &snippet->notification_deadline_)) {
- snippet->notification_deadline_ = base::Time::Max();
- }
- }
- }
- }
- // In the JSON dictionary contentType is an optional field. The field
- // content_type_ of the class |RemoteSuggestion| is by default initialized to
- // ContentType::UNKNOWN.
- std::string content_type;
- if (GetStringValue(dict, "contentType", &content_type)) {
- if (content_type == "VIDEO") {
- snippet->content_type_ = ContentType::VIDEO;
- } else {
- // The supported values are: VIDEO, UNKNOWN. Therefore if the field is
- // present the value has to be "UNKNOWN" here.
- DCHECK_EQ(content_type, "UNKNOWN");
- snippet->content_type_ = ContentType::UNKNOWN;
- }
- }
- return snippet;
- }
- // static
- std::unique_ptr<RemoteSuggestion> RemoteSuggestion::CreateFromProto(
- const SnippetProto& proto) {
- // Need at least the id.
- if (proto.ids_size() == 0 || proto.ids(0).empty()) {
- return nullptr;
- }
- int remote_category_id = proto.has_remote_category_id()
- ? proto.remote_category_id()
- : kArticlesRemoteId;
- std::vector<std::string> ids(proto.ids().begin(), proto.ids().end());
- auto snippet = MakeUnique(ids, remote_category_id);
- snippet->title_ = proto.title();
- snippet->snippet_ = proto.snippet();
- snippet->salient_image_url_ = GURL(proto.salient_image_url());
- if (proto.has_image_dominant_color()) {
- snippet->image_dominant_color_ = proto.image_dominant_color();
- }
- snippet->publish_date_ = DeserializeTime(proto.publish_date());
- snippet->expiry_date_ = DeserializeTime(proto.expiry_date());
- snippet->score_ = proto.score();
- snippet->is_dismissed_ = proto.dismissed();
- if (!proto.has_source()) {
- DLOG(WARNING) << "No source found for article " << snippet->id();
- return nullptr;
- }
- GURL url(proto.source().url());
- if (!url.is_valid()) {
- // We must at least have a valid source URL.
- DLOG(WARNING) << "Invalid article url " << proto.source().url();
- return nullptr;
- }
- GURL amp_url;
- if (proto.source().has_amp_url()) {
- amp_url = GURL(proto.source().amp_url());
- DLOG_IF(WARNING, !amp_url.is_valid())
- << "Invalid AMP URL " << proto.source().amp_url();
- }
- snippet->url_ = url;
- snippet->publisher_name_ = proto.source().publisher_name();
- snippet->amp_url_ = amp_url;
- if (proto.has_fetch_date()) {
- snippet->fetch_date_ = DeserializeTime(proto.fetch_date());
- }
- if (proto.content_type() == SnippetProto_ContentType_VIDEO) {
- snippet->content_type_ = ContentType::VIDEO;
- }
- snippet->rank_ =
- proto.has_rank() ? proto.rank() : std::numeric_limits<int>::max();
- return snippet;
- }
- SnippetProto RemoteSuggestion::ToProto() const {
- SnippetProto result;
- for (const std::string& id : ids_) {
- result.add_ids(id);
- }
- if (!title_.empty()) {
- result.set_title(title_);
- }
- if (!snippet_.empty()) {
- result.set_snippet(snippet_);
- }
- if (salient_image_url_.is_valid()) {
- result.set_salient_image_url(salient_image_url_.spec());
- }
- if (image_dominant_color_.has_value()) {
- result.set_image_dominant_color(*image_dominant_color_);
- }
- if (!publish_date_.is_null()) {
- result.set_publish_date(SerializeTime(publish_date_));
- }
- if (!expiry_date_.is_null()) {
- result.set_expiry_date(SerializeTime(expiry_date_));
- }
- result.set_score(score_);
- result.set_dismissed(is_dismissed_);
- result.set_remote_category_id(remote_category_id_);
- SnippetSourceProto* source_proto = result.mutable_source();
- source_proto->set_url(url_.spec());
- if (!publisher_name_.empty()) {
- source_proto->set_publisher_name(publisher_name_);
- }
- if (amp_url_.is_valid()) {
- source_proto->set_amp_url(amp_url_.spec());
- }
- if (!fetch_date_.is_null()) {
- result.set_fetch_date(SerializeTime(fetch_date_));
- }
- if (content_type_ == ContentType::VIDEO) {
- result.set_content_type(SnippetProto_ContentType_VIDEO);
- }
- result.set_rank(rank_);
- return result;
- }
- ContentSuggestion RemoteSuggestion::ToContentSuggestion(
- Category category) const {
- GURL url = url_;
- bool use_amp = !amp_url_.is_empty();
- if (use_amp) {
- url = amp_url_;
- }
- ContentSuggestion suggestion(category, id(), url);
- // Set url for fetching favicons if it differs from the main url (domains of
- // AMP URLs sometimes failed to provide favicons).
- if (use_amp) {
- suggestion.set_url_with_favicon(url_);
- }
- suggestion.set_title(base::UTF8ToUTF16(title_));
- suggestion.set_snippet_text(base::UTF8ToUTF16(snippet_));
- suggestion.set_publish_date(publish_date_);
- suggestion.set_publisher_name(base::UTF8ToUTF16(publisher_name_));
- suggestion.set_score(score_);
- suggestion.set_salient_image_url(salient_image_url_);
- if (should_notify_) {
- NotificationExtra extra;
- extra.deadline = notification_deadline_;
- suggestion.set_notification_extra(
- std::make_unique<NotificationExtra>(extra));
- }
- suggestion.set_fetch_date(fetch_date_);
- if (content_type_ == ContentType::VIDEO) {
- suggestion.set_is_video_suggestion(true);
- }
- suggestion.set_optional_image_dominant_color(image_dominant_color_);
- return suggestion;
- }
- // static
- std::unique_ptr<RemoteSuggestion> RemoteSuggestion::MakeUnique(
- const std::vector<std::string>& ids,
- int remote_category_id) {
- return base::WrapUnique(new RemoteSuggestion(ids, remote_category_id));
- }
- } // namespace ntp_snippets
|