/R/duckduck-answer.R

https://github.com/dirkschumacher/duckduckr · R · 115 lines · 72 code · 4 blank · 39 comment · 8 complexity · 87070c05d8df5e65fbe7e29e5597072e MD5 · raw file

  1. #' Call DuckDuckGo Instant Answer API
  2. #'
  3. #' Makes a synchronous API call to the DuckDuckGo Instant Answer API.
  4. #' Take a look at DuckDuckGo's terms of use (\url{https://api.duckduckgo.com/api}) before using it.
  5. #'
  6. #' @param query the query string
  7. #' @param no_redirect TRUE to skip HTTP redirects (for !bang commands)
  8. #' @param no_html TRUE to remove html from results
  9. #' @param skip_disambig TRUE to to skip disambiguation (D) Type.
  10. #' @param app_name the appname used to identify your application.
  11. #'
  12. #' @seealso \url{https://api.duckduckgo.com/api} for more information on the API and their terms of use.
  13. #'
  14. #' @return Always returns a list. If the API call was successful it contains the response of
  15. #' the duckduckgo API as parsed by \code{\link[jsonlite]{fromJSON}}. In addition the object's
  16. #' attributes contain additional meta data. Especially the \code{status} attribute indicates
  17. #' if something went wrong during the HTTP call or parsing of the JSON text.
  18. #'
  19. #' In case the call was successful the \code{status} attribute is equal to "OK".
  20. #'
  21. #' In case something went wrong, the \code{status} attribute is equal to "error" and in the
  22. #' \code{error} attribute you will find more information. In particular the \code{type}, which is
  23. #' either "http_error" or "json_parse_error" depending on the error's source.
  24. #'
  25. #' In case of a "http_error", there is an additional \code{message} and \code{http_status}
  26. #' element.
  27. #'
  28. #' In case of "json_parse_error", there is an additional \code{message} element.
  29. #'
  30. #' In addition there is always a \code{source} element with the URL used to query the data.
  31. #' @export
  32. #' @examples \dontrun{
  33. #' tmp <- duckduck_answer("duckduckgo")
  34. #' tmp$Abstract
  35. #' }
  36. duckduck_answer <- function(query, no_redirect = FALSE,
  37. no_html = FALSE, skip_disambig = FALSE,
  38. app_name = "duckduckr") {
  39. # lazy input checks
  40. stopifnot(length(app_name) == 1)
  41. stopifnot(length(no_redirect) == 1)
  42. stopifnot(length(no_html) == 1)
  43. stopifnot(length(skip_disambig) == 1)
  44. stopifnot(length(query) == 1)
  45. stopifnot(is.character(app_name))
  46. stopifnot(is.character(query))
  47. stopifnot(nchar(app_name) > 0)
  48. stopifnot(is.logical(no_redirect))
  49. stopifnot(is.logical(no_html))
  50. stopifnot(is.logical(skip_disambig))
  51. # construct the api call
  52. client <- crul::HttpClient$new(url = "https://api.duckduckgo.com/")
  53. query_params <- list(
  54. q = query,
  55. no_redirect = as.integer(no_redirect),
  56. no_html = as.integer(no_html),
  57. format = "json",
  58. skip_disambig = as.integer(skip_disambig),
  59. t = app_name
  60. )
  61. result <- client$get("", query_params)
  62. # format the result
  63. http_status <- result$status_http()$status_code
  64. if (http_status == "200") {
  65. parsed_response <- tryCatch({
  66. parsed_response <- jsonlite::fromJSON(
  67. suppressMessages(result$parse("UTF-8")),
  68. simplifyVector = TRUE,
  69. simplifyDataFrame = TRUE,
  70. simplifyMatrix = TRUE,
  71. flatten = FALSE
  72. )
  73. response_names <- names(parsed_response)
  74. attributes(parsed_response) <- list(
  75. status = "OK",
  76. source = result$url
  77. )
  78. names(parsed_response) <- response_names
  79. parsed_response
  80. }, error = function(e) {
  81. parsed_response <- list()
  82. attributes(parsed_response) <- make_error_object(
  83. result$url,
  84. list(
  85. "message" = e$message,
  86. "type" = "json_parse_error"
  87. )
  88. )
  89. parsed_response
  90. })
  91. } else {
  92. parsed_response <- list()
  93. attributes(parsed_response) <- make_error_object(
  94. result$url,
  95. list(
  96. "message" = "HTTP get call failed",
  97. "http_status" = http_status,
  98. "type" = "http_error"
  99. )
  100. )
  101. }
  102. parsed_response
  103. }
  104. #' @noRd
  105. make_error_object <- function(url, error) {
  106. list(
  107. status = "error",
  108. error = error,
  109. source = url
  110. )
  111. }