diff --git a/.gitignore b/.gitignore index 8632d44..889ebf7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ erl_crash.dump # Ignore package tarball (built via "mix hex.build"). data_for_seo-*.tar +.idea/ +.DS_Store +test/.DS_Store diff --git a/lib/data_for_seo/api/endpoint_handler.ex b/lib/data_for_seo/api/endpoint_handler.ex new file mode 100644 index 0000000..6587c89 --- /dev/null +++ b/lib/data_for_seo/api/endpoint_handler.ex @@ -0,0 +1,109 @@ +defmodule DataForSeo.API.EndpointHandler do + @moduledoc """ + Basic module with some shared functionality. + """ + + defmacro __using__(_) do + quote do + alias DataForSeo.Client + alias DataForSeo.Parser + + @type generic_response :: {:ok, any()} | {:error, any()} + + defp post_task(endpoint_url, payload) when is_map(payload) do + # wrap a single task + post_task(endpoint_url, [payload]) + end + + defp post_task(endpoint_url, payload) when is_list(payload) do + endpoint_url + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response() + |> case do + {:ok, resp} -> + {:ok, Parser.parse(resp, :task_post)} + + {:error, error} -> + {:error, error} + end + end + + defp get_live_task(endpoint_url, payload) when is_map(payload) do + # wrap a single task + get_live_task(endpoint_url, [payload]) + end + + defp get_live_task(endpoint_url, payload) when is_list(payload) do + endpoint_url + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response() + |> case do + {:ok, resp} -> + {:ok, Parser.parse(resp, :task_result)} + + {:error, error} -> + {:error, error} + end + end + + defp get_ready_tasks(endpoint_url) do + endpoint_url + |> Client.get() + |> Client.validate_status_code() + |> Client.decode_json_response() + |> case do + {:ok, resp} -> + {:ok, Parser.parse(resp, :tasks_ready)} + + {:error, error} -> + {:error, error} + end + end + + defp get_one_task(endpoint_url) do + endpoint_url + |> Client.get() + |> Client.validate_status_code() + |> Client.decode_json_response() + |> case do + {:ok, resp} -> + {:ok, Parser.parse(resp, :task_result)} + + {:error, error} -> + {:error, error} + end + end + + defp handle_response(resp), do: resp + + # Apply location to payload from params + # nil value skip adding + # otherwise it must be valid numberic location code or any valid location name + defp apply_location(attrs, nil), do: attrs + + defp apply_location(attrs, loc) do + # test location, if it's integer - we have location code + # we support code both: as int and string + case Integer.parse("#{loc}") do + :error -> Map.put(attrs, :location_name, loc) + {code, _} -> Map.put(attrs, :location_code, code) + end + end + + # Apply language to payload from params + # nil value skip adding + # otherwise it must be 2-char lang code or any valid language name + defp apply_language(attrs, nil), do: attrs + + defp apply_language(attrs, <>), + do: Map.put(attrs, :language_code, code) + + defp apply_language(attrs, lang_name), do: Map.put(attrs, :language_name, lang_name) + + defp apply_tag(attrs, nil), do: attrs + defp apply_tag(attrs, tag = <<_::binary-size(1), _::binary>>), do: Map.put(attrs, :tag, tag) + end + end +end diff --git a/lib/data_for_seo/api/keywords/google_ads/languages.ex b/lib/data_for_seo/api/keywords/google_ads/languages.ex new file mode 100644 index 0000000..af82d78 --- /dev/null +++ b/lib/data_for_seo/api/keywords/google_ads/languages.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.API.Keywords.GoogleAds.Languages do + @moduledoc """ + Provides API interfaces to Keyword Data API / Google Ads / Languages : + https://docs.dataforseo.com/v3/keywords_data/google_ads/languages/ + """ + + alias DataForSeo.Client + + @doc """ + Gets all languages for google ads + ## Examples + DataForSeo.API.Keywords.GoogleAds.Languages.get_all_languages() + """ + @spec get_all_languages(Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_languages(opts \\ []) do + Client.get("/v3/keywords_data/google_ads/languages") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/keywords/google_ads/locations.ex b/lib/data_for_seo/api/keywords/google_ads/locations.ex new file mode 100644 index 0000000..cf85828 --- /dev/null +++ b/lib/data_for_seo/api/keywords/google_ads/locations.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.API.Keywords.GoogleAds.Locations do + @moduledoc """ + Provides API interfaces to Keyword Data API / Google Ads / Locations : + https://docs.dataforseo.com/v3/keywords_data/google_ads/locations/ + """ + + alias DataForSeo.Client + + @doc """ + Gets all locations for Google Ads + ## Examples + DataForSeo.API.Keywords.GoogleAds.Locations.get_all_locations() + """ + @spec get_all_locations(Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_locations(opts \\ []) do + Client.get("/v3/keywords_data/google_ads/locations") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/keywords/google_trends/categories.ex b/lib/data_for_seo/api/keywords/google_trends/categories.ex new file mode 100644 index 0000000..bbe2574 --- /dev/null +++ b/lib/data_for_seo/api/keywords/google_trends/categories.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.API.Keywords.GoogleTrends.Categories do + @moduledoc """ + Provides API interfaces to Keyword Data API / Google Trends / Categories : + https://docs.dataforseo.com/v3/keywords_data/google_trends/categories/ + """ + + alias DataForSeo.Client + + @doc """ + Gets all categories for Google Trends + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Categories.get_all_categories() + """ + @spec get_all_categories(Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_categories(opts \\ []) do + Client.get("/v3/keywords_data/google_trends/categories") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/keywords/google_trends/explorer.ex b/lib/data_for_seo/api/keywords/google_trends/explorer.ex new file mode 100644 index 0000000..bc79fe6 --- /dev/null +++ b/lib/data_for_seo/api/keywords/google_trends/explorer.ex @@ -0,0 +1,74 @@ +defmodule DataForSeo.API.Keywords.GoogleTrends.Explorer do + @moduledoc """ + Provides API interfaces to Keyword Data API / Google Trends / Explore : + https://docs.dataforseo.com/v3/keywords_data/google_trends/explore/task_post/ + """ + + use DataForSeo.API.EndpointHandler + + @doc """ + This endpoint will provide you with the keyword popularity data from the ‘Explore’ feature of Google Trends. + You can check keyword trends for Google Search, Google News, Google Images, Google Shopping, and YouTube. + This is the Standard method of data retrieval. If you don’t need to receive data in real-time, + this method is the best option for you. Set a task and retrieve the results when our system collects them. + Execution time depends on the system workload. + + See https://docs.dataforseo.com/v3/keywords_data/google_trends/explore/task_post/ for available prameters. + + ## Examples + + ``` + DataForSeo.API.Keywords.GoogleTrends.Explorer.task_post(["seo api", "rank_api"], "Kyiv,Ukraine", "en", %{}) + ``` + """ + @spec task_post([String.t()], String.t() | nil, String.t() | nil, map()) :: generic_response() + def task_post(keywords, loc_code_or_name, lang_code_or_name, extra) do + payload = + %{keywords: keywords} + |> apply_location(loc_code_or_name) + |> apply_language(lang_code_or_name) + |> Map.merge(extra) + + post_task("/v3/keywords_data/google_trends/explore/task_post", payload) + end + + @doc """ + Gets result for a single task in a live mode: post it and retrieve result at the same time. + Read more: https://docs.dataforseo.com/v3/keywords_data/google_trends/explore/live/ + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Explorer.task_live("test-task-id") + """ + @spec task_live([String.t()], String.t() | nil, String.t() | nil, map()) :: generic_response() + def task_live(keywords, loc_code_or_name, lang_code_or_name, extra) do + payload = + %{keywords: keywords} + |> apply_location(loc_code_or_name) + |> apply_language(lang_code_or_name) + |> Map.merge(extra) + + get_live_task("/v3/keywords_data/google_trends/explore/live", payload) + end + + @doc """ + Gets the list of completed tasks ids. + Read more: https://docs.dataforseo.com/v3/keywords_data/google_trends/explore/tasks_ready/ + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Explorer.tasks_ready() + """ + @spec tasks_ready() :: generic_response() + def tasks_ready do + get_ready_tasks("/v3/keywords_data/google_trends/explore/tasks_ready") + end + + @doc """ + Gets result for a single task. + The fetched tasks are then removed from completed list. So be sure to save and process them. + Read more: https://docs.dataforseo.com/v3/keywords_data/google_trends/explore/task_get + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Explorer.task_get("test-task-id") + """ + @spec task_get(String.t()) :: generic_response() + def task_get(task_id) do + get_one_task("/v3/keywords_data/google_trends/explore/task_get/#{task_id}") + end +end diff --git a/lib/data_for_seo/api/keywords/google_trends/languages.ex b/lib/data_for_seo/api/keywords/google_trends/languages.ex new file mode 100644 index 0000000..4f295b6 --- /dev/null +++ b/lib/data_for_seo/api/keywords/google_trends/languages.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.API.Keywords.GoogleTrends.Languages do + @moduledoc """ + Provides API interfaces to Keyword Data API / Google Trends / Languages : + https://docs.dataforseo.com/v3/keywords_data/google_trends/languages/ + """ + + alias DataForSeo.Client + + @doc """ + Gets all languages for google trends + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Languages.get_all_languages() + """ + @spec get_all_languages(Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_languages(opts \\ []) do + Client.get("/v3/keywords_data/google_trends/languages") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/keywords/google_trends/locations.ex b/lib/data_for_seo/api/keywords/google_trends/locations.ex new file mode 100644 index 0000000..3256590 --- /dev/null +++ b/lib/data_for_seo/api/keywords/google_trends/locations.ex @@ -0,0 +1,46 @@ +defmodule DataForSeo.API.Keywords.GoogleTrends.Locations do + @moduledoc """ + Provides API interfaces to Keyword Data API / Google Trends / Locations : + https://docs.dataforseo.com/v3/keywords_data/google_trends/locations/ + """ + + alias DataForSeo.Client + + @doc """ + Gets all locations for google trends + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Locations.get_all_locations() + """ + @spec get_all_locations(Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_locations(opts \\ []) do + Client.get("/v3/keywords_data/google_trends/locations") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end + + @doc """ + Gets all locations for google trends + ## Examples + DataForSeo.API.Keywords.GoogleTrends.Locations.get_all_locations_by_country("ua") + """ + @spec get_all_locations_by_country(String.t(), Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_locations_by_country(country_code, opts \\ []) do + Client.get("/v3/keywords_data/google_trends/locations/#{country_code}") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/labs/google/categories.ex b/lib/data_for_seo/api/labs/google/categories.ex new file mode 100644 index 0000000..7240c3d --- /dev/null +++ b/lib/data_for_seo/api/labs/google/categories.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.API.Labs.Google.Categories do + @moduledoc """ + Provides API interfaces to Labs / Google / Categories API: + https://docs.dataforseo.com/v3/dataforseo_labs/categories_list/ + """ + + alias DataForSeo.Client + + @doc """ + Gets all locations by country for specific service: bing, google, youtube etc + ## Examples + DataForSeo.API.Labs.Google.Categories.get_all_categories() + """ + @spec get_all_categories(Keyword.t()) :: {:ok, map()} | {:error, term()} + def get_all_categories(opts \\ []) do + Client.get("/v3/dataforseo_labs/categories") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/labs/google/keyword_research.ex b/lib/data_for_seo/api/labs/google/keyword_research.ex new file mode 100644 index 0000000..64d0221 --- /dev/null +++ b/lib/data_for_seo/api/labs/google/keyword_research.ex @@ -0,0 +1,205 @@ +defmodule DataForSeo.API.Labs.Google.KeywordResearch do + @moduledoc """ + Access to Labs / Google / KeywordResearch API endpoints + """ + use DataForSeo.API.EndpointHandler + + @endpoints %{ + search_intent: "/v3/dataforseo_labs/google/search_intent/live", + keywords_for_site: "/v3/dataforseo_labs/google/keywords_for_site/live", + related_keywords: "/v3/dataforseo_labs/google/related_keywords/live", + keyword_ideas: "/v3/dataforseo_labs/google/keyword_ideas/live", + keyword_suggestions: "/v3/dataforseo_labs/google/keyword_suggestions/live" + } + + @doc """ + This endpoint will provide you with search intent data for up to 1,000 keywords. For each keyword that you specify + when setting a task, the API will return the keyword’s search intent and intent probability. + Besides the highest probable search intent, the results will also provide you with other likely search intent(s) + and their probability. + + Based on keyword data and search results data, our system has been trained to detect four types of search intent: + informational, navigational, commercial, transactional. + Read more: https://docs.dataforseo.com/v3/dataforseo_labs/google/search_intent/live/ + + ## Examples + DataForSeo.API.Labs.Google.KeywordResearch.search_intent(["audi a7", "milk store new york"], "en", nil) + + DataForSeo.API.Labs.Google.KeywordResearch.search_intent(["audi a7", "milk store new york"], "English", "uniq-tag") + """ + @spec search_intent([String.t()], String.t(), String.t() | nil, Keyword.t()) :: + {:ok, map()} | {:error, term()} + def search_intent(keywords, lang_name_or_code, tag, opts \\ []) do + payload = + %{keywords: keywords} + |> apply_language(lang_name_or_code) + |> apply_tag(tag) + |> List.wrap() + + @endpoints + |> Map.get(:search_intent) + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> handle_response() + end + + @doc """ + The Keywords For Site endpoint will provide you with a list of keywords relevant to the target domain. + Each keyword is supplied with relevant categories, search volume data for the last month, cost-per-click, + competition, and search volume trend values for the past 12 months. + Read more: https://docs.dataforseo.com/v3/dataforseo_labs/google/keywords_for_site/live/ + + ## Examples + DataForSeo.API.Labs.Google.KeywordResearch.keywords_for_site("apple.com", 2840, "en", %{}, nil) + + DataForSeo.API.Labs.Google.KeywordResearch.keywords_for_site("apple.com", "Uruguay", "English", %{}) + """ + @spec keywords_for_site( + String.t(), + String.t() | non_neg_integer(), + String.t(), + map(), + Keyword.t() + ) :: + {:ok, map()} | {:error, term()} + def keywords_for_site(target, loc_name_or_code, lang_name_or_code, optional_payload, opts \\ []) do + payload = + %{target: target} + |> apply_location(loc_name_or_code) + |> apply_language(lang_name_or_code) + |> Map.merge(optional_payload) + |> List.wrap() + + @endpoints + |> Map.get(:keywords_for_site) + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> handle_response() + end + + @doc """ + The Related Keywords endpoint provides keywords appearing in the "searches related to" SERP element + You can get up to 4680 keyword ideas by specifying the search depth. + Each related keyword comes with the list of relevant product categories, search volume rate for the last month, + search volume trend for the previous 12 months, as well as current cost-per-click and competition values. + Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, + clicks and CPC for each result. + Read more: https://docs.dataforseo.com/v3/dataforseo_labs/google/related_keywords/live/ + + ## Examples + DataForSeo.API.Labs.Google.KeywordResearch.related_keywords("apples", 2840, "en", %{}, nil) + + DataForSeo.API.Labs.Google.KeywordResearch.related_keywords("apples", "Uruguay", "English", %{}) + """ + @spec related_keywords( + String.t(), + String.t() | non_neg_integer(), + String.t(), + map(), + Keyword.t() + ) :: + {:ok, map()} | {:error, term()} + def related_keywords(keyword, loc_name_or_code, lang_name_or_code, optional_payload, opts \\ []) do + payload = + %{keyword: keyword} + |> apply_location(loc_name_or_code) + |> apply_language(lang_name_or_code) + |> Map.merge(optional_payload) + |> List.wrap() + + @endpoints + |> Map.get(:related_keywords) + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> handle_response() + end + + @doc """ + The Keyword Ideas endpoint provides search terms that are relevant to the product or service categories of the + specified keywords. + The algorithm selects the keywords which fall into the same categories as the seed keywords + specified in a POST array. + + + Read more: https://docs.dataforseo.com/v3/dataforseo_labs/google/keyword_ideas/live/ + + ## Examples + DataForSeo.API.Labs.Google.KeywordResearch.keyword_ideas("apples", 2840, "en", %{}, nil) + + DataForSeo.API.Labs.Google.KeywordResearch.keyword_ideas("apples", "Uruguay", "English", %{}) + """ + @spec keyword_ideas( + [String.t()], + String.t() | non_neg_integer(), + String.t(), + map(), + Keyword.t() + ) :: + {:ok, map()} | {:error, term()} + def keyword_ideas(keywords, loc_name_or_code, lang_name_or_code, optional_payload, opts \\ []) do + payload = + %{keywords: keywords} + |> apply_location(loc_name_or_code) + |> apply_language(lang_name_or_code) + |> Map.merge(optional_payload) + |> List.wrap() + + @endpoints + |> Map.get(:keyword_ideas) + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> handle_response() + end + + @doc """ + ### Keyword Suggestions + The Keyword Suggestions endpoint provides search queries that include the specified seed keyword. + The algorithm is based on the full-text search for the specified keyword and therefore returns + only those search terms that contain the keyword you set in the POST array with additional words before, after, + or within the specified key phrase. Returned keyword suggestions can contain the words from the specified key + phrase in a sequence different from the one you specify. + As a result, you will get a list of long-tail keywords with each keyword in the + list matching the specified search term. + + + Read more: https://docs.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live/ + + ## Examples + DataForSeo.API.Labs.Google.KeywordResearch.keyword_suggestions("apples", 2840, "en", %{}, nil) + + DataForSeo.API.Labs.Google.KeywordResearch.keyword_suggestions("apples", "Uruguay", "English", %{}) + """ + @spec keyword_suggestions( + String.t(), + String.t() | non_neg_integer(), + String.t(), + map(), + Keyword.t() + ) :: + {:ok, map()} | {:error, term()} + def keyword_suggestions( + keyword, + loc_name_or_code, + lang_name_or_code, + optional_payload, + opts \\ [] + ) do + payload = + %{keyword: keyword} + |> apply_location(loc_name_or_code) + |> apply_language(lang_name_or_code) + |> Map.merge(optional_payload) + |> List.wrap() + + @endpoints + |> Map.get(:keyword_suggestions) + |> Client.post(payload) + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> handle_response() + end +end diff --git a/lib/data_for_seo/api/serp/bing/organic.ex b/lib/data_for_seo/api/serp/bing/organic.ex new file mode 100644 index 0000000..d45375b --- /dev/null +++ b/lib/data_for_seo/api/serp/bing/organic.ex @@ -0,0 +1,25 @@ +defmodule DataForSeo.API.SERP.Bing.Organic do + @moduledoc """ + Provides SERP Bing API interfaces to organic search results. + """ + + use DataForSeo.API.EndpointHandler + + @doc "Post task" + def task_post(params) when is_list(params) do + post_task("/v3/serp/bing/organic/task_post", params) + end + + def task_post(params) when is_map(params), do: task_post([params]) + + @doc "Get ready tasks" + def tasks_ready do + get_ready_tasks("/v3/serp/bing/organic/tasks_ready") + end + + @doc "Get single task" + def task_get(task_id, opts \\ []) do + {type, _opts} = Keyword.pop(opts, :type, :regular) + get_one_task("/v3/serp/bing/organic/task_get/#{type}/#{task_id}") + end +end diff --git a/lib/data_for_seo/api/serp/google/location.ex b/lib/data_for_seo/api/serp/google/location.ex new file mode 100644 index 0000000..a7c5bc1 --- /dev/null +++ b/lib/data_for_seo/api/serp/google/location.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.API.SERP.Google.Location do + @moduledoc """ + Provides SERP Google Location API interfaces. + """ + + alias DataForSeo.Client + + @doc """ + Gets all locations by country for specific service: bing, google, youtube etc + ## Examples + DataForSeo.API.SERP.Google.Locations.get_location_by_service_and_country("us") + """ + @spec get_location_by_service_and_country(String.t(), String.t(), Keyword.t()) :: + {:ok, map()} | {:error, term()} + def get_location_by_service_and_country(service, country, opts \\ []) do + Client.get("/v3/serp/#{service}/locations/#{country}") + |> Client.validate_status_code() + |> Client.decode_json_response(opts) + |> case do + {:ok, resp} -> + {:ok, resp} + + {:error, error} -> + {:error, error} + end + end +end diff --git a/lib/data_for_seo/api/serp/google/organic.ex b/lib/data_for_seo/api/serp/google/organic.ex new file mode 100644 index 0000000..89d0a1e --- /dev/null +++ b/lib/data_for_seo/api/serp/google/organic.ex @@ -0,0 +1,10 @@ +defmodule DataForSeo.API.SERP.Google.Organic do + @moduledoc """ + Provides SERP Google API interfaces to organic search results + It's a wrapper b/c original lib had no namespaces and implementation is at `DataForSeo.API.Serp` + At the moment it's only import existing module but here is a room to improvements and keeping back compatibilities + at the same time. + """ + + import DataForSeo.API.Serp +end diff --git a/lib/data_for_seo/api/serp/youtube/organic.ex b/lib/data_for_seo/api/serp/youtube/organic.ex new file mode 100644 index 0000000..736dc22 --- /dev/null +++ b/lib/data_for_seo/api/serp/youtube/organic.ex @@ -0,0 +1,24 @@ +defmodule DataForSeo.API.SERP.Youtube.Organic do + @moduledoc """ + Provides SERP Youtube API interfaces to organic search results. + """ + + use DataForSeo.API.EndpointHandler + + @doc "Post task" + def task_post(params) when is_list(params) do + post_task("/v3/serp/youtube/organic/task_post", params) + end + + def task_post(params) when is_map(params), do: task_post([params]) + + @doc "Get ready tasks" + def tasks_ready do + get_ready_tasks("/v3/serp/youtube/organic/tasks_ready") + end + + @doc "Get single task" + def task_get(task_id, _opts \\ []) do + get_one_task("/v3/serp/youtube/organic/task_get/advanced/#{task_id}") + end +end diff --git a/lib/data_for_seo/client.ex b/lib/data_for_seo/client.ex index d2b22d4..50b07f2 100644 --- a/lib/data_for_seo/client.ex +++ b/lib/data_for_seo/client.ex @@ -134,19 +134,23 @@ defmodule DataForSeo.Client do defp execute_request(request, opts) do timeouts = timeout_options(opts) - - Finch.request(request, ApiFinch, timeouts) + config = Config.get_tuples() |> verify_config() + finch_name = opts[:finch_name] || config[:finch_name] || ApiFinch + Finch.request(request, finch_name, timeouts) end defp timeout_options(opts) do config = Config.get_tuples() |> verify_config() - receive_timeout = opts[:receive_timeout] || config[:receive_timeout] |> String.to_integer() - pool_timeout = opts[:pool_timeout] || config[:pool_timeout] |> String.to_integer() - - Keyword.new( - pool_timeout: pool_timeout, - receive_timeout: receive_timeout - ) + [:receive_timeout, :pool_timeout] + |> Enum.reduce(opts, fn key, acc -> + case opts[key] || config[key] do + nil -> acc + value -> Keyword.put(acc, key, to_integer(value)) + end + end) end + + defp to_integer(value) when is_binary(value), do: String.to_integer(value) + defp to_integer(value), do: value end diff --git a/lib/data_for_seo/data_model/category.ex b/lib/data_for_seo/data_model/category.ex new file mode 100644 index 0000000..1dcc217 --- /dev/null +++ b/lib/data_for_seo/data_model/category.ex @@ -0,0 +1,20 @@ +defmodule DataForSeo.DataModel.Category do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:category_code, :integer) + field(:category_name, :string) + field(:category_code_parent, :integer) + end + + @fields ~w( + category_code + category_name + category_code_parent + )a + + def changeset(:load, data) do + cast(%__MODULE__{}, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/entity.ex b/lib/data_for_seo/data_model/entity.ex new file mode 100644 index 0000000..b3766ee --- /dev/null +++ b/lib/data_for_seo/data_model/entity.ex @@ -0,0 +1,30 @@ +defmodule DataForSeo.DataModel.Entity do + defmacro __using__(_) do + quote do + use Ecto.Schema + import Ecto.Changeset + @type t :: %__MODULE__{} + + defp parse_utc_datetime_at_field(map, fields) when is_list(fields) do + Enum.reduce(fields, map, &parse_utc_datetime_at_field(&2, &1)) + end + + defp parse_utc_datetime_at_field(map, field) when is_binary(field) do + case Map.get(map, field) do + str when is_binary(str) -> Map.put(map, field, convert_utc_datetime(str)) + _ -> map + end + end + + defp convert_utc_datetime(nil), do: nil + + defp convert_utc_datetime(dt) when is_binary(dt) do + # May be it's alread ISO format? + case DateTime.from_iso8601(dt) do + {:ok, parsed, _offset} -> parsed + {:error, _} -> Timex.parse!(dt, "%F %T %:z", :strftime) + end + end + end + end +end diff --git a/lib/data_for_seo/data_model/generic/response.ex b/lib/data_for_seo/data_model/generic/response.ex new file mode 100644 index 0000000..2c311f5 --- /dev/null +++ b/lib/data_for_seo/data_model/generic/response.ex @@ -0,0 +1,32 @@ +defmodule DataForSeo.DataModel.Generic.Response do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Generic.Task + @primary_key false + embedded_schema do + field(:version, :string) + field(:status_code, :integer) + field(:status_message, :string) + + field(:time, :string) + field(:cost, :float) + field(:tasks_count, :integer) + field(:tasks_error, :integer) + embeds_many(:tasks, Task) + end + + @fields ~w( + version + status_code + status_message + time + cost + tasks_count + tasks_error + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:tasks) + end +end diff --git a/lib/data_for_seo/data_model/generic/task.ex b/lib/data_for_seo/data_model/generic/task.ex new file mode 100644 index 0000000..cf626cb --- /dev/null +++ b/lib/data_for_seo/data_model/generic/task.ex @@ -0,0 +1,32 @@ +defmodule DataForSeo.DataModel.Generic.Task do + use DataForSeo.DataModel.Entity + @primary_key false + embedded_schema do + field(:id, :string) + field(:status_code, :integer) + field(:status_message, :string) + + field(:time, :string) + field(:cost, :float) + field(:result_count, :integer) + field(:path, {:array, :string}) + field(:data, :map) + field(:result, {:array, :map}) + end + + @fields ~w( + id + status_code + status_message + time + cost + result_count + path + data + result + )a + def changeset(struct, data) do + struct + |> cast(data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/data_graph.ex b/lib/data_for_seo/data_model/keywords/google_trends/data_graph.ex new file mode 100644 index 0000000..788fa76 --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/data_graph.ex @@ -0,0 +1,23 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.DataGraph do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:date_from, :date) + field(:date_to, :date) + field(:timestamp, :integer) + field(:missing_data, :boolean) + field(:values, {:array, :integer}) + end + + @fields ~w( + date_from + date_to + timestamp + missing_data + values + )a + def changeset(%__MODULE__{} = item, data) do + cast(item, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/data_map.ex b/lib/data_for_seo/data_model/keywords/google_trends/data_map.ex new file mode 100644 index 0000000..235c41a --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/data_map.ex @@ -0,0 +1,21 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.DataMap do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:geo_id, :string) + field(:geo_name, :string) + field(:max_value_index, :integer) + field(:values, {:array, :integer}) + end + + @fields ~w( + geo_id + geo_name + max_value_index + values + )a + def changeset(%__MODULE__{} = item, data) do + cast(item, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/data_query.ex b/lib/data_for_seo/data_model/keywords/google_trends/data_query.ex new file mode 100644 index 0000000..c852c41 --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/data_query.ex @@ -0,0 +1,18 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.DataQuery do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataQueryNode + @primary_key false + embedded_schema do + embeds_many(:top, DataQueryNode) + embeds_many(:rising, DataQueryNode) + end + + @fields ~w( + )a + def changeset(%__MODULE__{} = item, data) do + item + |> cast(data, @fields) + |> cast_embed(:top) + |> cast_embed(:rising) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/data_query_node.ex b/lib/data_for_seo/data_model/keywords/google_trends/data_query_node.ex new file mode 100644 index 0000000..0d7226a --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/data_query_node.ex @@ -0,0 +1,17 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.DataQueryNode do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:query, :string) + field(:value, :integer) + end + + @fields ~w( + query + value + )a + def changeset(%__MODULE__{} = item, data) do + cast(item, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/data_topic.ex b/lib/data_for_seo/data_model/keywords/google_trends/data_topic.ex new file mode 100644 index 0000000..443be9c --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/data_topic.ex @@ -0,0 +1,18 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.DataTopic do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataTopicNode + @primary_key false + embedded_schema do + embeds_many(:top, DataTopicNode) + embeds_many(:rising, DataTopicNode) + end + + @fields ~w( + )a + def changeset(%__MODULE__{} = item, data) do + item + |> cast(data, @fields) + |> cast_embed(:top) + |> cast_embed(:rising) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/data_topic_node.ex b/lib/data_for_seo/data_model/keywords/google_trends/data_topic_node.ex new file mode 100644 index 0000000..eec46a3 --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/data_topic_node.ex @@ -0,0 +1,21 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.DataTopicNode do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:topic_id, :string) + field(:topic_title, :string) + field(:topic_type, :string) + field(:value, :integer) + end + + @fields ~w( + topic_id + topic_title + topic_type + value + )a + def changeset(%__MODULE__{} = item, data) do + cast(item, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/explorer_item.ex b/lib/data_for_seo/data_model/keywords/google_trends/explorer_item.ex new file mode 100644 index 0000000..3cce941 --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/explorer_item.ex @@ -0,0 +1,51 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.ExplorerItem do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataGraph + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataMap + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataQuery + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataTopic + @primary_key false + embedded_schema do + field(:type, :string) + field(:position, :integer) + field(:title, :string) + field(:keywords, {:array, :string}) + field(:averages, {:array, :integer}) + embeds_many(:graphs, DataGraph) + embeds_many(:maps, DataMap) + embeds_one(:queries_list, DataQuery) + embeds_one(:topics_list, DataTopic) + end + + @fields ~w( + type + position + title + keywords + averages + )a + def changeset(%__MODULE__{} = item, data) do + data = split_embedded_data(data) + + item + |> cast(data, @fields) + |> cast_embed(:graphs) + |> cast_embed(:maps) + |> cast_embed(:queries_list) + |> cast_embed(:topics_list) + end + + @split_config %{ + "google_trends_graph" => "graphs", + "google_trends_map" => "maps", + "google_trends_queries_list" => "queries_list", + "google_trends_topics_list" => "topics_list" + } + defp split_embedded_data(data) do + Map.put(data, @split_config[data["type"]], data["data"]) + end + + # assert length(grouped["google_trends_graph"]) == 1 + # assert length(grouped["google_trends_map"]) == 3 + # assert length(grouped["google_trends_queries_list"]) == 2 +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/explorer_result.ex b/lib/data_for_seo/data_model/keywords/google_trends/explorer_result.ex new file mode 100644 index 0000000..7b28bb1 --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/explorer_result.ex @@ -0,0 +1,43 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.ExplorerResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Keywords.GoogleTrends.ExplorerItem + @primary_key false + embedded_schema do + field(:keywords, {:array, :string}) + field(:type, :string) + + field(:location_code, :integer) + field(:language_code, :string) + + field(:check_url, :string) + field(:datetime, :utc_datetime) + + field(:items_count, :integer) + + embeds_many(:items, ExplorerItem) + end + + @fields ~w( + keywords + type + + location_code + language_code + + check_url + datetime + + items_count + )a + + def changeset(:load, data) do + data = + data + |> parse_utc_datetime_at_field("datetime") + |> Map.put("regular_items", data["items"]) + + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + end +end diff --git a/lib/data_for_seo/data_model/keywords/google_trends/task_ready_item.ex b/lib/data_for_seo/data_model/keywords/google_trends/task_ready_item.ex new file mode 100644 index 0000000..6dbe6f6 --- /dev/null +++ b/lib/data_for_seo/data_model/keywords/google_trends/task_ready_item.ex @@ -0,0 +1,26 @@ +defmodule DataForSeo.DataModel.Keywords.GoogleTrends.Explorer.TaskReadyItem do + use DataForSeo.DataModel.Entity + @primary_key false + embedded_schema do + field(:id, :string) + field(:se, :string) + field(:function, :string) + + field(:date_posted, :utc_datetime) + field(:tag, :string) + field(:endpoint, :string) + end + + @fields ~w( + id + se + function + tag + date_posted + endpoint + )a + def changeset(:load, data) do + data = parse_utc_datetime_at_field(data, "date_posted") + cast(%__MODULE__{}, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/avg_backlinks_info.ex b/lib/data_for_seo/data_model/labs/google/avg_backlinks_info.ex new file mode 100644 index 0000000..64212b8 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/avg_backlinks_info.ex @@ -0,0 +1,33 @@ +defmodule DataForSeo.DataModel.Labs.Google.AvgBackLinksInfo do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:backlinks, :float) + field(:dofollow, :float) + field(:referring_pages, :float) + field(:referring_domains, :float) + field(:referring_main_domains, :float) + field(:rank, :float) + field(:main_domain_rank, :float) + field(:last_updated_time, :utc_datetime) + end + + @fields ~w( + se_type + backlinks + dofollow + referring_pages + referring_domains + referring_main_domains + rank + main_domain_rank + last_updated_time + )a + + def changeset(struct, data) do + data = parse_utc_datetime_at_field(data, "last_updated_time") + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/bulk_keyword_difficuly_result.ex b/lib/data_for_seo/data_model/labs/google/bulk_keyword_difficuly_result.ex new file mode 100644 index 0000000..1b6224a --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/bulk_keyword_difficuly_result.ex @@ -0,0 +1,28 @@ +defmodule DataForSeo.DataModel.Labs.Google.BulkKeywordDifficultyResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordDifficulty + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:location_code, :integer) + field(:language_code, :string) + field(:items_count, :integer) + field(:total_count, :integer) + + embeds_many(:items, KeywordDifficulty) + end + + @fields ~w( + se_type + location_code + language_code + items_count + total_count + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_data.ex b/lib/data_for_seo/data_model/labs/google/keyword_data.ex new file mode 100644 index 0000000..38bcfbd --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_data.ex @@ -0,0 +1,40 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordData do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordInfo + alias DataForSeo.DataModel.Labs.Google.KeywordProperties + alias DataForSeo.DataModel.Labs.Google.KeywordImpressionsInfo + alias DataForSeo.DataModel.Labs.Google.KeywordSerpInfo + alias DataForSeo.DataModel.Labs.Google.AvgBackLinksInfo + alias DataForSeo.DataModel.Labs.Google.SearchIntentInfo + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:keyword, :string) + field(:location_code, :integer) + field(:language_code, :string) + embeds_one(:keyword_info, KeywordInfo) + embeds_one(:keyword_properties, KeywordProperties) + embeds_one(:impressions_info, KeywordImpressionsInfo) + embeds_one(:serp_info, KeywordSerpInfo) + embeds_one(:avg_backlinks_info, AvgBackLinksInfo) + embeds_one(:search_intent_info, SearchIntentInfo) + end + + @fields ~w( + se_type + keyword + location_code + language_code + )a + + def changeset(struct, data) do + struct + |> cast(data, @fields) + |> cast_embed(:keyword_info) + |> cast_embed(:keyword_properties) + |> cast_embed(:impressions_info) + |> cast_embed(:serp_info) + |> cast_embed(:avg_backlinks_info) + |> cast_embed(:search_intent_info) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_difficulty.ex b/lib/data_for_seo/data_model/labs/google/keyword_difficulty.ex new file mode 100644 index 0000000..e0de0d5 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_difficulty.ex @@ -0,0 +1,20 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordDifficulty do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:keyword, :string) + field(:keyword_difficulty, :integer) + end + + @fields ~w( + se_type + keyword + keyword_difficulty + )a + + def changeset(struct, data) do + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_ideas_result.ex b/lib/data_for_seo/data_model/labs/google/keyword_ideas_result.ex new file mode 100644 index 0000000..551a1a9 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_ideas_result.ex @@ -0,0 +1,34 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordsIdeasResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordData + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:seed_keywords, {:array, :string}) + field(:location_code, :integer) + field(:language_code, :string) + field(:total_count, :integer) + field(:items_count, :integer) + field(:offset, :integer) + field(:offset_token, :string) + + embeds_many(:items, KeywordData) + end + + @fields ~w( + se_type + seed_keywords + location_code + language_code + total_count + items_count + offset + offset_token + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_impressions_info.ex b/lib/data_for_seo/data_model/labs/google/keyword_impressions_info.ex new file mode 100644 index 0000000..78e245a --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_impressions_info.ex @@ -0,0 +1,55 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordImpressionsInfo do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:last_updated_time, :utc_datetime) + field(:match_type, :string) + field(:bid, :float) + + field(:ad_position_min, :float) + field(:ad_position_max, :float) + field(:ad_position_average, :float) + field(:cpc_min, :float) + field(:cpc_max, :float) + field(:cpc_average, :float) + + field(:daily_impressions_min, :float) + field(:daily_impressions_max, :float) + field(:daily_impressions_average, :float) + field(:daily_clicks_min, :float) + field(:daily_clicks_max, :float) + field(:daily_clicks_average, :float) + field(:daily_cost_min, :float) + field(:daily_cost_max, :float) + field(:daily_cost_average, :float) + end + + @fields ~w( + se_type + match_type + bid + ad_position_min + ad_position_max + ad_position_average + cpc_min + cpc_max + cpc_average + daily_impressions_min + daily_impressions_max + daily_impressions_average + daily_clicks_min + daily_clicks_max + daily_clicks_average + daily_cost_min + daily_cost_max + daily_cost_average + last_updated_time + )a + + def changeset(struct, data) do + data = parse_utc_datetime_at_field(data, "last_updated_time") + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_info.ex b/lib/data_for_seo/data_model/labs/google/keyword_info.ex new file mode 100644 index 0000000..1992276 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_info.ex @@ -0,0 +1,39 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordInfo do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.MonthlySearch + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:last_updated_time, :utc_datetime) + field(:competition, :float) + field(:competition_level, :string) + field(:cpc, :float) + field(:search_volume, :integer) + + field(:low_top_of_page_bid, :float) + field(:high_top_of_page_bid, :float) + field(:categories, {:array, :integer}) + embeds_many(:monthly_searches, MonthlySearch) + end + + @fields ~w( + se_type + last_updated_time + competition + competition_level + cpc + search_volume + low_top_of_page_bid + high_top_of_page_bid + categories + + )a + + def changeset(struct, data) do + data = parse_utc_datetime_at_field(data, "last_updated_time") + + struct + |> cast(data, @fields) + |> cast_embed(:monthly_searches) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_properties.ex b/lib/data_for_seo/data_model/labs/google/keyword_properties.ex new file mode 100644 index 0000000..8484647 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_properties.ex @@ -0,0 +1,26 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordProperties do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:core_keyword, :string) + field(:synonym_clustering_algorithm, :string) + field(:keyword_difficulty, :integer) + field(:detected_language, :string) + field(:is_another_language, :boolean) + end + + @fields ~w( + se_type + core_keyword + synonym_clustering_algorithm + keyword_difficulty + detected_language + is_another_language + )a + + def changeset(struct, data) do + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_serp_info.ex b/lib/data_for_seo/data_model/labs/google/keyword_serp_info.ex new file mode 100644 index 0000000..ba6017a --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_serp_info.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordSerpInfo do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:check_url, :string) + field(:serp_item_types, {:array, :string}) + field(:se_results_count, :integer) + field(:last_updated_time, :utc_datetime) + field(:previous_updated_time, :utc_datetime) + end + + @fields ~w( + se_type + check_url + serp_item_types + se_results_count + last_updated_time + previous_updated_time + )a + + def changeset(struct, data) do + data = parse_utc_datetime_at_field(data, ["last_updated_time", "previous_updated_time"]) + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keyword_suggestions_result.ex b/lib/data_for_seo/data_model/labs/google/keyword_suggestions_result.ex new file mode 100644 index 0000000..1f79f79 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keyword_suggestions_result.ex @@ -0,0 +1,36 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordsSuggestionsResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordData + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:seed_keyword, :string) + embeds_one(:seed_keyword_data, KeywordData) + field(:location_code, :integer) + field(:language_code, :string) + field(:total_count, :integer) + field(:items_count, :integer) + field(:offset, :integer) + field(:offset_token, :string) + + embeds_many(:items, KeywordData) + end + + @fields ~w( + se_type + seed_keyword + location_code + language_code + total_count + items_count + offset + offset_token + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + |> cast_embed(:seed_keyword_data) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/keywords_for_site_result.ex b/lib/data_for_seo/data_model/labs/google/keywords_for_site_result.ex new file mode 100644 index 0000000..1fd91cf --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/keywords_for_site_result.ex @@ -0,0 +1,34 @@ +defmodule DataForSeo.DataModel.Labs.Google.KeywordsForSiteResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordData + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:target, :string) + field(:location_code, :integer) + field(:language_code, :string) + field(:total_count, :integer) + field(:items_count, :integer) + field(:offset, :integer) + field(:offset_token, :string) + + embeds_many(:items, KeywordData) + end + + @fields ~w( + se_type + target + location_code + language_code + total_count + items_count + offset + offset_token + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/monthly_search.ex b/lib/data_for_seo/data_model/labs/google/monthly_search.ex new file mode 100644 index 0000000..5323b5a --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/monthly_search.ex @@ -0,0 +1,21 @@ +defmodule DataForSeo.DataModel.Labs.Google.MonthlySearch do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:year, :integer) + field(:month, :integer) + field(:search_volume, :integer) + end + + @fields ~w( + year + month + search_volume + + )a + + def changeset(struct, data) do + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/related_keyword_item.ex b/lib/data_for_seo/data_model/labs/google/related_keyword_item.ex new file mode 100644 index 0000000..d976beb --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/related_keyword_item.ex @@ -0,0 +1,24 @@ +defmodule DataForSeo.DataModel.Labs.Google.RelatedKeywordItem do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordData + @primary_key false + embedded_schema do + field(:se_type, :string) + embeds_one(:keyword_data, KeywordData) + field(:depth, :integer) + field(:related_keywords, {:array, :string}) + end + + @fields ~w( + se_type + depth + related_keywords + + )a + + def changeset(struct, data) do + struct + |> cast(data, @fields) + |> cast_embed(:keyword_data) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/related_keywords_result.ex b/lib/data_for_seo/data_model/labs/google/related_keywords_result.ex new file mode 100644 index 0000000..428d8cc --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/related_keywords_result.ex @@ -0,0 +1,33 @@ +defmodule DataForSeo.DataModel.Labs.Google.RelatedKeywordsResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.KeywordData + alias DataForSeo.DataModel.Labs.Google.RelatedKeywordItem + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:seed_keyword, :string) + embeds_one(:seed_keyword_data, KeywordData) + field(:location_code, :integer) + field(:language_code, :string) + field(:total_count, :integer) + field(:items_count, :integer) + + embeds_many(:items, RelatedKeywordItem) + end + + @fields ~w( + se_type + seed_keyword + location_code + language_code + total_count + items_count + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + |> cast_embed(:seed_keyword_data) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_for_keyword.ex b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_for_keyword.ex new file mode 100644 index 0000000..7a56564 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_for_keyword.ex @@ -0,0 +1,21 @@ +defmodule DataForSeo.DataModel.Labs.Google.SearchIntentForKeyword do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.SearchIntentItem + @primary_key false + embedded_schema do + field(:keyword, :string) + embeds_one(:keyword_intent, SearchIntentItem) + embeds_many(:secondary_keyword_intents, SearchIntentItem) + end + + @fields ~w( + keyword + )a + + def changeset(struct, data) do + struct + |> cast(data, @fields) + |> cast_embed(:keyword_intent) + |> cast_embed(:secondary_keyword_intents) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_info.ex b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_info.ex new file mode 100644 index 0000000..b775d79 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_info.ex @@ -0,0 +1,24 @@ +defmodule DataForSeo.DataModel.Labs.Google.SearchIntentInfo do + use DataForSeo.DataModel.Entity + @primary_key false + embedded_schema do + field(:se_type, :string) + field(:main_intent, :string) + field(:foreign_intent, {:array, :string}) + field(:last_updated_time, :utc_datetime) + end + + @fields ~w( + se_type + main_intent + foreign_intent + last_updated_time + )a + + def changeset(struct, data) do + data = parse_utc_datetime_at_field(data, "last_updated_time") + + struct + |> cast(data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_item.ex b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_item.ex new file mode 100644 index 0000000..089b409 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_item.ex @@ -0,0 +1,18 @@ +defmodule DataForSeo.DataModel.Labs.Google.SearchIntentItem do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:label, :string) + field(:probability, :float) + end + + @fields ~w( + label + probability + )a + + def changeset(struct, data) do + cast(struct, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_result.ex b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_result.ex new file mode 100644 index 0000000..52e6357 --- /dev/null +++ b/lib/data_for_seo/data_model/labs/google/search_intent/search_intent_result.ex @@ -0,0 +1,22 @@ +defmodule DataForSeo.DataModel.Labs.Google.SearchIntentResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.Labs.Google.SearchIntentForKeyword + @primary_key false + embedded_schema do + field(:language_code, :string) + field(:items_count, :integer) + + embeds_many(:items, SearchIntentForKeyword) + end + + @fields ~w( + language_code + items_count + )a + + def changeset(:load, data) do + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:items) + end +end diff --git a/lib/data_for_seo/data_model/language.ex b/lib/data_for_seo/data_model/language.ex new file mode 100644 index 0000000..7b32935 --- /dev/null +++ b/lib/data_for_seo/data_model/language.ex @@ -0,0 +1,18 @@ +defmodule DataForSeo.DataModel.Language do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:language_name, :string) + field(:language_code, :string) + end + + @fields ~w( + language_name + language_code + )a + + def changeset(:load, data) do + cast(%__MODULE__{}, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/location.ex b/lib/data_for_seo/data_model/location.ex new file mode 100644 index 0000000..d7dbdce --- /dev/null +++ b/lib/data_for_seo/data_model/location.ex @@ -0,0 +1,27 @@ +defmodule DataForSeo.DataModel.Location do + use DataForSeo.DataModel.Entity + @primary_key false + embedded_schema do + field(:location_code, :integer) + field(:location_name, :string) + field(:location_code_parent, :integer) + field(:country_iso_code, :string) + field(:location_type, :string) + field(:geo_name, :string) + field(:geo_id, :string) + end + + @fields ~w( + location_code + location_name + location_code_parent + country_iso_code + location_type + geo_name + geo_id + )a + + def changeset(:load, data) do + cast(%__MODULE__{}, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/serp/google/serp_item_regular.ex b/lib/data_for_seo/data_model/serp/google/serp_item_regular.ex new file mode 100644 index 0000000..5de2874 --- /dev/null +++ b/lib/data_for_seo/data_model/serp/google/serp_item_regular.ex @@ -0,0 +1,33 @@ +defmodule DataForSeo.DataModel.SERP.Google.SerpItemRegular do + use DataForSeo.DataModel.Entity + + @primary_key false + embedded_schema do + field(:type, :string) + + field(:rank_group, :integer) + field(:rank_absolute, :integer) + + field(:domain, :string) + field(:url, :string) + + field(:title, :string) + field(:description, :string) + field(:breadcrumb, :string) + end + + @fields ~w( + type + rank_group + rank_absolute + domain + url + title + description + breadcrumb + + )a + def changeset(%__MODULE__{} = item, data) do + cast(item, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/serp/google/serp_result.ex b/lib/data_for_seo/data_model/serp/google/serp_result.ex new file mode 100644 index 0000000..1e38a10 --- /dev/null +++ b/lib/data_for_seo/data_model/serp/google/serp_result.ex @@ -0,0 +1,46 @@ +defmodule DataForSeo.DataModel.SERP.Google.SerpResult do + use DataForSeo.DataModel.Entity + alias DataForSeo.DataModel.SERP.Google.SerpItemRegular + @primary_key false + embedded_schema do + field(:keyword, :string) + field(:type, :string) + field(:se_domain, :string) + field(:location_code, :integer) + + field(:language_code, :string) + field(:check_url, :string) + field(:datetime, :utc_datetime) + field(:spell, :string) + field(:item_types, {:array, :string}) + field(:se_results_count, :integer) + field(:items_count, :integer) + + embeds_many(:regular_items, SerpItemRegular) + end + + @fields ~w( + keyword + type + se_domain + location_code + language_code + check_url + datetime + spell + item_types + se_results_count + items_count + )a + + def changeset(:load_regular, data) do + data = + data + |> parse_utc_datetime_at_field("datetime") + |> Map.put("regular_items", data["items"]) + + %__MODULE__{} + |> cast(data, @fields) + |> cast_embed(:regular_items) + end +end diff --git a/lib/data_for_seo/data_model/serp/task_ready_item.ex b/lib/data_for_seo/data_model/serp/task_ready_item.ex new file mode 100644 index 0000000..0ee0022 --- /dev/null +++ b/lib/data_for_seo/data_model/serp/task_ready_item.ex @@ -0,0 +1,28 @@ +defmodule DataForSeo.DataModel.SERP.TaskReadyItem do + use DataForSeo.DataModel.Entity + @primary_key false + embedded_schema do + field(:id, :string) + field(:se, :string) + field(:se_type, :string) + + field(:date_posted, :utc_datetime) + field(:endpoint_regular, :string) + field(:endpoint_advanced, :string) + field(:endpoint_html, :string) + end + + @fields ~w( + id + se + se_type + date_posted + endpoint_regular + endpoint_advanced + endpoint_html + )a + def changeset(:load, data) do + data = parse_utc_datetime_at_field(data, "date_posted") + cast(%__MODULE__{}, data, @fields) + end +end diff --git a/lib/data_for_seo/data_model/transform/google_serp_translator.ex b/lib/data_for_seo/data_model/transform/google_serp_translator.ex new file mode 100644 index 0000000..726e886 --- /dev/null +++ b/lib/data_for_seo/data_model/transform/google_serp_translator.ex @@ -0,0 +1,23 @@ +defmodule DataForSeo.DataModel.Transform.GoogleSerpTranslator do + use DataForSeo.DataModel.Translator + + alias DataForSeo.DataModel.SERP.Google.SerpResult + alias DataForSeo.DataModel.SERP.TaskReadyItem + + @spec translate_organic_result(task_path(), input_result()) :: SerpResult.t() + def translate_organic_result(path, result) do + ["v3", "serp", "google", "organic" | path_tail] = path + + case path_tail do + ["tasks_ready"] -> + load_map_into(result, TaskReadyItem) + + ["task_post"] -> + nil + + ["task_get", "regular", _id] -> + load_map_into(result, SerpResult, :load_regular) + # ["v3", "serp", "google", "organic" | _] -> load_map_into(result, GoogleResult) + end + end +end diff --git a/lib/data_for_seo/data_model/transform/google_trends_translator.ex b/lib/data_for_seo/data_model/transform/google_trends_translator.ex new file mode 100644 index 0000000..45fd290 --- /dev/null +++ b/lib/data_for_seo/data_model/transform/google_trends_translator.ex @@ -0,0 +1,40 @@ +defmodule DataForSeo.DataModel.Transform.GoogleTrendsTranslator do + use DataForSeo.DataModel.Translator + + alias DataForSeo.DataModel.Keywords.GoogleTrends.ExplorerResult + alias DataForSeo.DataModel.Keywords.GoogleTrends.Explorer.TaskReadyItem + alias DataForSeo.DataModel.Category + alias DataForSeo.DataModel.Location + alias DataForSeo.DataModel.Language + + @spec translate_trends_result(task_path(), input_result()) :: SerpResult.t() + def translate_trends_result(["v3", "keywords_data", "google_trends" | path_tail], result) do + case path_tail do + ["categories"] -> + load_map_into(result, Category) + + ["locations"] -> + load_map_into(result, Location) + + ["languages"] -> + load_map_into(result, Language) + + ["explore", "tasks_ready"] -> + load_map_into(result, TaskReadyItem) + + ["explore", "task_post"] -> + nil + + ["explore", "task_get", _id] -> + load_map_into(result, ExplorerResult) + + ["explore", "live"] -> + load_map_into(result, ExplorerResult) + end + end + + # workaround, for some reasons API shows path different from another google trends paths + def translate_trends_result(["v3", "keywords_data", "google", "explore", "tasks_ready"], result) do + load_map_into(result, TaskReadyItem) + end +end diff --git a/lib/data_for_seo/data_model/transform/labs_google_translator.ex b/lib/data_for_seo/data_model/transform/labs_google_translator.ex new file mode 100644 index 0000000..52184b7 --- /dev/null +++ b/lib/data_for_seo/data_model/transform/labs_google_translator.ex @@ -0,0 +1,30 @@ +defmodule DataForSeo.DataModel.Transform.LabsGoogleTranslator do + use DataForSeo.DataModel.Translator + + alias DataForSeo.DataModel.Labs.Google.SearchIntentResult + alias DataForSeo.DataModel.Labs.Google.KeywordsForSiteResult + alias DataForSeo.DataModel.Labs.Google.KeywordsIdeasResult + alias DataForSeo.DataModel.Labs.Google.KeywordsSuggestionsResult + alias DataForSeo.DataModel.Labs.Google.RelatedKeywordsResult + alias DataForSeo.DataModel.Labs.Google.BulkKeywordDifficultyResult + alias DataForSeo.DataModel.Labs.Google.BulkKeywordDifficultyResult + alias DataForSeo.DataModel.Category + + @spec translate_google_result(task_path(), input_result()) :: SerpResult.t() + def translate_google_result(["v3", "dataforseo_labs", "google" | path_tail], result) do + case path_tail do + ["search_intent", "live"] -> load_map_into(result, SearchIntentResult) + ["keywords_for_site", "live"] -> load_map_into(result, KeywordsForSiteResult) + ["keyword_ideas", "live"] -> load_map_into(result, KeywordsIdeasResult) + ["keyword_suggestions", "live"] -> load_map_into(result, KeywordsSuggestionsResult) + ["related_keywords", "live"] -> load_map_into(result, RelatedKeywordsResult) + ["bulk_keyword_difficulty", "live"] -> load_map_into(result, BulkKeywordDifficultyResult) + end + end + + def translate_google_result(["v3", "dataforseo_labs" | path_tail], result) do + case path_tail do + ["categories"] -> load_map_into(result, Category) + end + end +end diff --git a/lib/data_for_seo/data_model/transform/response_translator.ex b/lib/data_for_seo/data_model/transform/response_translator.ex new file mode 100644 index 0000000..bf29312 --- /dev/null +++ b/lib/data_for_seo/data_model/transform/response_translator.ex @@ -0,0 +1,52 @@ +defmodule DataForSeo.DataModel.Transform.ResponseTranslator do + use DataForSeo.DataModel.Translator + alias DataForSeo.DataModel.Generic.Response + alias DataForSeo.DataModel.Generic.Task + alias Ecto.Changeset + + alias DataForSeo.DataModel.Transform.GoogleSerpTranslator + alias DataForSeo.DataModel.Transform.LabsGoogleTranslator + alias DataForSeo.DataModel.Transform.GoogleTrendsTranslator + + @spec load_response(map()) :: Response.t() + def load_response(data) do + :load + |> Response.changeset(data) + |> Changeset.apply_changes() + end + + @spec get_tasks(Response.t()) :: [Task.t()] + def get_tasks(%Response{tasks: tasks}), do: tasks + + @doc """ + Get result representation for specific task. It could a struct. If no struct defined it returns maps. + Splitting Result/Task and it's internal representation helps to avoid complicated logic with polymorph types + and use Ecto as an engine. + It returns result representation + """ + @spec translate_task_result(Task.t()) :: struct() | map() | [struct()] | [map()] + def translate_task_result(%Task{path: path, result: result}) do + case path do + ["v3", "serp", "google", "organic" | _] -> + GoogleSerpTranslator.translate_organic_result(path, result) + + ["v3", "dataforseo_labs" | _] -> + LabsGoogleTranslator.translate_google_result(path, result) + + ["v3", "keywords_data", "google", "explore", "tasks_ready"] -> + GoogleTrendsTranslator.translate_trends_result(path, result) + + ["v3", "keywords_data", "google_trends" | _] -> + GoogleTrendsTranslator.translate_trends_result(path, result) + end + end + + @doc """ + It's a wrapper to `translate_task_result/1` that put translated result back to the task internal field. + """ + @spec load_task_result(Task.t()) :: Task.t() + def load_task_result(task = %Task{}) do + translated_result = translate_task_result(task) + Map.put(task, :result, translated_result) + end +end diff --git a/lib/data_for_seo/data_model/translator.ex b/lib/data_for_seo/data_model/translator.ex new file mode 100644 index 0000000..406baf2 --- /dev/null +++ b/lib/data_for_seo/data_model/translator.ex @@ -0,0 +1,24 @@ +defmodule DataForSeo.DataModel.Translator do + defmacro __using__(_) do + quote do + alias Ecto.Changeset + @type input_result :: map() | [map()] | nil + @type task_path :: [String.t()] + defp load_map_into(list_of_maps, module, changeset_type \\ :load) + + defp load_map_into(list_of_maps, module, changeset_type) when is_list(list_of_maps) do + Enum.map(list_of_maps, &load_map_into(&1, module, changeset_type)) + end + + defp load_map_into(nil, _module, _changeset_type) do + nil + end + + defp load_map_into(map, module, changeset_type) do + changeset_type + |> module.changeset(map) + |> Changeset.apply_changes() + end + end + end +end diff --git a/lib/data_for_seo/test/response_factory.ex b/lib/data_for_seo/test/response_factory.ex new file mode 100644 index 0000000..254ee6e --- /dev/null +++ b/lib/data_for_seo/test/response_factory.ex @@ -0,0 +1,176 @@ +defmodule DataForSeo.Test.ResponseFactory do + # SERP + def task_post_serp_google_single_response do + get_raw_fixture(["serp", "google", "organic", "task-post-single"]) + end + + def task_post_serp_google_list_response do + get_raw_fixture(["serp", "google", "organic", "task-post"]) + end + + def tasks_ready_serp_google_response do + get_raw_fixture(["serp", "google", "organic", "tasks-ready"]) + end + + def task_result_serp_google_regular_response do + get_raw_fixture(["serp", "google", "organic", "task-get-regular"]) + end + + def task_post_serp_bing_single_response do + get_raw_fixture(["serp", "bing", "organic", "task-post-single"]) + end + + def task_post_serp_bing_list_response do + get_raw_fixture(["serp", "bing", "organic", "task-post"]) + end + + def tasks_ready_serp_bing_response do + get_raw_fixture(["serp", "bing", "organic", "tasks-ready"]) + end + + def task_result_serp_bing_regular_response do + get_raw_fixture(["serp", "bing", "organic", "task-get-regular"]) + end + + def task_post_serp_youtube_single_response do + get_raw_fixture(["serp", "youtube", "organic", "task-post-single"]) + end + + def task_post_serp_youtube_list_response do + get_raw_fixture(["serp", "youtube", "organic", "task-post"]) + end + + def tasks_ready_serp_youtube_response do + get_raw_fixture(["serp", "youtube", "organic", "tasks-ready"]) + end + + def task_result_serp_youtube_advanced_response do + get_raw_fixture(["serp", "youtube", "organic", "task-get-advanced"]) + end + + def task_get_serp_google_location_by_country(code) do + get_raw_fixture(["serp", "google", "locations-#{code}"]) + end + + # GOOGLE LAB + def task_get_labs_google_categories do + get_raw_fixture(["labs", "google", "categories"]) + end + + def task_get_labs_google_keywords_search_intent(payload) do + # probably it doesn't make sense and it'd nice to replace dynamic items to mock more complicated cases. + ["labs", "google", "keyword_research", "search-intent"] + |> get_json_fixture() + |> then(fn fixture -> + Enum.reduce(payload, fixture, fn {k, v}, acc -> + replace_in_fixture(acc, ["tasks", Access.all(), "data", k], v) + end) + end) + |> encode_fixture() + end + + def task_get_labs_google_keywords_for_site do + get_raw_fixture(["labs", "google", "keyword_research", "keywords-for-site"]) + end + + def task_get_labs_google_keywords_related do + get_raw_fixture(["labs", "google", "keyword_research", "related-keywords"]) + end + + def task_get_labs_google_keywords_ideas do + get_raw_fixture(["labs", "google", "keyword_research", "keyword-ideas"]) + end + + def task_get_labs_google_keywords_suggestions do + get_raw_fixture(["labs", "google", "keyword_research", "keyword-suggestions"]) + end + + # GOOGLE TRENDS + def task_get_google_trends_categories do + get_raw_fixture(["keywords", "google_trends", "categories"]) + end + + def task_get_google_trends_languages do + get_raw_fixture(["keywords", "google_trends", "languages"]) + end + + def task_get_google_trend_locations do + get_raw_fixture(["keywords", "google_trends", "locations"]) + end + + def task_get_google_trend_locations_by_country(_) do + # same fixture at the moment b/c there are no difference with all locations list + # only testing a success routing to the url and response format + get_raw_fixture(["keywords", "google_trends", "locations"]) + end + + def task_post_keywords_google_trends_explorer do + get_raw_fixture(["keywords", "google_trends", "explorer", "task-post"]) + end + + def tasks_ready_keywords_google_trends_explorer do + get_raw_fixture(["keywords", "google_trends", "explorer", "tasks-ready"]) + end + + def task_get_keywords_google_trends_explorer do + get_raw_fixture(["keywords", "google_trends", "explorer", "task-get"]) + end + + def task_get_keywords_google_trends_explorer_live do + get_raw_fixture(["keywords", "google_trends", "explorer", "task-live"]) + end + + # GOOGLE ADS + def task_get_google_ads_locations do + get_raw_fixture(["keywords", "google_ads", "locations"]) + end + + def task_get_google_ads_languages do + get_raw_fixture(["keywords", "google_ads", "languages"]) + end + + # FIXTURE METHODS + def get_raw_fixture(path = [_ | _]) do + get_fixture_base_path() + |> Path.join(Enum.join(path, "/")) + |> Kernel.<>(".json") + |> File.read!() + end + + def get_json_fixture(path = [_ | _]) do + path + |> get_raw_fixture() + |> Jason.decode!() + end + + def get_json_fixture(path) when is_binary(path) do + path + |> File.exists?() + |> case do + true -> + path + |> File.read!() + + false -> + # may be it's internal path in priv/fixtures dir? + get_fixture_base_path() + |> Path.join(path) + |> File.read!() + end + |> Jason.decode!() + end + + def replace_in_fixture(fixture, path, value) do + put_in(fixture, path, value) + end + + def encode_fixture(fixture) do + Jason.encode!(fixture) + end + + defp get_fixture_base_path do + :data_for_seo + |> Application.get_env(:fixtures_path, :code.priv_dir(:data_for_seo)) + |> Path.join("fixtures") + end +end diff --git a/mix.exs b/mix.exs index ffee972..f8a3906 100644 --- a/mix.exs +++ b/mix.exs @@ -31,9 +31,11 @@ defmodule DataForSeo.MixProject do defp deps do [ {:bypass, "~> 2.0", only: :test}, + {:ecto_sql, "~> 3.12.0"}, {:ex_doc, ">= 0.0.0", only: [:dev, :docs]}, + {:finch, "~> 0.13"}, {:jason, "~> 1.0"}, - {:finch, "~> 0.13"} + {:timex, "~> 3.7.11"} ] end diff --git a/mix.lock b/mix.lock index e7103e9..c753c15 100644 --- a/mix.lock +++ b/mix.lock @@ -1,25 +1,42 @@ %{ "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castore": {:hex, :castore, "0.1.19", "a2c3e46d62b7f3aa2e6f88541c21d7400381e53704394462b9fd4f06f6d42bb6", [:mix], [], "hexpm", "e96e0161a5dc82ef441da24d5fa74aefc40d920f3a6645d15e1f9f3e66bb2109"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, + "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"}, "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, + "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"}, + "gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, "nimble_options": {:hex, :nimble_options, "0.5.1", "5c166f7669e40333191bea38e3bd3811cc13f459f1e4be49e89128a21b5d8c4d", [:mix], [], "hexpm", "d176cf7baa4fef0ceb301ca3eb8b55bd7de3e45f489c4f8b4f2849f1f114ef3e"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/priv/fixtures/keywords/google_ads/languages.json b/priv/fixtures/keywords/google_ads/languages.json new file mode 100644 index 0000000..3bd484a --- /dev/null +++ b/priv/fixtures/keywords/google_ads/languages.json @@ -0,0 +1,48 @@ +{ + "version": "3.20191128", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.1773 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11061103-0696-0119-0000-a74d6a2ce740", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0000 sec.", + "cost": 0, + "result_count": 43, + "path": [ + "v3", + "keywords_data", + "google_ads", + "languages" + ], + "data": { + "api": "keywords_data", + "function": "languages", + "se": "google_ads" + }, + "result": [ + { + "language_name": "Arabic", + "language_code": "ar" + }, + { + "language_name": "Bulgarian", + "language_code": "bg" + }, + { + "language_name": "Catalan", + "language_code": "ca" + }, + { + "language_name": "Croatian", + "language_code": "hr" + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_ads/locations.json b/priv/fixtures/keywords/google_ads/locations.json new file mode 100644 index 0000000..2d4b5a5 --- /dev/null +++ b/priv/fixtures/keywords/google_ads/locations.json @@ -0,0 +1,60 @@ +{ + "version": "3.20191128", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.4305 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11061103-0696-0120-0000-268044305ce6", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.2547 sec.", + "cost": 0, + "result_count": 94933, + "path": [ + "v3", + "keywords_data", + "google_ads", + "locations" + ], + "data": { + "api": "keywords_data", + "function": "locations", + "se": "google_ads" + }, + "result": [ + { + "location_code": 2840, + "location_name": "United States", + "location_code_parent": null, + "country_iso_code": "US", + "location_type": "Country" + }, + { + "location_code": 21132, + "location_name": "Alaska,United States", + "location_code_parent": 2840, + "country_iso_code": "US", + "location_type": "State" + }, + { + "location_code": 21133, + "location_name": "Alabama,United States", + "location_code_parent": 2840, + "country_iso_code": "US", + "location_type": "State" + }, + { + "location_code": 21135, + "location_name": "Arkansas,United States", + "location_code_parent": 2840, + "country_iso_code": "US", + "location_type": "State" + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/categories.json b/priv/fixtures/keywords/google_trends/categories.json new file mode 100644 index 0000000..ad183ac --- /dev/null +++ b/priv/fixtures/keywords/google_trends/categories.json @@ -0,0 +1,50 @@ +{ + "version": "0.1.20200305", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0594 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03061224-1535-0197-0000-4d85996ce1db", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0015 sec.", + "cost": 0, + "result_count": 3180, + "path": [ + "v3", + "dataforseo_labs", + "categories" + ], + "data": { + "api": "dataforseo_labs", + "function": "categories" + }, + "result": [ + { + "category_code": 10021, + "category_name": "Apparel", + "category_code_parent": null + }, + { + "category_code": 10178, + "category_name": "Apparel Accessories", + "category_code_parent": 10021 + }, + { + "category_code": 10937, + "category_name": "Bags & Packs", + "category_code_parent": 10178 + }, + { + "category_code": 12262, + "category_name": "Backpacks & Utility Bags", + "category_code_parent": 10937 + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/explorer/task-get.json b/priv/fixtures/keywords/google_trends/explorer/task-get.json new file mode 100644 index 0000000..31f1b88 --- /dev/null +++ b/priv/fixtures/keywords/google_trends/explorer/task-get.json @@ -0,0 +1,7114 @@ +{ + "version": "0.1.20220420", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.2249 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "04212120-1535-0170-0000-46acef0a7cac", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0273 sec.", + "cost": 0, + "result_count": 1, + "path": [ + "v3", + "keywords_data", + "google_trends", + "explore", + "task_get", + "04212120-1535-0170-0000-46acef0a7cac" + ], + "data": { + "api": "keywords_data", + "function": "explore", + "se": "google_trends", + "date_from": "2019-01-01", + "date_to": "2020-01-01", + "keywords": [ + "seo api", + "rank api" + ] + }, + "result": [ + { + "keywords": [ + "seo api", + "rank api" + ], + "type": "trends", + "location_code": 0, + "language_code": "en", + "check_url": "https://trends.google.com/trends/explore?hl=en&date=2019-01-01%202020-01-01&q=seo%20api%2Crank%20api", + "datetime": "2022-04-21 18:22:05Z", + "items_count": 6, + "items": [ + { + "position": 1, + "type": "google_trends_graph", + "title": "Interest over time", + "keywords": [ + "seo api", + "rank api" + ], + "data": [ + { + "date_from": "2019-01-06", + "date_to": "2019-01-12", + "timestamp": 1546732800, + "missing_data": false, + "values": [ + 62, + 37 + ] + }, + { + "date_from": "2019-01-13", + "date_to": "2019-01-19", + "timestamp": 1547337600, + "missing_data": false, + "values": [ + 51, + 51 + ] + }, + { + "date_from": "2019-01-20", + "date_to": "2019-01-26", + "timestamp": 1547942400, + "missing_data": false, + "values": [ + 42, + 31 + ] + }, + { + "date_from": "2019-01-27", + "date_to": "2019-02-02", + "timestamp": 1548547200, + "missing_data": false, + "values": [ + 49, + 57 + ] + }, + { + "date_from": "2019-02-03", + "date_to": "2019-02-09", + "timestamp": 1549152000, + "missing_data": false, + "values": [ + 43, + 29 + ] + }, + { + "date_from": "2019-02-10", + "date_to": "2019-02-16", + "timestamp": 1549756800, + "missing_data": false, + "values": [ + 51, + 37 + ] + }, + { + "date_from": "2019-02-17", + "date_to": "2019-02-23", + "timestamp": 1550361600, + "missing_data": false, + "values": [ + 64, + 49 + ] + }, + { + "date_from": "2019-02-24", + "date_to": "2019-03-02", + "timestamp": 1550966400, + "missing_data": false, + "values": [ + 38, + 30 + ] + }, + { + "date_from": "2019-03-03", + "date_to": "2019-03-09", + "timestamp": 1551571200, + "missing_data": false, + "values": [ + 55, + 38 + ] + }, + { + "date_from": "2019-03-10", + "date_to": "2019-03-16", + "timestamp": 1552176000, + "missing_data": false, + "values": [ + 58, + 41 + ] + }, + { + "date_from": "2019-03-17", + "date_to": "2019-03-23", + "timestamp": 1552780800, + "missing_data": false, + "values": [ + 62, + 38 + ] + }, + { + "date_from": "2019-03-24", + "date_to": "2019-03-30", + "timestamp": 1553385600, + "missing_data": false, + "values": [ + 64, + 54 + ] + }, + { + "date_from": "2019-03-31", + "date_to": "2019-04-06", + "timestamp": 1553990400, + "missing_data": false, + "values": [ + 65, + 46 + ] + }, + { + "date_from": "2019-04-07", + "date_to": "2019-04-13", + "timestamp": 1554595200, + "missing_data": false, + "values": [ + 75, + 61 + ] + }, + { + "date_from": "2019-04-14", + "date_to": "2019-04-20", + "timestamp": 1555200000, + "missing_data": false, + "values": [ + 57, + 36 + ] + }, + { + "date_from": "2019-04-21", + "date_to": "2019-04-27", + "timestamp": 1555804800, + "missing_data": false, + "values": [ + 41, + 39 + ] + }, + { + "date_from": "2019-04-28", + "date_to": "2019-05-04", + "timestamp": 1556409600, + "missing_data": false, + "values": [ + 66, + 49 + ] + }, + { + "date_from": "2019-05-05", + "date_to": "2019-05-11", + "timestamp": 1557014400, + "missing_data": false, + "values": [ + 54, + 52 + ] + }, + { + "date_from": "2019-05-12", + "date_to": "2019-05-18", + "timestamp": 1557619200, + "missing_data": false, + "values": [ + 72, + 49 + ] + }, + { + "date_from": "2019-05-19", + "date_to": "2019-05-25", + "timestamp": 1558224000, + "missing_data": false, + "values": [ + 69, + 60 + ] + }, + { + "date_from": "2019-05-26", + "date_to": "2019-06-01", + "timestamp": 1558828800, + "missing_data": false, + "values": [ + 71, + 60 + ] + }, + { + "date_from": "2019-06-02", + "date_to": "2019-06-08", + "timestamp": 1559433600, + "missing_data": false, + "values": [ + 100, + 50 + ] + }, + { + "date_from": "2019-06-09", + "date_to": "2019-06-15", + "timestamp": 1560038400, + "missing_data": false, + "values": [ + 74, + 38 + ] + }, + { + "date_from": "2019-06-16", + "date_to": "2019-06-22", + "timestamp": 1560643200, + "missing_data": false, + "values": [ + 81, + 47 + ] + }, + { + "date_from": "2019-06-23", + "date_to": "2019-06-29", + "timestamp": 1561248000, + "missing_data": false, + "values": [ + 69, + 44 + ] + }, + { + "date_from": "2019-06-30", + "date_to": "2019-07-06", + "timestamp": 1561852800, + "missing_data": false, + "values": [ + 75, + 55 + ] + }, + { + "date_from": "2019-07-07", + "date_to": "2019-07-13", + "timestamp": 1562457600, + "missing_data": false, + "values": [ + 73, + 43 + ] + }, + { + "date_from": "2019-07-14", + "date_to": "2019-07-20", + "timestamp": 1563062400, + "missing_data": false, + "values": [ + 50, + 49 + ] + }, + { + "date_from": "2019-07-21", + "date_to": "2019-07-27", + "timestamp": 1563667200, + "missing_data": false, + "values": [ + 72, + 52 + ] + }, + { + "date_from": "2019-07-28", + "date_to": "2019-08-03", + "timestamp": 1564272000, + "missing_data": false, + "values": [ + 53, + 47 + ] + }, + { + "date_from": "2019-08-04", + "date_to": "2019-08-10", + "timestamp": 1564876800, + "missing_data": false, + "values": [ + 54, + 49 + ] + }, + { + "date_from": "2019-08-11", + "date_to": "2019-08-17", + "timestamp": 1565481600, + "missing_data": false, + "values": [ + 65, + 48 + ] + }, + { + "date_from": "2019-08-18", + "date_to": "2019-08-24", + "timestamp": 1566086400, + "missing_data": false, + "values": [ + 67, + 56 + ] + }, + { + "date_from": "2019-08-25", + "date_to": "2019-08-31", + "timestamp": 1566691200, + "missing_data": false, + "values": [ + 69, + 47 + ] + }, + { + "date_from": "2019-09-01", + "date_to": "2019-09-07", + "timestamp": 1567296000, + "missing_data": false, + "values": [ + 73, + 42 + ] + }, + { + "date_from": "2019-09-08", + "date_to": "2019-09-14", + "timestamp": 1567900800, + "missing_data": false, + "values": [ + 54, + 45 + ] + }, + { + "date_from": "2019-09-15", + "date_to": "2019-09-21", + "timestamp": 1568505600, + "missing_data": false, + "values": [ + 54, + 51 + ] + }, + { + "date_from": "2019-09-22", + "date_to": "2019-09-28", + "timestamp": 1569110400, + "missing_data": false, + "values": [ + 66, + 52 + ] + }, + { + "date_from": "2019-09-29", + "date_to": "2019-10-05", + "timestamp": 1569715200, + "missing_data": false, + "values": [ + 68, + 45 + ] + }, + { + "date_from": "2019-10-06", + "date_to": "2019-10-12", + "timestamp": 1570320000, + "missing_data": false, + "values": [ + 74, + 46 + ] + }, + { + "date_from": "2019-10-13", + "date_to": "2019-10-19", + "timestamp": 1570924800, + "missing_data": false, + "values": [ + 63, + 49 + ] + }, + { + "date_from": "2019-10-20", + "date_to": "2019-10-26", + "timestamp": 1571529600, + "missing_data": false, + "values": [ + 54, + 46 + ] + }, + { + "date_from": "2019-10-27", + "date_to": "2019-11-02", + "timestamp": 1572134400, + "missing_data": false, + "values": [ + 50, + 40 + ] + }, + { + "date_from": "2019-11-03", + "date_to": "2019-11-09", + "timestamp": 1572739200, + "missing_data": false, + "values": [ + 72, + 45 + ] + }, + { + "date_from": "2019-11-10", + "date_to": "2019-11-16", + "timestamp": 1573344000, + "missing_data": false, + "values": [ + 59, + 42 + ] + }, + { + "date_from": "2019-11-17", + "date_to": "2019-11-23", + "timestamp": 1573948800, + "missing_data": false, + "values": [ + 70, + 43 + ] + }, + { + "date_from": "2019-11-24", + "date_to": "2019-11-30", + "timestamp": 1574553600, + "missing_data": false, + "values": [ + 53, + 31 + ] + }, + { + "date_from": "2019-12-01", + "date_to": "2019-12-07", + "timestamp": 1575158400, + "missing_data": false, + "values": [ + 60, + 48 + ] + }, + { + "date_from": "2019-12-08", + "date_to": "2019-12-14", + "timestamp": 1575763200, + "missing_data": false, + "values": [ + 43, + 46 + ] + }, + { + "date_from": "2019-12-15", + "date_to": "2019-12-21", + "timestamp": 1576368000, + "missing_data": false, + "values": [ + 75, + 54 + ] + }, + { + "date_from": "2019-12-22", + "date_to": "2019-12-28", + "timestamp": 1576972800, + "missing_data": false, + "values": [ + 64, + 47 + ] + }, + { + "date_from": "2019-12-29", + "date_to": "2022-04-21", + "timestamp": 1577577600, + "missing_data": false, + "values": [ + 71, + 44 + ] + } + ], + "averages": [ + 62, + 46 + ] + }, + { + "position": 2, + "type": "google_trends_map", + "title": "Compared breakdown by region", + "keywords": [ + "seo api", + "rank api" + ], + "data": [ + { + "geo_id": "LA", + "geo_name": "Laos", + "values": [ + null, + 100 + ], + "max_value_index": 1 + }, + { + "geo_id": "VN", + "geo_name": "Vietnam", + "values": [ + 50, + 50 + ], + "max_value_index": 1 + }, + { + "geo_id": "EG", + "geo_name": "Egypt", + "values": [ + 51, + 49 + ], + "max_value_index": 0 + }, + { + "geo_id": "TH", + "geo_name": "Thailand", + "values": [ + 50, + 50 + ], + "max_value_index": 0 + }, + { + "geo_id": "ID", + "geo_name": "Indonesia", + "values": [ + 59, + 41 + ], + "max_value_index": 0 + }, + { + "geo_id": "PK", + "geo_name": "Pakistan", + "values": [ + 51, + 49 + ], + "max_value_index": 0 + }, + { + "geo_id": "PH", + "geo_name": "Philippines", + "values": [ + 62, + 38 + ], + "max_value_index": 0 + }, + { + "geo_id": "HK", + "geo_name": "Hong Kong", + "values": [ + 35, + 65 + ], + "max_value_index": 1 + }, + { + "geo_id": "TW", + "geo_name": "Taiwan", + "values": [ + 60, + 40 + ], + "max_value_index": 0 + }, + { + "geo_id": "IN", + "geo_name": "India", + "values": [ + 53, + 47 + ], + "max_value_index": 0 + }, + { + "geo_id": "UA", + "geo_name": "Ukraine", + "values": [ + 68, + 32 + ], + "max_value_index": 0 + }, + { + "geo_id": "KR", + "geo_name": "South Korea", + "values": [ + 65, + 35 + ], + "max_value_index": 0 + }, + { + "geo_id": "AR", + "geo_name": "Argentina", + "values": [ + 60, + 40 + ], + "max_value_index": 0 + }, + { + "geo_id": "BR", + "geo_name": "Brazil", + "values": [ + 47, + 53 + ], + "max_value_index": 1 + }, + { + "geo_id": "RU", + "geo_name": "Russia", + "values": [ + 52, + 48 + ], + "max_value_index": 0 + }, + { + "geo_id": "ES", + "geo_name": "Spain", + "values": [ + 100, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CA", + "geo_name": "Canada", + "values": [ + 71, + 29 + ], + "max_value_index": 0 + }, + { + "geo_id": "FR", + "geo_name": "France", + "values": [ + 100, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GB", + "geo_name": "United Kingdom", + "values": [ + 74, + 26 + ], + "max_value_index": 0 + }, + { + "geo_id": "MX", + "geo_name": "Mexico", + "values": [ + 50, + 50 + ], + "max_value_index": 1 + }, + { + "geo_id": "DE", + "geo_name": "Germany", + "values": [ + 100, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TR", + "geo_name": "Turkey", + "values": [ + 59, + 41 + ], + "max_value_index": 0 + }, + { + "geo_id": "US", + "geo_name": "United States", + "values": [ + 64, + 36 + ], + "max_value_index": 0 + }, + { + "geo_id": "JP", + "geo_name": "Japan", + "values": [ + 78, + 22 + ], + "max_value_index": 0 + }, + { + "geo_id": "AG", + "geo_name": "Antigua & Barbuda", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "TD", + "geo_name": "Chad", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EH", + "geo_name": "Western Sahara", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NC", + "geo_name": "New Caledonia", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "SL", + "geo_name": "Sierra Leone", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MN", + "geo_name": "Mongolia", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "BW", + "geo_name": "Botswana", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GY", + "geo_name": "Guyana", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KH", + "geo_name": "Cambodia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SN", + "geo_name": "Senegal", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MM", + "geo_name": "Myanmar (Burma)", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZM", + "geo_name": "Zambia", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "ML", + "geo_name": "Mali", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MD", + "geo_name": "Moldova", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ET", + "geo_name": "Ethiopia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BY", + "geo_name": "Belarus", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CN", + "geo_name": "China", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UG", + "geo_name": "Uganda", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NP", + "geo_name": "Nepal", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BN", + "geo_name": "Brunei", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "GH", + "geo_name": "Ghana", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "DZ", + "geo_name": "Algeria", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "AM", + "geo_name": "Armenia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AF", + "geo_name": "Afghanistan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LY", + "geo_name": "Libya", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NI", + "geo_name": "Nicaragua", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AZ", + "geo_name": "Azerbaijan", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "QA", + "geo_name": "Qatar", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BD", + "geo_name": "Bangladesh", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "CI", + "geo_name": "Côte d’Ivoire", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EE", + "geo_name": "Estonia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MA", + "geo_name": "Morocco", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HN", + "geo_name": "Honduras", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "YE", + "geo_name": "Yemen", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IS", + "geo_name": "Iceland", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BA", + "geo_name": "Bosnia & Herzegovina", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VE", + "geo_name": "Venezuela", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "TN", + "geo_name": "Tunisia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AO", + "geo_name": "Angola", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "SI", + "geo_name": "Slovenia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GE", + "geo_name": "Georgia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DO", + "geo_name": "Dominican Republic", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AE", + "geo_name": "United Arab Emirates", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EC", + "geo_name": "Ecuador", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CD", + "geo_name": "Congo - Kinshasa", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "MY", + "geo_name": "Malaysia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "OM", + "geo_name": "Oman", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SG", + "geo_name": "Singapore", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TT", + "geo_name": "Trinidad & Tobago", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BG", + "geo_name": "Bulgaria", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RO", + "geo_name": "Romania", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JO", + "geo_name": "Jordan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BH", + "geo_name": "Bahrain", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PS", + "geo_name": "Palestine", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PA", + "geo_name": "Panama", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PR", + "geo_name": "Puerto Rico", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AL", + "geo_name": "Albania", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CR", + "geo_name": "Costa Rica", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PT", + "geo_name": "Portugal", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GT", + "geo_name": "Guatemala", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "LV", + "geo_name": "Latvia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LB", + "geo_name": "Lebanon", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BE", + "geo_name": "Belgium", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SK", + "geo_name": "Slovakia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SD", + "geo_name": "Sudan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IE", + "geo_name": "Ireland", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZA", + "geo_name": "South Africa", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RS", + "geo_name": "Serbia", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "IL", + "geo_name": "Israel", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CY", + "geo_name": "Cyprus", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "IQ", + "geo_name": "Iraq", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "GR", + "geo_name": "Greece", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SA", + "geo_name": "Saudi Arabia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UY", + "geo_name": "Uruguay", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LT", + "geo_name": "Lithuania", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NL", + "geo_name": "Netherlands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SV", + "geo_name": "El Salvador", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "NO", + "geo_name": "Norway", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LK", + "geo_name": "Sri Lanka", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "CZ", + "geo_name": "Czechia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SY", + "geo_name": "Syria", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "CO", + "geo_name": "Colombia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AT", + "geo_name": "Austria", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CH", + "geo_name": "Switzerland", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DK", + "geo_name": "Denmark", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HU", + "geo_name": "Hungary", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "AU", + "geo_name": "Australia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IR", + "geo_name": "Iran", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "UZ", + "geo_name": "Uzbekistan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KW", + "geo_name": "Kuwait", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "KZ", + "geo_name": "Kazakhstan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CL", + "geo_name": "Chile", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "NG", + "geo_name": "Nigeria", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PE", + "geo_name": "Peru", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BO", + "geo_name": "Bolivia", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "PL", + "geo_name": "Poland", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NZ", + "geo_name": "New Zealand", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KE", + "geo_name": "Kenya", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "IT", + "geo_name": "Italy", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HR", + "geo_name": "Croatia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SE", + "geo_name": "Sweden", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "FI", + "geo_name": "Finland", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AW", + "geo_name": "Aruba", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AI", + "geo_name": "Anguilla", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AX", + "geo_name": "Åland Islands", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "AD", + "geo_name": "Andorra", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AS", + "geo_name": "American Samoa", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AQ", + "geo_name": "Antarctica", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TF", + "geo_name": "French Southern Territories", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BI", + "geo_name": "Burundi", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BJ", + "geo_name": "Benin", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BQ", + "geo_name": "Caribbean Netherlands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BF", + "geo_name": "Burkina Faso", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BS", + "geo_name": "Bahamas", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BL", + "geo_name": "St. Barthélemy", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BZ", + "geo_name": "Belize", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "BM", + "geo_name": "Bermuda", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BB", + "geo_name": "Barbados", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "BT", + "geo_name": "Bhutan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BV", + "geo_name": "Bouvet Island", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CF", + "geo_name": "Central African Republic", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CC", + "geo_name": "Cocos (Keeling) Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CM", + "geo_name": "Cameroon", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CG", + "geo_name": "Congo - Brazzaville", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CK", + "geo_name": "Cook Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KM", + "geo_name": "Comoros", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CV", + "geo_name": "Cape Verde", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CU", + "geo_name": "Cuba", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "CW", + "geo_name": "Curaçao", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CX", + "geo_name": "Christmas Island", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KY", + "geo_name": "Cayman Islands", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "DJ", + "geo_name": "Djibouti", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DM", + "geo_name": "Dominica", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ER", + "geo_name": "Eritrea", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FJ", + "geo_name": "Fiji", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FK", + "geo_name": "Falkland Islands (Islas Malvinas)", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FO", + "geo_name": "Faroe Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FM", + "geo_name": "Micronesia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GA", + "geo_name": "Gabon", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GG", + "geo_name": "Guernsey", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GI", + "geo_name": "Gibraltar", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GN", + "geo_name": "Guinea", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GP", + "geo_name": "Guadeloupe", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "GM", + "geo_name": "Gambia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GW", + "geo_name": "Guinea-Bissau", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GQ", + "geo_name": "Equatorial Guinea", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GD", + "geo_name": "Grenada", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GL", + "geo_name": "Greenland", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GF", + "geo_name": "French Guiana", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GU", + "geo_name": "Guam", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HM", + "geo_name": "Heard & McDonald Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HT", + "geo_name": "Haiti", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IM", + "geo_name": "Isle of Man", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IO", + "geo_name": "British Indian Ocean Territory", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JM", + "geo_name": "Jamaica", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "JE", + "geo_name": "Jersey", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KG", + "geo_name": "Kyrgyzstan", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "KI", + "geo_name": "Kiribati", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KN", + "geo_name": "St. Kitts & Nevis", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LR", + "geo_name": "Liberia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LC", + "geo_name": "St. Lucia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LI", + "geo_name": "Liechtenstein", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LS", + "geo_name": "Lesotho", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LU", + "geo_name": "Luxembourg", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MO", + "geo_name": "Macao", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "MF", + "geo_name": "St. Martin", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MC", + "geo_name": "Monaco", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MG", + "geo_name": "Madagascar", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MV", + "geo_name": "Maldives", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MH", + "geo_name": "Marshall Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MK", + "geo_name": "North Macedonia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MT", + "geo_name": "Malta", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ME", + "geo_name": "Montenegro", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MP", + "geo_name": "Northern Mariana Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MZ", + "geo_name": "Mozambique", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MR", + "geo_name": "Mauritania", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MS", + "geo_name": "Montserrat", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MQ", + "geo_name": "Martinique", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MU", + "geo_name": "Mauritius", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "MW", + "geo_name": "Malawi", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "YT", + "geo_name": "Mayotte", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NA", + "geo_name": "Namibia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NE", + "geo_name": "Niger", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NF", + "geo_name": "Norfolk Island", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NU", + "geo_name": "Niue", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NR", + "geo_name": "Nauru", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PN", + "geo_name": "Pitcairn Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PW", + "geo_name": "Palau", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PG", + "geo_name": "Papua New Guinea", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KP", + "geo_name": "North Korea", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PY", + "geo_name": "Paraguay", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "PF", + "geo_name": "French Polynesia", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RE", + "geo_name": "Réunion", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "RW", + "geo_name": "Rwanda", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GS", + "geo_name": "South Georgia & South Sandwich Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SH", + "geo_name": "St. Helena", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SJ", + "geo_name": "Svalbard & Jan Mayen", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SB", + "geo_name": "Solomon Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SM", + "geo_name": "San Marino", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SO", + "geo_name": "Somalia", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "PM", + "geo_name": "St. Pierre & Miquelon", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SS", + "geo_name": "South Sudan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ST", + "geo_name": "São Tomé & Príncipe", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SR", + "geo_name": "Suriname", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "SZ", + "geo_name": "Eswatini", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SX", + "geo_name": "Sint Maarten", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SC", + "geo_name": "Seychelles", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TC", + "geo_name": "Turks & Caicos Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TG", + "geo_name": "Togo", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TJ", + "geo_name": "Tajikistan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TK", + "geo_name": "Tokelau", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TM", + "geo_name": "Turkmenistan", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TL", + "geo_name": "Timor-Leste", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TO", + "geo_name": "Tonga", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TV", + "geo_name": "Tuvalu", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TZ", + "geo_name": "Tanzania", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UM", + "geo_name": "U.S. Outlying Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VA", + "geo_name": "Vatican City", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VC", + "geo_name": "St. Vincent & Grenadines", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VG", + "geo_name": "British Virgin Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VI", + "geo_name": "U.S. Virgin Islands", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VU", + "geo_name": "Vanuatu", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "WF", + "geo_name": "Wallis & Futuna", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "WS", + "geo_name": "Samoa", + "values": [ + null, + null + ], + "max_value_index": 0 + }, + { + "geo_id": "XK", + "geo_name": "Kosovo", + "values": [ + null, + null + ], + "max_value_index": 1 + }, + { + "geo_id": "ZW", + "geo_name": "Zimbabwe", + "values": [ + null, + null + ], + "max_value_index": 1 + } + ] + }, + { + "position": 3, + "type": "google_trends_map", + "title": "Interest by region", + "keywords": [ + "seo api" + ], + "data": [ + { + "geo_id": "VN", + "geo_name": "Vietnam", + "values": [ + 100 + ], + "max_value_index": 0 + }, + { + "geo_id": "EG", + "geo_name": "Egypt", + "values": [ + 98 + ], + "max_value_index": 0 + }, + { + "geo_id": "TH", + "geo_name": "Thailand", + "values": [ + 48 + ], + "max_value_index": 0 + }, + { + "geo_id": "ID", + "geo_name": "Indonesia", + "values": [ + 47 + ], + "max_value_index": 0 + }, + { + "geo_id": "PK", + "geo_name": "Pakistan", + "values": [ + 39 + ], + "max_value_index": 0 + }, + { + "geo_id": "BD", + "geo_name": "Bangladesh", + "values": [ + 39 + ], + "max_value_index": 0 + }, + { + "geo_id": "PH", + "geo_name": "Philippines", + "values": [ + 35 + ], + "max_value_index": 0 + }, + { + "geo_id": "TW", + "geo_name": "Taiwan", + "values": [ + 21 + ], + "max_value_index": 0 + }, + { + "geo_id": "IN", + "geo_name": "India", + "values": [ + 19 + ], + "max_value_index": 0 + }, + { + "geo_id": "UA", + "geo_name": "Ukraine", + "values": [ + 18 + ], + "max_value_index": 0 + }, + { + "geo_id": "KR", + "geo_name": "South Korea", + "values": [ + 16 + ], + "max_value_index": 0 + }, + { + "geo_id": "HK", + "geo_name": "Hong Kong", + "values": [ + 15 + ], + "max_value_index": 0 + }, + { + "geo_id": "AR", + "geo_name": "Argentina", + "values": [ + 13 + ], + "max_value_index": 0 + }, + { + "geo_id": "BR", + "geo_name": "Brazil", + "values": [ + 12 + ], + "max_value_index": 0 + }, + { + "geo_id": "RU", + "geo_name": "Russia", + "values": [ + 12 + ], + "max_value_index": 0 + }, + { + "geo_id": "ES", + "geo_name": "Spain", + "values": [ + 10 + ], + "max_value_index": 0 + }, + { + "geo_id": "CA", + "geo_name": "Canada", + "values": [ + 10 + ], + "max_value_index": 0 + }, + { + "geo_id": "FR", + "geo_name": "France", + "values": [ + 9 + ], + "max_value_index": 0 + }, + { + "geo_id": "GB", + "geo_name": "United Kingdom", + "values": [ + 9 + ], + "max_value_index": 0 + }, + { + "geo_id": "MX", + "geo_name": "Mexico", + "values": [ + 9 + ], + "max_value_index": 0 + }, + { + "geo_id": "DE", + "geo_name": "Germany", + "values": [ + 8 + ], + "max_value_index": 0 + }, + { + "geo_id": "TR", + "geo_name": "Turkey", + "values": [ + 7 + ], + "max_value_index": 0 + }, + { + "geo_id": "US", + "geo_name": "United States", + "values": [ + 6 + ], + "max_value_index": 0 + }, + { + "geo_id": "JP", + "geo_name": "Japan", + "values": [ + 6 + ], + "max_value_index": 0 + }, + { + "geo_id": "AG", + "geo_name": "Antigua & Barbuda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TD", + "geo_name": "Chad", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EH", + "geo_name": "Western Sahara", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LA", + "geo_name": "Laos", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NC", + "geo_name": "New Caledonia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SL", + "geo_name": "Sierra Leone", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MN", + "geo_name": "Mongolia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BW", + "geo_name": "Botswana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GY", + "geo_name": "Guyana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KH", + "geo_name": "Cambodia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SN", + "geo_name": "Senegal", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MM", + "geo_name": "Myanmar (Burma)", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZM", + "geo_name": "Zambia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ML", + "geo_name": "Mali", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MD", + "geo_name": "Moldova", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BY", + "geo_name": "Belarus", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CN", + "geo_name": "China", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UG", + "geo_name": "Uganda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BN", + "geo_name": "Brunei", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NP", + "geo_name": "Nepal", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GH", + "geo_name": "Ghana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DZ", + "geo_name": "Algeria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AF", + "geo_name": "Afghanistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LY", + "geo_name": "Libya", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NI", + "geo_name": "Nicaragua", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AZ", + "geo_name": "Azerbaijan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "QA", + "geo_name": "Qatar", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CI", + "geo_name": "Côte d’Ivoire", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EE", + "geo_name": "Estonia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ET", + "geo_name": "Ethiopia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HN", + "geo_name": "Honduras", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MA", + "geo_name": "Morocco", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "YE", + "geo_name": "Yemen", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IS", + "geo_name": "Iceland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VE", + "geo_name": "Venezuela", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TN", + "geo_name": "Tunisia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AO", + "geo_name": "Angola", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AM", + "geo_name": "Armenia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SI", + "geo_name": "Slovenia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GE", + "geo_name": "Georgia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DO", + "geo_name": "Dominican Republic", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AE", + "geo_name": "United Arab Emirates", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BA", + "geo_name": "Bosnia & Herzegovina", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CD", + "geo_name": "Congo - Kinshasa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MY", + "geo_name": "Malaysia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EC", + "geo_name": "Ecuador", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "OM", + "geo_name": "Oman", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SG", + "geo_name": "Singapore", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TT", + "geo_name": "Trinidad & Tobago", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BG", + "geo_name": "Bulgaria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RO", + "geo_name": "Romania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BH", + "geo_name": "Bahrain", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JO", + "geo_name": "Jordan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PS", + "geo_name": "Palestine", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PA", + "geo_name": "Panama", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AL", + "geo_name": "Albania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CR", + "geo_name": "Costa Rica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PR", + "geo_name": "Puerto Rico", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PT", + "geo_name": "Portugal", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LV", + "geo_name": "Latvia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GT", + "geo_name": "Guatemala", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BE", + "geo_name": "Belgium", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LB", + "geo_name": "Lebanon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SK", + "geo_name": "Slovakia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SD", + "geo_name": "Sudan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IE", + "geo_name": "Ireland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZA", + "geo_name": "South Africa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RS", + "geo_name": "Serbia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IL", + "geo_name": "Israel", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CY", + "geo_name": "Cyprus", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IQ", + "geo_name": "Iraq", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GR", + "geo_name": "Greece", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SA", + "geo_name": "Saudi Arabia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UY", + "geo_name": "Uruguay", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LT", + "geo_name": "Lithuania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NL", + "geo_name": "Netherlands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SV", + "geo_name": "El Salvador", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NO", + "geo_name": "Norway", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CZ", + "geo_name": "Czechia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LK", + "geo_name": "Sri Lanka", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SY", + "geo_name": "Syria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CO", + "geo_name": "Colombia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CH", + "geo_name": "Switzerland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DK", + "geo_name": "Denmark", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AU", + "geo_name": "Australia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HU", + "geo_name": "Hungary", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AT", + "geo_name": "Austria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IR", + "geo_name": "Iran", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UZ", + "geo_name": "Uzbekistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KW", + "geo_name": "Kuwait", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CL", + "geo_name": "Chile", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KZ", + "geo_name": "Kazakhstan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NG", + "geo_name": "Nigeria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PE", + "geo_name": "Peru", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BO", + "geo_name": "Bolivia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PL", + "geo_name": "Poland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NZ", + "geo_name": "New Zealand", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KE", + "geo_name": "Kenya", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IT", + "geo_name": "Italy", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HR", + "geo_name": "Croatia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SE", + "geo_name": "Sweden", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FI", + "geo_name": "Finland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AW", + "geo_name": "Aruba", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AI", + "geo_name": "Anguilla", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AX", + "geo_name": "Åland Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AD", + "geo_name": "Andorra", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AS", + "geo_name": "American Samoa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AQ", + "geo_name": "Antarctica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TF", + "geo_name": "French Southern Territories", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BI", + "geo_name": "Burundi", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BJ", + "geo_name": "Benin", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BQ", + "geo_name": "Caribbean Netherlands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BF", + "geo_name": "Burkina Faso", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BS", + "geo_name": "Bahamas", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BL", + "geo_name": "St. Barthélemy", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BZ", + "geo_name": "Belize", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BM", + "geo_name": "Bermuda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BB", + "geo_name": "Barbados", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BT", + "geo_name": "Bhutan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BV", + "geo_name": "Bouvet Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CF", + "geo_name": "Central African Republic", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CC", + "geo_name": "Cocos (Keeling) Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CM", + "geo_name": "Cameroon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CG", + "geo_name": "Congo - Brazzaville", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CK", + "geo_name": "Cook Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KM", + "geo_name": "Comoros", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CV", + "geo_name": "Cape Verde", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CU", + "geo_name": "Cuba", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CW", + "geo_name": "Curaçao", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CX", + "geo_name": "Christmas Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KY", + "geo_name": "Cayman Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DJ", + "geo_name": "Djibouti", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DM", + "geo_name": "Dominica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ER", + "geo_name": "Eritrea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FJ", + "geo_name": "Fiji", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FK", + "geo_name": "Falkland Islands (Islas Malvinas)", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FO", + "geo_name": "Faroe Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FM", + "geo_name": "Micronesia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GA", + "geo_name": "Gabon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GG", + "geo_name": "Guernsey", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GI", + "geo_name": "Gibraltar", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GN", + "geo_name": "Guinea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GP", + "geo_name": "Guadeloupe", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GM", + "geo_name": "Gambia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GW", + "geo_name": "Guinea-Bissau", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GQ", + "geo_name": "Equatorial Guinea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GD", + "geo_name": "Grenada", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GL", + "geo_name": "Greenland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GF", + "geo_name": "French Guiana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GU", + "geo_name": "Guam", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HM", + "geo_name": "Heard & McDonald Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HT", + "geo_name": "Haiti", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IM", + "geo_name": "Isle of Man", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IO", + "geo_name": "British Indian Ocean Territory", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JM", + "geo_name": "Jamaica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JE", + "geo_name": "Jersey", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KG", + "geo_name": "Kyrgyzstan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KI", + "geo_name": "Kiribati", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KN", + "geo_name": "St. Kitts & Nevis", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LR", + "geo_name": "Liberia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LC", + "geo_name": "St. Lucia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LI", + "geo_name": "Liechtenstein", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LS", + "geo_name": "Lesotho", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LU", + "geo_name": "Luxembourg", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MO", + "geo_name": "Macao", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MF", + "geo_name": "St. Martin", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MC", + "geo_name": "Monaco", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MG", + "geo_name": "Madagascar", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MV", + "geo_name": "Maldives", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MH", + "geo_name": "Marshall Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MK", + "geo_name": "North Macedonia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MT", + "geo_name": "Malta", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ME", + "geo_name": "Montenegro", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MP", + "geo_name": "Northern Mariana Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MZ", + "geo_name": "Mozambique", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MR", + "geo_name": "Mauritania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MS", + "geo_name": "Montserrat", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MQ", + "geo_name": "Martinique", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MU", + "geo_name": "Mauritius", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MW", + "geo_name": "Malawi", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "YT", + "geo_name": "Mayotte", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NA", + "geo_name": "Namibia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NE", + "geo_name": "Niger", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NF", + "geo_name": "Norfolk Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NU", + "geo_name": "Niue", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NR", + "geo_name": "Nauru", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PN", + "geo_name": "Pitcairn Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PW", + "geo_name": "Palau", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PG", + "geo_name": "Papua New Guinea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KP", + "geo_name": "North Korea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PY", + "geo_name": "Paraguay", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PF", + "geo_name": "French Polynesia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RE", + "geo_name": "Réunion", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RW", + "geo_name": "Rwanda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GS", + "geo_name": "South Georgia & South Sandwich Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SH", + "geo_name": "St. Helena", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SJ", + "geo_name": "Svalbard & Jan Mayen", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SB", + "geo_name": "Solomon Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SM", + "geo_name": "San Marino", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SO", + "geo_name": "Somalia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PM", + "geo_name": "St. Pierre & Miquelon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SS", + "geo_name": "South Sudan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ST", + "geo_name": "São Tomé & Príncipe", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SR", + "geo_name": "Suriname", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SZ", + "geo_name": "Eswatini", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SX", + "geo_name": "Sint Maarten", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SC", + "geo_name": "Seychelles", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TC", + "geo_name": "Turks & Caicos Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TG", + "geo_name": "Togo", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TJ", + "geo_name": "Tajikistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TK", + "geo_name": "Tokelau", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TM", + "geo_name": "Turkmenistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TL", + "geo_name": "Timor-Leste", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TO", + "geo_name": "Tonga", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TV", + "geo_name": "Tuvalu", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TZ", + "geo_name": "Tanzania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UM", + "geo_name": "U.S. Outlying Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VA", + "geo_name": "Vatican City", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VC", + "geo_name": "St. Vincent & Grenadines", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VG", + "geo_name": "British Virgin Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VI", + "geo_name": "U.S. Virgin Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VU", + "geo_name": "Vanuatu", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "WF", + "geo_name": "Wallis & Futuna", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "WS", + "geo_name": "Samoa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "XK", + "geo_name": "Kosovo", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZW", + "geo_name": "Zimbabwe", + "values": [ + null + ], + "max_value_index": 0 + } + ] + }, + { + "position": 4, + "type": "google_trends_queries_list", + "title": "Related queries", + "keywords": [ + "seo api" + ], + "data": { + "top": [ + { + "query": "google seo api", + "value": 100 + }, + { + "query": "api for seo", + "value": 87 + }, + { + "query": "seo api free", + "value": 58 + }, + { + "query": "seo api tools", + "value": 48 + }, + { + "query": "seo api php", + "value": 45 + }, + { + "query": "api for seo software", + "value": 39 + }, + { + "query": "seo ranking api", + "value": 35 + }, + { + "query": "seo data api", + "value": 34 + }, + { + "query": "seo rank api", + "value": 34 + }, + { + "query": "seo tool api", + "value": 32 + }, + { + "query": "analytics seo api", + "value": 29 + }, + { + "query": "seo checker api", + "value": 27 + }, + { + "query": "seo check api", + "value": 26 + }, + { + "query": "best seo api", + "value": 25 + }, + { + "query": "what is seo", + "value": 24 + }, + { + "query": "what is api", + "value": 24 + }, + { + "query": "serp seo api", + "value": 24 + }, + { + "query": "seo analysis api", + "value": 24 + }, + { + "query": "seo keyword api", + "value": 24 + }, + { + "query": "free seo tools api", + "value": 18 + }, + { + "query": "api for seo software projects", + "value": 18 + }, + { + "query": "rest api", + "value": 17 + }, + { + "query": "yoast seo", + "value": 17 + }, + { + "query": "seo score api", + "value": 16 + }, + { + "query": "google search api", + "value": 16 + } + ], + "rising": [ + { + "query": "api for seo software projects", + "value": 120 + }, + { + "query": "seo score api", + "value": 80 + }, + { + "query": "api for seo software", + "value": 80 + }, + { + "query": "alexa seo api", + "value": 50 + } + ] + } + }, + { + "position": 5, + "type": "google_trends_map", + "title": "Interest by region", + "keywords": [ + "rank api" + ], + "data": [ + { + "geo_id": "LA", + "geo_name": "Laos", + "values": [ + 100 + ], + "max_value_index": 0 + }, + { + "geo_id": "VN", + "geo_name": "Vietnam", + "values": [ + 57 + ], + "max_value_index": 0 + }, + { + "geo_id": "EG", + "geo_name": "Egypt", + "values": [ + 55 + ], + "max_value_index": 0 + }, + { + "geo_id": "TH", + "geo_name": "Thailand", + "values": [ + 26 + ], + "max_value_index": 0 + }, + { + "geo_id": "PK", + "geo_name": "Pakistan", + "values": [ + 22 + ], + "max_value_index": 0 + }, + { + "geo_id": "ID", + "geo_name": "Indonesia", + "values": [ + 17 + ], + "max_value_index": 0 + }, + { + "geo_id": "HK", + "geo_name": "Hong Kong", + "values": [ + 15 + ], + "max_value_index": 0 + }, + { + "geo_id": "PH", + "geo_name": "Philippines", + "values": [ + 13 + ], + "max_value_index": 0 + }, + { + "geo_id": "IN", + "geo_name": "India", + "values": [ + 9 + ], + "max_value_index": 0 + }, + { + "geo_id": "TW", + "geo_name": "Taiwan", + "values": [ + 8 + ], + "max_value_index": 0 + }, + { + "geo_id": "BR", + "geo_name": "Brazil", + "values": [ + 7 + ], + "max_value_index": 0 + }, + { + "geo_id": "AE", + "geo_name": "United Arab Emirates", + "values": [ + 6 + ], + "max_value_index": 0 + }, + { + "geo_id": "RU", + "geo_name": "Russia", + "values": [ + 6 + ], + "max_value_index": 0 + }, + { + "geo_id": "AR", + "geo_name": "Argentina", + "values": [ + 5 + ], + "max_value_index": 0 + }, + { + "geo_id": "CO", + "geo_name": "Colombia", + "values": [ + 5 + ], + "max_value_index": 0 + }, + { + "geo_id": "UA", + "geo_name": "Ukraine", + "values": [ + 5 + ], + "max_value_index": 0 + }, + { + "geo_id": "MX", + "geo_name": "Mexico", + "values": [ + 5 + ], + "max_value_index": 0 + }, + { + "geo_id": "KR", + "geo_name": "South Korea", + "values": [ + 4 + ], + "max_value_index": 0 + }, + { + "geo_id": "TR", + "geo_name": "Turkey", + "values": [ + 3 + ], + "max_value_index": 0 + }, + { + "geo_id": "CA", + "geo_name": "Canada", + "values": [ + 2 + ], + "max_value_index": 0 + }, + { + "geo_id": "US", + "geo_name": "United States", + "values": [ + 2 + ], + "max_value_index": 0 + }, + { + "geo_id": "GB", + "geo_name": "United Kingdom", + "values": [ + 2 + ], + "max_value_index": 0 + }, + { + "geo_id": "JP", + "geo_name": "Japan", + "values": [ + 1 + ], + "max_value_index": 0 + }, + { + "geo_id": "AG", + "geo_name": "Antigua & Barbuda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AX", + "geo_name": "Åland Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NC", + "geo_name": "New Caledonia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KY", + "geo_name": "Cayman Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TG", + "geo_name": "Togo", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BZ", + "geo_name": "Belize", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZW", + "geo_name": "Zimbabwe", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BN", + "geo_name": "Brunei", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MN", + "geo_name": "Mongolia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GH", + "geo_name": "Ghana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "XK", + "geo_name": "Kosovo", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SR", + "geo_name": "Suriname", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JM", + "geo_name": "Jamaica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BB", + "geo_name": "Barbados", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SO", + "geo_name": "Somalia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZM", + "geo_name": "Zambia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AZ", + "geo_name": "Azerbaijan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MM", + "geo_name": "Myanmar (Burma)", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GP", + "geo_name": "Guadeloupe", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MU", + "geo_name": "Mauritius", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CD", + "geo_name": "Congo - Kinshasa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CU", + "geo_name": "Cuba", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BD", + "geo_name": "Bangladesh", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DZ", + "geo_name": "Algeria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KH", + "geo_name": "Cambodia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MO", + "geo_name": "Macao", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LY", + "geo_name": "Libya", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AF", + "geo_name": "Afghanistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VE", + "geo_name": "Venezuela", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BY", + "geo_name": "Belarus", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SY", + "geo_name": "Syria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AO", + "geo_name": "Angola", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IQ", + "geo_name": "Iraq", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BO", + "geo_name": "Bolivia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CN", + "geo_name": "China", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PY", + "geo_name": "Paraguay", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SN", + "geo_name": "Senegal", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GT", + "geo_name": "Guatemala", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KG", + "geo_name": "Kyrgyzstan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RE", + "geo_name": "Réunion", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KW", + "geo_name": "Kuwait", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SV", + "geo_name": "El Salvador", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NI", + "geo_name": "Nicaragua", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MA", + "geo_name": "Morocco", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TN", + "geo_name": "Tunisia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RS", + "geo_name": "Serbia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "QA", + "geo_name": "Qatar", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EC", + "geo_name": "Ecuador", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GE", + "geo_name": "Georgia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BG", + "geo_name": "Bulgaria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PA", + "geo_name": "Panama", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DO", + "geo_name": "Dominican Republic", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LV", + "geo_name": "Latvia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AL", + "geo_name": "Albania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ET", + "geo_name": "Ethiopia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KE", + "geo_name": "Kenya", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UG", + "geo_name": "Uganda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PT", + "geo_name": "Portugal", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JO", + "geo_name": "Jordan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CY", + "geo_name": "Cyprus", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HU", + "geo_name": "Hungary", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EE", + "geo_name": "Estonia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IL", + "geo_name": "Israel", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CL", + "geo_name": "Chile", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MY", + "geo_name": "Malaysia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IR", + "geo_name": "Iran", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LK", + "geo_name": "Sri Lanka", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SG", + "geo_name": "Singapore", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NP", + "geo_name": "Nepal", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GR", + "geo_name": "Greece", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LB", + "geo_name": "Lebanon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ZA", + "geo_name": "South Africa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BA", + "geo_name": "Bosnia & Herzegovina", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DK", + "geo_name": "Denmark", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SI", + "geo_name": "Slovenia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SA", + "geo_name": "Saudi Arabia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BE", + "geo_name": "Belgium", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SE", + "geo_name": "Sweden", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KZ", + "geo_name": "Kazakhstan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NG", + "geo_name": "Nigeria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UY", + "geo_name": "Uruguay", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AT", + "geo_name": "Austria", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CZ", + "geo_name": "Czechia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PL", + "geo_name": "Poland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NO", + "geo_name": "Norway", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RO", + "geo_name": "Romania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NL", + "geo_name": "Netherlands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IT", + "geo_name": "Italy", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PE", + "geo_name": "Peru", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ES", + "geo_name": "Spain", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HR", + "geo_name": "Croatia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FR", + "geo_name": "France", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NZ", + "geo_name": "New Zealand", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AU", + "geo_name": "Australia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DE", + "geo_name": "Germany", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CH", + "geo_name": "Switzerland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AW", + "geo_name": "Aruba", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AI", + "geo_name": "Anguilla", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AD", + "geo_name": "Andorra", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AM", + "geo_name": "Armenia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AS", + "geo_name": "American Samoa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "AQ", + "geo_name": "Antarctica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TF", + "geo_name": "French Southern Territories", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BI", + "geo_name": "Burundi", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BJ", + "geo_name": "Benin", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BQ", + "geo_name": "Caribbean Netherlands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BF", + "geo_name": "Burkina Faso", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BH", + "geo_name": "Bahrain", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BS", + "geo_name": "Bahamas", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BL", + "geo_name": "St. Barthélemy", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BM", + "geo_name": "Bermuda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BT", + "geo_name": "Bhutan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BV", + "geo_name": "Bouvet Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "BW", + "geo_name": "Botswana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CF", + "geo_name": "Central African Republic", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CC", + "geo_name": "Cocos (Keeling) Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CI", + "geo_name": "Côte d’Ivoire", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CM", + "geo_name": "Cameroon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CG", + "geo_name": "Congo - Brazzaville", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CK", + "geo_name": "Cook Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KM", + "geo_name": "Comoros", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CV", + "geo_name": "Cape Verde", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CR", + "geo_name": "Costa Rica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CW", + "geo_name": "Curaçao", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "CX", + "geo_name": "Christmas Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DJ", + "geo_name": "Djibouti", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "DM", + "geo_name": "Dominica", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ER", + "geo_name": "Eritrea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "EH", + "geo_name": "Western Sahara", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FI", + "geo_name": "Finland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FJ", + "geo_name": "Fiji", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FK", + "geo_name": "Falkland Islands (Islas Malvinas)", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FO", + "geo_name": "Faroe Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "FM", + "geo_name": "Micronesia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GA", + "geo_name": "Gabon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GG", + "geo_name": "Guernsey", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GI", + "geo_name": "Gibraltar", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GN", + "geo_name": "Guinea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GM", + "geo_name": "Gambia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GW", + "geo_name": "Guinea-Bissau", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GQ", + "geo_name": "Equatorial Guinea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GD", + "geo_name": "Grenada", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GL", + "geo_name": "Greenland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GF", + "geo_name": "French Guiana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GU", + "geo_name": "Guam", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GY", + "geo_name": "Guyana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HM", + "geo_name": "Heard & McDonald Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HN", + "geo_name": "Honduras", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "HT", + "geo_name": "Haiti", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IM", + "geo_name": "Isle of Man", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IO", + "geo_name": "British Indian Ocean Territory", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IE", + "geo_name": "Ireland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "IS", + "geo_name": "Iceland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "JE", + "geo_name": "Jersey", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KI", + "geo_name": "Kiribati", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KN", + "geo_name": "St. Kitts & Nevis", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LR", + "geo_name": "Liberia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LC", + "geo_name": "St. Lucia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LI", + "geo_name": "Liechtenstein", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LS", + "geo_name": "Lesotho", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LT", + "geo_name": "Lithuania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "LU", + "geo_name": "Luxembourg", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MF", + "geo_name": "St. Martin", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MC", + "geo_name": "Monaco", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MD", + "geo_name": "Moldova", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MG", + "geo_name": "Madagascar", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MV", + "geo_name": "Maldives", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MH", + "geo_name": "Marshall Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MK", + "geo_name": "North Macedonia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ML", + "geo_name": "Mali", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MT", + "geo_name": "Malta", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ME", + "geo_name": "Montenegro", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MP", + "geo_name": "Northern Mariana Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MZ", + "geo_name": "Mozambique", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MR", + "geo_name": "Mauritania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MS", + "geo_name": "Montserrat", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MQ", + "geo_name": "Martinique", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "MW", + "geo_name": "Malawi", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "YT", + "geo_name": "Mayotte", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NA", + "geo_name": "Namibia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NE", + "geo_name": "Niger", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NF", + "geo_name": "Norfolk Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NU", + "geo_name": "Niue", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "NR", + "geo_name": "Nauru", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "OM", + "geo_name": "Oman", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PN", + "geo_name": "Pitcairn Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PW", + "geo_name": "Palau", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PG", + "geo_name": "Papua New Guinea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PR", + "geo_name": "Puerto Rico", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "KP", + "geo_name": "North Korea", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PS", + "geo_name": "Palestine", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PF", + "geo_name": "French Polynesia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "RW", + "geo_name": "Rwanda", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SD", + "geo_name": "Sudan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "GS", + "geo_name": "South Georgia & South Sandwich Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SH", + "geo_name": "St. Helena", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SJ", + "geo_name": "Svalbard & Jan Mayen", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SB", + "geo_name": "Solomon Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SL", + "geo_name": "Sierra Leone", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SM", + "geo_name": "San Marino", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "PM", + "geo_name": "St. Pierre & Miquelon", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SS", + "geo_name": "South Sudan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "ST", + "geo_name": "São Tomé & Príncipe", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SK", + "geo_name": "Slovakia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SZ", + "geo_name": "Eswatini", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SX", + "geo_name": "Sint Maarten", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "SC", + "geo_name": "Seychelles", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TC", + "geo_name": "Turks & Caicos Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TD", + "geo_name": "Chad", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TJ", + "geo_name": "Tajikistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TK", + "geo_name": "Tokelau", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TM", + "geo_name": "Turkmenistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TL", + "geo_name": "Timor-Leste", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TO", + "geo_name": "Tonga", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TT", + "geo_name": "Trinidad & Tobago", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TV", + "geo_name": "Tuvalu", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "TZ", + "geo_name": "Tanzania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UM", + "geo_name": "U.S. Outlying Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "UZ", + "geo_name": "Uzbekistan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VA", + "geo_name": "Vatican City", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VC", + "geo_name": "St. Vincent & Grenadines", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VG", + "geo_name": "British Virgin Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VI", + "geo_name": "U.S. Virgin Islands", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "VU", + "geo_name": "Vanuatu", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "WF", + "geo_name": "Wallis & Futuna", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "WS", + "geo_name": "Samoa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "YE", + "geo_name": "Yemen", + "values": [ + null + ], + "max_value_index": 0 + } + ] + }, + { + "position": 6, + "type": "google_trends_queries_list", + "title": "Related queries", + "keywords": [ + "rank api" + ], + "data": { + "top": [ + { + "query": "google rank api", + "value": 100 + }, + { + "query": "rank checker api", + "value": 83 + }, + { + "query": "rank tracker api", + "value": 60 + }, + { + "query": "google rank checker api", + "value": 57 + }, + { + "query": "rank tracking api", + "value": 50 + }, + { + "query": "keyword rank api", + "value": 47 + }, + { + "query": "serp rank api", + "value": 27 + }, + { + "query": "keyword rank checker api", + "value": 27 + }, + { + "query": "google page rank api", + "value": 23 + }, + { + "query": "seo rank api", + "value": 23 + }, + { + "query": "google rank tracker api", + "value": 19 + }, + { + "query": "website rank api", + "value": 15 + }, + { + "query": "serp rank checker api", + "value": 15 + }, + { + "query": "google rank tracking api", + "value": 15 + }, + { + "query": "google keyword rank checker api", + "value": 14 + }, + { + "query": "google page rank checker api", + "value": 14 + }, + { + "query": "keyword rank tracking api", + "value": 13 + }, + { + "query": "rank tracking tool api", + "value": 9 + }, + { + "query": "alexa rank", + "value": 9 + }, + { + "query": "domain rank api", + "value": 8 + }, + { + "query": "pro rank tracker api", + "value": 7 + }, + { + "query": "hacker rank", + "value": 4 + }, + { + "query": "best rank tracker with api", + "value": 4 + } + ], + "rising": [ + { + "query": "rank tracking tool api", + "value": 70 + }, + { + "query": "google page rank checker api", + "value": 50 + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/explorer/task-live.json b/priv/fixtures/keywords/google_trends/explorer/task-live.json new file mode 100644 index 0000000..07e6ad4 --- /dev/null +++ b/priv/fixtures/keywords/google_trends/explorer/task-live.json @@ -0,0 +1,1224 @@ +{ + "version": "0.1.20220420", + "status_code": 20000, + "status_message": "Ok.", + "time": "6.1707 sec.", + "cost": 0.009, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "04212140-1535-0173-0000-8209149d8105", + "status_code": 20000, + "status_message": "Ok.", + "time": "6.0432 sec.", + "cost": 0.009, + "result_count": 1, + "path": [ + "v3", + "keywords_data", + "google_trends", + "explore", + "live" + ], + "data": { + "api": "keywords_data", + "function": "explore", + "se": "google_trends", + "location_name": "United States", + "date_from": "2019-01-01", + "date_to": "2020-01-01", + "keywords": [ + "seo api" + ] + }, + "result": [ + { + "keywords": [ + "seo api" + ], + "type": "trends", + "location_code": 2840, + "language_code": "en", + "check_url": "https://trends.google.com/trends/explore?hl=en&geo=US&date=2019-01-01%202020-01-01&q=seo%20api", + "datetime": "2022-04-21 18:40:17 +00:00", + "items_count": 4, + "items": [ + { + "position": 1, + "type": "google_trends_graph", + "title": "Interest over time", + "keywords": [ + "seo api" + ], + "data": [ + { + "date_from": "2019-01-06", + "date_to": "2019-01-12", + "timestamp": 1546732800, + "missing_data": false, + "values": [ + 54 + ] + }, + { + "date_from": "2019-01-13", + "date_to": "2019-01-19", + "timestamp": 1547337600, + "missing_data": false, + "values": [ + 38 + ] + }, + { + "date_from": "2019-01-20", + "date_to": "2019-01-26", + "timestamp": 1547942400, + "missing_data": false, + "values": [ + 39 + ] + }, + { + "date_from": "2019-01-27", + "date_to": "2019-02-02", + "timestamp": 1548547200, + "missing_data": false, + "values": [ + 28 + ] + }, + { + "date_from": "2019-02-03", + "date_to": "2019-02-09", + "timestamp": 1549152000, + "missing_data": false, + "values": [ + 14 + ] + }, + { + "date_from": "2019-02-10", + "date_to": "2019-02-16", + "timestamp": 1549756800, + "missing_data": false, + "values": [ + 26 + ] + }, + { + "date_from": "2019-02-17", + "date_to": "2019-02-23", + "timestamp": 1550361600, + "missing_data": false, + "values": [ + 63 + ] + }, + { + "date_from": "2019-02-24", + "date_to": "2019-03-02", + "timestamp": 1550966400, + "missing_data": false, + "values": [ + 63 + ] + }, + { + "date_from": "2019-03-03", + "date_to": "2019-03-09", + "timestamp": 1551571200, + "missing_data": false, + "values": [ + 26 + ] + }, + { + "date_from": "2019-03-10", + "date_to": "2019-03-16", + "timestamp": 1552176000, + "missing_data": false, + "values": [ + 39 + ] + }, + { + "date_from": "2019-03-17", + "date_to": "2019-03-23", + "timestamp": 1552780800, + "missing_data": false, + "values": [ + 40 + ] + }, + { + "date_from": "2019-03-24", + "date_to": "2019-03-30", + "timestamp": 1553385600, + "missing_data": false, + "values": [ + 26 + ] + }, + { + "date_from": "2019-03-31", + "date_to": "2019-04-06", + "timestamp": 1553990400, + "missing_data": false, + "values": [ + 67 + ] + }, + { + "date_from": "2019-04-07", + "date_to": "2019-04-13", + "timestamp": 1554595200, + "missing_data": false, + "values": [ + 40 + ] + }, + { + "date_from": "2019-04-14", + "date_to": "2019-04-20", + "timestamp": 1555200000, + "missing_data": false, + "values": [ + 27 + ] + }, + { + "date_from": "2019-04-21", + "date_to": "2019-04-27", + "timestamp": 1555804800, + "missing_data": false, + "values": [ + 14 + ] + }, + { + "date_from": "2019-04-28", + "date_to": "2019-05-04", + "timestamp": 1556409600, + "missing_data": false, + "values": [ + 66 + ] + }, + { + "date_from": "2019-05-05", + "date_to": "2019-05-11", + "timestamp": 1557014400, + "missing_data": false, + "values": [ + 13 + ] + }, + { + "date_from": "2019-05-12", + "date_to": "2019-05-18", + "timestamp": 1557619200, + "missing_data": false, + "values": [ + 28 + ] + }, + { + "date_from": "2019-05-19", + "date_to": "2019-05-25", + "timestamp": 1558224000, + "missing_data": false, + "values": [ + 30 + ] + }, + { + "date_from": "2019-05-26", + "date_to": "2019-06-01", + "timestamp": 1558828800, + "missing_data": false, + "values": [ + 56 + ] + }, + { + "date_from": "2019-06-02", + "date_to": "2019-06-08", + "timestamp": 1559433600, + "missing_data": false, + "values": [ + 67 + ] + }, + { + "date_from": "2019-06-09", + "date_to": "2019-06-15", + "timestamp": 1560038400, + "missing_data": false, + "values": [ + 44 + ] + }, + { + "date_from": "2019-06-16", + "date_to": "2019-06-22", + "timestamp": 1560643200, + "missing_data": false, + "values": [ + 55 + ] + }, + { + "date_from": "2019-06-23", + "date_to": "2019-06-29", + "timestamp": 1561248000, + "missing_data": false, + "values": [ + 56 + ] + }, + { + "date_from": "2019-06-30", + "date_to": "2019-07-06", + "timestamp": 1561852800, + "missing_data": false, + "values": [ + 100 + ] + }, + { + "date_from": "2019-07-07", + "date_to": "2019-07-13", + "timestamp": 1562457600, + "missing_data": false, + "values": [ + 54 + ] + }, + { + "date_from": "2019-07-14", + "date_to": "2019-07-20", + "timestamp": 1563062400, + "missing_data": false, + "values": [ + 54 + ] + }, + { + "date_from": "2019-07-21", + "date_to": "2019-07-27", + "timestamp": 1563667200, + "missing_data": false, + "values": [ + 42 + ] + }, + { + "date_from": "2019-07-28", + "date_to": "2019-08-03", + "timestamp": 1564272000, + "missing_data": false, + "values": [ + 15 + ] + }, + { + "date_from": "2019-08-04", + "date_to": "2019-08-10", + "timestamp": 1564876800, + "missing_data": false, + "values": [ + null + ] + }, + { + "date_from": "2019-08-11", + "date_to": "2019-08-17", + "timestamp": 1565481600, + "missing_data": false, + "values": [ + 29 + ] + }, + { + "date_from": "2019-08-18", + "date_to": "2019-08-24", + "timestamp": 1566086400, + "missing_data": false, + "values": [ + 27 + ] + }, + { + "date_from": "2019-08-25", + "date_to": "2019-08-31", + "timestamp": 1566691200, + "missing_data": false, + "values": [ + 65 + ] + }, + { + "date_from": "2019-09-01", + "date_to": "2019-09-07", + "timestamp": 1567296000, + "missing_data": false, + "values": [ + 74 + ] + }, + { + "date_from": "2019-09-08", + "date_to": "2019-09-14", + "timestamp": 1567900800, + "missing_data": false, + "values": [ + 49 + ] + }, + { + "date_from": "2019-09-15", + "date_to": "2019-09-21", + "timestamp": 1568505600, + "missing_data": false, + "values": [ + null + ] + }, + { + "date_from": "2019-09-22", + "date_to": "2019-09-28", + "timestamp": 1569110400, + "missing_data": false, + "values": [ + 51 + ] + }, + { + "date_from": "2019-09-29", + "date_to": "2019-10-05", + "timestamp": 1569715200, + "missing_data": false, + "values": [ + null + ] + }, + { + "date_from": "2019-10-06", + "date_to": "2019-10-12", + "timestamp": 1570320000, + "missing_data": false, + "values": [ + 25 + ] + }, + { + "date_from": "2019-10-13", + "date_to": "2019-10-19", + "timestamp": 1570924800, + "missing_data": false, + "values": [ + 50 + ] + }, + { + "date_from": "2019-10-20", + "date_to": "2019-10-26", + "timestamp": 1571529600, + "missing_data": false, + "values": [ + 48 + ] + }, + { + "date_from": "2019-10-27", + "date_to": "2019-11-02", + "timestamp": 1572134400, + "missing_data": false, + "values": [ + 25 + ] + }, + { + "date_from": "2019-11-03", + "date_to": "2019-11-09", + "timestamp": 1572739200, + "missing_data": false, + "values": [ + 67 + ] + }, + { + "date_from": "2019-11-10", + "date_to": "2019-11-16", + "timestamp": 1573344000, + "missing_data": false, + "values": [ + 65 + ] + }, + { + "date_from": "2019-11-17", + "date_to": "2019-11-23", + "timestamp": 1573948800, + "missing_data": false, + "values": [ + 86 + ] + }, + { + "date_from": "2019-11-24", + "date_to": "2019-11-30", + "timestamp": 1574553600, + "missing_data": false, + "values": [ + 13 + ] + }, + { + "date_from": "2019-12-01", + "date_to": "2019-12-07", + "timestamp": 1575158400, + "missing_data": false, + "values": [ + 24 + ] + }, + { + "date_from": "2019-12-08", + "date_to": "2019-12-14", + "timestamp": 1575763200, + "missing_data": false, + "values": [ + 12 + ] + }, + { + "date_from": "2019-12-15", + "date_to": "2019-12-21", + "timestamp": 1576368000, + "missing_data": false, + "values": [ + 51 + ] + }, + { + "date_from": "2019-12-22", + "date_to": "2019-12-28", + "timestamp": 1576972800, + "missing_data": false, + "values": [ + 85 + ] + }, + { + "date_from": "2019-12-29", + "date_to": "2020-01-01", + "timestamp": 1577577600, + "missing_data": false, + "values": [ + 42 + ] + } + ], + "averages": [] + }, + { + "position": 2, + "type": "google_trends_map", + "title": "Interest by subregion", + "keywords": [ + "seo api" + ], + "data": [ + { + "geo_id": "US-NY", + "geo_name": "New York", + "values": [ + 100 + ], + "max_value_index": 0 + }, + { + "geo_id": "US-CA", + "geo_name": "California", + "values": [ + 37 + ], + "max_value_index": 0 + }, + { + "geo_id": "US-OR", + "geo_name": "Oregon", + "values": [ + 31 + ], + "max_value_index": 0 + }, + { + "geo_id": "US-TX", + "geo_name": "Texas", + "values": [ + 17 + ], + "max_value_index": 0 + }, + { + "geo_id": "US-DC", + "geo_name": "District of Columbia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-VT", + "geo_name": "Vermont", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-NH", + "geo_name": "New Hampshire", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-SD", + "geo_name": "South Dakota", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-NC", + "geo_name": "North Carolina", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-AR", + "geo_name": "Arkansas", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-RI", + "geo_name": "Rhode Island", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-WA", + "geo_name": "Washington", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-HI", + "geo_name": "Hawaii", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MN", + "geo_name": "Minnesota", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-UT", + "geo_name": "Utah", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-LA", + "geo_name": "Louisiana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MA", + "geo_name": "Massachusetts", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-NE", + "geo_name": "Nebraska", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-GA", + "geo_name": "Georgia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-FL", + "geo_name": "Florida", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-IL", + "geo_name": "Illinois", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-KS", + "geo_name": "Kansas", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-WI", + "geo_name": "Wisconsin", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-NV", + "geo_name": "Nevada", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-OK", + "geo_name": "Oklahoma", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MD", + "geo_name": "Maryland", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-CO", + "geo_name": "Colorado", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-TN", + "geo_name": "Tennessee", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-VA", + "geo_name": "Virginia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-PA", + "geo_name": "Pennsylvania", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-CT", + "geo_name": "Connecticut", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-OH", + "geo_name": "Ohio", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-AL", + "geo_name": "Alabama", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-SC", + "geo_name": "South Carolina", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-IN", + "geo_name": "Indiana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MO", + "geo_name": "Missouri", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-AZ", + "geo_name": "Arizona", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MI", + "geo_name": "Michigan", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-NJ", + "geo_name": "New Jersey", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-AK", + "geo_name": "Alaska", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-DE", + "geo_name": "Delaware", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-IA", + "geo_name": "Iowa", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-ID", + "geo_name": "Idaho", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-KY", + "geo_name": "Kentucky", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-ME", + "geo_name": "Maine", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MS", + "geo_name": "Mississippi", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-MT", + "geo_name": "Montana", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-ND", + "geo_name": "North Dakota", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-NM", + "geo_name": "New Mexico", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-WV", + "geo_name": "West Virginia", + "values": [ + null + ], + "max_value_index": 0 + }, + { + "geo_id": "US-WY", + "geo_name": "Wyoming", + "values": [ + null + ], + "max_value_index": 0 + } + ] + }, + { + "position": 3, + "type": "google_trends_topics_list", + "title": "Related topics", + "keywords": [ + "seo api" + ], + "data": { + "top": [ + { + "topic_id": "/m/019qb_", + "topic_title": "Search Engine Optimization", + "topic_type": "Topic", + "value": 100 + }, + { + "topic_id": "/m/0z5n", + "topic_title": "Application programming interface", + "topic_type": "Type of software", + "value": 96 + }, + { + "topic_id": "/m/045c7b", + "topic_title": "Google", + "topic_type": "Technology company", + "value": 32 + }, + { + "topic_id": "/m/085n4", + "topic_title": "Website", + "topic_type": "Topic", + "value": 17 + }, + { + "topic_id": "/m/0387r", + "topic_title": "Google Search", + "topic_type": "Website", + "value": 17 + }, + { + "topic_id": "/m/02vtpl", + "topic_title": "WordPress", + "topic_type": "System software", + "value": 11 + }, + { + "topic_id": "/m/0fr1w6", + "topic_title": "Index term", + "topic_type": "Topic", + "value": 10 + }, + { + "topic_id": "/m/026sq", + "topic_title": "Data", + "topic_type": "Topic", + "value": 9 + }, + { + "topic_id": "/m/07k1x", + "topic_title": "Tool", + "topic_type": "Topic", + "value": 8 + }, + { + "topic_id": "/m/05x35", + "topic_title": "Plug-in", + "topic_type": "Computing", + "value": 7 + }, + { + "topic_id": "/m/02mz1d", + "topic_title": "Review", + "topic_type": "Topic", + "value": 7 + }, + { + "topic_id": "/m/02gcn9", + "topic_title": "Analytics", + "topic_type": "Topic", + "value": 6 + }, + { + "topic_id": "/m/054zdv", + "topic_title": "Ranking", + "topic_type": "Topic", + "value": 6 + }, + { + "topic_id": "/m/055t58", + "topic_title": "Google Maps", + "topic_type": "Website", + "value": 5 + }, + { + "topic_id": "/g/11c5m28d6z", + "topic_title": "Yoast", + "topic_type": "Topic", + "value": 5 + }, + { + "topic_id": "/m/081rb", + "topic_title": "Writing", + "topic_type": "Language writing system", + "value": 5 + }, + { + "topic_id": "/m/07xy0", + "topic_title": "Uniform Resource Locator", + "topic_type": "Topic", + "value": 5 + }, + { + "topic_id": "/m/0g4gr", + "topic_title": "Marketing", + "topic_type": "Topic", + "value": 5 + }, + { + "topic_id": "/m/027cz92", + "topic_title": "Moz", + "topic_type": "Topic", + "value": 5 + }, + { + "topic_id": "/m/02x3zp_", + "topic_title": "Content", + "topic_type": "Media", + "value": 4 + }, + { + "topic_id": "/m/05fbf64", + "topic_title": "Google APIs", + "topic_type": "Topic", + "value": 4 + }, + { + "topic_id": "/m/05s_jbn", + "topic_title": "Application programming interface key", + "topic_type": "Topic", + "value": 4 + }, + { + "topic_id": "/g/120z18l3", + "topic_title": "Company", + "topic_type": "Topic", + "value": 4 + }, + { + "topic_id": "/m/060kv", + "topic_title": "PHP", + "topic_type": "Programming language", + "value": 4 + }, + { + "topic_id": "/m/03nsxd", + "topic_title": "Representational state transfer", + "topic_type": "Internet protocol", + "value": 4 + } + ], + "rising": [ + { + "topic_id": "/m/086df", + "topic_title": "Web design", + "topic_type": "Discipline", + "value": 400 + }, + { + "topic_id": "/m/07xy0", + "topic_title": "Uniform Resource Locator", + "topic_type": "Topic", + "value": 250 + }, + { + "topic_id": "/m/02cwm", + "topic_title": "Design", + "topic_type": "Topic", + "value": 140 + }, + { + "topic_id": "/m/055t58", + "topic_title": "Google Maps", + "topic_type": "Website", + "value": 130 + }, + { + "topic_id": "/m/05x35", + "topic_title": "Plug-in", + "topic_type": "Computing", + "value": 90 + }, + { + "topic_id": "/m/026sq", + "topic_title": "Data", + "topic_type": "Topic", + "value": 80 + }, + { + "topic_id": "/m/07k1x", + "topic_title": "Tool", + "topic_type": "Topic", + "value": 80 + }, + { + "topic_id": "/m/081rb", + "topic_title": "Writing", + "topic_type": "Language writing system", + "value": 70 + }, + { + "topic_id": "/m/085n4", + "topic_title": "Website", + "topic_type": "Topic", + "value": 70 + }, + { + "topic_id": "/m/05z1_", + "topic_title": "Python", + "topic_type": "Programming language", + "value": 60 + }, + { + "topic_id": "/m/0bngkg", + "topic_title": "Yelp", + "topic_type": "Company", + "value": 60 + }, + { + "topic_id": "/m/03l68z", + "topic_title": "Google Ads", + "topic_type": "Topic", + "value": 60 + }, + { + "topic_id": "/m/02x3zp_", + "topic_title": "Content", + "topic_type": "Media", + "value": 50 + }, + { + "topic_id": "/m/05fbf64", + "topic_title": "Google APIs", + "topic_type": "Topic", + "value": 50 + }, + { + "topic_id": "/m/05s_jbn", + "topic_title": "Application programming interface key", + "topic_type": "Topic", + "value": 50 + } + ] + } + }, + { + "position": 4, + "type": "google_trends_queries_list", + "title": "Related queries", + "keywords": [ + "seo api" + ], + "data": { + "top": [ + { + "query": "analytics seo api", + "value": 100 + }, + { + "query": "moz api", + "value": 64 + } + ], + "rising": [] + } + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/explorer/task-post.json b/priv/fixtures/keywords/google_trends/explorer/task-post.json new file mode 100644 index 0000000..29bae03 --- /dev/null +++ b/priv/fixtures/keywords/google_trends/explorer/task-post.json @@ -0,0 +1,41 @@ +{ + "version": "0.1.20200130", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0882 sec.", + "cost": 0.15, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "02122119-1535-0170-0000-0228cf083d8e", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0017 sec.", + "cost": 0.05, + "result_count": 0, + "path": [ + "v3", + "keywords_data", + "google_trends", + "explore", + "task_post" + ], + "data": { + "api": "keywords_data", + "function": "explore", + "se": "google_trends", + "location_name": "United States", + "type": "youtube", + "date_from": "2019-01-15", + "date_to": "2020-01-01", + "category_code": 3, + "keywords": [ + "seo api", + "rank api" + ] + }, + "result": null + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/explorer/tasks-ready.json b/priv/fixtures/keywords/google_trends/explorer/tasks-ready.json new file mode 100644 index 0000000..24c8dcb --- /dev/null +++ b/priv/fixtures/keywords/google_trends/explorer/tasks-ready.json @@ -0,0 +1,57 @@ +{ + "version": "3.20191128", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.1927 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11211342-0696-0153-0000-12436ded1ab2", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0892 sec.", + "cost": 0, + "result_count": 3, + "path": [ + "v3", + "keywords_data", + "google", + "explore", + "tasks_ready" + ], + "data": { + "api": "keywords_data", + "function": "explore", + "se": "google" + }, + "result": [ + { + "id": "10311535-0696-0093-0000-8fe3770f079b", + "se": "google", + "function": "explore", + "date_posted": "2019-10-31 11:35:04 +00:00", + "tag": "tag1", + "endpoint": "/v3/keywords_data/google_trends/explore/task_get/10311535-0696-0093-0000-8fe3770f079b" + }, + { + "id": "11111403-0696-0110-0000-c69794ecf661", + "se": "google", + "function": "explore", + "date_posted": "2019-11-11 10:03:59 +00:00", + "tag": "tag2", + "endpoint": "/v3/keywords_data/google_trends/explore/task_get/11111403-0696-0110-0000-c69794ecf661" + }, + { + "id": "11201044-0696-0110-0000-9f560a19543e", + "se": "google", + "function": "explore", + "date_posted": "2019-11-20 06:44:26 +00:00", + "tag": "tag3", + "endpoint": "/v3/keywords_data/google_trends/explore/task_get/11201044-0696-0110-0000-9f560a19543e" + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/languages.json b/priv/fixtures/keywords/google_trends/languages.json new file mode 100644 index 0000000..6da448c --- /dev/null +++ b/priv/fixtures/keywords/google_trends/languages.json @@ -0,0 +1,48 @@ +{ + "version": "0.1.20200408", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.1977 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "04081302-1535-0119-0000-cf474e770569", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0002 sec.", + "cost": 0, + "result_count": 231, + "path": [ + "v3", + "keywords_data", + "google_trends", + "languages" + ], + "data": { + "api": "keywords_data", + "function": "languages", + "se": "google_trends" + }, + "result": [ + { + "language_name": "Afrikaans", + "language_code": "af" + }, + { + "language_name": "Albanian", + "language_code": "sq" + }, + { + "language_name": "English", + "language_code": "en" + }, + { + "language_name": "German", + "language_code": "de" + } + ] + } + ] +} diff --git a/priv/fixtures/keywords/google_trends/locations.json b/priv/fixtures/keywords/google_trends/locations.json new file mode 100644 index 0000000..30af7a1 --- /dev/null +++ b/priv/fixtures/keywords/google_trends/locations.json @@ -0,0 +1,104 @@ +{ + "version": "0.1.20210917", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.1012 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "09201832-1535-0120-0000-012e5cc7e0d9", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0244 sec.", + "cost": 0, + "result_count": 2383, + "path": [ + "v3", + "keywords_data", + "google_trends", + "locations" + ], + "data": { + "api": "keywords_data", + "function": "locations", + "se": "google_trends" + }, + "result": [ + { + "location_code": 2004, + "location_name": "Afghanistan", + "location_code_parent": null, + "country_iso_code": "AF", + "location_type": "Country", + "geo_name": "Afghanistan", + "geo_id": "AF" + }, + { + "location_code": 2008, + "location_name": "Albania", + "location_code_parent": null, + "country_iso_code": "AL", + "location_type": "Country", + "geo_name": "Albania", + "geo_id": "AL" + }, + { + "location_code": 2010, + "location_name": "Antarctica", + "location_code_parent": null, + "country_iso_code": "AQ", + "location_type": "Country", + "geo_name": "Antarctica", + "geo_id": "AQ" + }, + { + "location_code": 2012, + "location_name": "Algeria", + "location_code_parent": null, + "country_iso_code": "DZ", + "location_type": "Country", + "geo_name": "Algeria", + "geo_id": "DZ" + }, + { + "location_code": 2016, + "location_name": "American Samoa", + "location_code_parent": null, + "country_iso_code": "AS", + "location_type": "Country", + "geo_name": "American Samoa", + "geo_id": "AS" + }, + { + "location_code": 2020, + "location_name": "Andorra", + "location_code_parent": null, + "country_iso_code": "AD", + "location_type": "Country", + "geo_name": "Andorra", + "geo_id": "AD" + }, + { + "location_code": 2024, + "location_name": "Angola", + "location_code_parent": null, + "country_iso_code": "AO", + "location_type": "Country", + "geo_name": "Angola", + "geo_id": "AO" + }, + { + "location_code": 21113, + "location_name": "Donetsk Oblast,Ukraine", + "location_code_parent": 2804, + "country_iso_code": "UA", + "location_type": "Region", + "geo_name": "Donetsk Oblast,Ukraine", + "geo_id": "Donetsk Oblast,Ukraine" + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/categories-nil-result.json b/priv/fixtures/labs/google/categories-nil-result.json new file mode 100644 index 0000000..bd048e0 --- /dev/null +++ b/priv/fixtures/labs/google/categories-nil-result.json @@ -0,0 +1,29 @@ +{ + "version": "0.1.20200305", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0594 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03061224-1535-0197-0000-4d85996ce1db", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0015 sec.", + "cost": 0, + "result_count": 3180, + "path": [ + "v3", + "dataforseo_labs", + "categories" + ], + "data": { + "api": "dataforseo_labs", + "function": "categories" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/labs/google/categories.json b/priv/fixtures/labs/google/categories.json new file mode 100644 index 0000000..ad183ac --- /dev/null +++ b/priv/fixtures/labs/google/categories.json @@ -0,0 +1,50 @@ +{ + "version": "0.1.20200305", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0594 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03061224-1535-0197-0000-4d85996ce1db", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0015 sec.", + "cost": 0, + "result_count": 3180, + "path": [ + "v3", + "dataforseo_labs", + "categories" + ], + "data": { + "api": "dataforseo_labs", + "function": "categories" + }, + "result": [ + { + "category_code": 10021, + "category_name": "Apparel", + "category_code_parent": null + }, + { + "category_code": 10178, + "category_name": "Apparel Accessories", + "category_code_parent": 10021 + }, + { + "category_code": 10937, + "category_name": "Bags & Packs", + "category_code_parent": 10178 + }, + { + "category_code": 12262, + "category_name": "Backpacks & Utility Bags", + "category_code_parent": 10937 + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/bulk-keyword-difficulty.json b/priv/fixtures/labs/google/keyword_research/bulk-keyword-difficulty.json new file mode 100644 index 0000000..b841514 --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/bulk-keyword-difficulty.json @@ -0,0 +1,64 @@ +{ + "version": "0.1.20220216", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0510 sec.", + "cost": 0.0103, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03021544-0696-0392-0000-f2ae21b8a66e", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0190 sec.", + "cost": 0.0103, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "bulk_keyword_difficulty", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "bulk_keyword_difficulty", + "se_type": "google", + "location_code": 2840, + "language_code": "en", + "keywords": [ + "dentist new york", + "pizza brooklyn", + "car dealer los angeles" + ] + }, + "result": [ + { + "se_type": "google", + "location_code": 2840, + "language_code": "en", + "total_count": 3, + "items_count": 3, + "items": [ + { + "se_type": "google", + "keyword": "car dealer los angeles", + "keyword_difficulty": 50 + }, + { + "se_type": "google", + "keyword": "dentist new york", + "keyword_difficulty": 50 + }, + { + "se_type": "google", + "keyword": "pizza brooklyn", + "keyword_difficulty": 44 + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/keyword-ideas.json b/priv/fixtures/labs/google/keyword_research/keyword-ideas.json new file mode 100644 index 0000000..a30a10c --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/keyword-ideas.json @@ -0,0 +1,445 @@ +{ + "version": "0.1.20221214", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.4790 sec.", + "cost": 0.0103, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03231838-1535-0400-0000-21ff7a0ecead", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.4413 sec.", + "cost": 0.0103, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "keyword_ideas", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "keyword_ideas", + "se_type": "google", + "keywords": [ + "phone", + "watch" + ], + "location_code": 2840, + "language_code": "en", + "include_serp_info": true, + "limit": 3 + }, + "result": [ + { + "se_type": "google", + "seed_keywords": [ + "phone", + "watch" + ], + "location_code": 2840, + "language_code": "en", + "total_count": 2321099, + "items_count": 3, + "offset": 0, + "offset_token": "eyJDdXJyZW50T2Zmc2V0IjozLCJSZXF1ZXN0RGF0YSI6eyJrZXl3b3JkcyI6WyJwaG9uZSIsIndhdGNoIl0sImxvY2F0aW9uIjoyODQwLCJsYW5ndWFnZSI6ImVuIiwiY2xvc2VseV92YXJpYW50cyI6ZmFsc2UsIm5ld2VzdCI6ZmFsc2UsImV4dGVuZGVkIjpmYWxzZSwibG9hZF9zZXJwX2luZm8iOnRydWUsImF1dG9jb3JyZWN0Ijp0cnVlLCJJc09sZCI6ZmFsc2UsInNlYXJjaF9hZnRlcl90b2tlbiI6bnVsbCwiaWdub3JlX3N5bm9ueW1zIjpmYWxzZSwic2VhcmNoX2VuZ2luZSI6Imdvb2dsZSIsIm9yZGVyX2J5Ijp7Im9yZGVyX2ZpZWxkIjoiX3Njb3JlIiwib3JkZXJfdHlwZSI6IkRlc2MiLCJuZXh0IjpudWxsfSwibGltaXQiOjMsIm9mZnNldCI6MCwiYWlkIjoxNTM1fSwiUmF3UXVlcnkiOm51bGwsIlNlYXJjaEFmdGVyRGF0YSI6WzI1LjE3MzE4MiwiMDA4ZGEyMWMtNGQxZi03M2YzLTEwMGYtMzA5YjFkMjkxMjBmIl19", + "items": [ + { + "se_type": "google", + "keyword": "refer to our telephone conversation", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-22 16:20:58 +00:00", + "competition": null, + "competition_level": "LOW", + "cpc": null, + "search_volume": 10, + "low_top_of_page_bid": null, + "high_top_of_page_bid": null, + "categories": [ + 10007, + 10878, + 12171 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 10 + }, + { + "year": 2023, + "month": 1, + "search_volume": 0 + }, + { + "year": 2022, + "month": 12, + "search_volume": 0 + }, + { + "year": 2022, + "month": 11, + "search_volume": 10 + }, + { + "year": 2022, + "month": 10, + "search_volume": 10 + }, + { + "year": 2022, + "month": 9, + "search_volume": 10 + }, + { + "year": 2022, + "month": 8, + "search_volume": 10 + }, + { + "year": 2022, + "month": 7, + "search_volume": 10 + }, + { + "year": 2022, + "month": 6, + "search_volume": 0 + }, + { + "year": 2022, + "month": 5, + "search_volume": 0 + }, + { + "year": 2022, + "month": 4, + "search_volume": 10 + }, + { + "year": 2022, + "month": 3, + "search_volume": 10 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": null, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-22 02:43:34 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": null, + "ad_position_max": null, + "ad_position_average": null, + "cpc_min": null, + "cpc_max": null, + "cpc_average": null, + "daily_impressions_min": null, + "daily_impressions_max": null, + "daily_impressions_average": null, + "daily_clicks_min": null, + "daily_clicks_max": null, + "daily_clicks_average": null, + "daily_cost_min": null, + "daily_cost_max": null, + "daily_cost_average": null + }, + "serp_info": null, + "avg_backlinks_info": null, + "search_intent_info": { + "se_type": "google", + "main_intent": "transactional", + "foreign_intent": [ + "commercial" + ], + "last_updated_time": "2023-03-01 16:55:11 +00:00" + } + }, + { + "se_type": "google", + "keyword": "telephone in japan", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-21 10:09:17 +00:00", + "competition": 0.07, + "competition_level": "LOW", + "cpc": null, + "search_volume": 210, + "low_top_of_page_bid": null, + "high_top_of_page_bid": null, + "categories": [ + 10007, + 10878, + 12171 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 210 + }, + { + "year": 2023, + "month": 1, + "search_volume": 260 + }, + { + "year": 2022, + "month": 12, + "search_volume": 210 + }, + { + "year": 2022, + "month": 11, + "search_volume": 210 + }, + { + "year": 2022, + "month": 10, + "search_volume": 260 + }, + { + "year": 2022, + "month": 9, + "search_volume": 260 + }, + { + "year": 2022, + "month": 8, + "search_volume": 260 + }, + { + "year": 2022, + "month": 7, + "search_volume": 170 + }, + { + "year": 2022, + "month": 6, + "search_volume": 210 + }, + { + "year": 2022, + "month": 5, + "search_volume": 210 + }, + { + "year": 2022, + "month": 4, + "search_volume": 210 + }, + { + "year": 2022, + "month": 3, + "search_volume": 210 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 48, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-03-26 19:32:21 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": null, + "ad_position_max": null, + "ad_position_average": null, + "cpc_min": null, + "cpc_max": null, + "cpc_average": null, + "daily_impressions_min": null, + "daily_impressions_max": null, + "daily_impressions_average": null, + "daily_clicks_min": null, + "daily_clicks_max": null, + "daily_clicks_average": null, + "daily_cost_min": null, + "daily_cost_max": null, + "daily_cost_average": null + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=telephone%20in%20japan&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "featured_snippet", + "people_also_ask", + "organic", + "images", + "people_also_search", + "related_searches" + ], + "se_results_count": 332000000, + "last_updated_time": "2023-03-15 22:05:45 +00:00", + "previous_updated_time": "2023-02-09 15:33:06 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 29.3, + "dofollow": 14.6, + "referring_pages": 26, + "referring_domains": 17, + "referring_main_domains": 15.4, + "rank": 103.5, + "main_domain_rank": 478.1, + "last_updated_time": "2023-03-15 22:05:52 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "commercial", + "foreign_intent": [ + "informational", + "transactional" + ], + "last_updated_time": "2023-03-02 18:35:26 +00:00" + } + }, + { + "se_type": "google", + "keyword": "telefon qiymetleri 2018", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-22 06:14:05 +00:00", + "competition": null, + "competition_level": null, + "cpc": null, + "search_volume": 0, + "low_top_of_page_bid": null, + "high_top_of_page_bid": null, + "categories": [ + 10007, + 10878, + 12171 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 0 + }, + { + "year": 2023, + "month": 1, + "search_volume": 0 + }, + { + "year": 2022, + "month": 12, + "search_volume": 0 + }, + { + "year": 2022, + "month": 11, + "search_volume": 0 + }, + { + "year": 2022, + "month": 10, + "search_volume": 0 + }, + { + "year": 2022, + "month": 9, + "search_volume": 0 + }, + { + "year": 2022, + "month": 8, + "search_volume": 0 + }, + { + "year": 2022, + "month": 7, + "search_volume": 0 + }, + { + "year": 2022, + "month": 6, + "search_volume": 0 + }, + { + "year": 2022, + "month": 5, + "search_volume": 0 + }, + { + "year": 2022, + "month": 4, + "search_volume": 0 + }, + { + "year": 2022, + "month": 3, + "search_volume": 0 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": null, + "detected_language": "hu", + "is_another_language": true + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-03-23 06:47:01 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": null, + "ad_position_max": null, + "ad_position_average": null, + "cpc_min": null, + "cpc_max": null, + "cpc_average": null, + "daily_impressions_min": null, + "daily_impressions_max": null, + "daily_impressions_average": null, + "daily_clicks_min": null, + "daily_clicks_max": null, + "daily_clicks_average": null, + "daily_cost_min": null, + "daily_cost_max": null, + "daily_cost_average": null + }, + "serp_info": null, + "avg_backlinks_info": null, + "search_intent_info": { + "se_type": "google", + "main_intent": "transactional", + "foreign_intent": null, + "last_updated_time": "2023-03-02 18:33:29 +00:00" + } + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/keyword-suggestions.json b/priv/fixtures/labs/google/keyword_research/keyword-suggestions.json new file mode 100644 index 0000000..e109570 --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/keyword-suggestions.json @@ -0,0 +1,489 @@ +{ + "version": "0.1.20221214", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.4117 sec.", + "cost": 0.0103, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03231835-1535-0399-0000-cb09ada1b499", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.3568 sec.", + "cost": 0.0103, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "keyword_suggestions", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "keyword_suggestions", + "se_type": "google", + "keyword": "phone", + "location_code": 2840, + "language_code": "en", + "include_serp_info": true, + "limit": 3 + }, + "result": [ + { + "se_type": "google", + "seed_keyword": "phone", + "seed_keyword_data": null, + "location_code": 2840, + "language_code": "en", + "total_count": 2582247, + "items_count": 3, + "offset": 0, + "offset_token": "eyJDdXJyZW50T2Zmc2V0IjozLCJSZXF1ZXN0RGF0YSI6eyJrZXl3b3JkIjoicGhvbmUiLCJpbmNsdWRlX3NlZWRfa2V5d29yZCI6ZmFsc2UsImZ1bGxfbWF0Y2giOmZhbHNlLCJsb2FkX3NlcnBfaW5mbyI6dHJ1ZSwic2VhcmNoX2FmdGVyX3Rva2VuIjpudWxsLCJpZ25vcmVfc3lub255bXMiOmZhbHNlLCJsYW5ndWFnZSI6ImVuIiwic2VhcmNoX2VuZ2luZSI6Imdvb2dsZSIsImxvY2F0aW9uIjoyODQwLCJvcmRlcl9ieSI6eyJvcmRlcl9maWVsZCI6ImtleXdvcmRfaW5mby5zZWFyY2hfdm9sdW1lIiwib3JkZXJfdHlwZSI6IkRlc2MiLCJuZXh0IjpudWxsfSwibGltaXQiOjMsIm9mZnNldCI6MCwiYWlkIjoxNTM1fSwiUmF3UXVlcnkiOm51bGwsIlNlYXJjaEFmdGVyRGF0YSI6WzY3MzAwMCwiMTE0YzI3MGYtNWUxYi1kZTk4LTQ3NzMtMWRiZDY0OTgzZDM4Il19", + "items": [ + { + "se_type": "google", + "keyword": "the black phone", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-21 06:47:08 +00:00", + "competition": null, + "competition_level": "LOW", + "cpc": 0.55, + "search_volume": 1220000, + "low_top_of_page_bid": 0.27, + "high_top_of_page_bid": 0.55, + "categories": [ + 10021, + 10176, + 10915, + 13779 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 201000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 450000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 1000000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 2240000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 4090000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 4090000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 135000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 68, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-25 03:54:48 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.12, + "ad_position_max": 1, + "ad_position_average": 1.06, + "cpc_min": 27.35, + "cpc_max": 33.43, + "cpc_average": 30.39, + "daily_impressions_min": 90.42, + "daily_impressions_max": 110.52, + "daily_impressions_average": 100.47, + "daily_clicks_min": 5.01, + "daily_clicks_max": 6.12, + "daily_clicks_average": 5.57, + "daily_cost_min": 152.23, + "daily_cost_max": 186.06, + "daily_cost_average": 169.15 + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=the%20black%20phone&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "video", + "people_also_ask", + "organic", + "people_also_search", + "related_searches", + "knowledge_graph" + ], + "se_results_count": 8150000000, + "last_updated_time": "2023-03-13 07:08:36 +00:00", + "previous_updated_time": "2023-02-06 16:29:55 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 1546.5, + "dofollow": 1195.8, + "referring_pages": 1384.9, + "referring_domains": 268.4, + "referring_main_domains": 244.6, + "rank": 237.2, + "main_domain_rank": 634.8, + "last_updated_time": "2023-03-13 07:08:39 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "commercial", + "foreign_intent": null, + "last_updated_time": "2023-03-03 18:50:59 +00:00" + } + }, + { + "se_type": "google", + "keyword": "black phone", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-22 15:04:16 +00:00", + "competition": 0.02, + "competition_level": "LOW", + "cpc": 6.38, + "search_volume": 823000, + "low_top_of_page_bid": 0.31, + "high_top_of_page_bid": 6.38, + "categories": [ + 10007, + 10019, + 10167, + 10878, + 12161, + 13055, + 13381 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 135000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 201000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 201000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 550000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 823000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 1830000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 3350000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 2240000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 201000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 74000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 49500 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 72, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-17 07:38:20 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.11, + "ad_position_max": 1, + "ad_position_average": 1.06, + "cpc_min": 39.02, + "cpc_max": 47.69, + "cpc_average": 43.36, + "daily_impressions_min": 14.39, + "daily_impressions_max": 17.59, + "daily_impressions_average": 15.99, + "daily_clicks_min": 0.73, + "daily_clicks_max": 0.89, + "daily_clicks_average": 0.81, + "daily_cost_min": 31.49, + "daily_cost_max": 38.49, + "daily_cost_average": 34.99 + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=black%20phone&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "video", + "people_also_ask", + "organic", + "people_also_search", + "related_searches", + "knowledge_graph" + ], + "se_results_count": 10040000000, + "last_updated_time": "2023-03-13 07:11:45 +00:00", + "previous_updated_time": "2023-02-06 16:30:25 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 1567.3, + "dofollow": 1212, + "referring_pages": 1400.4, + "referring_domains": 275.8, + "referring_main_domains": 251.2, + "rank": 248.2, + "main_domain_rank": 650.5, + "last_updated_time": "2023-03-13 07:11:45 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "commercial", + "foreign_intent": null, + "last_updated_time": "2023-03-03 20:27:03 +00:00" + } + }, + { + "se_type": "google", + "keyword": "find to my phone", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-21 00:31:34 +00:00", + "competition": 0.13, + "competition_level": "LOW", + "cpc": 1.37, + "search_volume": 673000, + "low_top_of_page_bid": 0.53, + "high_top_of_page_bid": 1.37, + "categories": [ + 10019, + 10167, + 12153 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 550000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 673000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 823000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 823000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 823000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 823000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 823000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 823000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": "find my phone", + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 100, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-03-22 08:34:45 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.11, + "ad_position_max": 1, + "ad_position_average": 1.06, + "cpc_min": 68.97, + "cpc_max": 84.3, + "cpc_average": 76.63, + "daily_impressions_min": 0.15, + "daily_impressions_max": 0.18, + "daily_impressions_average": 0.16, + "daily_clicks_min": 0.01, + "daily_clicks_max": 0.02, + "daily_clicks_average": 0.01, + "daily_cost_min": 0.98, + "daily_cost_max": 1.2, + "daily_cost_average": 1.09 + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=find%20to%20my%20phone&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "organic", + "people_also_ask", + "people_also_search", + "related_searches", + "knowledge_graph" + ], + "se_results_count": 10960000000, + "last_updated_time": "2023-02-06 16:30:24 +00:00", + "previous_updated_time": "2022-10-20 13:54:33 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 47525.7, + "dofollow": 26457.9, + "referring_pages": 30839.7, + "referring_domains": 2395, + "referring_main_domains": 1958.1, + "rank": 387.3, + "main_domain_rank": 841.3, + "last_updated_time": "2023-02-06 16:31:22 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "commercial", + "foreign_intent": [ + "navigational" + ], + "last_updated_time": "2023-03-02 07:46:22 +00:00" + } + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/keywords-for-site-null-result.json b/priv/fixtures/labs/google/keyword_research/keywords-for-site-null-result.json new file mode 100644 index 0000000..d61c8a9 --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/keywords-for-site-null-result.json @@ -0,0 +1,46 @@ +{ + "version": "0.1.20221214", + "status_code": 20000, + "status_message": "Ok.", + "time": "2.3689 sec.", + "cost": 0.0103, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03231831-1535-0398-0000-86e0a30305cb", + "status_code": 20000, + "status_message": "Ok.", + "time": "2.3189 sec.", + "cost": 0.0103, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "keywords_for_site", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "keywords_for_site", + "se_type": "google", + "target": "apple.com", + "language_code": "en", + "location_code": 2840, + "include_serp_info": true, + "include_subdomains": true, + "filters": [ + "serp_info.se_results_count", + ">", + 0 + ], + "order_by": [ + "keyword_info.search_volume,desc" + ], + "limit": 3 + }, + "result": null + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/keywords-for-site.json b/priv/fixtures/labs/google/keyword_research/keywords-for-site.json new file mode 100644 index 0000000..124df2f --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/keywords-for-site.json @@ -0,0 +1,490 @@ +{ + "version": "0.1.20221214", + "status_code": 20000, + "status_message": "Ok.", + "time": "2.3689 sec.", + "cost": 0.0103, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03231831-1535-0398-0000-86e0a30305cb", + "status_code": 20000, + "status_message": "Ok.", + "time": "2.3189 sec.", + "cost": 0.0103, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "keywords_for_site", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "keywords_for_site", + "se_type": "google", + "target": "apple.com", + "language_code": "en", + "location_code": 2840, + "include_serp_info": true, + "include_subdomains": true, + "filters": [ + "serp_info.se_results_count", + ">", + 0 + ], + "order_by": [ + "keyword_info.search_volume,desc" + ], + "limit": 3 + }, + "result": [ + { + "se_type": "google", + "target": "apple.com", + "location_code": 2840, + "language_code": "en", + "total_count": 6883566, + "items_count": 3, + "offset": 0, + "offset_token": "eyJDdXJyZW50T2Zmc2V0IjozLCJSZXF1ZXN0RGF0YSI6eyJzaXRlIjoiYXBwbGUuY29tIiwibG9jYXRpb24iOjI4NDAsImxhbmd1YWdlIjoiZW4iLCJpbmNsdWRlX3N1YmRvbWFpbnMiOnRydWUsImxvYWRfc2VycF9pbmZvIjp0cnVlLCJzZWFyY2hfYWZ0ZXJfdG9rZW4iOm51bGwsImlnbm9yZV9zeW5vbnltcyI6ZmFsc2UsInNlYXJjaF9lbmdpbmUiOiJnb29nbGUiLCJvcmRlcl9ieSI6eyJvcmRlcl9maWVsZCI6ImtleXdvcmRfaW5mby5zZWFyY2hfdm9sdW1lIiwib3JkZXJfdHlwZSI6IkRlc2MiLCJuZXh0IjpudWxsfSwibGltaXQiOjMsIm9mZnNldCI6MCwiYWlkIjoxNTM1fSwiUmF3UXVlcnkiOnsiZmllbGQiOiJzZXJwX2luZm8uc2VfcmVzdWx0c19jb3VudCIsInR5cGUiOiJndCIsInZhbHVlIjowfSwiU2VhcmNoQWZ0ZXJEYXRhIjpbMTg1MDAwMDAwLCI0YjYzMDYzOS1iYTNkLTg0Y2QtMjVlYS05M2MwYTVhZWQ4OWYiXX0=", + "items": [ + { + "se_type": "google", + "keyword": "you tuh", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-22 02:01:09 +00:00", + "competition": null, + "competition_level": "LOW", + "cpc": 0.04, + "search_volume": 185000000, + "low_top_of_page_bid": 0.01, + "high_top_of_page_bid": 0.04, + "categories": [ + 10108, + 13691 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 151000000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 151000000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 151000000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 185000000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 18, + "detected_language": null, + "is_another_language": true + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": null, + "bid": null, + "match_type": null, + "ad_position_min": null, + "ad_position_max": null, + "ad_position_average": null, + "cpc_min": null, + "cpc_max": null, + "cpc_average": null, + "daily_impressions_min": null, + "daily_impressions_max": null, + "daily_impressions_average": null, + "daily_clicks_min": null, + "daily_clicks_max": null, + "daily_clicks_average": null, + "daily_cost_min": null, + "daily_cost_max": null, + "daily_cost_average": null + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=you%20tuh&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "video", + "organic", + "images", + "related_searches", + "knowledge_graph" + ], + "se_results_count": 29800000, + "last_updated_time": "2023-03-01 09:44:41 +00:00", + "previous_updated_time": "2023-01-26 20:00:01 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 2.1, + "dofollow": 0.2, + "referring_pages": 1.5, + "referring_domains": 1.2, + "referring_main_domains": 1.2, + "rank": 14.4, + "main_domain_rank": 626.1, + "last_updated_time": "2023-03-01 09:44:43 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "informational", + "foreign_intent": null, + "last_updated_time": "2023-03-03 16:01:12 +00:00" + } + }, + { + "se_type": "google", + "keyword": "you", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-21 07:07:46 +00:00", + "competition": null, + "competition_level": "LOW", + "cpc": 0.04, + "search_volume": 185000000, + "low_top_of_page_bid": 0.01, + "high_top_of_page_bid": 0.04, + "categories": [ + 10013, + 10109, + 13546 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 151000000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 151000000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 151000000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 185000000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 23, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-17 18:54:48 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.11, + "ad_position_max": 1, + "ad_position_average": 1.06, + "cpc_min": 2.69, + "cpc_max": 3.29, + "cpc_average": 2.99, + "daily_impressions_min": 515.37, + "daily_impressions_max": 629.9, + "daily_impressions_average": 572.63, + "daily_clicks_min": 55.58, + "daily_clicks_max": 67.93, + "daily_clicks_average": 61.75, + "daily_cost_min": 166.29, + "daily_cost_max": 203.25, + "daily_cost_average": 184.77 + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=you&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "organic", + "video", + "twitter", + "images", + "related_searches", + "knowledge_graph" + ], + "se_results_count": 100, + "last_updated_time": "2023-03-01 09:44:24 +00:00", + "previous_updated_time": "2023-01-26 19:59:58 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 3962.6, + "dofollow": 2203, + "referring_pages": 2590.4, + "referring_domains": 418.5, + "referring_main_domains": 307.3, + "rank": 180.7, + "main_domain_rank": 930.8, + "last_updated_time": "2023-03-01 09:44:45 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "informational", + "foreign_intent": [ + "navigational" + ], + "last_updated_time": "2023-03-03 15:55:21 +00:00" + } + }, + { + "se_type": "google", + "keyword": "yoututbe", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-22 06:16:56 +00:00", + "competition": null, + "competition_level": "LOW", + "cpc": 0.04, + "search_volume": 185000000, + "low_top_of_page_bid": 0.01, + "high_top_of_page_bid": 0.04, + "categories": [ + 10108, + 13691 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 151000000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 151000000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 151000000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 185000000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 185000000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 100, + "detected_language": null, + "is_another_language": true + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": null, + "bid": null, + "match_type": null, + "ad_position_min": null, + "ad_position_max": null, + "ad_position_average": null, + "cpc_min": null, + "cpc_max": null, + "cpc_average": null, + "daily_impressions_min": null, + "daily_impressions_max": null, + "daily_impressions_average": null, + "daily_clicks_min": null, + "daily_clicks_max": null, + "daily_clicks_average": null, + "daily_cost_min": null, + "daily_cost_max": null, + "daily_cost_average": null + }, + "serp_info": { + "se_type": "google", + "check_url": "https://www.google.com/search?q=yoututbe&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "serp_item_types": [ + "organic", + "twitter", + "top_stories", + "video", + "related_searches", + "knowledge_graph" + ], + "se_results_count": 52, + "last_updated_time": "2022-05-13 13:37:40 +00:00", + "previous_updated_time": "2022-04-12 13:31:22 +00:00" + }, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 43194108.1, + "dofollow": 28723980.5, + "referring_pages": 34785918, + "referring_domains": 64957.8, + "referring_main_domains": 50468.6, + "rank": 670.8333129882812, + "main_domain_rank": 891.3, + "last_updated_time": "2022-09-15 20:06:37 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "navigational", + "foreign_intent": null, + "last_updated_time": "2023-03-03 16:23:38 +00:00" + } + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/related-keywords.json b/priv/fixtures/labs/google/keyword_research/related-keywords.json new file mode 100644 index 0000000..507e58c --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/related-keywords.json @@ -0,0 +1,489 @@ +{ + "version": "0.1.20221214", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.1020 sec.", + "cost": 0.0103, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "03210009-4426-0387-0000-5a33a48f3334", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0514 sec.", + "cost": 0.0103, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "related_keywords", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "related_keywords", + "se_type": "google", + "keyword": "phone", + "language_name": "English", + "location_code": 2840, + "limit": 3 + }, + "result": [ + { + "se_type": "google", + "seed_keyword": "phone", + "seed_keyword_data": null, + "location_code": 2840, + "language_code": "en", + "total_count": 8, + "items_count": 3, + "items": [ + { + "se_type": "google", + "keyword_data": { + "se_type": "google", + "keyword": "phone", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-16 07:27:50 +00:00", + "competition": 1, + "competition_level": "HIGH", + "cpc": 4.53, + "search_volume": 368000, + "low_top_of_page_bid": 1.53, + "high_top_of_page_bid": 4.53, + "categories": [ + 10007, + 10878, + 12171 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 301000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 368000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": "phone", + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 100, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-25 10:51:53 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.32, + "ad_position_max": 1, + "ad_position_average": 1.2, + "cpc_min": 339.24, + "cpc_max": 414.63, + "cpc_average": 376.94, + "daily_impressions_min": 3778.83, + "daily_impressions_max": 4618.57, + "daily_impressions_average": 4198.7, + "daily_clicks_min": 123.94, + "daily_clicks_max": 151.48, + "daily_clicks_average": 137.71, + "daily_cost_min": 46717.04, + "daily_cost_max": 57098.6, + "daily_cost_average": 51907.82 + }, + "serp_info": null, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 16546.7, + "dofollow": 5534.6, + "referring_pages": 11223.3, + "referring_domains": 544.6, + "referring_main_domains": 475.6, + "rank": 340.1, + "main_domain_rank": 688.4, + "last_updated_time": "2023-03-13 07:14:56 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "navigational", + "foreign_intent": [ + "commercial" + ], + "last_updated_time": "2023-03-02 03:54:21 +00:00" + } + }, + "depth": 0, + "related_keywords": [ + "phone call", + "phone, samsung", + "phone number", + "phone call app", + "phone app", + "my phone", + "phone google", + "phone meaning" + ] + }, + { + "se_type": "google", + "keyword_data": { + "se_type": "google", + "keyword": "phone call", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-14 06:38:51 +00:00", + "competition": 1, + "competition_level": "HIGH", + "cpc": 1.78, + "search_volume": 301000, + "low_top_of_page_bid": 0.44, + "high_top_of_page_bid": 1.78, + "categories": [ + 10007, + 10878, + 11510, + 12762, + 13419 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 201000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 246000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 246000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 246000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 368000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": "phone call", + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 80, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-19 09:16:51 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.29, + "ad_position_max": 1, + "ad_position_average": 1.17, + "cpc_min": 333.03, + "cpc_max": 407.04, + "cpc_average": 370.04, + "daily_impressions_min": 169.51, + "daily_impressions_max": 207.18, + "daily_impressions_average": 188.34, + "daily_clicks_min": 35.27, + "daily_clicks_max": 43.1, + "daily_clicks_average": 39.18, + "daily_cost_min": 13049.67, + "daily_cost_max": 15949.6, + "daily_cost_average": 14499.63 + }, + "serp_info": null, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 6012.9, + "dofollow": 2240.2, + "referring_pages": 5117.3, + "referring_domains": 969, + "referring_main_domains": 819, + "rank": 262, + "main_domain_rank": 840, + "last_updated_time": "2023-03-13 07:14:38 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "commercial", + "foreign_intent": null, + "last_updated_time": "2023-03-02 03:54:30 +00:00" + } + }, + "depth": 1, + "related_keywords": [ + "phone call online", + "phone call app", + "phone call list", + "phone call from computer", + "free phone call", + "phone dial to call", + "phone call app download", + "make a phone call to someone" + ] + }, + { + "se_type": "google", + "keyword_data": { + "se_type": "google", + "keyword": "phone number", + "location_code": 2840, + "language_code": "en", + "keyword_info": { + "se_type": "google", + "last_updated_time": "2023-03-13 15:00:09 +00:00", + "competition": 0.07, + "competition_level": "LOW", + "cpc": 1.48, + "search_volume": 301000, + "low_top_of_page_bid": 0.39, + "high_top_of_page_bid": 1.48, + "categories": [ + 10007, + 10108, + 10756, + 11498, + 13418, + 13710 + ], + "monthly_searches": [ + { + "year": 2023, + "month": 2, + "search_volume": 201000 + }, + { + "year": 2023, + "month": 1, + "search_volume": 246000 + }, + { + "year": 2022, + "month": 12, + "search_volume": 246000 + }, + { + "year": 2022, + "month": 11, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 10, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 9, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 8, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 7, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 6, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 5, + "search_volume": 368000 + }, + { + "year": 2022, + "month": 4, + "search_volume": 301000 + }, + { + "year": 2022, + "month": 3, + "search_volume": 368000 + } + ] + }, + "keyword_properties": { + "se_type": "google", + "core_keyword": null, + "synonym_clustering_algorithm": "text_processing", + "keyword_difficulty": 69, + "detected_language": "en", + "is_another_language": false + }, + "impressions_info": { + "se_type": "google", + "last_updated_time": "2022-04-17 06:40:51 +00:00", + "bid": 999, + "match_type": "exact", + "ad_position_min": 1.15, + "ad_position_max": 1, + "ad_position_average": 1.08, + "cpc_min": 184.47, + "cpc_max": 225.47, + "cpc_average": 204.97, + "daily_impressions_min": 2041.07, + "daily_impressions_max": 2494.65, + "daily_impressions_average": 2267.86, + "daily_clicks_min": 137.9, + "daily_clicks_max": 168.54, + "daily_clicks_average": 153.22, + "daily_cost_min": 28264.79, + "daily_cost_max": 34545.86, + "daily_cost_average": 31405.33 + }, + "serp_info": null, + "avg_backlinks_info": { + "se_type": "google", + "backlinks": 12021.4, + "dofollow": 4286.8, + "referring_pages": 9216.5, + "referring_domains": 1228, + "referring_main_domains": 1053.1, + "rank": 293.4, + "main_domain_rank": 731.1, + "last_updated_time": "2023-03-13 07:15:43 +00:00" + }, + "search_intent_info": { + "se_type": "google", + "main_intent": "informational", + "foreign_intent": [ + "navigational" + ], + "last_updated_time": "2023-03-02 03:55:07 +00:00" + } + }, + "depth": 1, + "related_keywords": [ + "phone number free", + "phone number generator", + "phone numbers that work", + "phone number check", + "phone number game", + "my phone number", + "phone number example", + "whose telephone number is this" + ] + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/labs/google/keyword_research/search-intent.json b/priv/fixtures/labs/google/keyword_research/search-intent.json new file mode 100644 index 0000000..98b0d10 --- /dev/null +++ b/priv/fixtures/labs/google/keyword_research/search-intent.json @@ -0,0 +1,87 @@ +{ + "version": "0.1.20221214", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.1285 sec.", + "cost": 0.0014, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "02271900-1535-0541-0000-2ab771deb8e2", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0253 sec.", + "cost": 0.0014, + "result_count": 1, + "path": [ + "v3", + "dataforseo_labs", + "google", + "search_intent", + "live" + ], + "data": { + "api": "dataforseo_labs", + "function": "search_intent", + "se_type": "google", + "language_code": "en", + "keywords": [ + "login page", + "audi a7", + "elon musk", + "milk" + ] + }, + "result": [ + { + "language_code": "en", + "items_count": 4, + "items": [ + { + "keyword": "login page", + "keyword_intent": { + "label": "navigational", + "probability": 0.9694191 + }, + "secondary_keyword_intents": null + }, + { + "keyword": "audi a7", + "keyword_intent": { + "label": "commercial", + "probability": 1 + }, + "secondary_keyword_intents": null + }, + { + "keyword": "elon musk", + "keyword_intent": { + "label": "informational", + "probability": 0.95328856 + }, + "secondary_keyword_intents": null + }, + { + "keyword": "milk", + "keyword_intent": { + "label": "commercial", + "probability": 0.49751797 + }, + "secondary_keyword_intents": [ + { + "label": "transactional", + "probability": 0.37083894 + }, + { + "label": "informational", + "probability": 0.30781138 + } + ] + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/serp/bing/organic/task-get-regular.json b/priv/fixtures/serp/bing/organic/task-get-regular.json new file mode 100644 index 0000000..f7c21d2 --- /dev/null +++ b/priv/fixtures/serp/bing/organic/task-get-regular.json @@ -0,0 +1,151 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.3059 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11151456-0696-0066-0000-002a5915da37", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0952 sec.", + "cost": 0, + "result_count": 1, + "path": [ + "v3", + "serp", + "bing", + "organic", + "task_get", + "regular", + "11151456-0696-0066-0000-002a5915da37" + ], + "data": { + "api": "serp", + "function": "task_get", + "se": "bing", + "se_type": "organic", + "language_name": "English", + "location_name": "United States", + "keyword": "flight ticket new york san francisco", + "priority": "2", + "tag": "tag2", + "device": "desktop", + "os": "windows" + }, + "result": [ + { + "keyword": "flight ticket new york san francisco", + "type": "organic", + "se_domain": "bing.com", + "location_code": 2840, + "language_code": "en", + "check_url": "https://www.bing.com/search?q=flight%20ticket%20new%20york%20san%20francisco&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "datetime": "2019-11-15 12:57:46 +00:00", + "spell": null, + "item_types": [ + "organic", + "paid" + ], + "se_results_count": 85600000, + "items_count": 96, + "items": [ + { + "type": "paid", + "rank_group": 1, + "rank_absolute": 1, + "domain": "www.bookingbuddy.com", + "title": "Flights To Lwo | Unbelievably Cheap Flights | BookingBuddy.com‎", + "description": "Compare Airlines & Sites. Cheap Flights on BookingBuddy, a TripAdvisor Company", + "url": "https://www.bookingbuddy.com/en/hero/", + "breadcrumb": "www.bookingbuddy.com/Flights" + }, + { + "type": "paid", + "rank_group": 2, + "rank_absolute": 2, + "domain": "www.trip.com", + "title": "Cheap Flight Tickets | Search & Find Deals on Flights | trip.com‎", + "description": "Wide Selection of Cheap Flights Online. Explore & Save with Trip.com! Fast, Easy & Secure...", + "url": "https://www.trip.com/flights/index?utm_campaign=GG_SE_All_en_Flight_Generic_NA_Phrase", + "breadcrumb": "www.trip.com/" + }, + { + "type": "paid", + "rank_group": 3, + "rank_absolute": 4, + "domain": "www.kayak.com", + "title": "Find the Cheapest Flights | Search, Compare & Save Today‎", + "description": "Cheap Flights, Airline Tickets and Flight Deals. Compare 100s of Airlines Worldwide. Search...", + "url": "https://www.kayak.com/horizon/sem/flights/general", + "breadcrumb": "www.kayak.com/flights" + }, + { + "type": "organic", + "rank_group": 1, + "rank_absolute": 5, + "domain": "www.kayak.com", + "title": "Cheap Flights from New York to San Francisco from $182 ...", + "description": "Fly from New York to San Francisco on Frontier from $182, United Airlines from ... the cheapest round-trip tickets were found on Frontier ($182), United Airlines ...", + "url": "https://www.kayak.com/flight-routes/New-York-NYC/San-Francisco-SFO", + "breadcrumb": "https://www.kayak.com › Flights › North America › United States › California" + }, + { + "type": "organic", + "rank_group": 2, + "rank_absolute": 6, + "domain": "www.skyscanner.com", + "title": "Cheap flights from New York to San Francisco SFO from $123 ...", + "description": "Flight information New York to San Francisco International .... tool will help you find the cheapest tickets from New York in San Francisco in just a few clicks.", + "url": "https://www.skyscanner.com/routes/nyca/sfo/new-york-to-san-francisco-international.html", + "breadcrumb": "https://www.skyscanner.com › United States › New York" + }, + { + "type": "organic", + "rank_group": 3, + "rank_absolute": 7, + "domain": "www.expedia.com", + "title": "JFK to SFO: Flights from New York to San Francisco for 2019 ...", + "description": "Book your New York (JFK) to San Francisco (SFO) flight with our Best Price ... How much is a plane ticket to San Francisco (SFO) from New York (JFK)?.", + "url": "https://www.expedia.com/lp/flights/jfk/sfo/new-york-to-san-francisco", + "breadcrumb": "https://www.expedia.com › flights › jfk › sfo › new-york-to-san-francisco" + }, + { + "type": "organic", + "rank_group": 94, + "rank_absolute": 97, + "domain": "www.ethiopianairlines.com", + "title": "Ethiopian Airlines | Book your next flight online and Fly Ethiopian", + "description": "Fly to your Favorite International Destination with Ethiopian Airlines. Book your Flights Online for Best Offers/Discounts and Enjoy African Flavored Hospitality.", + "url": "https://www.ethiopianairlines.com/", + "breadcrumb": "https://www.ethiopianairlines.com" + }, + { + "type": "organic", + "rank_group": 95, + "rank_absolute": 98, + "domain": "www.vietnamairlines.com", + "title": "Vietnam Airlines | Reach Further | Official website", + "description": "Great value fares with Vietnam Airlines. Book today and save! Skytrax – 4 Star airline. Official website. Earn frequent flyer miles with Lotusmiles.", + "url": "https://www.vietnamairlines.com/", + "breadcrumb": "https://www.vietnamairlines.com" + }, + { + "type": "organic", + "rank_group": 96, + "rank_absolute": 99, + "domain": "books.bing.com", + "title": "Code of Federal Regulations: 1985-1999", + "description": "A purchases in New York a round-trip ticket for transportation by air from New York to ... B purchases a ticket in San Francisco for Combination rail and water ...", + "url": "https://books.bing.com/books?id=av3umFsqbAEC&pg=PA305&lpg=PA305&dq=flight+ticket+new+york+san+francisco&source=bl&ots=fJJY5RUS9l&sig=ACfU3U16ejUqNf23jHD32QNCxDCa05Vn9g&hl=en&ppis=_e&sa=X&ved=2ahUKEwjs_4OnouzlAhXJ4zgGHeBcD3oQ6AEwdXoECHEQAQ", + "breadcrumb": "https://books.bing.com › books" + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/serp/bing/organic/task-post-single.json b/priv/fixtures/serp/bing/organic/task-post-single.json new file mode 100644 index 0000000..2d287a6 --- /dev/null +++ b/priv/fixtures/serp/bing/organic/task-post-single.json @@ -0,0 +1,38 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0818 sec.", + "cost": 0.0045, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "01291721-1535-0066-0000-8f0635c0dc89", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0038 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "bing", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "bing", + "se_type": "organic", + "language_code": "en", + "location_code": 2840, + "keyword": "Schrauben", + "device": "desktop", + "os": "windows" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/serp/bing/organic/task-post.json b/priv/fixtures/serp/bing/organic/task-post.json new file mode 100644 index 0000000..10d1bdc --- /dev/null +++ b/priv/fixtures/serp/bing/organic/task-post.json @@ -0,0 +1,68 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0818 sec.", + "cost": 0.0045, + "tasks_count": 2, + "tasks_error": 0, + "tasks": [ + { + "id": "01291721-1535-0066-0000-8f0635c0dc89", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0038 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "bing", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "bing", + "se_type": "organic", + "language_code": "en", + "location_code": 2840, + "keyword": "Schrauben", + "device": "desktop", + "os": "windows" + }, + "result": null + }, + { + "id": "01291721-1535-0066-0000-2e7a8bf7302c", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0050 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "bing", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "bing", + "se_type": "organic", + "language_name": "English", + "location_name": "United States", + "keyword": "Blumen", + "priority":2, + "pingback_url": "https://your-server.com/pingscript?id=$id&tag=$tag", + "tag": "some_string_123", + "device": "desktop", + "os": "windows" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/serp/bing/organic/tasks-ready.json b/priv/fixtures/serp/bing/organic/tasks-ready.json new file mode 100644 index 0000000..130f204 --- /dev/null +++ b/priv/fixtures/serp/bing/organic/tasks-ready.json @@ -0,0 +1,52 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.2270 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11151406-0696-0087-0000-e781cb0144a1", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0388 sec.", + "cost": 0, + "result_count": 2, + "path": [ + "v3", + "serp", + "bing", + "organic", + "tasks_ready" + ], + "data": { + "api": "serp", + "function": "tasks_ready", + "se": "bing", + "se_type": "organic" + }, + "result": [ + { + "id": "11081554-0696-0066-0000-27e68ec15871", + "se": "bing", + "se_type": "organic", + "date_posted": "2019-11-08 13:54:43 +00:00", + "endpoint_regular": "/v3/serp/bing/organic/task_get/regular/11081554-0696-0066-0000-27e68ec15871", + "endpoint_advanced": "/v3/serp/bing/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", + "endpoint_html": "/v3/serp/bing/organic/task_get/html/11081554-0696-0066-0000-27e68ec15871" + }, + { + "id": "11151406-0696-0066-0000-c4ece317cdb2", + "se": "bing", + "se_type": "organic", + "date_posted": "2019-11-15 12:06:09 +00:00", + "endpoint_regular": "/v3/serp/bing/organic/task_get/regular/11151406-0696-0066-0000-c4ece317cdb2", + "endpoint_advanced": "/v3/serp/bing/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", + "endpoint_html": "/v3/serp/bing/organic/task_get/html/11151406-0696-0066-0000-c4ece317cdb2" + } + ] + } + ] +} diff --git a/priv/fixtures/serp/google/locations-lu.json b/priv/fixtures/serp/google/locations-lu.json new file mode 100644 index 0000000..4a57e35 --- /dev/null +++ b/priv/fixtures/serp/google/locations-lu.json @@ -0,0 +1,152 @@ +{ + "cost": 0, + "status_code": 20000, + "status_message": "Ok.", + "tasks_count": 1, + "tasks_error": 0, + "time": "0.0629 sec.", + "version": "0.1.20230825", + "tasks": [ + { + "cost": 0, + "data": { + "api": "serp", + "function": "locations", + "se": "google" + }, + "id": "11151921-6990-0096-0000-383626e07f8a", + "path": [ + "v3", + "serp", + "google", + "locations", + "lu" + ], + "result": [ + { + "country_iso_code": "LU", + "location_code": 2442, + "location_code_parent": null, + "location_name": "Luxembourg", + "location_type": "Country" + }, + { + "country_iso_code": "LU", + "location_code": 1009944, + "location_code_parent": 2442, + "location_name": "Kaerjeng,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 1009948, + "location_code_parent": 2442, + "location_name": "Bissen,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 1009953, + "location_code_parent": 2442, + "location_name": "Kirchberg,Luxembourg", + "location_type": "Neighborhood" + }, + { + "country_iso_code": "LU", + "location_code": 1009955, + "location_code_parent": 2442, + "location_name": "Mamer,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 1009958, + "location_code_parent": 2442, + "location_name": "Sanem,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 1009960, + "location_code_parent": 2442, + "location_name": "Strassen,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 1009964, + "location_code_parent": 2442, + "location_name": "Wormeldange,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067741, + "location_code_parent": 2442, + "location_name": "Clemency,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067742, + "location_code_parent": 2442, + "location_name": "Wiltz,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067743, + "location_code_parent": 2442, + "location_name": "Troisvierges,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067744, + "location_code_parent": 2442, + "location_name": "Munsbach,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067745, + "location_code_parent": 2442, + "location_name": "Ettelbruck,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067746, + "location_code_parent": 2442, + "location_name": "Bertrange,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067747, + "location_code_parent": 2442, + "location_name": "Bettembourg,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067748, + "location_code_parent": 2442, + "location_name": "Dudelange,Luxembourg", + "location_type": "City" + }, + { + "country_iso_code": "LU", + "location_code": 9067749, + "location_code_parent": 2442, + "location_name": "Luxembourg,Luxembourg", + "location_type": "City" + } + ], + "result_count": 17, + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0058 sec." + } + ] +} diff --git a/priv/fixtures/serp/google/organic/task-get-regular.json b/priv/fixtures/serp/google/organic/task-get-regular.json new file mode 100644 index 0000000..6afacc6 --- /dev/null +++ b/priv/fixtures/serp/google/organic/task-get-regular.json @@ -0,0 +1,151 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.3059 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11151456-0696-0066-0000-002a5915da37", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0952 sec.", + "cost": 0, + "result_count": 1, + "path": [ + "v3", + "serp", + "google", + "organic", + "task_get", + "regular", + "11151456-0696-0066-0000-002a5915da37" + ], + "data": { + "api": "serp", + "function": "task_get", + "se": "google", + "se_type": "organic", + "language_name": "English", + "location_name": "United States", + "keyword": "flight ticket new york san francisco", + "priority": "2", + "tag": "tag2", + "device": "desktop", + "os": "windows" + }, + "result": [ + { + "keyword": "flight ticket new york san francisco", + "type": "organic", + "se_domain": "google.com", + "location_code": 2840, + "language_code": "en", + "check_url": "https://www.google.com/search?q=flight%20ticket%20new%20york%20san%20francisco&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "datetime": "2019-11-15 12:57:46 +00:00", + "spell": null, + "item_types": [ + "organic", + "paid" + ], + "se_results_count": 85600000, + "items_count": 96, + "items": [ + { + "type": "paid", + "rank_group": 1, + "rank_absolute": 1, + "domain": "www.bookingbuddy.com", + "title": "Flights To Lwo | Unbelievably Cheap Flights | BookingBuddy.com‎", + "description": "Compare Airlines & Sites. Cheap Flights on BookingBuddy, a TripAdvisor Company", + "url": "https://www.bookingbuddy.com/en/hero/", + "breadcrumb": "www.bookingbuddy.com/Flights" + }, + { + "type": "paid", + "rank_group": 2, + "rank_absolute": 2, + "domain": "www.trip.com", + "title": "Cheap Flight Tickets | Search & Find Deals on Flights | trip.com‎", + "description": "Wide Selection of Cheap Flights Online. Explore & Save with Trip.com! Fast, Easy & Secure...", + "url": "https://www.trip.com/flights/index?utm_campaign=GG_SE_All_en_Flight_Generic_NA_Phrase", + "breadcrumb": "www.trip.com/" + }, + { + "type": "paid", + "rank_group": 3, + "rank_absolute": 4, + "domain": "www.kayak.com", + "title": "Find the Cheapest Flights | Search, Compare & Save Today‎", + "description": "Cheap Flights, Airline Tickets and Flight Deals. Compare 100s of Airlines Worldwide. Search...", + "url": "https://www.kayak.com/horizon/sem/flights/general", + "breadcrumb": "www.kayak.com/flights" + }, + { + "type": "organic", + "rank_group": 1, + "rank_absolute": 5, + "domain": "www.kayak.com", + "title": "Cheap Flights from New York to San Francisco from $182 ...", + "description": "Fly from New York to San Francisco on Frontier from $182, United Airlines from ... the cheapest round-trip tickets were found on Frontier ($182), United Airlines ...", + "url": "https://www.kayak.com/flight-routes/New-York-NYC/San-Francisco-SFO", + "breadcrumb": "https://www.kayak.com › Flights › North America › United States › California" + }, + { + "type": "organic", + "rank_group": 2, + "rank_absolute": 6, + "domain": "www.skyscanner.com", + "title": "Cheap flights from New York to San Francisco SFO from $123 ...", + "description": "Flight information New York to San Francisco International .... tool will help you find the cheapest tickets from New York in San Francisco in just a few clicks.", + "url": "https://www.skyscanner.com/routes/nyca/sfo/new-york-to-san-francisco-international.html", + "breadcrumb": "https://www.skyscanner.com › United States › New York" + }, + { + "type": "organic", + "rank_group": 3, + "rank_absolute": 7, + "domain": "www.expedia.com", + "title": "JFK to SFO: Flights from New York to San Francisco for 2019 ...", + "description": "Book your New York (JFK) to San Francisco (SFO) flight with our Best Price ... How much is a plane ticket to San Francisco (SFO) from New York (JFK)?.", + "url": "https://www.expedia.com/lp/flights/jfk/sfo/new-york-to-san-francisco", + "breadcrumb": "https://www.expedia.com › flights › jfk › sfo › new-york-to-san-francisco" + }, + { + "type": "organic", + "rank_group": 94, + "rank_absolute": 97, + "domain": "www.ethiopianairlines.com", + "title": "Ethiopian Airlines | Book your next flight online and Fly Ethiopian", + "description": "Fly to your Favorite International Destination with Ethiopian Airlines. Book your Flights Online for Best Offers/Discounts and Enjoy African Flavored Hospitality.", + "url": "https://www.ethiopianairlines.com/", + "breadcrumb": "https://www.ethiopianairlines.com" + }, + { + "type": "organic", + "rank_group": 95, + "rank_absolute": 98, + "domain": "www.vietnamairlines.com", + "title": "Vietnam Airlines | Reach Further | Official website", + "description": "Great value fares with Vietnam Airlines. Book today and save! Skytrax – 4 Star airline. Official website. Earn frequent flyer miles with Lotusmiles.", + "url": "https://www.vietnamairlines.com/", + "breadcrumb": "https://www.vietnamairlines.com" + }, + { + "type": "organic", + "rank_group": 96, + "rank_absolute": 99, + "domain": "books.google.com", + "title": "Code of Federal Regulations: 1985-1999", + "description": "A purchases in New York a round-trip ticket for transportation by air from New York to ... B purchases a ticket in San Francisco for Combination rail and water ...", + "url": "https://books.google.com/books?id=av3umFsqbAEC&pg=PA305&lpg=PA305&dq=flight+ticket+new+york+san+francisco&source=bl&ots=fJJY5RUS9l&sig=ACfU3U16ejUqNf23jHD32QNCxDCa05Vn9g&hl=en&ppis=_e&sa=X&ved=2ahUKEwjs_4OnouzlAhXJ4zgGHeBcD3oQ6AEwdXoECHEQAQ", + "breadcrumb": "https://books.google.com › books" + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/serp/google/organic/task-post-single.json b/priv/fixtures/serp/google/organic/task-post-single.json new file mode 100644 index 0000000..49264a9 --- /dev/null +++ b/priv/fixtures/serp/google/organic/task-post-single.json @@ -0,0 +1,38 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0818 sec.", + "cost": 0.0045, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "01291721-1535-0066-0000-8f0635c0dc89", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0038 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "google", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "google", + "se_type": "organic", + "language_code": "en", + "location_code": 2840, + "keyword": "Schrauben", + "device": "desktop", + "os": "windows" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/serp/google/organic/task-post.json b/priv/fixtures/serp/google/organic/task-post.json new file mode 100644 index 0000000..fbc58ab --- /dev/null +++ b/priv/fixtures/serp/google/organic/task-post.json @@ -0,0 +1,68 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0818 sec.", + "cost": 0.0045, + "tasks_count": 2, + "tasks_error": 0, + "tasks": [ + { + "id": "01291721-1535-0066-0000-8f0635c0dc89", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0038 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "google", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "google", + "se_type": "organic", + "language_code": "en", + "location_code": 2840, + "keyword": "Schrauben", + "device": "desktop", + "os": "windows" + }, + "result": null + }, + { + "id": "01291721-1535-0066-0000-2e7a8bf7302c", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0050 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "google", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "google", + "se_type": "organic", + "language_name": "English", + "location_name": "United States", + "keyword": "Blumen", + "priority":2, + "pingback_url": "https://your-server.com/pingscript?id=$id&tag=$tag", + "tag": "some_string_123", + "device": "desktop", + "os": "windows" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/serp/google/organic/tasks-ready.json b/priv/fixtures/serp/google/organic/tasks-ready.json new file mode 100644 index 0000000..40d71eb --- /dev/null +++ b/priv/fixtures/serp/google/organic/tasks-ready.json @@ -0,0 +1,52 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.2270 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11151406-0696-0087-0000-e781cb0144a1", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0388 sec.", + "cost": 0, + "result_count": 2, + "path": [ + "v3", + "serp", + "google", + "organic", + "tasks_ready" + ], + "data": { + "api": "serp", + "function": "tasks_ready", + "se": "google", + "se_type": "organic" + }, + "result": [ + { + "id": "11081554-0696-0066-0000-27e68ec15871", + "se": "google", + "se_type": "organic", + "date_posted": "2019-11-08 13:54:43 +00:00", + "endpoint_regular": "/v3/serp/google/organic/task_get/regular/11081554-0696-0066-0000-27e68ec15871", + "endpoint_advanced": "/v3/serp/google/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", + "endpoint_html": "/v3/serp/google/organic/task_get/html/11081554-0696-0066-0000-27e68ec15871" + }, + { + "id": "11151406-0696-0066-0000-c4ece317cdb2", + "se": "google", + "se_type": "organic", + "date_posted": "2019-11-15 12:06:09 +00:00", + "endpoint_regular": "/v3/serp/google/organic/task_get/regular/11151406-0696-0066-0000-c4ece317cdb2", + "endpoint_advanced": "/v3/serp/google/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", + "endpoint_html": "/v3/serp/google/organic/task_get/html/11151406-0696-0066-0000-c4ece317cdb2" + } + ] + } + ] +} diff --git a/priv/fixtures/serp/youtube/organic/task-get-advanced.json b/priv/fixtures/serp/youtube/organic/task-get-advanced.json new file mode 100644 index 0000000..506b1ed --- /dev/null +++ b/priv/fixtures/serp/youtube/organic/task-get-advanced.json @@ -0,0 +1,151 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.3059 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11151456-0696-0066-0000-002a5915da37", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0952 sec.", + "cost": 0, + "result_count": 1, + "path": [ + "v3", + "serp", + "youtube", + "organic", + "task_get", + "regular", + "11151456-0696-0066-0000-002a5915da37" + ], + "data": { + "api": "serp", + "function": "task_get", + "se": "youtube", + "se_type": "organic", + "language_name": "English", + "location_name": "United States", + "keyword": "flight ticket new york san francisco", + "priority": "2", + "tag": "tag2", + "device": "desktop", + "os": "windows" + }, + "result": [ + { + "keyword": "flight ticket new york san francisco", + "type": "organic", + "se_domain": "youtube.com", + "location_code": 2840, + "language_code": "en", + "check_url": "https://www.youtube.com/search?q=flight%20ticket%20new%20york%20san%20francisco&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + "datetime": "2019-11-15 12:57:46 +00:00", + "spell": null, + "item_types": [ + "organic", + "paid" + ], + "se_results_count": 85600000, + "items_count": 96, + "items": [ + { + "type": "paid", + "rank_group": 1, + "rank_absolute": 1, + "domain": "www.bookingbuddy.com", + "title": "Flights To Lwo | Unbelievably Cheap Flights | BookingBuddy.com‎", + "description": "Compare Airlines & Sites. Cheap Flights on BookingBuddy, a TripAdvisor Company", + "url": "https://www.bookingbuddy.com/en/hero/", + "breadcrumb": "www.bookingbuddy.com/Flights" + }, + { + "type": "paid", + "rank_group": 2, + "rank_absolute": 2, + "domain": "www.trip.com", + "title": "Cheap Flight Tickets | Search & Find Deals on Flights | trip.com‎", + "description": "Wide Selection of Cheap Flights Online. Explore & Save with Trip.com! Fast, Easy & Secure...", + "url": "https://www.trip.com/flights/index?utm_campaign=GG_SE_All_en_Flight_Generic_NA_Phrase", + "breadcrumb": "www.trip.com/" + }, + { + "type": "paid", + "rank_group": 3, + "rank_absolute": 4, + "domain": "www.kayak.com", + "title": "Find the Cheapest Flights | Search, Compare & Save Today‎", + "description": "Cheap Flights, Airline Tickets and Flight Deals. Compare 100s of Airlines Worldwide. Search...", + "url": "https://www.kayak.com/horizon/sem/flights/general", + "breadcrumb": "www.kayak.com/flights" + }, + { + "type": "organic", + "rank_group": 1, + "rank_absolute": 5, + "domain": "www.kayak.com", + "title": "Cheap Flights from New York to San Francisco from $182 ...", + "description": "Fly from New York to San Francisco on Frontier from $182, United Airlines from ... the cheapest round-trip tickets were found on Frontier ($182), United Airlines ...", + "url": "https://www.kayak.com/flight-routes/New-York-NYC/San-Francisco-SFO", + "breadcrumb": "https://www.kayak.com › Flights › North America › United States › California" + }, + { + "type": "organic", + "rank_group": 2, + "rank_absolute": 6, + "domain": "www.skyscanner.com", + "title": "Cheap flights from New York to San Francisco SFO from $123 ...", + "description": "Flight information New York to San Francisco International .... tool will help you find the cheapest tickets from New York in San Francisco in just a few clicks.", + "url": "https://www.skyscanner.com/routes/nyca/sfo/new-york-to-san-francisco-international.html", + "breadcrumb": "https://www.skyscanner.com › United States › New York" + }, + { + "type": "organic", + "rank_group": 3, + "rank_absolute": 7, + "domain": "www.expedia.com", + "title": "JFK to SFO: Flights from New York to San Francisco for 2019 ...", + "description": "Book your New York (JFK) to San Francisco (SFO) flight with our Best Price ... How much is a plane ticket to San Francisco (SFO) from New York (JFK)?.", + "url": "https://www.expedia.com/lp/flights/jfk/sfo/new-york-to-san-francisco", + "breadcrumb": "https://www.expedia.com › flights › jfk › sfo › new-york-to-san-francisco" + }, + { + "type": "organic", + "rank_group": 94, + "rank_absolute": 97, + "domain": "www.ethiopianairlines.com", + "title": "Ethiopian Airlines | Book your next flight online and Fly Ethiopian", + "description": "Fly to your Favorite International Destination with Ethiopian Airlines. Book your Flights Online for Best Offers/Discounts and Enjoy African Flavored Hospitality.", + "url": "https://www.ethiopianairlines.com/", + "breadcrumb": "https://www.ethiopianairlines.com" + }, + { + "type": "organic", + "rank_group": 95, + "rank_absolute": 98, + "domain": "www.vietnamairlines.com", + "title": "Vietnam Airlines | Reach Further | Official website", + "description": "Great value fares with Vietnam Airlines. Book today and save! Skytrax – 4 Star airline. Official website. Earn frequent flyer miles with Lotusmiles.", + "url": "https://www.vietnamairlines.com/", + "breadcrumb": "https://www.vietnamairlines.com" + }, + { + "type": "organic", + "rank_group": 96, + "rank_absolute": 99, + "domain": "books.youtube.com", + "title": "Code of Federal Regulations: 1985-1999", + "description": "A purchases in New York a round-trip ticket for transportation by air from New York to ... B purchases a ticket in San Francisco for Combination rail and water ...", + "url": "https://books.youtube.com/books?id=av3umFsqbAEC&pg=PA305&lpg=PA305&dq=flight+ticket+new+york+san+francisco&source=bl&ots=fJJY5RUS9l&sig=ACfU3U16ejUqNf23jHD32QNCxDCa05Vn9g&hl=en&ppis=_e&sa=X&ved=2ahUKEwjs_4OnouzlAhXJ4zgGHeBcD3oQ6AEwdXoECHEQAQ", + "breadcrumb": "https://books.youtube.com › books" + } + ] + } + ] + } + ] +} diff --git a/priv/fixtures/serp/youtube/organic/task-post-single.json b/priv/fixtures/serp/youtube/organic/task-post-single.json new file mode 100644 index 0000000..ded61e6 --- /dev/null +++ b/priv/fixtures/serp/youtube/organic/task-post-single.json @@ -0,0 +1,38 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0818 sec.", + "cost": 0.0045, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "01291721-1535-0066-0000-8f0635c0dc89", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0038 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "youtube", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "youtube", + "se_type": "organic", + "language_code": "en", + "location_code": 2840, + "keyword": "Schrauben", + "device": "desktop", + "os": "windows" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/serp/youtube/organic/task-post.json b/priv/fixtures/serp/youtube/organic/task-post.json new file mode 100644 index 0000000..161a513 --- /dev/null +++ b/priv/fixtures/serp/youtube/organic/task-post.json @@ -0,0 +1,68 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0818 sec.", + "cost": 0.0045, + "tasks_count": 2, + "tasks_error": 0, + "tasks": [ + { + "id": "01291721-1535-0066-0000-8f0635c0dc89", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0038 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "youtube", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "youtube", + "se_type": "organic", + "language_code": "en", + "location_code": 2840, + "keyword": "Schrauben", + "device": "desktop", + "os": "windows" + }, + "result": null + }, + { + "id": "01291721-1535-0066-0000-2e7a8bf7302c", + "status_code": 20100, + "status_message": "Task Created.", + "time": "0.0050 sec.", + "cost": 0.0015, + "result_count": 0, + "path": [ + "v3", + "serp", + "youtube", + "organic", + "task_post" + ], + "data": { + "api": "serp", + "function": "task_post", + "se": "youtube", + "se_type": "organic", + "language_name": "English", + "location_name": "United States", + "keyword": "Blumen", + "priority":2, + "pingback_url": "https://your-server.com/pingscript?id=$id&tag=$tag", + "tag": "some_string_123", + "device": "desktop", + "os": "windows" + }, + "result": null + } + ] +} diff --git a/priv/fixtures/serp/youtube/organic/tasks-ready.json b/priv/fixtures/serp/youtube/organic/tasks-ready.json new file mode 100644 index 0000000..6848805 --- /dev/null +++ b/priv/fixtures/serp/youtube/organic/tasks-ready.json @@ -0,0 +1,52 @@ +{ + "version": "0.1.20200129", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.2270 sec.", + "cost": 0, + "tasks_count": 1, + "tasks_error": 0, + "tasks": [ + { + "id": "11151406-0696-0087-0000-e781cb0144a1", + "status_code": 20000, + "status_message": "Ok.", + "time": "0.0388 sec.", + "cost": 0, + "result_count": 2, + "path": [ + "v3", + "serp", + "youtube", + "organic", + "tasks_ready" + ], + "data": { + "api": "serp", + "function": "tasks_ready", + "se": "youtube", + "se_type": "organic" + }, + "result": [ + { + "id": "11081554-0696-0066-0000-27e68ec15871", + "se": "youtube", + "se_type": "organic", + "date_posted": "2019-11-08 13:54:43 +00:00", + "endpoint_regular": "/v3/serp/youtube/organic/task_get/regular/11081554-0696-0066-0000-27e68ec15871", + "endpoint_advanced": "/v3/serp/youtube/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", + "endpoint_html": "/v3/serp/youtube/organic/task_get/html/11081554-0696-0066-0000-27e68ec15871" + }, + { + "id": "11151406-0696-0066-0000-c4ece317cdb2", + "se": "youtube", + "se_type": "organic", + "date_posted": "2019-11-15 12:06:09 +00:00", + "endpoint_regular": "/v3/serp/youtube/organic/task_get/regular/11151406-0696-0066-0000-c4ece317cdb2", + "endpoint_advanced": "/v3/serp/youtube/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", + "endpoint_html": "/v3/serp/youtube/organic/task_get/html/11151406-0696-0066-0000-c4ece317cdb2" + } + ] + } + ] +} diff --git a/test/data_for_seo/api/keywords/google_ads/languages_test.exs b/test/data_for_seo/api/keywords/google_ads/languages_test.exs new file mode 100644 index 0000000..dc6a28d --- /dev/null +++ b/test/data_for_seo/api/keywords/google_ads/languages_test.exs @@ -0,0 +1,45 @@ +defmodule DataForSeo.Api.Keywords.GoogleAds.LanguagesTest do + use ExUnit.Case + + alias DataForSeo.API.Keywords.GoogleAds.Languages + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read languages", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_ads/languages" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_google_ads_languages() + ) + end) + + assert {:ok, resp} = Languages.get_all_languages() + + assert %{"tasks" => [%{"result" => languages}], "tasks_count" => 1} = resp + + assert Enum.any?( + languages, + &(&1["language_name"] == "Bulgarian" and &1["language_code"] == "bg") + ) + + assert Enum.any?( + languages, + &(&1["language_name"] == "Croatian" and &1["language_code"] == "hr") + ) + end +end diff --git a/test/data_for_seo/api/keywords/google_ads/locations_test.exs b/test/data_for_seo/api/keywords/google_ads/locations_test.exs new file mode 100644 index 0000000..3d2cc46 --- /dev/null +++ b/test/data_for_seo/api/keywords/google_ads/locations_test.exs @@ -0,0 +1,45 @@ +defmodule DataForSeo.Api.Keywords.GoogleAds.LocationsTest do + use ExUnit.Case + + alias DataForSeo.API.Keywords.GoogleAds.Locations + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read all locations", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_ads/locations" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_google_ads_locations() + ) + end) + + assert {:ok, resp} = Locations.get_all_locations() + + assert %{"tasks" => [%{"result" => locations}], "tasks_count" => 1} = resp + + loc = %{ + "location_code" => 21133, + "location_name" => "Alabama,United States", + "location_code_parent" => 2840, + "country_iso_code" => "US", + "location_type" => "State" + } + + assert Enum.any?(locations, &(&1 == loc)) + end +end diff --git a/test/data_for_seo/api/keywords/google_trends/categories_test.exs b/test/data_for_seo/api/keywords/google_trends/categories_test.exs new file mode 100644 index 0000000..8487ce5 --- /dev/null +++ b/test/data_for_seo/api/keywords/google_trends/categories_test.exs @@ -0,0 +1,47 @@ +defmodule DataForSeo.Api.Keywords.GoogleTrends.CategoriesTest do + use ExUnit.Case + + alias DataForSeo.API.Keywords.GoogleTrends.Categories + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read categories", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_trends/categories" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_google_trends_categories() + ) + end) + + assert {:ok, resp} = Categories.get_all_categories() + + assert %{"tasks" => [%{"result" => locations}], "tasks_count" => 1} = resp + + assert Enum.any?( + locations, + &(&1["category_name"] == "Apparel" and &1["category_code"] == 10021 and + &1["category_code_parent"] == nil) + ) + + assert Enum.any?( + locations, + &(&1["category_name"] == "Apparel Accessories" and &1["category_code"] == 10178 and + &1["category_code_parent"] == 10021) + ) + end +end diff --git a/test/data_for_seo/api/keywords/google_trends/explorer_test.exs b/test/data_for_seo/api/keywords/google_trends/explorer_test.exs new file mode 100644 index 0000000..dbeeac1 --- /dev/null +++ b/test/data_for_seo/api/keywords/google_trends/explorer_test.exs @@ -0,0 +1,140 @@ +defmodule DataForSeo.Api.Keywords.GoogleTrends.ExplorerTest do + use ExUnit.Case + + alias DataForSeo.API.Keywords.GoogleTrends.Explorer + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + describe "task_post/4 with a string keyword" do + test "it makes task_post POST request with params", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/keywords_data/google_trends/explore/task_post" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + {:ok, body, _} = Plug.Conn.read_body(conn) + + assert( + body == + Jason.encode!([ + %{ + keywords: ["bananas", "apples"], + language_code: "en", + location_name: "San Francisco,California,United States", + type: "youtube" + } + ]) + ) + + Plug.Conn.resp( + conn, + 200, + task_post_keywords_google_trends_explorer() + ) + end) + + assert {:ok, resp} = + Explorer.task_post( + ["bananas", "apples"], + "San Francisco,California,United States", + "en", + %{type: "youtube"} + ) + + assert %{"tasks" => tasks, "tasks_count" => 1} = resp + task = tasks |> hd + assert task["id"] == "02122119-1535-0170-0000-0228cf083d8e" + assert Enum.count(tasks) == 1 + end + end + + describe "tasks_ready/0" do + test "it returns a list of completed tasks ids", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_trends/explore/tasks_ready" = conn.request_path + + Plug.Conn.resp( + conn, + 200, + tasks_ready_keywords_google_trends_explorer() + ) + end) + + assert {:ok, %{"tasks" => [%{"result" => results}]}} = Explorer.tasks_ready() + + task_ids = Enum.map(results, & &1["id"]) + + assert Enum.member?(task_ids, "10311535-0696-0093-0000-8fe3770f079b") + assert Enum.member?(task_ids, "11111403-0696-0110-0000-c69794ecf661") + assert Enum.member?(task_ids, "11201044-0696-0110-0000-9f560a19543e") + assert length(task_ids) == 3 + end + end + + describe "task_get/2" do + test "returns task as map", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + + assert "/v3/keywords_data/google_trends/explore/task_get/04212120-1535-0170-0000-46acef0a7cac" = + conn.request_path + + Plug.Conn.resp( + conn, + 200, + task_get_keywords_google_trends_explorer() + ) + end) + + resp = Explorer.task_get("04212120-1535-0170-0000-46acef0a7cac") + + assert {:ok, response} = resp + task = response["tasks"] |> hd() + result = task["result"] |> hd() + assert ["seo api", "rank api"] = result["keywords"] + end + end + + describe "task_live/4" do + test "returns task as map", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/keywords_data/google_trends/explore/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + {:ok, body, _} = Plug.Conn.read_body(conn) + + assert( + body == + Jason.encode!([ + %{ + keywords: ["seo api"] + } + ]) + ) + + Plug.Conn.resp( + conn, + 200, + task_get_keywords_google_trends_explorer_live() + ) + end) + + assert {:ok, resp} = Explorer.task_live(["seo api"], nil, nil, %{}) + + assert %{"tasks" => tasks, "tasks_count" => 1} = resp + task = tasks |> hd + assert task["id"] == "04212140-1535-0173-0000-8209149d8105" + assert Enum.count(tasks) == 1 + end + end +end diff --git a/test/data_for_seo/api/keywords/google_trends/languages_test.exs b/test/data_for_seo/api/keywords/google_trends/languages_test.exs new file mode 100644 index 0000000..0aa8b5b --- /dev/null +++ b/test/data_for_seo/api/keywords/google_trends/languages_test.exs @@ -0,0 +1,45 @@ +defmodule DataForSeo.Api.Keywords.GoogleTrends.LanguagesTest do + use ExUnit.Case + + alias DataForSeo.API.Keywords.GoogleTrends.Languages + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read languages", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_trends/languages" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_google_trends_languages() + ) + end) + + assert {:ok, resp} = Languages.get_all_languages() + + assert %{"tasks" => [%{"result" => languages}], "tasks_count" => 1} = resp + + assert Enum.any?( + languages, + &(&1["language_name"] == "Afrikaans" and &1["language_code"] == "af") + ) + + assert Enum.any?( + languages, + &(&1["language_name"] == "German" and &1["language_code"] == "de") + ) + end +end diff --git a/test/data_for_seo/api/keywords/google_trends/locations_test.exs b/test/data_for_seo/api/keywords/google_trends/locations_test.exs new file mode 100644 index 0000000..7a40407 --- /dev/null +++ b/test/data_for_seo/api/keywords/google_trends/locations_test.exs @@ -0,0 +1,79 @@ +defmodule DataForSeo.Api.Keywords.GoogleTrends.LocationsTest do + use ExUnit.Case + + alias DataForSeo.API.Keywords.GoogleTrends.Locations + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read all locations", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_trends/locations" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_google_trend_locations() + ) + end) + + assert {:ok, resp} = Locations.get_all_locations() + + assert %{"tasks" => [%{"result" => locations}], "tasks_count" => 1} = resp + + loc = %{ + "location_code" => 2004, + "location_name" => "Afghanistan", + "location_code_parent" => nil, + "country_iso_code" => "AF", + "location_type" => "Country", + "geo_name" => "Afghanistan", + "geo_id" => "AF" + } + + assert Enum.any?(locations, &(&1 == loc)) + end + + test "read locations by country", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/keywords_data/google_trends/locations/ua" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_google_trend_locations_by_country("ua") + ) + end) + + assert {:ok, resp} = Locations.get_all_locations_by_country("ua") + + assert %{"tasks" => [%{"result" => locations}], "tasks_count" => 1} = resp + + loc = %{ + "location_code" => 21113, + "location_name" => "Donetsk Oblast,Ukraine", + "location_code_parent" => 2804, + "country_iso_code" => "UA", + "location_type" => "Region", + "geo_name" => "Donetsk Oblast,Ukraine", + "geo_id" => "Donetsk Oblast,Ukraine" + } + + assert Enum.any?(locations, &(&1 == loc)) + end +end diff --git a/test/data_for_seo/api/labs/google/categories_test.exs b/test/data_for_seo/api/labs/google/categories_test.exs new file mode 100644 index 0000000..6e52ef5 --- /dev/null +++ b/test/data_for_seo/api/labs/google/categories_test.exs @@ -0,0 +1,67 @@ +defmodule DataForSeo.Api.Labs.Google.CategoriesTest do + use ExUnit.Case + + alias DataForSeo.API.Labs.Google.Categories + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read categories", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/dataforseo_labs/categories" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_categories() + ) + end) + + assert {:ok, resp} = Categories.get_all_categories() + + assert %{"tasks" => [%{"result" => locations}], "tasks_count" => 1} = resp + + assert Enum.any?( + locations, + &(&1["category_name"] == "Apparel" and &1["category_code"] == 10021 and + &1["category_code_parent"] == nil) + ) + + assert Enum.any?( + locations, + &(&1["category_name"] == "Apparel Accessories" and &1["category_code"] == 10178 and + &1["category_code_parent"] == 10021) + ) + end + + test "result is empty", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/dataforseo_labs/categories" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + get_raw_fixture(["labs", "google", "categories-nil-result"]) + ) + end) + + assert {:ok, resp} = Categories.get_all_categories() + + assert %{"tasks" => [%{"result" => nil}], "tasks_count" => 1} = resp + end +end diff --git a/test/data_for_seo/api/labs/google/keyword_research_test.exs b/test/data_for_seo/api/labs/google/keyword_research_test.exs new file mode 100644 index 0000000..c3a8f88 --- /dev/null +++ b/test/data_for_seo/api/labs/google/keyword_research_test.exs @@ -0,0 +1,336 @@ +defmodule DataForSeo.Api.Labs.Google.KeywordResearchTest do + use ExUnit.Case + + # Notes on testing. + # There is no a lot of sense to test fixture since we know it was returned and correct. + # However it could be useful it each type of data will have it's own structs. Then it'll make sense + # if data parsed and mapped properly. Right now it's enough to test that payload is valid. + # I tried to test more on search_intent/3, but it's useless b/c fixtures are stale. + # Just checking now if it returns a proper decoded fixture as response + + alias DataForSeo.API.Labs.Google.KeywordResearch + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + describe "search_intent/3" do + test "request by language code", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/search_intent/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keywords"] == ["milk"] + assert payload["language_code"] == "en" + refute Map.has_key?(payload, "language_name") + refute Map.has_key?(payload, "tag") + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_search_intent(payload) + ) + end) + + {:ok, response} = KeywordResearch.search_intent(["milk"], "en", nil) + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "02271900-1535-0541-0000-2ab771deb8e2" + assert task["data"]["keywords"] == ["milk"] + + assert %{ + "keyword" => "milk", + "keyword_intent" => %{"label" => "commercial", "probability" => 0.49751797}, + "secondary_keyword_intents" => [ + %{"label" => "transactional", "probability" => 0.37083894}, + %{"label" => "informational", "probability" => 0.30781138} + ] + } = find_intent_in_task_response_for_keyword(task, "milk") + end + + test "request by language name with tag", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/search_intent/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keywords"] == ["audi a7"] + assert payload["tag"] == "mytag" + assert payload["language_name"] == "English" + refute Map.has_key?(payload, "language_code") + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_search_intent(payload) + ) + end) + + {:ok, response} = KeywordResearch.search_intent(["audi a7"], "English", "mytag") + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "02271900-1535-0541-0000-2ab771deb8e2" + assert task["data"]["keywords"] == ["audi a7"] + + assert %{ + "keyword" => "audi a7", + "keyword_intent" => %{"label" => "commercial", "probability" => 1}, + "secondary_keyword_intents" => nil + } = find_intent_in_task_response_for_keyword(task, "audi a7") + end + + defp find_intent_in_task_response_for_keyword(task, keyword) do + task + |> Map.get("result") + |> hd() + |> Map.get("items") + |> Enum.filter(&(&1["keyword"] == keyword)) + |> hd() + end + end + + describe "keywords_for_site/4" do + test "request by language code and loc code", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/keywords_for_site/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["target"] == "apple.com" + assert payload["language_code"] == "en" + assert payload["location_code"] == 3346 + # shouldn't be any other data in payload + assert map_size(payload) == 3 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_for_site() + ) + end) + + {:ok, response} = KeywordResearch.keywords_for_site("apple.com", 3346, "en", %{}) + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03231831-1535-0398-0000-86e0a30305cb" + assert task["data"]["target"] == "apple.com" + end + + test "request by language name and loc name with extra filters", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/keywords_for_site/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["target"] == "apple.com" + assert payload["language_name"] == "English" + assert payload["location_name"] == "United Kingdom" + assert payload["include_serp_info"] == true + assert payload["limit"] == 100 + # shouldn't be any other data in payload + assert map_size(payload) == 5 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_for_site() + ) + end) + + {:ok, response} = + KeywordResearch.keywords_for_site("apple.com", "United Kingdom", "English", %{ + include_serp_info: true, + limit: 100 + }) + + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03231831-1535-0398-0000-86e0a30305cb" + assert task["data"]["target"] == "apple.com" + end + end + + describe "related_keywords/4" do + test "request by language code and loc code", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/related_keywords/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keyword"] == "apples" + assert payload["language_code"] == "en" + assert payload["location_code"] == 3346 + # shouldn't be any other data in payload + assert map_size(payload) == 3 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_related() + ) + end) + + {:ok, response} = KeywordResearch.related_keywords("apples", 3346, "en", %{}) + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03210009-4426-0387-0000-5a33a48f3334" + end + + test "request by language name and loc name with extra filters", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/related_keywords/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keyword"] == "bananas" + assert payload["language_name"] == "English" + assert payload["location_name"] == "United Kingdom" + assert payload["limit"] == 5 + # shouldn't be any other data in payload + assert map_size(payload) == 4 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_related() + ) + end) + + {:ok, response} = + KeywordResearch.related_keywords("bananas", "United Kingdom", "English", %{limit: 5}) + + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03210009-4426-0387-0000-5a33a48f3334" + end + end + + describe "keyword_ideas/4" do + test "request by language code and loc code", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/keyword_ideas/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keywords"] == ["phone", "watch"] + assert payload["language_code"] == "en" + assert payload["location_code"] == 3346 + # shouldn't be any other data in payload + assert map_size(payload) == 3 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_ideas() + ) + end) + + {:ok, response} = KeywordResearch.keyword_ideas(["phone", "watch"], 3346, "en", %{}) + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03231838-1535-0400-0000-21ff7a0ecead" + end + + test "request by language name and loc name with extra filters", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/keyword_ideas/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keywords"] == ["bananas", "fruits"] + assert payload["language_name"] == "English" + assert payload["location_name"] == "United Kingdom" + assert payload["limit"] == 5 + # shouldn't be any other data in payload + assert map_size(payload) == 4 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_ideas() + ) + end) + + {:ok, response} = + KeywordResearch.keyword_ideas(["bananas", "fruits"], "United Kingdom", "English", %{ + limit: 5 + }) + + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03231838-1535-0400-0000-21ff7a0ecead" + end + end + + describe "keyword_suggestions/4" do + test "request by language code and loc code", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/keyword_suggestions/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keyword"] == "apples" + assert payload["language_code"] == "en" + assert payload["location_code"] == 3346 + # shouldn't be any other data in payload + assert map_size(payload) == 3 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_suggestions() + ) + end) + + {:ok, response} = KeywordResearch.keyword_suggestions("apples", 3346, "en", %{}) + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03231835-1535-0399-0000-cb09ada1b499" + end + + test "request by language name and loc name with extra filters", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/dataforseo_labs/google/keyword_suggestions/live" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, body, _} = Plug.Conn.read_body(conn) + [payload] = Jason.decode!(body) + assert payload["keyword"] == "bananas" + assert payload["language_name"] == "English" + assert payload["location_name"] == "United Kingdom" + assert payload["limit"] == 5 + # shouldn't be any other data in payload + assert map_size(payload) == 4 + + Plug.Conn.resp( + conn, + 200, + task_get_labs_google_keywords_suggestions() + ) + end) + + {:ok, response} = + KeywordResearch.keyword_suggestions("bananas", "United Kingdom", "English", %{limit: 5}) + + assert %{"tasks" => [task | _], "tasks_count" => 1} = response + assert task["id"] == "03231835-1535-0399-0000-cb09ada1b499" + end + end +end diff --git a/test/data_for_seo/api/serp/bing/bing_organic_test.exs b/test/data_for_seo/api/serp/bing/bing_organic_test.exs new file mode 100644 index 0000000..6a259c2 --- /dev/null +++ b/test/data_for_seo/api/serp/bing/bing_organic_test.exs @@ -0,0 +1,168 @@ +defmodule DataForSeo.Api.Bing.OrganicTest do + use ExUnit.Case + + alias DataForSeo.API.SERP.Bing.Organic, as: BingOrganic + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + describe "task_post/1 with a string keyword" do + test "it makes task_post POST request with params", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/serp/bing/organic/task_post" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + {:ok, body, _} = Plug.Conn.read_body(conn) + + assert( + body == + Jason.encode!([ + %{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + } + ]) + ) + + Plug.Conn.resp( + conn, + 200, + task_post_serp_bing_single_response() + ) + end) + + assert {:ok, resp} = + BingOrganic.task_post(%{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + }) + + assert %{"tasks" => tasks, "tasks_count" => 1} = resp + task = tasks |> hd + assert task["id"] == "01291721-1535-0066-0000-8f0635c0dc89" + assert task["data"]["keyword"] == "Schrauben" + assert Enum.count(tasks) == 1 + end + end + + describe "task_post/1 with a list of keywords" do + test "it makes task_post POST request with params", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/serp/bing/organic/task_post" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + {:ok, body, _} = Plug.Conn.read_body(conn) + + assert( + body == + Jason.encode!([ + %{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + }, + %{ + keyword: "Blumen", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + } + ]) + ) + + Plug.Conn.resp( + conn, + 200, + task_post_serp_bing_list_response() + ) + end) + + assert {:ok, resp} = + BingOrganic.task_post([ + %{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + }, + %{ + keyword: "Blumen", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + } + ]) + + assert %{"tasks" => tasks, "tasks_count" => 2} = resp + [task1, task2] = tasks + + assert task1["id"] == "01291721-1535-0066-0000-8f0635c0dc89" + assert task1["data"]["keyword"] == "Schrauben" + + assert task2["id"] == "01291721-1535-0066-0000-2e7a8bf7302c" + assert task2["data"]["keyword"] == "Blumen" + end + end + + describe "tasks_ready/0" do + test "it returns a list of completed tasks ids", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/serp/bing/organic/tasks_ready" = conn.request_path + + Plug.Conn.resp( + conn, + 200, + tasks_ready_serp_bing_response() + ) + end) + + assert {:ok, %{"tasks" => [%{"result" => results}]}} = BingOrganic.tasks_ready() + + task_ids = Enum.map(results, & &1["id"]) + + assert Enum.member?(task_ids, "11081554-0696-0066-0000-27e68ec15871") + assert Enum.member?(task_ids, "11151406-0696-0066-0000-c4ece317cdb2") + end + end + + describe "task_get/2" do + test "returns task as map", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + + assert "/v3/serp/bing/organic/task_get/regular/03101638-9334-0066-0000-44b65a6119fb" = + conn.request_path + + Plug.Conn.resp( + conn, + 200, + task_result_serp_bing_regular_response() + ) + end) + + resp = BingOrganic.task_get("03101638-9334-0066-0000-44b65a6119fb") + + assert {:ok, response} = resp + task = response["tasks"] |> hd() + result = task["result"] |> hd() + item = result["items"] |> hd() + + assert item["domain"] == "www.bookingbuddy.com" + end + end +end diff --git a/test/data_for_seo/api/serp/google/location_test.exs b/test/data_for_seo/api/serp/google/location_test.exs new file mode 100644 index 0000000..cb8e44b --- /dev/null +++ b/test/data_for_seo/api/serp/google/location_test.exs @@ -0,0 +1,45 @@ +defmodule DataForSeo.Api.SERP.Google.LocationTest do + use ExUnit.Case + + alias DataForSeo.API.SERP.Google.Location + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + test "read locations by country", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/serp/google/locations/lu" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + assert {:ok, "", _} = Plug.Conn.read_body(conn) + + Plug.Conn.resp( + conn, + 200, + task_get_serp_google_location_by_country("lu") + ) + end) + + assert {:ok, resp} = Location.get_location_by_service_and_country("google", "lu") + + assert %{"tasks" => [%{"result" => locations}], "tasks_count" => 1} = resp + + assert Enum.any?( + locations, + &(&1["location_type"] == "Country" and &1["location_name"] == "Luxembourg") + ) + + assert Enum.any?( + locations, + &(&1["location_type"] == "City" and &1["location_name"] == "Kaerjeng,Luxembourg") + ) + end +end diff --git a/test/data_for_seo/api/serp/youtube/youtube_organic_test.exs b/test/data_for_seo/api/serp/youtube/youtube_organic_test.exs new file mode 100644 index 0000000..d5f2739 --- /dev/null +++ b/test/data_for_seo/api/serp/youtube/youtube_organic_test.exs @@ -0,0 +1,168 @@ +defmodule DataForSeo.Api.Youtube.OrganicTest do + use ExUnit.Case + + alias DataForSeo.API.SERP.Youtube.Organic, as: YoutubeOrganic + + import DataForSeo.Test.ResponseFactory + + setup do + bypass = Bypass.open() + + DataForSeo.Config.add(:process, base_url: "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + describe "task_post/1 with a string keyword" do + test "it makes task_post POST request with params", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/serp/youtube/organic/task_post" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + {:ok, body, _} = Plug.Conn.read_body(conn) + + assert( + body == + Jason.encode!([ + %{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + } + ]) + ) + + Plug.Conn.resp( + conn, + 200, + task_post_serp_youtube_single_response() + ) + end) + + assert {:ok, resp} = + YoutubeOrganic.task_post(%{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + }) + + assert %{"tasks" => tasks, "tasks_count" => 1} = resp + task = tasks |> hd + assert task["id"] == "01291721-1535-0066-0000-8f0635c0dc89" + assert task["data"]["keyword"] == "Schrauben" + assert Enum.count(tasks) == 1 + end + end + + describe "task_post/1 with a list of keywords" do + test "it makes task_post POST request with params", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "POST" = conn.method + assert "/v3/serp/youtube/organic/task_post" = conn.request_path + assert Enum.member?(conn.req_headers, {"content-type", "application/json"}) + + {:ok, body, _} = Plug.Conn.read_body(conn) + + assert( + body == + Jason.encode!([ + %{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + }, + %{ + keyword: "Blumen", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + } + ]) + ) + + Plug.Conn.resp( + conn, + 200, + task_post_serp_youtube_list_response() + ) + end) + + assert {:ok, resp} = + YoutubeOrganic.task_post([ + %{ + keyword: "Schrauben", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + }, + %{ + keyword: "Blumen", + language_code: "en", + location_name: "San Francisco,California,United States", + se_domain: "bing.com" + } + ]) + + assert %{"tasks" => tasks, "tasks_count" => 2} = resp + [task1, task2] = tasks + + assert task1["id"] == "01291721-1535-0066-0000-8f0635c0dc89" + assert task1["data"]["keyword"] == "Schrauben" + + assert task2["id"] == "01291721-1535-0066-0000-2e7a8bf7302c" + assert task2["data"]["keyword"] == "Blumen" + end + end + + describe "tasks_ready/0" do + test "it returns a list of completed tasks ids", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + assert "/v3/serp/youtube/organic/tasks_ready" = conn.request_path + + Plug.Conn.resp( + conn, + 200, + tasks_ready_serp_youtube_response() + ) + end) + + assert {:ok, %{"tasks" => [%{"result" => results}]}} = YoutubeOrganic.tasks_ready() + + task_ids = Enum.map(results, & &1["id"]) + + assert Enum.member?(task_ids, "11081554-0696-0066-0000-27e68ec15871") + assert Enum.member?(task_ids, "11151406-0696-0066-0000-c4ece317cdb2") + end + end + + describe "task_get/2" do + test "returns task as map", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + assert "GET" = conn.method + + assert "/v3/serp/youtube/organic/task_get/advanced/03101638-9334-0066-0000-44b65a6119fb" = + conn.request_path + + Plug.Conn.resp( + conn, + 200, + task_result_serp_youtube_advanced_response() + ) + end) + + resp = YoutubeOrganic.task_get("03101638-9334-0066-0000-44b65a6119fb") + + assert {:ok, response} = resp + task = response["tasks"] |> hd() + result = task["result"] |> hd() + item = result["items"] |> hd() + + assert item["domain"] == "www.bookingbuddy.com" + end + end +end diff --git a/test/data_for_seo/api/serp_test.exs b/test/data_for_seo/api/serp_test.exs index 6e17aa4..650281a 100644 --- a/test/data_for_seo/api/serp_test.exs +++ b/test/data_for_seo/api/serp_test.exs @@ -3,7 +3,7 @@ defmodule DataForSeo.Api.SerpTest do alias DataForSeo.API.Serp - import RespFactory + import DataForSeo.Test.ResponseFactory setup do bypass = Bypass.open() @@ -37,7 +37,7 @@ defmodule DataForSeo.Api.SerpTest do Plug.Conn.resp( conn, 200, - task_post_single_response() + task_post_serp_google_single_response() ) end) @@ -87,7 +87,7 @@ defmodule DataForSeo.Api.SerpTest do Plug.Conn.resp( conn, 200, - task_post_list_response() + task_post_serp_google_list_response() ) end) @@ -127,7 +127,7 @@ defmodule DataForSeo.Api.SerpTest do Plug.Conn.resp( conn, 200, - tasks_ready_response() + tasks_ready_serp_google_response() ) end) @@ -151,7 +151,7 @@ defmodule DataForSeo.Api.SerpTest do Plug.Conn.resp( conn, 200, - task_result_response() + task_result_serp_google_regular_response() ) end) diff --git a/test/data_for_seo/data_model/google_serp_translator_test.exs b/test/data_for_seo/data_model/google_serp_translator_test.exs new file mode 100644 index 0000000..a8db254 --- /dev/null +++ b/test/data_for_seo/data_model/google_serp_translator_test.exs @@ -0,0 +1,61 @@ +defmodule DataForSeo.DataModel.GoogleSerpTranslatorTest do + use ExUnit.Case + use DataForSeo.TranslatorCase + + alias DataForSeo.DataModel.SERP.TaskReadyItem + alias DataForSeo.DataModel.SERP.Google.SerpItemRegular + + describe "serp/google/organic" do + test "task-post" do + assert %Task{result: nil} = + translate_task_from_fixture(["serp", "google", "organic", "task-post"]) + end + + test "task-get-regular" do + assert %Task{result: result} = + translate_task_from_fixture(["serp", "google", "organic", "task-get-regular"]) + + assert [ + %{ + datetime: ~U[2019-11-15 12:57:46Z], + item_types: ["organic", "paid"], + items_count: 96, + keyword: "flight ticket new york san francisco", + language_code: "en", + location_code: 2840, + regular_items: items, + se_domain: "google.com", + se_results_count: 85_600_000, + spell: nil, + type: "organic" + } + ] = result + + assert length(items) == 9 + + assert %SerpItemRegular{ + breadcrumb: "www.bookingbuddy.com/Flights", + description: + "Compare Airlines & Sites. Cheap Flights on BookingBuddy, a TripAdvisor Company", + domain: "www.bookingbuddy.com", + rank_absolute: 1, + rank_group: 1, + title: "Flights To Lwo | Unbelievably Cheap Flights | BookingBuddy.com‎", + type: "paid", + url: "https://www.bookingbuddy.com/en/hero/" + } == hd(items) + end + + test "tasks-ready" do + %Task{result: tasks} = + translate_task_from_fixture(["serp", "google", "organic", "tasks-ready"]) + + assert length(tasks) == 2 + ids = ["11081554-0696-0066-0000-27e68ec15871", "11151406-0696-0066-0000-c4ece317cdb2"] + + Enum.each(tasks, fn %TaskReadyItem{id: id} -> + assert Enum.member?(ids, id) + end) + end + end +end diff --git a/test/data_for_seo/data_model/google_trends_translator_test.exs b/test/data_for_seo/data_model/google_trends_translator_test.exs new file mode 100644 index 0000000..2aa2602 --- /dev/null +++ b/test/data_for_seo/data_model/google_trends_translator_test.exs @@ -0,0 +1,252 @@ +defmodule DataForSeo.DataModel.GoogleTrendsTranslatorTest do + use ExUnit.Case + use DataForSeo.TranslatorCase + + alias DataForSeo.DataModel.Keywords.GoogleTrends.Explorer.TaskReadyItem + alias DataForSeo.DataModel.Keywords.GoogleTrends.ExplorerResult + alias alias DataForSeo.DataModel.Keywords.GoogleTrends.ExplorerItem + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataGraph + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataMap + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataQuery + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataQueryNode + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataTopic + alias DataForSeo.DataModel.Keywords.GoogleTrends.DataTopicNode + alias alias DataForSeo.DataModel.Category + alias alias DataForSeo.DataModel.Language + alias alias DataForSeo.DataModel.Location + + describe "keywords/google_trends/explorer" do + test "task-post" do + assert %Task{id: "02122119-1535-0170-0000-0228cf083d8e", result: nil} = + translate_task_from_fixture(["keywords", "google_trends", "explorer", "task-post"]) + end + + test "task-get" do + assert %Task{result: result} = + translate_task_from_fixture(["keywords", "google_trends", "explorer", "task-get"]) + + assert [ + %ExplorerResult{ + keywords: [ + "seo api", + "rank api" + ], + type: "trends", + location_code: 0, + language_code: "en", + check_url: + "https://trends.google.com/trends/explore?hl=en&date=2019-01-01%202020-01-01&q=seo%20api%2Crank%20api", + datetime: ~U[2022-04-21 18:22:05Z], + items_count: 6, + items: items + } + ] = result + + assert length(items) == 6 + + grouped = Enum.group_by(items, & &1.type) + assert length(grouped["google_trends_graph"]) == 1 + assert length(grouped["google_trends_map"]) == 3 + assert length(grouped["google_trends_queries_list"]) == 2 + + assert %ExplorerItem{ + type: "google_trends_graph", + graphs: graphs, + title: "Interest over time", + keywords: [ + "seo api", + "rank api" + ], + averages: [ + 62, + 46 + ] + } = hd(grouped["google_trends_graph"]) + + assert %DataGraph{ + date_from: ~D[2019-01-06], + date_to: ~D[2019-01-12], + timestamp: 1_546_732_800, + missing_data: false, + values: [ + 62, + 37 + ] + } == hd(graphs) + + assert %ExplorerItem{ + type: "google_trends_map", + maps: maps, + title: "Compared breakdown by region", + keywords: [ + "seo api", + "rank api" + ] + } = hd(grouped["google_trends_map"]) + + assert %DataMap{ + geo_id: "LA", + geo_name: "Laos", + values: [ + nil, + 100 + ], + max_value_index: 1 + } == hd(maps) + + assert %ExplorerItem{ + position: 4, + type: "google_trends_queries_list", + title: "Related queries", + keywords: [ + "seo api" + ], + queries_list: %DataQuery{ + top: top_queries, + rising: rising_queries + } + } = hd(grouped["google_trends_queries_list"]) + + assert %DataQueryNode{ + query: "google seo api", + value: 100 + } == hd(top_queries) + + assert %DataQueryNode{ + query: "api for seo software projects", + value: 120 + } == hd(rising_queries) + end + + test "task-live" do + assert %Task{result: result} = + translate_task_from_fixture(["keywords", "google_trends", "explorer", "task-live"]) + + assert [ + %ExplorerResult{ + keywords: [ + "seo api" + ], + type: "trends", + location_code: 2840, + language_code: "en", + check_url: + "https://trends.google.com/trends/explore?hl=en&geo=US&date=2019-01-01%202020-01-01&q=seo%20api", + datetime: ~U[2022-04-21 18:40:17Z], + items_count: 4, + items: items + } + ] = result + + assert length(items) == 4 + + grouped = Enum.group_by(items, & &1.type) + assert length(grouped["google_trends_graph"]) == 1 + assert length(grouped["google_trends_map"]) == 1 + assert length(grouped["google_trends_queries_list"]) == 1 + assert length(grouped["google_trends_topics_list"]) == 1 + + # It's the same as task-get, test only topics_list here b/c it wasn't included in task-get fixture + assert %ExplorerItem{ + position: 3, + type: "google_trends_topics_list", + title: "Related topics", + keywords: [ + "seo api" + ], + topics_list: %DataTopic{ + top: top_topics, + rising: rising_topics + } + } = hd(grouped["google_trends_topics_list"]) + + assert %DataTopicNode{ + topic_id: "/m/019qb_", + topic_title: "Search Engine Optimization", + topic_type: "Topic", + value: 100 + } == hd(top_topics) + + assert %DataTopicNode{ + topic_id: "/m/086df", + topic_title: "Web design", + topic_type: "Discipline", + value: 400 + } == hd(rising_topics) + end + + test "tasks-ready" do + %Task{result: tasks} = + translate_task_from_fixture(["keywords", "google_trends", "explorer", "tasks-ready"]) + + assert length(tasks) == 3 + + ids = [ + "10311535-0696-0093-0000-8fe3770f079b", + "11111403-0696-0110-0000-c69794ecf661", + "11201044-0696-0110-0000-9f560a19543e" + ] + + Enum.each(tasks, fn %TaskReadyItem{id: id} -> + assert Enum.member?(ids, id) + end) + end + end + + describe "categories/languages/locations" do + test "categories" do + assert %Task{ + result: items + } = translate_task_from_fixture(["keywords", "google_trends", "categories"]) + + assert 4 == length(items) + + [ + %Category{category_code: 10021, category_code_parent: nil, category_name: "Apparel"}, + %Category{ + category_code: 10178, + category_code_parent: 10021, + category_name: "Apparel Accessories" + } + | _ + ] = items + end + + test "locations" do + assert %Task{ + result: items + } = translate_task_from_fixture(["keywords", "google_trends", "locations"]) + + assert 8 == length(items) + + [ + %Location{ + location_code: 2004, + location_name: "Afghanistan", + location_code_parent: nil, + country_iso_code: "AF", + location_type: "Country", + geo_name: "Afghanistan", + geo_id: "AF" + } + | _ + ] = items + end + + test "languages" do + assert %Task{ + result: items + } = translate_task_from_fixture(["keywords", "google_trends", "languages"]) + + assert 4 == length(items) + + [ + %Language{ + language_name: "Afrikaans", + language_code: "af" + } + | _ + ] = items + end + end +end diff --git a/test/data_for_seo/data_model/labs_google_keyword_research_translator_test.exs b/test/data_for_seo/data_model/labs_google_keyword_research_translator_test.exs new file mode 100644 index 0000000..eedc440 --- /dev/null +++ b/test/data_for_seo/data_model/labs_google_keyword_research_translator_test.exs @@ -0,0 +1,529 @@ +defmodule DataForSeo.DataModel.LabsGooogleKeywordResearchTranslatorTest do + use ExUnit.Case + use DataForSeo.TranslatorCase + + alias DataForSeo.DataModel.Labs.Google.SearchIntentResult + alias DataForSeo.DataModel.Labs.Google.SearchIntentItem + alias DataForSeo.DataModel.Labs.Google.SearchIntentForKeyword + + alias DataForSeo.DataModel.Labs.Google.KeywordsForSiteResult + alias DataForSeo.DataModel.Labs.Google.KeywordData + alias DataForSeo.DataModel.Labs.Google.KeywordInfo + alias DataForSeo.DataModel.Labs.Google.KeywordProperties + alias DataForSeo.DataModel.Labs.Google.KeywordImpressionsInfo + alias DataForSeo.DataModel.Labs.Google.KeywordSerpInfo + alias DataForSeo.DataModel.Labs.Google.AvgBackLinksInfo + alias DataForSeo.DataModel.Labs.Google.SearchIntentInfo + alias DataForSeo.DataModel.Labs.Google.MonthlySearch + + alias DataForSeo.DataModel.Labs.Google.KeywordsIdeasResult + alias DataForSeo.DataModel.Labs.Google.KeywordsSuggestionsResult + alias DataForSeo.DataModel.Labs.Google.RelatedKeywordsResult + alias DataForSeo.DataModel.Labs.Google.RelatedKeywordItem + alias DataForSeo.DataModel.Labs.Google.BulkKeywordDifficultyResult + alias DataForSeo.DataModel.Labs.Google.KeywordDifficulty + alias DataForSeo.DataModel.Category + + describe "labs/google" do + test "categories" do + assert %Task{ + result: items + } = translate_task_from_fixture(["labs", "google", "categories"]) + + assert 4 == length(items) + + [ + %Category{category_code: 10021, category_code_parent: nil, category_name: "Apparel"}, + %Category{ + category_code: 10178, + category_code_parent: 10021, + category_name: "Apparel Accessories" + } + | _ + ] = items + end + end + + describe "labs/google/keywords-research" do + test "search intent" do + assert %Task{ + result: [%SearchIntentResult{items_count: 4, language_code: "en", items: items}] + } = + translate_task_from_fixture(["labs", "google", "keyword_research", "search-intent"]) + + assert length(items) == 4 + + %SearchIntentForKeyword{keyword_intent: main, secondary_keyword_intents: secondary} = + Enum.find(items, &(&1.keyword == "milk")) + + assert %SearchIntentItem{label: "commercial", probability: 0.49751797} = main + + assert [ + %SearchIntentItem{ + label: "transactional", + probability: 0.37083894 + }, + %SearchIntentItem{ + label: "informational", + probability: 0.30781138 + } + ] = secondary + end + + test "keywords for site" do + assert %Task{ + result: [ + %KeywordsForSiteResult{ + se_type: "google", + target: "apple.com", + location_code: 2840, + language_code: "en", + total_count: 6_883_566, + items_count: 3, + offset: 0, + offset_token: <<"eyJDdXJyZW5"::binary, _::binary>>, + items: items + } + ] + } = + translate_task_from_fixture([ + "labs", + "google", + "keyword_research", + "keywords-for-site" + ]) + + assert 3 == length(items) + + %KeywordData{ + search_intent_info: intent, + avg_backlinks_info: backlinks, + serp_info: serp_info, + impressions_info: impressions, + keyword_properties: keyword_properties, + keyword_info: keyword_info + } = Enum.find(items, &(&1.keyword == "you")) + + assert %SearchIntentInfo{ + se_type: "google", + main_intent: "informational", + foreign_intent: ["navigational"], + last_updated_time: ~U[2023-03-03 15:55:21Z] + } == intent + + assert %AvgBackLinksInfo{ + se_type: "google", + backlinks: 3962.6, + dofollow: 2203, + referring_pages: 2590.4, + referring_domains: 418.5, + referring_main_domains: 307.3, + rank: 180.7, + main_domain_rank: 930.8, + last_updated_time: ~U[2023-03-01 09:44:45Z] + } == backlinks + + assert %KeywordSerpInfo{ + se_type: "google", + check_url: + "https://www.google.com/search?q=you&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + serp_item_types: [ + "organic", + "video", + "twitter", + "images", + "related_searches", + "knowledge_graph" + ], + se_results_count: 100, + last_updated_time: ~U[2023-03-01 09:44:24Z], + previous_updated_time: ~U[2023-01-26 19:59:58Z] + } == serp_info + + assert %KeywordImpressionsInfo{ + se_type: "google", + last_updated_time: ~U[2022-04-17 18:54:48Z], + bid: 999, + match_type: "exact", + ad_position_min: 1.11, + ad_position_max: 1, + ad_position_average: 1.06, + cpc_min: 2.69, + cpc_max: 3.29, + cpc_average: 2.99, + daily_impressions_min: 515.37, + daily_impressions_max: 629.9, + daily_impressions_average: 572.63, + daily_clicks_min: 55.58, + daily_clicks_max: 67.93, + daily_clicks_average: 61.75, + daily_cost_min: 166.29, + daily_cost_max: 203.25, + daily_cost_average: 184.77 + } == impressions + + assert %KeywordProperties{ + se_type: "google", + core_keyword: nil, + synonym_clustering_algorithm: "text_processing", + keyword_difficulty: 23, + detected_language: "en", + is_another_language: false + } == keyword_properties + + assert %KeywordInfo{ + se_type: "google", + last_updated_time: ~U[2023-03-21 07:07:46Z], + competition: nil, + competition_level: "LOW", + cpc: 0.04, + search_volume: 185_000_000, + low_top_of_page_bid: 0.01, + high_top_of_page_bid: 0.04, + categories: [ + 10013, + 10109, + 13546 + ], + monthly_searches: monthly_searches + } = keyword_info + + assert length(monthly_searches) == 12 + + Enum.each(1..12, fn month -> + year = if(month in [1, 2], do: 2023, else: 2022) + + assert %MonthlySearch{month: ^month, year: ^year, search_volume: volume} = + Enum.find(monthly_searches, &(&1.month == month)) + + assert volume > 0 + end) + end + + test "keyword for site result is null" do + translate_task_from_fixture([ + "labs", + "google", + "keyword_research", + "keywords-for-site-null-result" + ]) + end + + test "keyword ideas" do + assert %Task{ + result: [ + %KeywordsIdeasResult{ + items_count: 3, + total_count: 2_321_099, + language_code: "en", + items: items + } + ] + } = + translate_task_from_fixture(["labs", "google", "keyword_research", "keyword-ideas"]) + + assert length(items) == 3 + + %KeywordData{ + search_intent_info: intent, + avg_backlinks_info: backlinks, + serp_info: serp_info, + impressions_info: impressions, + keyword_properties: keyword_properties, + keyword_info: keyword_info + } = Enum.find(items, &(&1.keyword == "telephone in japan")) + + assert %SearchIntentInfo{ + se_type: "google", + main_intent: "commercial", + foreign_intent: ["informational", "transactional"], + last_updated_time: ~U[2023-03-02 18:35:26Z] + } == intent + + assert %AvgBackLinksInfo{ + se_type: "google", + backlinks: 29.3, + dofollow: 14.6, + referring_pages: 26, + referring_domains: 17, + referring_main_domains: 15.4, + rank: 103.5, + main_domain_rank: 478.1, + last_updated_time: ~U[2023-03-15 22:05:52Z] + } == backlinks + + assert %KeywordSerpInfo{ + se_type: "google", + check_url: + "https://www.google.com/search?q=telephone%20in%20japan&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + serp_item_types: [ + "featured_snippet", + "people_also_ask", + "organic", + "images", + "people_also_search", + "related_searches" + ], + se_results_count: 332_000_000, + last_updated_time: ~U[2023-03-15 22:05:45Z], + previous_updated_time: ~U[2023-02-09 15:33:06Z] + } == serp_info + + assert %KeywordImpressionsInfo{ + se_type: "google", + last_updated_time: ~U[2022-03-26 19:32:21Z], + bid: 999, + match_type: "exact", + ad_position_min: nil, + ad_position_max: nil, + ad_position_average: nil, + cpc_min: nil, + cpc_max: nil, + cpc_average: nil, + daily_impressions_min: nil, + daily_impressions_max: nil, + daily_impressions_average: nil, + daily_clicks_min: nil, + daily_clicks_max: nil, + daily_clicks_average: nil, + daily_cost_min: nil, + daily_cost_max: nil, + daily_cost_average: nil + } == impressions + + assert %KeywordProperties{ + se_type: "google", + core_keyword: nil, + synonym_clustering_algorithm: "text_processing", + keyword_difficulty: 48, + detected_language: "en", + is_another_language: false + } == keyword_properties + + assert %KeywordInfo{ + se_type: "google", + last_updated_time: ~U[2023-03-21 10:09:17Z], + competition: 0.07, + competition_level: "LOW", + cpc: nil, + search_volume: 210, + low_top_of_page_bid: nil, + high_top_of_page_bid: nil, + categories: [10007, 10878, 12171], + monthly_searches: monthly_searches + } = keyword_info + + assert length(monthly_searches) == 12 + + Enum.each(1..12, fn month -> + year = if(month in [1, 2], do: 2023, else: 2022) + + assert %MonthlySearch{month: ^month, year: ^year, search_volume: volume} = + Enum.find(monthly_searches, &(&1.month == month)) + + assert volume > 0 + end) + end + + test "keyword suggestions" do + assert %Task{ + result: [ + %KeywordsSuggestionsResult{ + items_count: 3, + total_count: 2_582_247, + language_code: "en", + seed_keyword: "phone", + seed_keyword_data: nil, + offset: 0, + offset_token: <<"eyJDdXJyZW5"::binary, _::binary>>, + items: items + } + ] + } = + translate_task_from_fixture([ + "labs", + "google", + "keyword_research", + "keyword-suggestions" + ]) + + assert length(items) == 3 + + %KeywordData{ + search_intent_info: intent, + avg_backlinks_info: backlinks, + serp_info: serp_info, + impressions_info: impressions, + keyword_properties: keyword_properties, + keyword_info: keyword_info + } = Enum.find(items, &(&1.keyword == "find to my phone")) + + assert %SearchIntentInfo{ + se_type: "google", + main_intent: "commercial", + foreign_intent: ["navigational"], + last_updated_time: ~U[2023-03-02 07:46:22Z] + } == intent + + assert %AvgBackLinksInfo{ + se_type: "google", + backlinks: 47525.7, + dofollow: 26457.9, + referring_pages: 30839.7, + referring_domains: 2395, + referring_main_domains: 1958.1, + rank: 387.3, + main_domain_rank: 841.3, + last_updated_time: ~U[2023-02-06 16:31:22Z] + } == backlinks + + assert %KeywordSerpInfo{ + se_type: "google", + check_url: + "https://www.google.com/search?q=find%20to%20my%20phone&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", + serp_item_types: [ + "organic", + "people_also_ask", + "people_also_search", + "related_searches", + "knowledge_graph" + ], + se_results_count: 10_960_000_000, + last_updated_time: ~U[2023-02-06 16:30:24Z], + previous_updated_time: ~U[2022-10-20 13:54:33Z] + } == serp_info + + assert %KeywordImpressionsInfo{ + se_type: "google", + last_updated_time: ~U[2022-03-22 08:34:45Z], + bid: 999, + match_type: "exact", + ad_position_min: 1.11, + ad_position_max: 1, + ad_position_average: 1.06, + cpc_min: 68.97, + cpc_max: 84.3, + cpc_average: 76.63, + daily_impressions_min: 0.15, + daily_impressions_max: 0.18, + daily_impressions_average: 0.16, + daily_clicks_min: 0.01, + daily_clicks_max: 0.02, + daily_clicks_average: 0.01, + daily_cost_min: 0.98, + daily_cost_max: 1.2, + daily_cost_average: 1.09 + } == impressions + + assert %KeywordProperties{ + se_type: "google", + core_keyword: "find my phone", + synonym_clustering_algorithm: "text_processing", + keyword_difficulty: 100, + detected_language: "en", + is_another_language: false + } == keyword_properties + + assert %KeywordInfo{ + se_type: "google", + last_updated_time: ~U[2023-03-21 00:31:34Z], + competition: 0.13, + competition_level: "LOW", + cpc: 1.37, + search_volume: 673_000, + low_top_of_page_bid: 0.53, + high_top_of_page_bid: 1.37, + categories: [10019, 10167, 12153], + monthly_searches: monthly_searches + } = keyword_info + + assert length(monthly_searches) == 12 + + Enum.each(1..12, fn month -> + year = if(month in [1, 2], do: 2023, else: 2022) + + assert %MonthlySearch{month: ^month, year: ^year, search_volume: volume} = + Enum.find(monthly_searches, &(&1.month == month)) + + assert volume > 0 + end) + end + + test "related keyword " do + assert %Task{ + result: [ + %RelatedKeywordsResult{ + items_count: 3, + total_count: 8, + language_code: "en", + seed_keyword: "phone", + seed_keyword_data: nil, + items: items + } + ] + } = + translate_task_from_fixture([ + "labs", + "google", + "keyword_research", + "related-keywords" + ]) + + assert length(items) == 3 + + %RelatedKeywordItem{ + depth: 0, + se_type: "google", + related_keywords: [ + "phone call", + "phone, samsung", + "phone number", + "phone call app", + "phone app", + "my phone", + "phone google", + "phone meaning" + ], + keyword_data: keyword_data + } = hd(items) + + assert %KeywordData{ + keyword: "phone", + keyword_info: %KeywordInfo{competition_level: "HIGH"}, + keyword_properties: %KeywordProperties{core_keyword: "phone"}, + impressions_info: %KeywordImpressionsInfo{daily_cost_average: 51907.82}, + serp_info: nil, + avg_backlinks_info: %AvgBackLinksInfo{last_updated_time: ~U[2023-03-13 07:14:56Z]}, + search_intent_info: %SearchIntentInfo{main_intent: "navigational"} + } = keyword_data + end + + test "bulk keyword difficulty " do + assert %Task{ + result: [ + %BulkKeywordDifficultyResult{ + items_count: 3, + total_count: 3, + language_code: "en", + location_code: 2840, + items: items + } + ] + } = + translate_task_from_fixture([ + "labs", + "google", + "keyword_research", + "bulk-keyword-difficulty" + ]) + + assert length(items) == 3 + + [{"car dealer los angeles", 50}, {"dentist new york", 50}, {"pizza brooklyn", 44}] + |> Enum.each(fn {k, d} -> + assert %KeywordDifficulty{se_type: "google", keyword: ^k, keyword_difficulty: ^d} = + Enum.find(items, &(&1.keyword == k)) + end) + end + end +end diff --git a/test/data_for_seo/data_model/response_translator_test.exs b/test/data_for_seo/data_model/response_translator_test.exs new file mode 100644 index 0000000..ea06064 --- /dev/null +++ b/test/data_for_seo/data_model/response_translator_test.exs @@ -0,0 +1,97 @@ +defmodule DataForSeo.DataModel.ResponseTranslatorTest do + use ExUnit.Case + + alias DataForSeo.DataModel.Transform.ResponseTranslator + alias DataForSeo.DataModel.Generic.Response + alias DataForSeo.DataModel.Generic.Task + alias DataForSeo.DataModel.SERP.TaskReadyItem + alias DataForSeo.DataModel.SERP.Google.SerpItemRegular, as: GoogleSerpItemRegular + import DataForSeo.Test.ResponseFactory + + test "test translating response into object" do + # Get all available fixtures and check if they could be loaded into response struct + :data_for_seo + |> :code.priv_dir() + |> Path.join("/**/*.json") + |> Path.wildcard() + |> Enum.each(fn f -> + json = get_json_fixture(f) + r = %Response{} = ResponseTranslator.load_response(json) + assert r.cost == json["cost"] + assert r.status_code == json["status_code"] + assert r.status_message == json["status_message"] + + assert length(r.tasks) == length(json["tasks"]) + assert %Task{} = hd(r.tasks) + end) + end + + describe "load_task_result/1" do + test "serp/google/organic/task-post" do + assert %Task{result: nil} = + ["serp", "google", "organic", "task-post"] + |> get_json_fixture() + |> ResponseTranslator.load_response() + |> ResponseTranslator.get_tasks() + |> hd() + |> ResponseTranslator.load_task_result() + end + + test "serp/google/organic/task-get" do + assert %Task{result: result} = + ["serp", "google", "organic", "task-get-regular"] + |> get_json_fixture() + |> ResponseTranslator.load_response() + |> ResponseTranslator.get_tasks() + |> hd() + |> ResponseTranslator.load_task_result() + + assert [ + %{ + datetime: ~U[2019-11-15 12:57:46Z], + item_types: ["organic", "paid"], + items_count: 96, + keyword: "flight ticket new york san francisco", + language_code: "en", + location_code: 2840, + regular_items: items, + se_domain: "google.com", + se_results_count: 85_600_000, + spell: nil, + type: "organic" + } + ] = result + + assert length(items) == 9 + + assert %GoogleSerpItemRegular{ + breadcrumb: "www.bookingbuddy.com/Flights", + description: + "Compare Airlines & Sites. Cheap Flights on BookingBuddy, a TripAdvisor Company", + domain: "www.bookingbuddy.com", + rank_absolute: 1, + rank_group: 1, + title: "Flights To Lwo | Unbelievably Cheap Flights | BookingBuddy.com‎", + type: "paid", + url: "https://www.bookingbuddy.com/en/hero/" + } == hd(items) + end + + test "serp/google/organic/tasks-ready" do + %Task{result: tasks} = + ["serp", "google", "organic", "tasks-ready"] + |> get_json_fixture() + |> ResponseTranslator.load_response() + |> ResponseTranslator.get_tasks() + |> hd() + |> ResponseTranslator.load_task_result() + + assert length(tasks) == 2 + ids = ["11081554-0696-0066-0000-27e68ec15871", "11151406-0696-0066-0000-c4ece317cdb2"] + + Enum.each(tasks, fn %TaskReadyItem{id: id} -> + assert Enum.member?(ids, id) + end) + end + end +end diff --git a/test/support/resp_factory.ex b/test/support/resp_factory.ex deleted file mode 100644 index 023f9bd..0000000 --- a/test/support/resp_factory.ex +++ /dev/null @@ -1,330 +0,0 @@ -defmodule RespFactory do - def task_post_single_response do - """ - { - "version": "0.1.20200129", - "status_code": 20000, - "status_message": "Ok.", - "time": "0.0818 sec.", - "cost": 0.0045, - "tasks_count": 1, - "tasks_error": 0, - "tasks": [ - { - "id": "01291721-1535-0066-0000-8f0635c0dc89", - "status_code": 20100, - "status_message": "Task Created.", - "time": "0.0038 sec.", - "cost": 0.0015, - "result_count": 0, - "path": [ - "v3", - "serp", - "google", - "organic", - "task_post" - ], - "data": { - "api": "serp", - "function": "task_post", - "se": "google", - "se_type": "organic", - "language_code": "en", - "location_code": 2840, - "keyword": "Schrauben", - "device": "desktop", - "os": "windows" - }, - "result": null - } - ] - } - """ - end - - def task_post_list_response do - """ - { - "version": "0.1.20200129", - "status_code": 20000, - "status_message": "Ok.", - "time": "0.0818 sec.", - "cost": 0.0045, - "tasks_count": 2, - "tasks_error": 0, - "tasks": [ - { - "id": "01291721-1535-0066-0000-8f0635c0dc89", - "status_code": 20100, - "status_message": "Task Created.", - "time": "0.0038 sec.", - "cost": 0.0015, - "result_count": 0, - "path": [ - "v3", - "serp", - "google", - "organic", - "task_post" - ], - "data": { - "api": "serp", - "function": "task_post", - "se": "google", - "se_type": "organic", - "language_code": "en", - "location_code": 2840, - "keyword": "Schrauben", - "device": "desktop", - "os": "windows" - }, - "result": null - }, - { - "id": "01291721-1535-0066-0000-2e7a8bf7302c", - "status_code": 20100, - "status_message": "Task Created.", - "time": "0.0050 sec.", - "cost": 0.0015, - "result_count": 0, - "path": [ - "v3", - "serp", - "google", - "organic", - "task_post" - ], - "data": { - "api": "serp", - "function": "task_post", - "se": "google", - "se_type": "organic", - "language_name": "English", - "location_name": "United States", - "keyword": "Blumen", - "priority":2, - "pingback_url": "https://your-server.com/pingscript?id=$id&tag=$tag", - "tag": "some_string_123", - "device": "desktop", - "os": "windows" - }, - "result": null - } - ] - } - """ - end - - def tasks_ready_response do - """ - { - "version": "0.1.20200129", - "status_code": 20000, - "status_message": "Ok.", - "time": "0.2270 sec.", - "cost": 0, - "tasks_count": 1, - "tasks_error": 0, - "tasks": [ - { - "id": "11151406-0696-0087-0000-e781cb0144a1", - "status_code": 20000, - "status_message": "Ok.", - "time": "0.0388 sec.", - "cost": 0, - "result_count": 2, - "path": [ - "v3", - "serp", - "google", - "organic", - "tasks_ready" - ], - "data": { - "api": "serp", - "function": "tasks_ready", - "se": "google", - "se_type": "organic" - }, - "result": [ - { - "id": "11081554-0696-0066-0000-27e68ec15871", - "se": "google", - "se_type": "organic", - "date_posted": "2019-11-08 13:54:43 +00:00", - "endpoint_regular": "/v3/serp/google/organic/task_get/regular/11081554-0696-0066-0000-27e68ec15871", - "endpoint_advanced": "/v3/serp/google/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", - "endpoint_html": "/v3/serp/google/organic/task_get/html/11081554-0696-0066-0000-27e68ec15871" - }, - { - "id": "11151406-0696-0066-0000-c4ece317cdb2", - "se": "google", - "se_type": "organic", - "date_posted": "2019-11-15 12:06:09 +00:00", - "endpoint_regular": "/v3/serp/google/organic/task_get/regular/11151406-0696-0066-0000-c4ece317cdb2", - "endpoint_advanced": "/v3/serp/google/organic/task_get/advanced/11081554-0696-0066-0000-27e68ec15871", - "endpoint_html": "/v3/serp/google/organic/task_get/html/11151406-0696-0066-0000-c4ece317cdb2" - } - ] - } - ] - } - """ - end - - def task_result_response do - """ - { - "version": "0.1.20200129", - "status_code": 20000, - "status_message": "Ok.", - "time": "0.3059 sec.", - "cost": 0, - "tasks_count": 1, - "tasks_error": 0, - "tasks": [ - { - "id": "11151456-0696-0066-0000-002a5915da37", - "status_code": 20000, - "status_message": "Ok.", - "time": "0.0952 sec.", - "cost": 0, - "result_count": 1, - "path": [ - "v3", - "serp", - "google", - "organic", - "task_get", - "regular", - "11151456-0696-0066-0000-002a5915da37" - ], - "data": { - "api": "serp", - "function": "task_get", - "se": "google", - "se_type": "organic", - "language_name": "English", - "location_name": "United States", - "keyword": "flight ticket new york san francisco", - "priority": "2", - "tag": "tag2", - "device": "desktop", - "os": "windows" - }, - "result": [ - { - "keyword": "flight ticket new york san francisco", - "type": "organic", - "se_domain": "google.com", - "location_code": 2840, - "language_code": "en", - "check_url": "https://www.google.com/search?q=flight%20ticket%20new%20york%20san%20francisco&num=100&hl=en&gl=US&gws_rd=cr&ie=UTF-8&oe=UTF-8&uule=w+CAIQIFISCQs2MuSEtepUEUK33kOSuTsc", - "datetime": "2019-11-15 12:57:46 +00:00", - "spell": null, - "item_types": [ - "organic", - "paid" - ], - "se_results_count": 85600000, - "items_count": 96, - "items": [ - { - "type": "paid", - "rank_group": 1, - "rank_absolute": 1, - "domain": "www.bookingbuddy.com", - "title": "Flights To Lwo | Unbelievably Cheap Flights | BookingBuddy.com‎", - "description": "Compare Airlines & Sites. Cheap Flights on BookingBuddy, a TripAdvisor Company", - "url": "https://www.bookingbuddy.com/en/hero/", - "breadcrumb": "www.bookingbuddy.com/Flights" - }, - { - "type": "paid", - "rank_group": 2, - "rank_absolute": 2, - "domain": "www.trip.com", - "title": "Cheap Flight Tickets | Search & Find Deals on Flights | trip.com‎", - "description": "Wide Selection of Cheap Flights Online. Explore & Save with Trip.com! Fast, Easy & Secure...", - "url": "https://www.trip.com/flights/index?utm_campaign=GG_SE_All_en_Flight_Generic_NA_Phrase", - "breadcrumb": "www.trip.com/" - }, - { - "type": "paid", - "rank_group": 3, - "rank_absolute": 4, - "domain": "www.kayak.com", - "title": "Find the Cheapest Flights | Search, Compare & Save Today‎", - "description": "Cheap Flights, Airline Tickets and Flight Deals. Compare 100s of Airlines Worldwide. Search...", - "url": "https://www.kayak.com/horizon/sem/flights/general", - "breadcrumb": "www.kayak.com/flights" - }, - { - "type": "organic", - "rank_group": 1, - "rank_absolute": 5, - "domain": "www.kayak.com", - "title": "Cheap Flights from New York to San Francisco from $182 ...", - "description": "Fly from New York to San Francisco on Frontier from $182, United Airlines from ... the cheapest round-trip tickets were found on Frontier ($182), United Airlines ...", - "url": "https://www.kayak.com/flight-routes/New-York-NYC/San-Francisco-SFO", - "breadcrumb": "https://www.kayak.com › Flights › North America › United States › California" - }, - { - "type": "organic", - "rank_group": 2, - "rank_absolute": 6, - "domain": "www.skyscanner.com", - "title": "Cheap flights from New York to San Francisco SFO from $123 ...", - "description": "Flight information New York to San Francisco International .... tool will help you find the cheapest tickets from New York in San Francisco in just a few clicks.", - "url": "https://www.skyscanner.com/routes/nyca/sfo/new-york-to-san-francisco-international.html", - "breadcrumb": "https://www.skyscanner.com › United States › New York" - }, - { - "type": "organic", - "rank_group": 3, - "rank_absolute": 7, - "domain": "www.expedia.com", - "title": "JFK to SFO: Flights from New York to San Francisco for 2019 ...", - "description": "Book your New York (JFK) to San Francisco (SFO) flight with our Best Price ... How much is a plane ticket to San Francisco (SFO) from New York (JFK)?.", - "url": "https://www.expedia.com/lp/flights/jfk/sfo/new-york-to-san-francisco", - "breadcrumb": "https://www.expedia.com › flights › jfk › sfo › new-york-to-san-francisco" - }, - { - "type": "organic", - "rank_group": 94, - "rank_absolute": 97, - "domain": "www.ethiopianairlines.com", - "title": "Ethiopian Airlines | Book your next flight online and Fly Ethiopian", - "description": "Fly to your Favorite International Destination with Ethiopian Airlines. Book your Flights Online for Best Offers/Discounts and Enjoy African Flavored Hospitality.", - "url": "https://www.ethiopianairlines.com/", - "breadcrumb": "https://www.ethiopianairlines.com" - }, - { - "type": "organic", - "rank_group": 95, - "rank_absolute": 98, - "domain": "www.vietnamairlines.com", - "title": "Vietnam Airlines | Reach Further | Official website", - "description": "Great value fares with Vietnam Airlines. Book today and save! Skytrax – 4 Star airline. Official website. Earn frequent flyer miles with Lotusmiles.", - "url": "https://www.vietnamairlines.com/", - "breadcrumb": "https://www.vietnamairlines.com" - }, - { - "type": "organic", - "rank_group": 96, - "rank_absolute": 99, - "domain": "books.google.com", - "title": "Code of Federal Regulations: 1985-1999", - "description": "A purchases in New York a round-trip ticket for transportation by air from New York to ... B purchases a ticket in San Francisco for Combination rail and water ...", - "url": "https://books.google.com/books?id=av3umFsqbAEC&pg=PA305&lpg=PA305&dq=flight+ticket+new+york+san+francisco&source=bl&ots=fJJY5RUS9l&sig=ACfU3U16ejUqNf23jHD32QNCxDCa05Vn9g&hl=en&ppis=_e&sa=X&ved=2ahUKEwjs_4OnouzlAhXJ4zgGHeBcD3oQ6AEwdXoECHEQAQ", - "breadcrumb": "https://books.google.com › books" - } - ] - } - ] - } - ] - } - """ - end -end diff --git a/test/support/translator_case.ex b/test/support/translator_case.ex new file mode 100644 index 0000000..6029fbc --- /dev/null +++ b/test/support/translator_case.ex @@ -0,0 +1,18 @@ +defmodule DataForSeo.TranslatorCase do + defmacro __using__(_) do + quote do + import DataForSeo.Test.ResponseFactory + alias DataForSeo.DataModel.Generic.Task + alias DataForSeo.DataModel.Transform.ResponseTranslator + + defp translate_task_from_fixture(path) do + path + |> get_json_fixture() + |> ResponseTranslator.load_response() + |> ResponseTranslator.get_tasks() + |> hd() + |> ResponseTranslator.load_task_result() + end + end + end +end