AutoComplete.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /*
  2. * Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "AutoComplete.h"
  7. #include "Settings.h"
  8. #include <AK/JsonArray.h>
  9. #include <AK/JsonObject.h>
  10. #include <AK/JsonParser.h>
  11. #include <LibURL/URL.h>
  12. namespace Ladybird {
  13. AutoComplete::AutoComplete(QWidget* parent)
  14. : QCompleter(parent)
  15. {
  16. m_tree_view = new QTreeView(parent);
  17. m_manager = new QNetworkAccessManager(this);
  18. m_auto_complete_model = new AutoCompleteModel(this);
  19. setCompletionMode(QCompleter::UnfilteredPopupCompletion);
  20. setModel(m_auto_complete_model);
  21. setPopup(m_tree_view);
  22. m_tree_view->setRootIsDecorated(false);
  23. m_tree_view->setHeaderHidden(true);
  24. connect(this, QOverload<QModelIndex const&>::of(&QCompleter::activated), this, [&](QModelIndex const& index) {
  25. emit activated(index);
  26. });
  27. connect(m_manager, &QNetworkAccessManager::finished, this, [&](QNetworkReply* reply) {
  28. auto result = got_network_response(reply);
  29. if (result.is_error())
  30. dbgln("AutoComplete::got_network_response: Error {}", result.error());
  31. });
  32. }
  33. ErrorOr<Vector<String>> AutoComplete::parse_google_autocomplete(Vector<JsonValue> const& json)
  34. {
  35. if (json.size() != 5)
  36. return Error::from_string_literal("Invalid JSON, expected 5 elements in array");
  37. if (!json[0].is_string())
  38. return Error::from_string_literal("Invalid JSON, expected first element to be a string");
  39. auto query = TRY(String::from_byte_string(json[0].as_string()));
  40. if (!json[1].is_array())
  41. return Error::from_string_literal("Invalid JSON, expected second element to be an array");
  42. auto suggestions_array = json[1].as_array().values();
  43. if (query != m_query)
  44. return Error::from_string_literal("Invalid JSON, query does not match");
  45. Vector<String> results;
  46. results.ensure_capacity(suggestions_array.size());
  47. for (auto& suggestion : suggestions_array)
  48. results.unchecked_append(MUST(String::from_byte_string(suggestion.as_string())));
  49. return results;
  50. }
  51. ErrorOr<Vector<String>> AutoComplete::parse_duckduckgo_autocomplete(Vector<JsonValue> const& json)
  52. {
  53. Vector<String> results;
  54. for (auto const& suggestion : json) {
  55. auto maybe_value = suggestion.as_object().get("phrase"sv);
  56. if (!maybe_value.has_value())
  57. continue;
  58. results.append(MUST(String::from_byte_string(maybe_value->as_string())));
  59. }
  60. return results;
  61. }
  62. ErrorOr<Vector<String>> AutoComplete::parse_yahoo_autocomplete(JsonObject const& json)
  63. {
  64. if (!json.get("q"sv).has_value() || !json.get("q"sv)->is_string())
  65. return Error::from_string_view("Invalid JSON, expected \"q\" to be a string"sv);
  66. auto query = TRY(String::from_byte_string(json.get("q"sv)->as_string()));
  67. if (!json.get("r"sv).has_value() || !json.get("r"sv)->is_array())
  68. return Error::from_string_view("Invalid JSON, expected \"r\" to be an object"sv);
  69. auto suggestions_object = json.get("r"sv)->as_array().values();
  70. if (query != m_query)
  71. return Error::from_string_literal("Invalid JSON, query does not match");
  72. Vector<String> results;
  73. results.ensure_capacity(suggestions_object.size());
  74. for (auto& suggestion_object : suggestions_object) {
  75. if (!suggestion_object.is_object())
  76. return Error::from_string_literal("Invalid JSON, expected value to be an object");
  77. auto suggestion = suggestion_object.as_object();
  78. if (!suggestion.get("k"sv).has_value() || !suggestion.get("k"sv)->is_string())
  79. return Error::from_string_view("Invalid JSON, expected \"k\" to be a string"sv);
  80. results.unchecked_append(MUST(String::from_byte_string(suggestion.get("k"sv)->as_string())));
  81. }
  82. return results;
  83. }
  84. ErrorOr<void> AutoComplete::got_network_response(QNetworkReply* reply)
  85. {
  86. if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError)
  87. return {};
  88. AK::JsonParser parser(ak_byte_string_from_qstring(reply->readAll()));
  89. auto json = TRY(parser.parse());
  90. auto engine_name = Settings::the()->autocomplete_engine().name;
  91. Vector<String> results;
  92. if (engine_name == "Google") {
  93. results = TRY(parse_google_autocomplete(json.as_array().values()));
  94. } else if (engine_name == "DuckDuckGo") {
  95. results = TRY(parse_duckduckgo_autocomplete(json.as_array().values()));
  96. } else if (engine_name == "Yahoo")
  97. results = TRY(parse_yahoo_autocomplete(json.as_object()));
  98. else {
  99. return Error::from_string_literal("Invalid engine name");
  100. }
  101. constexpr size_t MAX_AUTOCOMPLETE_RESULTS = 6;
  102. if (results.is_empty()) {
  103. results.append(m_query);
  104. } else if (results.size() > MAX_AUTOCOMPLETE_RESULTS) {
  105. results.shrink(MAX_AUTOCOMPLETE_RESULTS);
  106. }
  107. m_auto_complete_model->replace_suggestions(move(results));
  108. return {};
  109. }
  110. String AutoComplete::auto_complete_url_from_query(StringView query)
  111. {
  112. auto autocomplete_engine = ak_string_from_qstring(Settings::the()->autocomplete_engine().url);
  113. return MUST(autocomplete_engine.replace("{}"sv, URL::percent_encode(query), ReplaceMode::FirstOnly));
  114. }
  115. void AutoComplete::clear_suggestions()
  116. {
  117. m_auto_complete_model->clear();
  118. }
  119. void AutoComplete::get_search_suggestions(String search_string)
  120. {
  121. m_query = move(search_string);
  122. if (m_reply)
  123. m_reply->abort();
  124. QNetworkRequest request { QUrl(qstring_from_ak_string(auto_complete_url_from_query(m_query))) };
  125. m_reply = m_manager->get(request);
  126. }
  127. }