├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── pr-commands.yaml ├── .gitignore ├── CONTRIBUTING.md ├── CRAN-RELEASE ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── auth.R ├── pocket_add.R ├── pocket_archive.R ├── pocket_delete.R ├── pocket_favorite.R ├── pocket_get.R ├── pocket_modify.R ├── pocket_tag.R ├── pocket_unfavorite.R ├── utils-pipe.R └── utils.R ├── README.md ├── codecov.yml ├── cran-comments.md ├── man ├── check_for_add_success_.Rd ├── create_authorize_url.Rd ├── extract_action_results_.Rd ├── gen_action_.Rd ├── gen_add_action_.Rd ├── gen_tag_action_.Rd ├── get_access_token.Rd ├── get_request_token.Rd ├── parse_item_.Rd ├── pipe.Rd ├── pocket_add.Rd ├── pocket_archive.Rd ├── pocket_delete.Rd ├── pocket_favorite.Rd ├── pocket_get.Rd ├── pocket_modify.Rd ├── pocket_modify_bulk_.Rd ├── pocket_post_.Rd ├── pocket_tag.Rd ├── pocket_unfavorite.Rd └── warn_for_failures_.Rd ├── pocketapi.Rproj ├── tests ├── .gitignore ├── testthat.R └── testthat │ ├── getpocket.com │ └── v3 │ │ ├── get-40608f-POST.json │ │ ├── get-86fba0-POST.R │ │ ├── oauth │ │ ├── authorize-1384bf-POST.R │ │ ├── authorize-7becba-POST.R │ │ ├── authorize-b9d4e4-POST.R │ │ ├── request-b9df5b-POST.R │ │ └── request-da037e-POST.R │ │ ├── send-055179-POST.json │ │ ├── send-1972e8-POST.json │ │ ├── send-28a90b-POST.json │ │ ├── send-2c60b2-POST.json │ │ ├── send-44b5bc-POST.json │ │ ├── send-476df7-POST.json │ │ ├── send-49cc63-POST.json │ │ ├── send-4dda31-POST.json │ │ ├── send-53161a-POST.json │ │ ├── send-560791-POST.json │ │ ├── send-6be7e5-POST.json │ │ ├── send-bb8dd5-POST.json │ │ └── send-e6ec69-POST.json │ ├── helper.R │ ├── test_auth.R │ ├── test_pocket_add.R │ ├── test_pocket_archive.R │ ├── test_pocket_delete.R │ ├── test_pocket_favorite.R │ ├── test_pocket_get.R │ ├── test_pocket_modify.R │ ├── test_pocket_tag.R │ ├── test_pocket_unfavorite.R │ └── test_utils.R └── vignettes ├── .gitignore ├── introduction_to_pocketapi └── 0 │ └── getpocket.com │ └── v3 │ ├── get-1f4b80-POST.json │ └── send-1c07b4-POST.json └── pocketapi.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.travis\.yml$ 2 | ^pocketapi\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^README.Rmd 5 | ^.github 6 | ^\.github$ 7 | ^codecov\.yml$ 8 | ^CONTRIBUTING.md 9 | ^pocketapi.code-workspace 10 | ^doc$ 11 | ^Meta$ 12 | ^LICENSE\.md$ 13 | ^cran-comments\.md$ 14 | ^CRAN-RELEASE$ 15 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 2 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | name: R-CMD-check 12 | 13 | jobs: 14 | R-CMD-check: 15 | runs-on: ${{ matrix.config.os }} 16 | 17 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | config: 23 | - {os: windows-latest, r: 'release'} 24 | - {os: windows-latest, r: 'devel'} 25 | - {os: macOS-latest, r: 'release'} 26 | - {os: macOS-latest, r: 'devel'} 27 | - {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 28 | - {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 29 | 30 | env: 31 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 32 | RSPM: ${{ matrix.config.rspm }} 33 | POCKET_CONSUMER_KEY: ${{ secrets.POCKET_CONSUMER_KEY }} 34 | POCKET_ACCESS_TOKEN: ${{ secrets.POCKET_ACCESS_TOKEN }} 35 | 36 | steps: 37 | - uses: actions/checkout@v2 38 | 39 | - uses: r-lib/actions/setup-r@master 40 | with: 41 | r-version: ${{ matrix.config.r }} 42 | 43 | - uses: r-lib/actions/setup-pandoc@master 44 | 45 | - name: Query dependencies 46 | run: | 47 | install.packages('remotes') 48 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 49 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 50 | shell: Rscript {0} 51 | 52 | - name: Cache R packages 53 | if: runner.os != 'Windows' 54 | uses: actions/cache@v1 55 | with: 56 | path: ${{ env.R_LIBS_USER }} 57 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 58 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 59 | 60 | - name: Install system dependencies 61 | if: runner.os == 'Linux' 62 | run: | 63 | while read -r cmd 64 | do 65 | eval sudo $cmd 66 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') 67 | 68 | - name: Install dependencies 69 | run: | 70 | remotes::install_deps(dependencies = TRUE) 71 | remotes::install_cran("rcmdcheck") 72 | shell: Rscript {0} 73 | 74 | - name: Check 75 | env: 76 | _R_CHECK_CRAN_INCOMING_REMOTE_: false 77 | run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 78 | shell: Rscript {0} 79 | 80 | - name: Upload check results 81 | if: failure() 82 | uses: actions/upload-artifact@main 83 | with: 84 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 85 | path: check 86 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | issue_comment: 3 | types: [created] 4 | name: Commands 5 | jobs: 6 | document: 7 | if: startsWith(github.event.comment.body, '/document') 8 | name: document 9 | runs-on: macOS-latest 10 | env: 11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: r-lib/actions/pr-fetch@master 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | - uses: r-lib/actions/setup-r@master 18 | - name: Install dependencies 19 | run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE)' 20 | - name: Document 21 | run: Rscript -e 'roxygen2::roxygenise()' 22 | - name: commit 23 | run: | 24 | git config --local user.email "actions@github.com" 25 | git config --local user.name "GitHub Actions" 26 | git add man/\* NAMESPACE 27 | git commit -m 'Document' 28 | - uses: r-lib/actions/pr-push@master 29 | with: 30 | repo-token: ${{ secrets.GITHUB_TOKEN }} 31 | style: 32 | if: startsWith(github.event.comment.body, '/style') 33 | name: style 34 | runs-on: macOS-latest 35 | env: 36 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: r-lib/actions/pr-fetch@master 40 | with: 41 | repo-token: ${{ secrets.GITHUB_TOKEN }} 42 | - uses: r-lib/actions/setup-r@master 43 | - name: Install dependencies 44 | run: Rscript -e 'install.packages("styler")' 45 | - name: Style 46 | run: Rscript -e 'styler::style_pkg()' 47 | - name: commit 48 | run: | 49 | git config --local user.email "actions@github.com" 50 | git config --local user.name "GitHub Actions" 51 | git add \*.R 52 | git commit -m 'Style' 53 | - uses: r-lib/actions/pr-push@master 54 | with: 55 | repo-token: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # Example code in package build process 9 | *-Ex.R 10 | 11 | # Output files from R CMD build 12 | /*.tar.gz 13 | 14 | # Output files from R CMD check 15 | /*.Rcheck/ 16 | 17 | # RStudio files 18 | .Rproj.user/ 19 | .DS_Store 20 | 21 | # produced vignettes 22 | vignettes/*.html 23 | vignettes/*.pdf 24 | 25 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 26 | .httr-oauth 27 | 28 | # knitr and R markdown default cache directories 29 | /*_cache/ 30 | /cache/ 31 | 32 | # Temporary files created by R markdown 33 | *.utf8.md 34 | *.knit.md 35 | 36 | # Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html 37 | rsconnect/ 38 | .Rproj.user 39 | 40 | # getpocket test files from httptest in root 41 | /getpocket.com 42 | inst/doc 43 | 44 | # vscode workspace 45 | pocketapi.code-workspace 46 | 47 | # check 48 | check/ 49 | doc 50 | Meta 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pocketapi 2 | 3 | You're welcome to propose a change to pocketapi. 4 | 5 | ## Prerequisites 6 | Before making any substantial changes to pocketapi with a pull request, please file an issue first describing the problem so that someone from the project team can look into it and confirm that it needs adaptation. 7 | If you want to report a bug, please provide a minimal [reprex](https://www.tidyverse.org/help/) to make the issue reproducible for the developers. 8 | 9 | ## Pull request process 10 | - Please create a Git branch for your pull request (PR). 11 | - make sure the badges in the README before and after making changes to ensure everything is still working as expected. 12 | - Please provide tests in the tests folder. This makes it easier for us to accept your PR. 13 | - Please format your code using the [`styler`](https://styler.r-lib.org/) package before committing it. 14 | - If you're change is user-facing (e.g. changing function parameters), include a bullet to the top of NEWS.md below the current development version header describing the changes made followed by your GitHub username and links to relevant issue(s)/PR(s). 15 | 16 | The following code of conduct is to be respected. 17 | 18 | ## Code of Conduct 19 | 20 | As contributors and maintainers of this project, we pledge to respect all people who 21 | contribute through reporting issues, posting feature requests, updating documentation, 22 | submitting pull requests or patches, and other activities. 23 | 24 | We are committed to making participation in this project a harassment-free experience for 25 | everyone, regardless of level of experience, gender, gender identity and expression, 26 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 27 | 28 | Examples of unacceptable behavior by participants include the use of sexual language or 29 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 30 | insults, or other unprofessional conduct. 31 | 32 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 33 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 34 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 35 | from the project team. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 38 | opening an issue or contacting one or more of the project maintainers. 39 | 40 | This Code of Conduct is adapted from the Contributor Covenant 41 | (http://contributor-covenant.org), version 1.0.0, available at 42 | http://contributor-covenant.org/version/1/0/0/ 43 | -------------------------------------------------------------------------------- /CRAN-RELEASE: -------------------------------------------------------------------------------- 1 | This package was submitted to CRAN on 2020-11-18. 2 | Once it is accepted, delete this file and tag the release (commit e578cc9). 3 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pocketapi 2 | Title: Wrapper Around the 'Pocket' API 3 | Version: 0.1 4 | Authors@R: c(person("Frie", "Preu", email = "fr1e@pm.me", role = c("aut", "cre")), 5 | person("Yannik", "Buhl", email = "yannik.buhl@posteo.de", role = "aut"), 6 | person("Max", "Alletsee", email = "max.alletsee@gmail.com", role = "aut"), 7 | person("Dorian", "Le Jeune", email = "D.Le-Jeune@gmx.de", role = "ctb"), 8 | person("Christoph", "Dworschak", email = "c.dworschak@essex.ac.uk", role = "ctb"), 9 | person("Sarah", "Jukna", role = "ctb"), 10 | person("CorrelAid e.V.", role = "cph") 11 | ) 12 | Description: Functions that interface with the 'Pocket' API (). Allows the user to get, add, and modify items in their own 'Pocket' account. 13 | Depends: R (>= 3.5.0) 14 | Imports: 15 | httr, 16 | purrr, 17 | tibble, 18 | dplyr, 19 | glue, 20 | jsonlite, 21 | magrittr, 22 | usethis 23 | License: MIT + file LICENSE 24 | Encoding: UTF-8 25 | LazyData: true 26 | BugReports: https://github.com/CorrelAid/pocketapi//issues 27 | URL: https://github.com/CorrelAid/pocketapi/ 28 | RoxygenNote: 7.1.1 29 | Suggests: 30 | testthat, 31 | mockery, 32 | covr, 33 | httptest, 34 | knitr, 35 | rmarkdown, 36 | ggplot2 37 | VignetteBuilder: knitr 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: CorrelAid e.V. 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 CorrelAid e.V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(create_authorize_url) 5 | export(get_access_token) 6 | export(get_request_token) 7 | export(pocket_add) 8 | export(pocket_archive) 9 | export(pocket_delete) 10 | export(pocket_favorite) 11 | export(pocket_get) 12 | export(pocket_modify) 13 | export(pocket_tag) 14 | export(pocket_unfavorite) 15 | importFrom(glue,glue) 16 | importFrom(httr,POST) 17 | importFrom(httr,content) 18 | importFrom(httr,parse_url) 19 | importFrom(httr,status_code) 20 | importFrom(httr,stop_for_status) 21 | importFrom(jsonlite,toJSON) 22 | importFrom(magrittr,"%>%") 23 | importFrom(purrr,map) 24 | importFrom(purrr,map_chr) 25 | importFrom(purrr,map_dfr) 26 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # development version 2 | 3 | # pocketapi 0.1 4 | - initial CRAN release 5 | - support for authentication 6 | - supported functions 7 | - pocket_add() 8 | - pocket_get() 9 | - pocket_archive() 10 | - pocket_delete() 11 | - pocket_favorite() 12 | - pocket_unfavorite() 13 | - pocket_tag() 14 | -------------------------------------------------------------------------------- /R/auth.R: -------------------------------------------------------------------------------- 1 | #' get_request_token 2 | #' @description Requests a request_token for the Pocket application with a given consumer key from the Pocket Authentication API. The request token can then be used in \code{\link{create_authorize_url}} and \code{\link{get_access_token}}. 3 | #' @param consumer_key Character string. Here goes your Pocket consumer key. 4 | #' @return Character string. Request token for the Pocket Application corresponding to your consumer_key. 5 | #' @details See the \href{https://github.com/CorrelAid/pocketapi}{GitHub README} for details on Authentication. 6 | #' @importFrom httr POST stop_for_status content 7 | #' @family authentication functions 8 | #' @export 9 | get_request_token <- function(consumer_key) { 10 | if (!is.character(consumer_key) || length(consumer_key) != 1) { 11 | usethis::ui_stop("Argument consumer_key must be a character vector of length 1.") 12 | } 13 | 14 | # Define relevant URLs for the function 15 | REQUEST_URL <- "https://getpocket.com/v3/oauth/request" 16 | REDIRECT_URI <- "https://github.com/CorrelAid/pocketapi" 17 | 18 | # Get request_token 19 | res <- httr::POST(REQUEST_URL, 20 | body = list(consumer_key = consumer_key, redirect_uri = REDIRECT_URI), encode = "form" 21 | ) 22 | pocket_stop_for_status_(res) 23 | 24 | request_token <- httr::content(res)$code 25 | return(request_token) 26 | } 27 | 28 | #' create_authorize_url 29 | #' @description Creates the URL the user needs to enter into her browser in order to authorize the Pocket application/the request token. 30 | #' @param request_token Character string. Pocket request token generated by the function \code{\link{get_request_token}}. 31 | #' @details See the \href{https://github.com/CorrelAid/pocketapi}{GitHub README} for details on Authentication. 32 | #' @return Character string. The URL for authorizing the Pocket application for which the request token was requested. This is what you enter into your browser. 33 | #' @importFrom glue glue 34 | #' @family authentication functions 35 | #' @export 36 | #' 37 | create_authorize_url <- function(request_token) { 38 | # Only accept a string 39 | if (!is.character(request_token) || length(request_token) != 1) { 40 | usethis::ui_stop("Argument request_token must be a character vector of length 1.") 41 | } 42 | # Create URL to give the app access 43 | AUTHORIZE_URL <- "https://getpocket.com/auth/authorize" 44 | REDIRECT_URI <- "https://github.com/CorrelAid/pocketapi" 45 | 46 | auth_url <- glue::glue("{AUTHORIZE_URL}?request_token={request_token}&redirect_uri={REDIRECT_URI}") 47 | 48 | usethis::ui_todo("Enter this URL into your browser and grant your app access:") 49 | return(auth_url) 50 | } 51 | 52 | #' get_access_token 53 | #' @description Creates the URL the user needs to enter into her browser in order to authorize the Pocket application/the request token. 54 | #' @param consumer_key Character string. Here goes your Pocket consumer key. 55 | #' @param request_token Character string. Pocket request token generated by \code{\link{get_request_token}}. 56 | #' @return Character string. Returns the access token. 57 | #' @details See the \href{https://github.com/CorrelAid/pocketapi}{GitHub README} for details on Authentication. 58 | #' @importFrom httr POST stop_for_status content parse_url 59 | #' @family authentication functions 60 | #' @export 61 | #' 62 | get_access_token <- function(consumer_key, request_token) { 63 | if (!is.character(consumer_key) || length(consumer_key) != 1) { 64 | usethis::ui_stop("Argument consumer_key must be a character vector of length 1.") 65 | } 66 | 67 | if (!is.character(request_token) || length(request_token) != 1) { 68 | usethis::ui_stop("Argument request_token must be a character vector of length 1.") 69 | } 70 | 71 | AUTH_URL <- "https://getpocket.com/v3/oauth/authorize" 72 | 73 | authorize_url <- httr::parse_url(AUTH_URL) 74 | res <- httr::POST(authorize_url, body = list(consumer_key = consumer_key, code = request_token)) 75 | 76 | pocket_stop_for_status_(res) 77 | access_token <- httr::content(res)$access_token 78 | return(access_token) 79 | } 80 | -------------------------------------------------------------------------------- /R/pocket_add.R: -------------------------------------------------------------------------------- 1 | #' pocket_add 2 | #' @description Add one or more items to your Pocket account. 3 | #' @param add_urls Character vector. The URL or URLs you want to add to your Pocket list. 4 | #' @param item_ids Character vector. The item_ids of the items you want to add. Defaults to empty character vector. 5 | #' @param tags Character vector. One or more tags to be applied to all of the newly added URLs. Defaults to NULL. 6 | #' @param success Logical. Enables success/failure messages for every URL. Defaults to TRUE. Needs GET permission if TRUE. 7 | #' @param consumer_key Character string. Your Pocket consumer key. Defaults to \code{Sys.getenv("POCKET_CONSUMER_KEY")}. 8 | #' @param access_token Character string. Your Pocket request token. Defaults to \code{Sys.getenv("POCKET_ACCESS_TOKEN")}. 9 | #' @export 10 | #' @details This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 11 | #' For example, even if a `modify` action is not successful, the API will still return "success". 12 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 13 | #' @return the response from the httr call, invisibly 14 | pocket_add <- function(add_urls, 15 | item_ids = "", 16 | tags = NULL, 17 | success = TRUE, 18 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 19 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 20 | 21 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 22 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 23 | if (missing(add_urls)) usethis::ui_stop("Argument 'add_urls' is missing.") 24 | 25 | action_list <- add_urls %>% purrr::map( 26 | action_name = "add", 27 | item_id = item_ids, 28 | tags = paste(tags, collapse = ","), 29 | .f = gen_add_action_) 30 | 31 | actions_json <- jsonlite::toJSON(action_list, auto_unbox = TRUE) 32 | 33 | res <- pocket_post_("send", 34 | consumer_key, 35 | access_token, 36 | actions = actions_json 37 | ) 38 | 39 | if (success == TRUE) { 40 | check_for_add_success_(add_urls, consumer_key, access_token) 41 | } 42 | 43 | return(invisible(res)) 44 | 45 | } 46 | 47 | #' check_for_add_success_ 48 | #' @description Check whether all URLs were successfully added to Pocket by \code{pocket_add()}. 49 | #' @param urls Character vector containing URLs to be checked. 50 | #' @param consumer_key Character string. Your Pocket consumer key. 51 | #' @param access_token Character string. Your Pocket request token. 52 | #' @return Returns messages of success and/or failure of the URLs wished to be added to Pocket. 53 | #' @keywords internal 54 | check_for_add_success_ <- function(urls, consumer_key, access_token) { 55 | 56 | pocket_content <- pocketapi::pocket_get(consumer_key = consumer_key, access_token = access_token) 57 | 58 | checked <- urls %in% pocket_content$given_url 59 | 60 | false_indexes <- which(checked %in% FALSE) 61 | true_indexes <- which(checked %in% TRUE) 62 | 63 | false_urls <- urls[false_indexes] 64 | true_urls <- urls[true_indexes] 65 | 66 | if (!is.null(true_urls)) { 67 | 68 | print(glue::glue("The following URL has been successfully added: {true_urls}.")) 69 | 70 | } 71 | 72 | if (!is.null(false_urls)) { 73 | 74 | warning(glue::glue("The following URL has not been successfully added: {false_urls}. Hint: URLs need to begin with 'http://' or 'https://'.")) 75 | 76 | } 77 | 78 | } 79 | 80 | 81 | #' gen_add_action_ 82 | #' @description Generate an action list element for adding URLs to Pocket. 83 | #' @param add_urls Character vector. URLs that are to be added to Pocket. 84 | #' @param action_name Character. Name of the action to be used (ADD, in this case). 85 | #' @param ... Additional named arguments to be added to the action list. 86 | #' @return List of actions and URLs to add to Pocket. 87 | #' @keywords internal 88 | gen_add_action_ <- function(add_urls, action_name, ...) { 89 | return(list( 90 | action = action_name, 91 | url = add_urls, 92 | ... 93 | )) 94 | } 95 | 96 | 97 | # #' extract_action_no_id_results_ 98 | # #' #' @description Generate an action list element for a given action name 99 | # #' #' @param add_urls character vector. URLs that are to be added 100 | # #' @param action_name character. Name of the action to be used (add) 101 | # #' @param ... additional named arguments to be added to the action list. 102 | # #' @return list 103 | # extract_action_no_id_results_ <- function(res) { 104 | # 105 | # content <- httr::content(res) 106 | # 107 | # success_ids <- map(content$action_results, "item_id") 108 | # failure_ids <- map(content$action_errors, "item_id") 109 | # 110 | # return(list(success_ids = success_ids, failure_ids = failure_ids)) 111 | # } 112 | 113 | -------------------------------------------------------------------------------- /R/pocket_archive.R: -------------------------------------------------------------------------------- 1 | #' pocket_archive 2 | #' @description archive items from your Pocket list. 3 | #' @param item_ids character vector. Pocket item ids you want to archive. 4 | #' @param consumer_key character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY"). 5 | #' @param access_token character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN"). 6 | #' @details This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 7 | #' For example, even if a `modify` action is not successful, the API will still return "success". 8 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 9 | #' @importFrom purrr map 10 | #' @return Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 11 | #' @export 12 | pocket_archive <- function(item_ids, consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 14 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 15 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 16 | 17 | # Generate "array" with actions (list of list in R) 18 | results <- pocket_modify_bulk_(item_ids, "archive", consumer_key, access_token) 19 | 20 | return(invisible(results)) 21 | } -------------------------------------------------------------------------------- /R/pocket_delete.R: -------------------------------------------------------------------------------- 1 | #' pocket_delete 2 | #' @description delete items from your Pocket list. 3 | #' @param item_ids character vector. Pocket item ids you want to delete from your list. 4 | #' @param consumer_key character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY"). 5 | #' @param access_token character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN"). 6 | #' @details This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 7 | #' For example, even if a `modify` action is not successful, the API will still return "success". 8 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 9 | #' @importFrom purrr map 10 | #' @return Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 11 | #' @export 12 | pocket_delete <- function(item_ids, consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 14 | 15 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 16 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 17 | # generate "array" with actions (list of list in R) 18 | results <- pocket_modify_bulk_(item_ids, "delete", consumer_key, access_token) 19 | 20 | return(invisible(results)) 21 | } 22 | -------------------------------------------------------------------------------- /R/pocket_favorite.R: -------------------------------------------------------------------------------- 1 | #' pocket_favorite 2 | #' @description favorite items from your Pocket list. 3 | #' @param item_ids character vector. Pocket item ids you want to favorite 4 | #' @param consumer_key character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY"). 5 | #' @param access_token character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN"). 6 | #' @details This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 7 | #' For example, even if a `modify` action is not successful, the API will still return "success". 8 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 9 | #' @importFrom purrr map 10 | #' @return Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 11 | #' @export 12 | pocket_favorite <- function(item_ids, consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 14 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 15 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 16 | 17 | # generate "array" with actions (list of list in R) 18 | results <- pocket_modify_bulk_(item_ids, "favorite", consumer_key, access_token) 19 | 20 | return(invisible(results)) 21 | } 22 | -------------------------------------------------------------------------------- /R/pocket_get.R: -------------------------------------------------------------------------------- 1 | #' pocket_get 2 | #' @description Get a data frame with your pocket data. 3 | #' @param favorite boolean. Default NULL. Allows to filter for favorited items. If TRUE, only favorited items will be returned. If FALSE, only un-favorited items will be returned. 4 | #' @param item_type character. Default NULL. Allows to filter for content type of items. Valid values are: "image", "article", "video". Please note that there might be Pocket items that do not belong to any of those types. The Pocket API documentation only mentions those three. 5 | #' @param tag character. Default NULL. Only one tag can be filtered at a time. Set to '_untagged_' if you only want to get untagged items. 6 | #' @param state character. Default "all". Allows to filter on unread/archived items or return all. Valid values are "unread", "archive", "all". 7 | #' @param consumer_key character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY"). 8 | #' @param access_token character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN"). 9 | #' @return tibble. Tibble with one row for each Pocket item. 10 | #' @details See https://getpocket.com/developer/docs/v3/retrieve for the meaning of certain variable values. 11 | #' @importFrom purrr map_dfr 12 | #' @export 13 | pocket_get <- function(favorite = NULL, 14 | item_type = NULL, 15 | tag = NULL, 16 | state = "all", 17 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 18 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 19 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 20 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 21 | 22 | # Arguments to call the 'post' function with later 23 | # We do this so that we can add additional arguments to ... conditional on the if statements 24 | post_fun_args <- list( 25 | endpoint = "get", 26 | consumer_key = consumer_key, 27 | access_token = access_token, 28 | detailType = "complete", # all variables 29 | state = state 30 | ) 31 | 32 | if (!is.null(favorite)) { 33 | if (!is.logical(favorite) || length(favorite) != 1) usethis::ui_stop("The favorite argument can only be TRUE or FALSE.") 34 | post_fun_args$favorite <- as.integer(favorite) 35 | } 36 | 37 | if (!is.null(item_type)) { 38 | if (!item_type %in% c("image", "video", "article")) usethis::ui_stop("The item_type argument can only be one of the following: 'image', 'article', 'video'.") 39 | post_fun_args$contentType <- item_type 40 | } 41 | 42 | if (!is.null(tag)) { 43 | if (!is.character(tag) || length(tag) != 1) usethis::ui_stop("The tag argument can only be a character string.") 44 | post_fun_args$tag <- tag 45 | } 46 | 47 | 48 | if (!is.null(state)) { 49 | if (!is.character(state) || length(state) != 1) usethis::ui_stop("The state argument can only be one of the following: 'unread', 'archive', 'all'") 50 | if (!(state %in% c("unread", "archive", "all"))) usethis::ui_stop("The state argument can only be one of the following: 'unread', 'archive', 'all'") 51 | post_fun_args$state <- state 52 | } 53 | 54 | res <- do.call(pocket_post_, args = post_fun_args) 55 | pocket_stop_for_status_(res) 56 | 57 | pocket_content <- content(res) 58 | pocket_items <- pocket_content$l 59 | 60 | items_df <- purrr::map_dfr(pocket_items, parse_item_) 61 | return(items_df) 62 | } 63 | 64 | #' parse_item_ 65 | #' @description Parse item in the response list into a mini tibble with one row. 66 | #' @param item Pocket item from the Pocket entry list. 67 | #' @return Tibble 68 | #' @keywords internal 69 | parse_item_ <- function(item) { 70 | item_df <- tibble::tibble( 71 | item_id = item$item_id, 72 | resolved_id = item$resolved_id, 73 | given_url = item$given_url, 74 | given_title = item$given_title, 75 | resolved_title = item$resolved_title, 76 | favorite = char_to_bool_(item$favorite), 77 | status = as.integer(item$status), 78 | excerpt = item$excerpt, 79 | is_article = char_to_bool_(item$is_article), 80 | has_image = as.integer(item$has_image), 81 | has_video = as.integer(item$has_video), 82 | word_count = as.integer(item$word_count), 83 | tags = ifelse(is.null(item$tags), NA, paste(item$tags %>% purrr::map_chr("tag"), collapse = ",")), 84 | authors = ifelse(is.null(item$authors), NA, item$authors), 85 | images = ifelse(is.null(item$images), NA, item$images), 86 | image = ifelse(is.null(item$images), NA, item$image) 87 | ) 88 | 89 | item_df 90 | } 91 | 92 | 93 | char_to_bool_ <- function(char) { 94 | return(as.logical(as.integer(char))) 95 | } 96 | -------------------------------------------------------------------------------- /R/pocket_modify.R: -------------------------------------------------------------------------------- 1 | #' pocket_modify 2 | #' @description Function that sends a request with a list of actions to the 'modify' Pocket API endpoint. 3 | #' @param actions List. List of lists where each element is an action object. See https://getpocket.com/developer/docs/v3/modify. 4 | #' @param consumer_key Character string. Your Pocket consumer key. Defaults to \code{Sys.getenv("POCKET_CONSUMER_KEY")}. 5 | #' @param access_token Character string. Your Pocket request token. Defaults to \code{Sys.getenv("POCKET_ACCESS_TOKEN")}. 6 | #' @importFrom httr content 7 | #' @importFrom jsonlite toJSON 8 | #' @importFrom purrr map_chr 9 | #' @details see https://getpocket.com/developer/docs/v3/modify. This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 10 | #' For example, even if a `modify` action is not successful, the API will still return "success". 11 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 12 | #' @export 13 | pocket_modify <- function(actions, consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 14 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 15 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 16 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 17 | 18 | # Set auto_unbox = TRUE because otherwise jsonlite will en-array single values, e.g. ["archive"] 19 | actions_json <- jsonlite::toJSON(actions, auto_unbox = TRUE) 20 | res <- pocket_post_("send", 21 | consumer_key, 22 | access_token, 23 | actions = actions_json 24 | ) 25 | pocket_stop_for_status_(res) 26 | 27 | item_ids <- purrr::map_chr(actions, "item_id") 28 | action_results <- extract_action_results_(res, item_ids) 29 | 30 | message_for_successes_(action_results$success_ids) 31 | warn_for_failures_(action_results$failures) 32 | 33 | return(action_results) 34 | } 35 | 36 | #' pocket_modify_bulk_ 37 | #' @description Bulk modify for a given action, i.e., the action is the same for all item_ids. 38 | #' @param item_ids Character vector. Pocket item IDs that should be modified. 39 | #' @param action_name Character. The action that should be performed on all specified items. 40 | #' @param consumer_key Character string. Your Pocket consumer key. Defaults to \code{Sys.getenv("POCKET_CONSUMER_KEY")}. 41 | #' @param access_token Character string. Your Pocket request token. Defaults to \code{Sys.getenv("POCKET_ACCESS_TOKEN")}. 42 | #' @param ... Additional named arguments to be added to the action list items. 43 | #' @importFrom purrr map 44 | #' @keywords internal 45 | pocket_modify_bulk_ <- function(item_ids, action_name, consumer_key, access_token, ...) { 46 | 47 | # Generate "array" with actions (list of list in R) 48 | action_list <- item_ids %>% purrr::map(action_name = action_name, .f = gen_action_, ...) 49 | 50 | # Call internal function 51 | action_results <- pocket_modify(action_list, consumer_key, access_token) 52 | 53 | return(action_results) 54 | } 55 | 56 | message_for_successes_ <- function(success_ids) { 57 | success_ids_collapsed <- paste(success_ids, collapse = ", ") 58 | usethis::ui_done(glue::glue("Action was successful for the items: {success_ids_collapsed}")) 59 | } 60 | 61 | #' warn_for_failures_ 62 | #' @description Generate warnings for all failures. 63 | #' @param failures List of failures. 64 | #' @keywords internal 65 | warn_for_failures_ <- function(failures) { 66 | purrr::walk2(failures, names(failures), function(failure, failure_name) { 67 | usethis::ui_warn(glue::glue("Action on {failure_name} failed with error: {failure$action_errors}")) 68 | }) 69 | } 70 | 71 | #' gen_action_ 72 | #' @description Generate a Pocket action list element for a given ID and action name. 73 | #' @param item_id Character. ID of Pocket item. 74 | #' @param action_name Character. Name of Pocket action as a string. 75 | #' @param ... Additional, named arguments to be added to the action list. 76 | #' @return List representing a Pocket API action. 77 | #' @keywords internal 78 | gen_action_ <- function(item_id, action_name, ...) { 79 | return(list( 80 | action = action_name, 81 | item_id = item_id, 82 | time = as.POSIXct(Sys.time()), 83 | ... 84 | )) 85 | } 86 | 87 | #' extract_action_results_ 88 | #' @description Extract results from list that is returned by the send endpoint of the Pocket API. 89 | #' @param res List. Httr response object. 90 | #' @param item_ids Character vector. Pocket item IDs that were modified. 91 | #' @return Named list with the IDs for which the action was successful, the ids for which it failed and the list of failures. 92 | #' @keywords internal 93 | extract_action_results_ <- function(res, item_ids) { 94 | content <- httr::content(res) 95 | 96 | # Check whether any action has failed (status == 0) 97 | if (content$status != 0) { 98 | return(list(failure_ids = c(), success_ids = item_ids, failures = list())) 99 | } 100 | 101 | # Combine item_ids with action_results and transpose list 102 | # Set item_ids as list names 103 | content$status <- NULL 104 | content_t <- content %>% 105 | purrr::transpose() %>% 106 | purrr::set_names(item_ids) 107 | 108 | # Get IDs where action was successful 109 | successes <- content_t %>% 110 | purrr::keep(function(x) x$action_results == TRUE) 111 | success_ids <- names(successes) 112 | 113 | # Get IDs where action failed 114 | failures <- content_t %>% 115 | purrr::keep(function(x) x$action_results == FALSE) 116 | failure_ids <- names(failures) 117 | 118 | return(list(success_ids = success_ids, failure_ids = failure_ids, failures = failures)) 119 | } 120 | -------------------------------------------------------------------------------- /R/pocket_tag.R: -------------------------------------------------------------------------------- 1 | #' pocket_tag 2 | #' @description modify the tags of the items in pocket. 3 | #' @param action_name character vector. The kind of tag action you want to undertake. Possible values: 'tags_add', 'tags_remove', 'tags_replace', 'tags_clear', 'tag_rename', or 'tag_delete'. 4 | #' @param item_ids character vector. Pocket item ids you want to modify the tags for. 5 | #' @param tags character vector. The names of the tags to work with the chosen action. 6 | #' @param consumer_key character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY"). 7 | #' @param access_token character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN"). 8 | #' @details This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 9 | #' For example, even if a `modify` action is not successful, the API will still return "success". 10 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 11 | #' @export 12 | pocket_tag <- function(action_name = c("tags_replace", "tags_remove", "tags_add", "tags_clear", "tag_rename", "tag_delete"), item_ids = NULL, tags = NULL, consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 14 | 15 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 16 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 17 | 18 | # Validity checks 19 | stop_for_invalid_tag_action_(item_ids = item_ids, action_name = action_name, tags = tags) 20 | 21 | # Pre-process tags to comma separated string 22 | tags <- paste(tags, collapse = ",") 23 | 24 | # Processing 25 | # Actions that require item_ids and tags 26 | if (action_name %in% c("tags_replace", "tags_remove", "tags_add")) { 27 | action_results <- pocket_modify_bulk_(item_ids, action_name, consumer_key, access_token, tags = tags) 28 | return(invisible(action_results)) 29 | } 30 | 31 | # Clearing all tags from item(s) requires item_ids but not tags 32 | if (action_name == "tags_clear") { 33 | action_results <- pocket_modify_bulk_(item_ids, action_name, consumer_key, access_token) 34 | return(invisible(action_results)) 35 | } 36 | 37 | # Renaming a tag requires old name and new name 38 | if (action_name == "tag_rename") { 39 | # Compile list of lists with action 40 | action_list <- action_name %>% purrr::map( 41 | old_tag = tags[1], 42 | new_tag = tags[2], 43 | .f = gen_tag_action_ 44 | ) 45 | 46 | # Convert list of lists to JSON 47 | actions_json <- jsonlite::toJSON(action_list, auto_unbox = TRUE) 48 | 49 | # Send request to Pocket 50 | res <- pocket_post_("send", 51 | consumer_key, 52 | access_token, 53 | actions = actions_json 54 | ) 55 | 56 | # Return success message 57 | pocket_stop_for_status_(res) 58 | usethis::ui_done(glue::glue("Successfully renamed tag '{tags[1]}' for '{tags[2]}'.")) 59 | } 60 | 61 | 62 | # Execute tag action for "delete" 63 | if (action_name == "tag_delete") { 64 | action_list <- action_name %>% purrr::map( 65 | tag = tags, 66 | .f = gen_tag_action_ 67 | ) 68 | 69 | # Compile list of lists for action 70 | action_list <- action_name %>% purrr::map( 71 | tag = tags, 72 | .f = gen_tag_action_ 73 | ) 74 | 75 | # Convert list of lists to JSON 76 | actions_json <- jsonlite::toJSON(action_list, auto_unbox = TRUE) 77 | 78 | res <- pocket_post_("send", 79 | consumer_key, 80 | access_token, 81 | actions = actions_json 82 | ) 83 | pocket_stop_for_status_(res) 84 | usethis::ui_done(glue::glue("Successfully removed tag '{tags}'.")) 85 | } 86 | } 87 | 88 | 89 | #' gen_tag_action_ 90 | #' @description Generate an action list element for a given action name. 91 | #' @param action_name Character. Name of Pocket action as a string. 92 | #' @param ... Additional, named arguments to be added to the action list. 93 | #' @return Action list. 94 | #' @keywords internal 95 | gen_tag_action_ <- function(action_name, ...) { 96 | return(list( 97 | action = action_name, 98 | time = as.POSIXct(Sys.time()), 99 | ... 100 | )) 101 | } 102 | 103 | stop_for_invalid_tag_action_ <- function(item_ids, action_name, tags) { 104 | actions <- c("tags_add", "tags_remove", "tags_replace", "tags_clear", "tag_rename", "tag_delete") 105 | 106 | if (!action_name %in% actions) { 107 | usethis::ui_stop("Tag actions can be only be: 'tags_add', 'tags_remove', 'tags_replace', 'tags_clear', 'tag_rename', or 'tag_delete'.") 108 | } 109 | 110 | if (is.null(item_ids) & !action_name %in% c("tag_delete", "tag_rename")) { 111 | usethis::ui_stop("If your action_name is not 'tag_delete' or 'tag_rename', you need to provide at least one item_id.") 112 | } 113 | 114 | if (action_name == "tag_delete") { 115 | if (length(tags) > 1) { 116 | usethis::ui_stop("For 'tag_delete', you can only specify an atomic vector of one tag.") 117 | } 118 | if (is.null(tags)) { 119 | usethis::ui_stop("For 'tag_delete', you need to specify an atomic vector of one tag.") 120 | } 121 | } 122 | 123 | if (action_name == "tag_rename" & length(tags) != 2) { 124 | usethis::ui_stop("If your action is 'tag_rename', your tags vector must be of length 2, format: c('old tag', 'new tag').") 125 | } 126 | 127 | if (action_name == "tags_clear" & !is.null(tags)) { 128 | usethis::ui_stop("If your action is 'tags_clear', you must not provide tags.") 129 | } 130 | 131 | if (action_name == "tags_replace" & is.null(tags)) { 132 | usethis::ui_stop("For 'tags_replace', you need to specify the tags argument.") 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /R/pocket_unfavorite.R: -------------------------------------------------------------------------------- 1 | #' pocket_unfavorite 2 | #' @description Unfavorite items from your Pocket list. 3 | #' @param item_ids Character vector. Pocket item IDs you want to unfavorite. Get them, e.g., via \code{pocket_get}. 4 | #' @param consumer_key character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY"). 5 | #' @param access_token character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN"). 6 | #' @details This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 7 | #' For example, even if a `modify` action is not successful, the API will still return "success". 8 | #' See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 9 | #' @importFrom purrr map 10 | #' @return Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 11 | #' @export 12 | pocket_unfavorite <- function(item_ids, consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN")) { 14 | if (consumer_key == "") usethis::ui_stop(error_message_consumer_key()) 15 | if (access_token == "") usethis::ui_stop(error_message_access_token()) 16 | 17 | # generate "array" with actions (list of list in R) 18 | results <- pocket_modify_bulk_(item_ids, "unfavorite", consumer_key, access_token) 19 | 20 | return(invisible(results)) 21 | } 22 | -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | NULL 12 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' pocket_post_ 2 | #' @description internal function to make a POST requests to a Pocket API endpoint. 3 | #' @param endpoint character. endpoint to make a request to. 4 | #' @param consumer_key character. Pocket consumer key. 5 | #' @param access_token character. Pocket access token. 6 | #' @param ... additional named arguments to be put into the body of the POST request. 7 | #' @keywords internal 8 | pocket_post_ <- function(endpoint, consumer_key, access_token, ...) { 9 | BASE_URL <- "https://getpocket.com/v3/" 10 | res <- httr::POST( 11 | glue::glue(BASE_URL, endpoint), 12 | body = list( 13 | consumer.key = consumer_key, 14 | access.token = access_token, 15 | ... 16 | ) 17 | ) 18 | return(res) 19 | } 20 | 21 | #' @importFrom httr status_code 22 | #' @importFrom glue glue 23 | pocket_stop_for_status_ <- function(res) { 24 | if (httr::status_code(res) >= 300) { 25 | status <- res$headers$status 26 | x_error <- res$headers$`x-error` 27 | 28 | if (is.null(status)) { 29 | status <- httr::status_code(res) 30 | } 31 | usethis::ui_stop(glue::glue("Error during API request: 32 | {status}: {x_error}")) 33 | } 34 | } 35 | 36 | error_message_consumer_key <- function() { 37 | return("POCKET_CONSUMER_KEY does not exist as environment variable. Add it to your R environment or manually specify the consumer_key argument.") 38 | } 39 | 40 | error_message_access_token <- function() { 41 | return("POCKET_ACCESS_TOKEN does not exist as environment variable. Add it to your R environment or manually specify the consumer_key argument.") 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pocketapi 2 | 3 | 4 | 5 | [![R build status](https://github.com/CorrelAid/pocketapi/workflows/R-CMD-check/badge.svg)](https://github.com/CorrelAid/pocketapi/actions) 6 | [![Codecov test coverage](https://codecov.io/gh/CorrelAid/pocketapi/branch/master/graph/badge.svg)](https://codecov.io/gh/CorrelAid/pocketapi?branch=master) 7 | [![CRAN status](https://www.r-pkg.org/badges/version/pocketapi)](https://CRAN.R-project.org/package=pocketapi) 8 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 9 | [![CRAN\_Download\_Badge](https://cranlogs.r-pkg.org/badges/pocketapi)](https://cran.r-project.org/web/packages/pocketapi/index.html) 10 | 11 | 12 | This is a R wrapper for the [Pocket API](https://getpocket.com/developer/docs/overview). You can use `pocketapi` to access and modify your _own_ pockets and to add new links to your Pocket programmatically. 13 | 14 | Functions include: 15 | 16 | - `pocket_add()` 17 | - `pocket_get()` 18 | - `pocket_archive()` 19 | - `pocket_delete()` 20 | - `pocket_favorite()` 21 | - `pocket_unfavorite()` 22 | - `pocket_tag()` 23 | 24 | # Installation 25 | 26 | ## CRAN Version 27 | 28 | ```r 29 | # install via CRAN 30 | install.packages("pocketapi") 31 | ``` 32 | 33 | ## Current Development Version 34 | 35 | You can install the latest development version using `devtools`. 36 | 37 | ```r 38 | # install devtools package if it's not already 39 | install.packages("devtools") 40 | 41 | # install package from GitHub 42 | devtools::install_github("correlaid/pocketapi") 43 | 44 | # load package 45 | library(pocketapi) 46 | ``` 47 | 48 | # Get Started 49 | 50 | ## Authentication 51 | 52 | ### Create a Pocket Application 53 | 54 | You need to create a Pocket _application_ in the Pocket developer portal to access your Pocket data. Don't worry: this app will only be visible to you and only serves the purpose of acquiring the credentials for `pocketapi`. 55 | 56 | 1. Log in to your Pocket account and go to [https://getpocket.com/developer/apps/new](https://getpocket.com/developer/apps/new). 57 | 2. Click "Create New Application". You'll be presented with a form. Choose a sensible application _name_ and a _description_. 58 | 3. Give your app _permissions_: Depending on the permissions of your app, you'll be able to use all or a subset of the `pocketapi` functions: 59 | 60 | | `pocketapi` function | what it does | needed permission | 61 | | ------------------------------ | ---------------------------------- | ----------------- | 62 | | `pocket_get` | get data frame of all your pockets | Retrieve | 63 | | all other `pocket_*` functions | add new Pocket entries | Modify | 64 | 65 | 4. Check any _platform_ for your app - this does not really matter but you need to check at least one box. 66 | 5. Accept the _terms of service_ and click "Create Application". 67 | 68 | ### Get consumer key and token 69 | 70 | `pocketapi` uses the OAuth2 flow provided by the [Pocket Authentication API](https://getpocket.com/developer/docs/authentication) to get an access token for your App. Because Pocket does not closely follow the OAuth standard, we could not provide as smooth an experience as other packages do (e.g. [googlesheets4](https://github.com/tidyverse/googlesheets4)). Instead, the user has to do the following **once** to obtain an access token: 71 | 72 | 1. Request a request token. 73 | ``` 74 | request_token <- get_request_token(consumer_key) 75 | ``` 76 | 77 | 2. Authorize your app by entering the URL created by `create_authorize_url` **in your browser**: 78 | 79 | ```r 80 | create_authorize_url(request_token) 81 | ``` 82 | 83 | :warning: This step is critical: **Even if you have authorized your app before** and you want to get a new access token, you need to do the authorization in your browser again. Otherwise, the request token will not be authorized to generate an access token! 84 | 85 | 3. Get access token using the now authorized request token 86 | 87 | ```r 88 | access_token <- get_access_token(consumer_key, request_token) 89 | ``` 90 | 91 | You can now already use the package by specifying the consumer key and access token manually as arguments to each function call: 92 | 93 | ``` 94 | pocketapi::pocket_get(consumer_key = consumer_key, access_token = access_token) 95 | ``` 96 | 97 | To make it easier to work with the package, you should set them as environment variables. 98 | 99 | ### Add the consumer key and access token to your environment 100 | 101 | **Important**: Never make your `consumer_key` and `access_token` publicly available - anyone will be able to access your Pockets! 102 | 103 | It is common practice to set API keys in your R environment file so that every time you start R the key is loaded. 104 | 105 | All `pocketapi` functions access your `consumer_key` and `access_token` automatically by executing `Sys.getenv("POCKET_CONSUMER_KEY")` respectively `Sys.getenv("POCKET_ACCESS_TOKEN")` . Alternatively, you can provide an explicit definition of your `consumer_key` and `access_token` with each function call. 106 | 107 | In order to add your key to your environment file, you can use the function `edit_r_environ` from the `usethis` package: 108 | 109 | ```r 110 | usethis::edit_r_environ() 111 | ``` 112 | 113 | This will open your `.Renviron` file in the RStudio editor. Now, you can add the following lines to it: 114 | 115 | ``` 116 | POCKET_CONSUMER_KEY="yourkeygoeshere" 117 | POCKET_ACCESS_TOKEN="youraccesstokengoeshere" 118 | ``` 119 | 120 | Save the file and restart R for the changes to take effect. 121 | 122 | If your `.Renviron` lives at a non-conventional place, you can also edit it manually using RStudio or your favorite text editor. 123 | 124 | If you don't want to clutter the `.Renviron` file in your home folder, you can also use a local `.Renviron` file in your project folder and read it in using the `readRenviron()` function. In this case, make sure to never share your local `.Renviron` file. 125 | 126 | ## Get, modify, and add Pockets 127 | 128 | Check out the vignette to see in more detail how to add, get, and modify your Pockets using those functions. 129 | 130 | ```r 131 | library(pocketapi) 132 | vignette("pocketapi") 133 | ``` 134 | 135 | # Limitations 136 | 137 | This package has some limitations: 138 | 139 | - You can only modify your own Pockets. 140 | - The Pocket API does not follow REST API standards and exhibits some weird behaviours. For example, even if a `modify` action is not successful, the API will still return "success". See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion of weird behaviours of the API. 141 | 142 | # Contribute 143 | 144 | Contributions to this package are welcome. Please see `CONTRIBUTING.md`. 145 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Resubmission 2 | This is a resubmission. In this version we have: 3 | 4 | * Fixed a possibly invalid URL in the vignette 5 | 6 | ## Test environments 7 | * local R installation, R 3.6.2 8 | * win-builder (devel) 9 | 10 | ## R CMD check results 11 | 12 | 0 errors | 1 warnings | 0 note 13 | 14 | * This is a new release. 15 | 16 | * `dplyr` is added as import because `map_dfr` requires it under the hood. 17 | -------------------------------------------------------------------------------- /man/check_for_add_success_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_add.R 3 | \name{check_for_add_success_} 4 | \alias{check_for_add_success_} 5 | \title{check_for_add_success_} 6 | \usage{ 7 | check_for_add_success_(urls, consumer_key, access_token) 8 | } 9 | \arguments{ 10 | \item{urls}{Character vector containing URLs to be checked.} 11 | 12 | \item{consumer_key}{Character string. Your Pocket consumer key.} 13 | 14 | \item{access_token}{Character string. Your Pocket request token.} 15 | } 16 | \value{ 17 | Returns messages of success and/or failure of the URLs wished to be added to Pocket. 18 | } 19 | \description{ 20 | Check whether all URLs were successfully added to Pocket by \code{pocket_add()}. 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/create_authorize_url.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/auth.R 3 | \name{create_authorize_url} 4 | \alias{create_authorize_url} 5 | \title{create_authorize_url} 6 | \usage{ 7 | create_authorize_url(request_token) 8 | } 9 | \arguments{ 10 | \item{request_token}{Character string. Pocket request token generated by the function \code{\link{get_request_token}}.} 11 | } 12 | \value{ 13 | Character string. The URL for authorizing the Pocket application for which the request token was requested. This is what you enter into your browser. 14 | } 15 | \description{ 16 | Creates the URL the user needs to enter into her browser in order to authorize the Pocket application/the request token. 17 | } 18 | \details{ 19 | See the \href{https://github.com/CorrelAid/pocketapi}{GitHub README} for details on Authentication. 20 | } 21 | \seealso{ 22 | Other authentication functions: 23 | \code{\link{get_access_token}()}, 24 | \code{\link{get_request_token}()} 25 | } 26 | \concept{authentication functions} 27 | -------------------------------------------------------------------------------- /man/extract_action_results_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_modify.R 3 | \name{extract_action_results_} 4 | \alias{extract_action_results_} 5 | \title{extract_action_results_} 6 | \usage{ 7 | extract_action_results_(res, item_ids) 8 | } 9 | \arguments{ 10 | \item{res}{List. Httr response object.} 11 | 12 | \item{item_ids}{Character vector. Pocket item IDs that were modified.} 13 | } 14 | \value{ 15 | Named list with the IDs for which the action was successful, the ids for which it failed and the list of failures. 16 | } 17 | \description{ 18 | Extract results from list that is returned by the send endpoint of the Pocket API. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/gen_action_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_modify.R 3 | \name{gen_action_} 4 | \alias{gen_action_} 5 | \title{gen_action_} 6 | \usage{ 7 | gen_action_(item_id, action_name, ...) 8 | } 9 | \arguments{ 10 | \item{item_id}{Character. ID of Pocket item.} 11 | 12 | \item{action_name}{Character. Name of Pocket action as a string.} 13 | 14 | \item{...}{Additional, named arguments to be added to the action list.} 15 | } 16 | \value{ 17 | List representing a Pocket API action. 18 | } 19 | \description{ 20 | Generate a Pocket action list element for a given ID and action name. 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/gen_add_action_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_add.R 3 | \name{gen_add_action_} 4 | \alias{gen_add_action_} 5 | \title{gen_add_action_} 6 | \usage{ 7 | gen_add_action_(add_urls, action_name, ...) 8 | } 9 | \arguments{ 10 | \item{add_urls}{Character vector. URLs that are to be added to Pocket.} 11 | 12 | \item{action_name}{Character. Name of the action to be used (ADD, in this case).} 13 | 14 | \item{...}{Additional named arguments to be added to the action list.} 15 | } 16 | \value{ 17 | List of actions and URLs to add to Pocket. 18 | } 19 | \description{ 20 | Generate an action list element for adding URLs to Pocket. 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/gen_tag_action_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_tag.R 3 | \name{gen_tag_action_} 4 | \alias{gen_tag_action_} 5 | \title{gen_tag_action_} 6 | \usage{ 7 | gen_tag_action_(action_name, ...) 8 | } 9 | \arguments{ 10 | \item{action_name}{Character. Name of Pocket action as a string.} 11 | 12 | \item{...}{Additional, named arguments to be added to the action list.} 13 | } 14 | \value{ 15 | Action list. 16 | } 17 | \description{ 18 | Generate an action list element for a given action name. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/get_access_token.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/auth.R 3 | \name{get_access_token} 4 | \alias{get_access_token} 5 | \title{get_access_token} 6 | \usage{ 7 | get_access_token(consumer_key, request_token) 8 | } 9 | \arguments{ 10 | \item{consumer_key}{Character string. Here goes your Pocket consumer key.} 11 | 12 | \item{request_token}{Character string. Pocket request token generated by \code{\link{get_request_token}}.} 13 | } 14 | \value{ 15 | Character string. Returns the access token. 16 | } 17 | \description{ 18 | Creates the URL the user needs to enter into her browser in order to authorize the Pocket application/the request token. 19 | } 20 | \details{ 21 | See the \href{https://github.com/CorrelAid/pocketapi}{GitHub README} for details on Authentication. 22 | } 23 | \seealso{ 24 | Other authentication functions: 25 | \code{\link{create_authorize_url}()}, 26 | \code{\link{get_request_token}()} 27 | } 28 | \concept{authentication functions} 29 | -------------------------------------------------------------------------------- /man/get_request_token.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/auth.R 3 | \name{get_request_token} 4 | \alias{get_request_token} 5 | \title{get_request_token} 6 | \usage{ 7 | get_request_token(consumer_key) 8 | } 9 | \arguments{ 10 | \item{consumer_key}{Character string. Here goes your Pocket consumer key.} 11 | } 12 | \value{ 13 | Character string. Request token for the Pocket Application corresponding to your consumer_key. 14 | } 15 | \description{ 16 | Requests a request_token for the Pocket application with a given consumer key from the Pocket Authentication API. The request token can then be used in \code{\link{create_authorize_url}} and \code{\link{get_access_token}}. 17 | } 18 | \details{ 19 | See the \href{https://github.com/CorrelAid/pocketapi}{GitHub README} for details on Authentication. 20 | } 21 | \seealso{ 22 | Other authentication functions: 23 | \code{\link{create_authorize_url}()}, 24 | \code{\link{get_access_token}()} 25 | } 26 | \concept{authentication functions} 27 | -------------------------------------------------------------------------------- /man/parse_item_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_get.R 3 | \name{parse_item_} 4 | \alias{parse_item_} 5 | \title{parse_item_} 6 | \usage{ 7 | parse_item_(item) 8 | } 9 | \arguments{ 10 | \item{item}{Pocket item from the Pocket entry list.} 11 | } 12 | \value{ 13 | Tibble 14 | } 15 | \description{ 16 | Parse item in the response list into a mini tibble with one row. 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \description{ 10 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/pocket_add.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_add.R 3 | \name{pocket_add} 4 | \alias{pocket_add} 5 | \title{pocket_add} 6 | \usage{ 7 | pocket_add( 8 | add_urls, 9 | item_ids = "", 10 | tags = NULL, 11 | success = TRUE, 12 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 14 | ) 15 | } 16 | \arguments{ 17 | \item{add_urls}{Character vector. The URL or URLs you want to add to your Pocket list.} 18 | 19 | \item{item_ids}{Character vector. The item_ids of the items you want to add. Defaults to empty character vector.} 20 | 21 | \item{tags}{Character vector. One or more tags to be applied to all of the newly added URLs. Defaults to NULL.} 22 | 23 | \item{success}{Logical. Enables success/failure messages for every URL. Defaults to TRUE. Needs GET permission if TRUE.} 24 | 25 | \item{consumer_key}{Character string. Your Pocket consumer key. Defaults to \code{Sys.getenv("POCKET_CONSUMER_KEY")}.} 26 | 27 | \item{access_token}{Character string. Your Pocket request token. Defaults to \code{Sys.getenv("POCKET_ACCESS_TOKEN")}.} 28 | } 29 | \value{ 30 | the response from the httr call, invisibly 31 | } 32 | \description{ 33 | Add one or more items to your Pocket account. 34 | } 35 | \details{ 36 | This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 37 | For example, even if a `modify` action is not successful, the API will still return "success". 38 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 39 | } 40 | -------------------------------------------------------------------------------- /man/pocket_archive.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_archive.R 3 | \name{pocket_archive} 4 | \alias{pocket_archive} 5 | \title{pocket_archive} 6 | \usage{ 7 | pocket_archive( 8 | item_ids, 9 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 10 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 11 | ) 12 | } 13 | \arguments{ 14 | \item{item_ids}{character vector. Pocket item ids you want to archive.} 15 | 16 | \item{consumer_key}{character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY").} 17 | 18 | \item{access_token}{character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN").} 19 | } 20 | \value{ 21 | Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 22 | } 23 | \description{ 24 | archive items from your Pocket list. 25 | } 26 | \details{ 27 | This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 28 | For example, even if a `modify` action is not successful, the API will still return "success". 29 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 30 | } 31 | -------------------------------------------------------------------------------- /man/pocket_delete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_delete.R 3 | \name{pocket_delete} 4 | \alias{pocket_delete} 5 | \title{pocket_delete} 6 | \usage{ 7 | pocket_delete( 8 | item_ids, 9 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 10 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 11 | ) 12 | } 13 | \arguments{ 14 | \item{item_ids}{character vector. Pocket item ids you want to delete from your list.} 15 | 16 | \item{consumer_key}{character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY").} 17 | 18 | \item{access_token}{character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN").} 19 | } 20 | \value{ 21 | Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 22 | } 23 | \description{ 24 | delete items from your Pocket list. 25 | } 26 | \details{ 27 | This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 28 | For example, even if a `modify` action is not successful, the API will still return "success". 29 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 30 | } 31 | -------------------------------------------------------------------------------- /man/pocket_favorite.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_favorite.R 3 | \name{pocket_favorite} 4 | \alias{pocket_favorite} 5 | \title{pocket_favorite} 6 | \usage{ 7 | pocket_favorite( 8 | item_ids, 9 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 10 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 11 | ) 12 | } 13 | \arguments{ 14 | \item{item_ids}{character vector. Pocket item ids you want to favorite} 15 | 16 | \item{consumer_key}{character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY").} 17 | 18 | \item{access_token}{character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN").} 19 | } 20 | \value{ 21 | Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 22 | } 23 | \description{ 24 | favorite items from your Pocket list. 25 | } 26 | \details{ 27 | This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 28 | For example, even if a `modify` action is not successful, the API will still return "success". 29 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 30 | } 31 | -------------------------------------------------------------------------------- /man/pocket_get.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_get.R 3 | \name{pocket_get} 4 | \alias{pocket_get} 5 | \title{pocket_get} 6 | \usage{ 7 | pocket_get( 8 | favorite = NULL, 9 | item_type = NULL, 10 | tag = NULL, 11 | state = "all", 12 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 14 | ) 15 | } 16 | \arguments{ 17 | \item{favorite}{boolean. Default NULL. Allows to filter for favorited items. If TRUE, only favorited items will be returned. If FALSE, only un-favorited items will be returned.} 18 | 19 | \item{item_type}{character. Default NULL. Allows to filter for content type of items. Valid values are: "image", "article", "video". Please note that there might be Pocket items that do not belong to any of those types. The Pocket API documentation only mentions those three.} 20 | 21 | \item{tag}{character. Default NULL. Only one tag can be filtered at a time. Set to '_untagged_' if you only want to get untagged items.} 22 | 23 | \item{state}{character. Default "all". Allows to filter on unread/archived items or return all. Valid values are "unread", "archive", "all".} 24 | 25 | \item{consumer_key}{character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY").} 26 | 27 | \item{access_token}{character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN").} 28 | } 29 | \value{ 30 | tibble. Tibble with one row for each Pocket item. 31 | } 32 | \description{ 33 | Get a data frame with your pocket data. 34 | } 35 | \details{ 36 | See https://getpocket.com/developer/docs/v3/retrieve for the meaning of certain variable values. 37 | } 38 | -------------------------------------------------------------------------------- /man/pocket_modify.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_modify.R 3 | \name{pocket_modify} 4 | \alias{pocket_modify} 5 | \title{pocket_modify} 6 | \usage{ 7 | pocket_modify( 8 | actions, 9 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 10 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 11 | ) 12 | } 13 | \arguments{ 14 | \item{actions}{List. List of lists where each element is an action object. See https://getpocket.com/developer/docs/v3/modify.} 15 | 16 | \item{consumer_key}{Character string. Your Pocket consumer key. Defaults to \code{Sys.getenv("POCKET_CONSUMER_KEY")}.} 17 | 18 | \item{access_token}{Character string. Your Pocket request token. Defaults to \code{Sys.getenv("POCKET_ACCESS_TOKEN")}.} 19 | } 20 | \description{ 21 | Function that sends a request with a list of actions to the 'modify' Pocket API endpoint. 22 | } 23 | \details{ 24 | see https://getpocket.com/developer/docs/v3/modify. This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 25 | For example, even if a `modify` action is not successful, the API will still return "success". 26 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 27 | } 28 | -------------------------------------------------------------------------------- /man/pocket_modify_bulk_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_modify.R 3 | \name{pocket_modify_bulk_} 4 | \alias{pocket_modify_bulk_} 5 | \title{pocket_modify_bulk_} 6 | \usage{ 7 | pocket_modify_bulk_(item_ids, action_name, consumer_key, access_token, ...) 8 | } 9 | \arguments{ 10 | \item{item_ids}{Character vector. Pocket item IDs that should be modified.} 11 | 12 | \item{action_name}{Character. The action that should be performed on all specified items.} 13 | 14 | \item{consumer_key}{Character string. Your Pocket consumer key. Defaults to \code{Sys.getenv("POCKET_CONSUMER_KEY")}.} 15 | 16 | \item{access_token}{Character string. Your Pocket request token. Defaults to \code{Sys.getenv("POCKET_ACCESS_TOKEN")}.} 17 | 18 | \item{...}{Additional named arguments to be added to the action list items.} 19 | } 20 | \description{ 21 | Bulk modify for a given action, i.e., the action is the same for all item_ids. 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/pocket_post_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{pocket_post_} 4 | \alias{pocket_post_} 5 | \title{pocket_post_} 6 | \usage{ 7 | pocket_post_(endpoint, consumer_key, access_token, ...) 8 | } 9 | \arguments{ 10 | \item{endpoint}{character. endpoint to make a request to.} 11 | 12 | \item{consumer_key}{character. Pocket consumer key.} 13 | 14 | \item{access_token}{character. Pocket access token.} 15 | 16 | \item{...}{additional named arguments to be put into the body of the POST request.} 17 | } 18 | \description{ 19 | internal function to make a POST requests to a Pocket API endpoint. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/pocket_tag.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_tag.R 3 | \name{pocket_tag} 4 | \alias{pocket_tag} 5 | \title{pocket_tag} 6 | \usage{ 7 | pocket_tag( 8 | action_name = c("tags_replace", "tags_remove", "tags_add", "tags_clear", 9 | "tag_rename", "tag_delete"), 10 | item_ids = NULL, 11 | tags = NULL, 12 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 13 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 14 | ) 15 | } 16 | \arguments{ 17 | \item{action_name}{character vector. The kind of tag action you want to undertake. Possible values: 'tags_add', 'tags_remove', 'tags_replace', 'tags_clear', 'tag_rename', or 'tag_delete'.} 18 | 19 | \item{item_ids}{character vector. Pocket item ids you want to modify the tags for.} 20 | 21 | \item{tags}{character vector. The names of the tags to work with the chosen action.} 22 | 23 | \item{consumer_key}{character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY").} 24 | 25 | \item{access_token}{character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN").} 26 | } 27 | \description{ 28 | modify the tags of the items in pocket. 29 | } 30 | \details{ 31 | This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 32 | For example, even if a `modify` action is not successful, the API will still return "success". 33 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 34 | } 35 | -------------------------------------------------------------------------------- /man/pocket_unfavorite.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_unfavorite.R 3 | \name{pocket_unfavorite} 4 | \alias{pocket_unfavorite} 5 | \title{pocket_unfavorite} 6 | \usage{ 7 | pocket_unfavorite( 8 | item_ids, 9 | consumer_key = Sys.getenv("POCKET_CONSUMER_KEY"), 10 | access_token = Sys.getenv("POCKET_ACCESS_TOKEN") 11 | ) 12 | } 13 | \arguments{ 14 | \item{item_ids}{Character vector. Pocket item IDs you want to unfavorite. Get them, e.g., via \code{pocket_get}.} 15 | 16 | \item{consumer_key}{character. Your Pocket consumer key. Defaults to Sys.getenv("POCKET_CONSUMER_KEY").} 17 | 18 | \item{access_token}{character. Your Pocket request token. Defaults to Sys.getenv("POCKET_ACCESS_TOKEN").} 19 | } 20 | \value{ 21 | Invisibly returns a list containing information on whether the action failed or succeeded, including the respective item ID. 22 | } 23 | \description{ 24 | Unfavorite items from your Pocket list. 25 | } 26 | \details{ 27 | This function uses the \code{modify} endpoint of the Pocket API which exhibits some weird behaviour. 28 | For example, even if a `modify` action is not successful, the API will still return "success". 29 | See [issue [#26](https://github.com/CorrelAid/pocketapi/issues/26) for a discussion. 30 | } 31 | -------------------------------------------------------------------------------- /man/warn_for_failures_.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pocket_modify.R 3 | \name{warn_for_failures_} 4 | \alias{warn_for_failures_} 5 | \title{warn_for_failures_} 6 | \usage{ 7 | warn_for_failures_(failures) 8 | } 9 | \arguments{ 10 | \item{failures}{List of failures.} 11 | } 12 | \description{ 13 | Generate warnings for all failures. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /pocketapi.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(pocketapi) 3 | 4 | test_check("pocketapi") 5 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/get-40608f-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 1, 3 | "complete": 1, 4 | "list": { 5 | "2120364086": { 6 | "item_id": "2120364086", 7 | "resolved_id": "2120364086", 8 | "given_url": "https://katherinemwood.github.io/post/testthat/", 9 | "given_title": "", 10 | "favorite": "0", 11 | "status": "0", 12 | "time_added": "1584183618", 13 | "time_updated": "1584183618", 14 | "time_read": "0", 15 | "time_favorited": "0", 16 | "sort_id": 0, 17 | "resolved_title": "Intro to unit testing in R", 18 | "resolved_url": "https://katherinemwood.github.io/post/testthat/", 19 | "excerpt": "I’ve mentioned before that a great coding practice you can ingrain into your work is unit testing. The idea is simple: you write a script that automatically evaluates pieces of your code and checks it against expected behavior.", 20 | "is_article": "1", 21 | "is_index": "1", 22 | "has_video": "0", 23 | "has_image": "0", 24 | "word_count": "1588", 25 | "lang": "en", 26 | "time_to_read": 7, 27 | "authors": { 28 | "8694801": { 29 | "item_id": "2120364086", 30 | "author_id": "8694801", 31 | "name": "Katherine Wood", 32 | "url": "" 33 | } 34 | }, 35 | "listen_duration_estimate": 615 36 | }, 37 | "679092330": { 38 | "item_id": "679092330", 39 | "resolved_id": "679092330", 40 | "given_url": "https://cran.r-project.org/web/packages/httr/vignettes/api-packages.html", 41 | "given_title": "Best practices for API packages", 42 | "favorite": "0", 43 | "status": "0", 44 | "time_added": "1554449375", 45 | "time_updated": "1583958300", 46 | "time_read": "0", 47 | "time_favorited": "0", 48 | "sort_id": 1, 49 | "resolved_title": "Best practices for API packages", 50 | "resolved_url": "https://cran.r-project.org/web/packages/httr/vignettes/api-packages.html", 51 | "excerpt": "APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API.", 52 | "is_article": "0", 53 | "is_index": "0", 54 | "has_video": "0", 55 | "has_image": "0", 56 | "word_count": "3132", 57 | "lang": "", 58 | "tags": { 59 | "rstats": { 60 | "item_id": "679092330", 61 | "tag": "rstats" 62 | }, 63 | "rtata": { 64 | "item_id": "679092330", 65 | "tag": "rtata" 66 | }, 67 | "test": { 68 | "item_id": "679092330", 69 | "tag": "test" 70 | } 71 | }, 72 | "authors": { 73 | "2656911": { 74 | "item_id": "679092330", 75 | "author_id": "2656911", 76 | "name": "Hadley Wickham", 77 | "url": "" 78 | } 79 | }, 80 | "listen_duration_estimate": 1212 81 | }, 82 | "1785361118": { 83 | "item_id": "1785361118", 84 | "resolved_id": "1785361118", 85 | "given_url": "https://github.com/r-lib/httr", 86 | "given_title": "r-lib/httr: httr: a friendly http package for R", 87 | "favorite": "0", 88 | "status": "0", 89 | "time_added": "1554449373", 90 | "time_updated": "1554449373", 91 | "time_read": "0", 92 | "time_favorited": "0", 93 | "sort_id": 2, 94 | "resolved_title": "r-lib/httr", 95 | "resolved_url": "https://github.com/r-lib/httr", 96 | "excerpt": "The aim of httr is to provide a wrapper for the curl package, customised to the demands of modern web APIs. Functions for the most important http verbs: GET(), HEAD(), PATCH(), PUT(), DELETE() and POST().", 97 | "is_article": "1", 98 | "is_index": "0", 99 | "has_video": "0", 100 | "has_image": "1", 101 | "word_count": "237", 102 | "lang": "en", 103 | "top_image_url": "https://avatars2.githubusercontent.com/u/22618716?v=3&s=400", 104 | "image": { 105 | "item_id": "1785361118", 106 | "src": "https://camo.githubusercontent.com/ba8688a6b8d2b23f2eba0d974e956809bd2dd2bc/68747470733a2f2f7472617669732d63692e6f72672f722d6c69622f687474722e7376673f6272616e63683d6d6173746572", 107 | "width": "0", 108 | "height": "0" 109 | }, 110 | "images": { 111 | "1": { 112 | "item_id": "1785361118", 113 | "image_id": "1", 114 | "src": "https://camo.githubusercontent.com/ba8688a6b8d2b23f2eba0d974e956809bd2dd2bc/68747470733a2f2f7472617669732d63692e6f72672f722d6c69622f687474722e7376673f6272616e63683d6d6173746572", 115 | "width": "0", 116 | "height": "0", 117 | "credit": "", 118 | "caption": "" 119 | }, 120 | "2": { 121 | "item_id": "1785361118", 122 | "image_id": "2", 123 | "src": "https://camo.githubusercontent.com/23e8ac623a2e54c5440c063aaea2963db99baacb/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f722d6c69622f687474722f6d61737465722e737667", 124 | "width": "0", 125 | "height": "0", 126 | "credit": "", 127 | "caption": "" 128 | }, 129 | "3": { 130 | "item_id": "1785361118", 131 | "image_id": "3", 132 | "src": "https://camo.githubusercontent.com/7746f09d0c3dd6c7ed797d16883a7ec8a5a5aa5a/687474703a2f2f7777772e722d706b672e6f72672f6261646765732f76657273696f6e2f68747472", 133 | "width": "0", 134 | "height": "0", 135 | "credit": "", 136 | "caption": "" 137 | } 138 | }, 139 | "domain_metadata": { 140 | "name": "GitHub", 141 | "logo": "https://logo.clearbit.com/github.com?size=800", 142 | "greyscale_logo": "https://logo.clearbit.com/github.com?size=800&greyscale=true" 143 | }, 144 | "listen_duration_estimate": 92 145 | }, 146 | "2333373270": { 147 | "item_id": "2333373270", 148 | "resolved_id": "2333373270", 149 | "given_url": "https://medium.com/@Pocket/your-pocket-journey-starts-now-make-the-most-of-it-cc30770e6c53", 150 | "given_title": "", 151 | "favorite": "0", 152 | "status": "0", 153 | "time_added": "1554449248", 154 | "time_updated": "1554449248", 155 | "time_read": "0", 156 | "time_favorited": "0", 157 | "sort_id": 3, 158 | "resolved_title": "Your Pocket journey starts now. Make the most of it.", 159 | "resolved_url": "https://medium.com/@Pocket/your-pocket-journey-starts-now-make-the-most-of-it-cc30770e6c53", 160 | "excerpt": "Welcome to Pocket. You’re about to embark on a journey, one where the vast swaths of information you discover online become knowledge. Everywhere you go with Pocket, the words, sounds, and stories that delight, enlighten, and even shape you will be at your fingertips.", 161 | "is_article": "1", 162 | "is_index": "0", 163 | "has_video": "0", 164 | "has_image": "1", 165 | "word_count": "501", 166 | "lang": "en", 167 | "top_image_url": "https://miro.medium.com/max/1200/1*hKCxwuSJWmmAGa4bVi_V7Q.png", 168 | "authors": { 169 | "34265082": { 170 | "item_id": "2333373270", 171 | "author_id": "34265082", 172 | "name": "Pocket", 173 | "url": "https://medium.com/@Pocket" 174 | } 175 | }, 176 | "image": { 177 | "item_id": "2333373270", 178 | "src": "https://miro.medium.com/max/3840/1*hKCxwuSJWmmAGa4bVi_V7Q.png", 179 | "width": "1920", 180 | "height": "1167" 181 | }, 182 | "images": { 183 | "1": { 184 | "item_id": "2333373270", 185 | "image_id": "1", 186 | "src": "https://miro.medium.com/max/3840/1*hKCxwuSJWmmAGa4bVi_V7Q.png", 187 | "width": "1920", 188 | "height": "1167", 189 | "credit": "", 190 | "caption": "" 191 | }, 192 | "2": { 193 | "item_id": "2333373270", 194 | "image_id": "2", 195 | "src": "https://miro.medium.com/max/3840/1*p1j8hjytlS5RrAs9R3K8XA.png", 196 | "width": "1920", 197 | "height": "1303", 198 | "credit": "", 199 | "caption": "" 200 | }, 201 | "3": { 202 | "item_id": "2333373270", 203 | "image_id": "3", 204 | "src": "https://miro.medium.com/max/3840/1*sTCgI5ZfYEFQtKhE0ADiLg.png", 205 | "width": "1920", 206 | "height": "1259", 207 | "credit": "", 208 | "caption": "" 209 | }, 210 | "4": { 211 | "item_id": "2333373270", 212 | "image_id": "4", 213 | "src": "https://miro.medium.com/max/4400/1*E8xc0m-eP_Tqqs-MKnJqDA.png", 214 | "width": "2200", 215 | "height": "1418", 216 | "credit": "", 217 | "caption": "" 218 | } 219 | }, 220 | "domain_metadata": { 221 | "name": "Medium", 222 | "logo": "https://logo.clearbit.com/medium.com?size=800", 223 | "greyscale_logo": "https://logo.clearbit.com/medium.com?size=800&greyscale=true" 224 | }, 225 | "listen_duration_estimate": 194 226 | } 227 | }, 228 | "error": null, 229 | "search_meta": { 230 | "search_type": "normal" 231 | }, 232 | "since": 1584184064 233 | } 234 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/get-86fba0-POST.R: -------------------------------------------------------------------------------- 1 | structure(list( 2 | url = "https://getpocket.com/v3/get", status_code = 401L, 3 | headers = structure(list( 4 | date = "Sat, 14 Mar 2020 11:25:25 GMT", 5 | `content-type` = "text/html; charset=UTF-8", `content-length` = "16", 6 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 7 | `x-error` = "A valid access token is required to access the requested API endpoint.", 8 | `x-error-code` = "107", status = "401 Unauthorized", 9 | `x-limit-key-limit` = "10000", `x-limit-key-remaining` = "9988", 10 | `x-limit-key-reset` = "2450", `x-source` = "Pocket", 11 | `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 12 | ), class = c( 13 | "insensitive", 14 | "list" 15 | )), all_headers = list(list( 16 | status = 401L, version = "HTTP/2", 17 | headers = structure(list( 18 | date = "Sat, 14 Mar 2020 11:25:25 GMT", 19 | `content-type` = "text/html; charset=UTF-8", `content-length` = "16", 20 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 21 | `x-error` = "A valid access token is required to access the requested API endpoint.", 22 | `x-error-code` = "107", status = "401 Unauthorized", 23 | `x-limit-key-limit` = "10000", `x-limit-key-remaining` = "9988", 24 | `x-limit-key-reset` = "2450", `x-source` = "Pocket", 25 | `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 26 | ), class = c( 27 | "insensitive", 28 | "list" 29 | )) 30 | )), cookies = structure(list(domain = c( 31 | "#HttpOnly_getpocket.com", 32 | "#HttpOnly_getpocket.com" 33 | ), flag = c(FALSE, FALSE), path = c( 34 | "/", 35 | "/" 36 | ), secure = c(FALSE, FALSE), expiration = structure(c( 37 | Inf, 38 | 1584188725 39 | ), class = c("POSIXct", "POSIXt")), name = c( 40 | "PHPSESSID", 41 | "AUTH_BEARER_default" 42 | ), value = c("REDACTED", "REDACTED")), row.names = c( 43 | NA, 44 | -2L 45 | ), class = "data.frame"), content = charToRaw("401 Unauthorized"), 46 | date = structure(1584185125, class = c("POSIXct", "POSIXt"), tzone = "GMT"), times = c( 47 | redirect = 0, namelookup = 0.019885, 48 | connect = 0.145447, pretransfer = 0.413067, starttransfer = 0.413107, 49 | total = 0.607324 50 | ) 51 | ), class = "response") 52 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/oauth/authorize-1384bf-POST.R: -------------------------------------------------------------------------------- 1 | structure(list( 2 | url = "https://getpocket.com/v3/oauth/authorize", 3 | status_code = 400L, headers = structure(list( 4 | date = "Sun, 15 Mar 2020 11:06:13 GMT", 5 | `content-type` = "text/html; charset=UTF-8", `content-length` = "15", 6 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 7 | `x-error` = "Code not found.", `x-error-code` = "185", 8 | status = "400 Bad Request", `cache-control` = "private", 9 | `x-source` = "Pocket", `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 10 | ), class = c( 11 | "insensitive", 12 | "list" 13 | )), all_headers = list(list( 14 | status = 400L, version = "HTTP/2", 15 | headers = structure(list( 16 | date = "Sun, 15 Mar 2020 11:06:13 GMT", 17 | `content-type` = "text/html; charset=UTF-8", `content-length` = "15", 18 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 19 | `x-error` = "Code not found.", `x-error-code` = "185", 20 | status = "400 Bad Request", `cache-control` = "private", 21 | `x-source` = "Pocket", `set-cookie` = "REDACTED", 22 | p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 23 | ), class = c( 24 | "insensitive", 25 | "list" 26 | )) 27 | )), cookies = structure(list(domain = c( 28 | "#HttpOnly_getpocket.com", 29 | "#HttpOnly_getpocket.com" 30 | ), flag = c(FALSE, FALSE), path = c( 31 | "/", 32 | "/" 33 | ), secure = c(FALSE, FALSE), expiration = structure(c( 34 | Inf, 35 | 1584273973 36 | ), class = c("POSIXct", "POSIXt")), name = c( 37 | "PHPSESSID", 38 | "AUTH_BEARER_default" 39 | ), value = c("REDACTED", "REDACTED")), row.names = c( 40 | NA, 41 | -2L 42 | ), class = "data.frame"), content = charToRaw("400 Bad Request"), 43 | date = structure(1584270373, class = c("POSIXct", "POSIXt"), tzone = "GMT"), times = c( 44 | redirect = 0, namelookup = 7e-05, 45 | connect = 7.2e-05, pretransfer = 0.000202, starttransfer = 0.00022, 46 | total = 0.164206 47 | ) 48 | ), class = "response") 49 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/oauth/authorize-7becba-POST.R: -------------------------------------------------------------------------------- 1 | structure(list( 2 | url = "https://getpocket.com/v3/oauth/authorize", 3 | status_code = 403L, headers = structure(list( 4 | date = "Sun, 15 Mar 2020 11:05:30 GMT", 5 | `content-type` = "text/html; charset=UTF-8", `content-length` = "13", 6 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 7 | `x-error` = "Invalid consumer key.", `x-error-code` = "152", 8 | status = "403 Forbidden", `cache-control` = "private", 9 | `x-source` = "Pocket", `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 10 | ), class = c( 11 | "insensitive", 12 | "list" 13 | )), all_headers = list(list( 14 | status = 403L, version = "HTTP/2", 15 | headers = structure(list( 16 | date = "Sun, 15 Mar 2020 11:05:30 GMT", 17 | `content-type` = "text/html; charset=UTF-8", `content-length` = "13", 18 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 19 | `x-error` = "Invalid consumer key.", `x-error-code` = "152", 20 | status = "403 Forbidden", `cache-control` = "private", 21 | `x-source` = "Pocket", `set-cookie` = "REDACTED", 22 | p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 23 | ), class = c( 24 | "insensitive", 25 | "list" 26 | )) 27 | )), cookies = structure(list(domain = c( 28 | "#HttpOnly_getpocket.com", 29 | "#HttpOnly_getpocket.com" 30 | ), flag = c(FALSE, FALSE), path = c( 31 | "/", 32 | "/" 33 | ), secure = c(FALSE, FALSE), expiration = structure(c( 34 | Inf, 35 | 1584273930 36 | ), class = c("POSIXct", "POSIXt")), name = c( 37 | "PHPSESSID", 38 | "AUTH_BEARER_default" 39 | ), value = c("REDACTED", "REDACTED")), row.names = c( 40 | NA, 41 | -2L 42 | ), class = "data.frame"), content = charToRaw("403 Forbidden"), 43 | date = structure(1584270330, class = c("POSIXct", "POSIXt"), tzone = "GMT"), times = c( 44 | redirect = 0, namelookup = 0.019347, 45 | connect = 0.133203, pretransfer = 0.382742, starttransfer = 0.38282, 46 | total = 0.588804 47 | ) 48 | ), class = "response") 49 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/oauth/authorize-b9d4e4-POST.R: -------------------------------------------------------------------------------- 1 | structure(list( 2 | url = "https://getpocket.com/v3/oauth/authorize", 3 | status_code = 200L, headers = structure(list( 4 | date = "Sun, 15 Mar 2020 11:23:51 GMT", 5 | `content-type` = "application/x-www-form-urlencoded", 6 | `content-length` = "65", server = "Apache/2.4.25 (Debian)", 7 | `x-frame-options` = "SAMEORIGIN", status = "200 OK", 8 | `cache-control` = "private", `x-source` = "Pocket", `set-cookie` = "REDACTED", 9 | p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 10 | ), class = c( 11 | "insensitive", 12 | "list" 13 | )), all_headers = list(list( 14 | status = 200L, version = "HTTP/2", 15 | headers = structure(list( 16 | date = "Sun, 15 Mar 2020 11:23:51 GMT", 17 | `content-type` = "application/x-www-form-urlencoded", 18 | `content-length` = "65", server = "Apache/2.4.25 (Debian)", 19 | `x-frame-options` = "SAMEORIGIN", status = "200 OK", 20 | `cache-control` = "private", `x-source` = "Pocket", 21 | `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 22 | ), class = c( 23 | "insensitive", 24 | "list" 25 | )) 26 | )), cookies = structure(list(domain = c( 27 | "#HttpOnly_getpocket.com", 28 | "#HttpOnly_getpocket.com" 29 | ), flag = c(FALSE, FALSE), path = c( 30 | "/", 31 | "/" 32 | ), secure = c(FALSE, FALSE), expiration = structure(c( 33 | Inf, 34 | 1584275031 35 | ), class = c("POSIXct", "POSIXt")), name = c( 36 | "PHPSESSID", 37 | "AUTH_BEARER_default" 38 | ), value = c("REDACTED", "REDACTED")), row.names = c( 39 | NA, 40 | -2L 41 | ), class = "data.frame"), content = charToRaw("access_token=successfullaccesstokenyey"), 42 | date = structure(1584271431, class = c("POSIXct", "POSIXt"), tzone = "GMT"), times = c( 43 | redirect = 0, namelookup = 0.018857, 44 | connect = 0.13618, pretransfer = 0.441234, starttransfer = 0.441287, 45 | total = 0.675572 46 | ) 47 | ), class = "response") 48 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/oauth/request-b9df5b-POST.R: -------------------------------------------------------------------------------- 1 | structure(list( 2 | url = "https://getpocket.com/v3/oauth/request", 3 | status_code = 403L, headers = structure(list( 4 | date = "Sun, 15 Mar 2020 11:21:17 GMT", 5 | `content-type` = "text/html; charset=UTF-8", `content-length` = "13", 6 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 7 | `x-error` = "Invalid consumer key.", `x-error-code` = "152", 8 | status = "403 Forbidden", `cache-control` = "private", 9 | `x-source` = "Pocket", `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 10 | ), class = c( 11 | "insensitive", 12 | "list" 13 | )), all_headers = list(list( 14 | status = 403L, version = "HTTP/2", 15 | headers = structure(list( 16 | date = "Sun, 15 Mar 2020 11:21:17 GMT", 17 | `content-type` = "text/html; charset=UTF-8", `content-length` = "13", 18 | server = "Apache/2.4.25 (Debian)", `x-frame-options` = "SAMEORIGIN", 19 | `x-error` = "Invalid consumer key.", `x-error-code` = "152", 20 | status = "403 Forbidden", `cache-control` = "private", 21 | `x-source` = "Pocket", `set-cookie` = "REDACTED", 22 | p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 23 | ), class = c( 24 | "insensitive", 25 | "list" 26 | )) 27 | )), cookies = structure(list(domain = c( 28 | "#HttpOnly_getpocket.com", 29 | "#HttpOnly_getpocket.com" 30 | ), flag = c(FALSE, FALSE), path = c( 31 | "/", 32 | "/" 33 | ), secure = c(FALSE, FALSE), expiration = structure(c( 34 | Inf, 35 | 1584274877 36 | ), class = c("POSIXct", "POSIXt")), name = c( 37 | "PHPSESSID", 38 | "AUTH_BEARER_default" 39 | ), value = c("REDACTED", "REDACTED")), row.names = c( 40 | NA, 41 | -2L 42 | ), class = "data.frame"), content = charToRaw("403 Forbidden"), 43 | date = structure(1584271277, class = c("POSIXct", "POSIXt"), tzone = "GMT"), times = c( 44 | redirect = 0, namelookup = 5.8e-05, 45 | connect = 6e-05, pretransfer = 0.000164, starttransfer = 0.000184, 46 | total = 0.168642 47 | ) 48 | ), class = "response") 49 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/oauth/request-da037e-POST.R: -------------------------------------------------------------------------------- 1 | structure(list( 2 | url = "https://getpocket.com/v3/oauth/request", 3 | status_code = 200L, headers = structure(list( 4 | date = "Sun, 15 Mar 2020 11:21:07 GMT", 5 | `content-type` = "application/x-www-form-urlencoded", 6 | `content-length` = "35", server = "Apache/2.4.25 (Debian)", 7 | `x-frame-options` = "SAMEORIGIN", status = "200 OK", 8 | `cache-control` = "private", `x-source` = "Pocket", `set-cookie` = "REDACTED", 9 | p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 10 | ), class = c( 11 | "insensitive", 12 | "list" 13 | )), all_headers = list(list( 14 | status = 200L, version = "HTTP/2", 15 | headers = structure(list( 16 | date = "Sun, 15 Mar 2020 11:21:07 GMT", 17 | `content-type` = "application/x-www-form-urlencoded", 18 | `content-length` = "35", server = "Apache/2.4.25 (Debian)", 19 | `x-frame-options` = "SAMEORIGIN", status = "200 OK", 20 | `cache-control` = "private", `x-source` = "Pocket", 21 | `set-cookie` = "REDACTED", p3p = "policyref=\"/w3c/p3p.xml\", CP=\"ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE\"" 22 | ), class = c( 23 | "insensitive", 24 | "list" 25 | )) 26 | )), cookies = structure(list(domain = c( 27 | "#HttpOnly_getpocket.com", 28 | "#HttpOnly_getpocket.com" 29 | ), flag = c(FALSE, FALSE), path = c( 30 | "/", 31 | "/" 32 | ), secure = c(FALSE, FALSE), expiration = structure(c( 33 | Inf, 34 | 1584274867 35 | ), class = c("POSIXct", "POSIXt")), name = c( 36 | "PHPSESSID", 37 | "AUTH_BEARER_default" 38 | ), value = c("REDACTED", "REDACTED")), row.names = c( 39 | NA, 40 | -2L 41 | ), class = "data.frame"), content = charToRaw("code=successrequesttokenyey"), 42 | date = structure(1584271267, class = c("POSIXct", "POSIXt"), tzone = "GMT"), times = c( 43 | redirect = 0, namelookup = 0.019189, 44 | connect = 0.142407, pretransfer = 0.409698, starttransfer = 0.409801, 45 | total = 0.572897 46 | ) 47 | ), class = "response") 48 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-055179-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true 4 | ], 5 | "action_errors": [ 6 | null 7 | ], 8 | "status": 1 9 | } 10 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-1972e8-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true 4 | ], 5 | "action_errors": [ 6 | null 7 | ], 8 | "status": 1 9 | } 10 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-28a90b-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true 4 | ], 5 | "action_errors": [ 6 | null 7 | ], 8 | "status": 1 9 | } 10 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-2c60b2-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | true 5 | ], 6 | "action_errors": [ 7 | null, 8 | null 9 | ], 10 | "status": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-44b5bc-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | true 5 | ], 6 | "action_errors": [ 7 | null, 8 | null 9 | ], 10 | "status": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-476df7-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | true 5 | ], 6 | "action_errors": [ 7 | null, 8 | null 9 | ], 10 | "status": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-49cc63-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | true 5 | ], 6 | "action_errors": [ 7 | null, 8 | null 9 | ], 10 | "status": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-4dda31-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | true 5 | ], 6 | "action_errors": [ 7 | null, 8 | null 9 | ], 10 | "status": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-53161a-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | true 5 | ], 6 | "action_errors": [ 7 | null, 8 | null 9 | ], 10 | "status": 1 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-560791-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true 4 | ], 5 | "action_errors": [ 6 | null 7 | ], 8 | "status": 1 9 | } 10 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-6be7e5-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | false 5 | ], 6 | "action_errors": [ 7 | null, 8 | "some error occurred" 9 | ], 10 | "status": 0 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-bb8dd5-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true 4 | ], 5 | "action_errors": [ 6 | null 7 | ], 8 | "status": 1 9 | } 10 | -------------------------------------------------------------------------------- /tests/testthat/getpocket.com/v3/send-e6ec69-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | true, 4 | false 5 | ], 6 | "action_errors": [ 7 | null, 8 | "some error occurred" 9 | ], 10 | "status": 0 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | library(httptest) 2 | -------------------------------------------------------------------------------- /tests/testthat/test_auth.R: -------------------------------------------------------------------------------- 1 | context("authorization related functions") 2 | 3 | test_that("create_authorize_url works as expected", { 4 | url <- create_authorize_url("reqtokengibberish") 5 | expect_equal(url, "https://getpocket.com/auth/authorize?request_token=reqtokengibberish&redirect_uri=https://github.com/CorrelAid/pocketapi") 6 | }) 7 | 8 | test_that("create_authorize_url throws error for invalid request_token argument", { 9 | expect_error(create_authorize_url(33212), "Argument request_token must be a character vector of length 1." , class = "usethis_error") 10 | expect_error(create_authorize_url(c("foo", "bar")), "Argument request_token must be a character vector of length 1.", class = "usethis_error") 11 | }) 12 | 13 | 14 | test_that("get_request_token throws error for invalid consumer_key argument", { 15 | expect_error(get_request_token(33212), "Argument consumer_key must be a character vector of length 1.", class = "usethis_error") 16 | expect_error(get_request_token(c("foo", "bar")), "Argument consumer_key must be a character vector of length 1.", class = "usethis_error") 17 | }) 18 | 19 | 20 | # request-b9df5b-POST.R 21 | with_mock_api({ 22 | test_that("get_request_token throws 400 error for invalid consumer_key", { 23 | expect_error(get_request_token("invalidconsumerkey"), regexp = "403 Forbidden: Invalid consumer key.", class = "usethis_error") 24 | }) 25 | }) 26 | 27 | # request-da037e-POST.R 28 | with_mock_api({ 29 | test_that("get_request_token valid case", { 30 | request_token <- get_request_token("myconsumerkey") 31 | expect_equal(request_token, "successrequesttokenyey") 32 | }) 33 | }) 34 | 35 | 36 | test_that("get_request_token throws error for invalid arguments", { 37 | expect_error(get_access_token(33212, "myrequesttoken"), "Argument consumer_key must be a character vector of length 1.", class = "usethis_error") 38 | expect_error(get_access_token(c("foo", "bar"), "myrequesttoken"), "Argument consumer_key must be a character vector of length 1.", class = "usethis_error") 39 | expect_error(get_access_token("myconsumerkey", 33212), "Argument request_token must be a character vector of length 1.", class = "usethis_error") 40 | expect_error(get_access_token("myconsumerkey", c("foo", "bar")), "Argument request_token must be a character vector of length 1.", class = "usethis_error") 41 | }) 42 | 43 | 44 | # authorize-7becba-POST.R 45 | with_mock_api({ 46 | test_that("get_access_token throws 403 error for invalid consumer_key", { 47 | expect_error(get_access_token("invalidconsumerkey", "myrequesttoken"), regexp = "403 Forbidden: Invalid consumer key", class = "usethis_error") 48 | }) 49 | }) 50 | 51 | # authorize-1384bf-POST.R 52 | with_mock_api({ 53 | test_that("get_access_token throws 400 error for invalid request_token", { 54 | expect_error(get_access_token("myconsumerkey", "invalidrequesttoken"), regexp = "400 Bad Request: Code not found.", class = "usethis_error") 55 | }) 56 | }) 57 | 58 | # authorize-b9d4e4-POST.R 59 | with_mock_api({ 60 | test_that("get_access_token valid case", { 61 | access_token <- get_access_token("myconsumerkey", "myrequesttoken") 62 | expect_equal(access_token, "successfullaccesstokenyey") 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_add.R: -------------------------------------------------------------------------------- 1 | context("pocket_add") 2 | 3 | POCKET_TEST_CONSUMER_KEY <- "fakekey" 4 | POCKET_TEST_ACCESS_TOKEN <- "faketoken" 5 | 6 | test_that("missing consumer key causes error", { 7 | expect_error( 8 | pocket_add(add_url = "xAsdfcm13413", consumer_key = "", access_token = POCKET_TEST_ACCESS_TOKEN), 9 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable.", class = "usethis_error" 10 | ) 11 | }) 12 | 13 | test_that("missing access token causes error", { 14 | expect_error( 15 | pocket_add(add_url = "xAsdfcm13413", consumer_key = POCKET_TEST_CONSUMER_KEY, access_token = ""), 16 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable.", class = "usethis_error" 17 | ) 18 | }) 19 | 20 | test_that("missing URL causes error", { 21 | expect_error( 22 | pocket_add(consumer_key = "dasidadw", access_token = POCKET_TEST_ACCESS_TOKEN), 23 | regexp = "Argument 'add_urls' is missing.", 24 | class = "usethis_error" 25 | ) 26 | }) 27 | 28 | # send-fae745-POST.json 29 | with_mock_api({ 30 | test_that("Valid case", { 31 | time_stub <- "2020-04-14 12:51:02 CET" 32 | with_mock( 33 | Sys.time = function() time_stub, 34 | { 35 | result <- 36 | pocket_add( 37 | consumer_key = POCKET_TEST_CONSUMER_KEY, 38 | access_token = POCKET_TEST_ACCESS_TOKEN, 39 | add_urls = "https://katherinemwood.github.io/post/testthat/", 40 | success = FALSE) 41 | expect_equal(result$status_code, 200) 42 | } 43 | ) 44 | }) 45 | }) -------------------------------------------------------------------------------- /tests/testthat/test_pocket_archive.R: -------------------------------------------------------------------------------- 1 | context("pocket_archive") 2 | 3 | 4 | test_that("missing consumer key causes error", { 5 | expect_error( 6 | pocket_archive(item_ids = c("foobarid"), consumer_key = "", access_token = "faketoken"), 7 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable.", class = "usethis_error" 8 | ) 9 | }) 10 | 11 | test_that("missing access token causes error", { 12 | expect_error( 13 | pocket_archive(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = ""), 14 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable.", class = "usethis_error" 15 | ) 16 | }) 17 | 18 | # send-560791-POST.json 19 | with_mock_api({ 20 | test_that("pocket_archive - success generates message", { 21 | time_stub <- "2020-03-14 12:51:02 CET" 22 | with_mock( 23 | Sys.time = function() time_stub, 24 | expect_message( 25 | pocket_archive(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = "faketoken"), 26 | regexp = "Action was successful" 27 | ) 28 | ) 29 | }) 30 | }) 31 | 32 | 33 | 34 | # send-e6ec69-POST.json 35 | with_mock_api({ 36 | test_that("pocket_archive - one success, one error", { 37 | time_stub <- "2020-03-14 12:51:02 CET" 38 | with_mock( 39 | Sys.time = function() time_stub, 40 | expect_warning( 41 | pocket_archive(item_ids = c("foo", "bar"), consumer_key = "fakekey", access_token = "faketoken"), 42 | regexp = "Action on bar failed with error: some error occurred" 43 | ) 44 | ) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_delete.R: -------------------------------------------------------------------------------- 1 | context("pocket_delete") 2 | 3 | 4 | test_that("missing consumer key causes error", { 5 | expect_error( 6 | pocket_delete(item_ids = c("foobarid"), consumer_key = "", access_token = "faketoken"), 7 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable.", class = "usethis_error" 8 | ) 9 | }) 10 | 11 | test_that("missing access token causes error", { 12 | expect_error( 13 | pocket_delete(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = ""), 14 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable.", class = "usethis_error" 15 | ) 16 | }) 17 | 18 | 19 | # send-1972e8-POST.json 20 | with_mock_api({ 21 | test_that("pocket_delete - success generates message", { 22 | time_stub <- "2020-03-14 12:51:02 CET" 23 | with_mock( 24 | Sys.time = function() time_stub, 25 | expect_message( 26 | pocket_delete(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = "faketoken"), 27 | regexp = "Action was successful for the items: foobarid" 28 | ) 29 | ) 30 | }) 31 | }) 32 | 33 | 34 | # send-476df7-POST.json 35 | with_mock_api({ 36 | test_that("pocket_delete - two successes", { 37 | time_stub <- "2020-03-14 12:51:02 CET" 38 | with_mock( 39 | Sys.time = function() time_stub, 40 | expect_message( 41 | pocket_delete(item_ids = c("faz", "bar"), consumer_key = "fakekey", access_token = "faketoken"), 42 | regexp = "Action was successful for the items: faz, bar" 43 | ) 44 | ) 45 | }) 46 | }) 47 | 48 | # send-6be7e5-POST.json 49 | with_mock_api({ 50 | test_that("pocket_delete - one success, one error", { 51 | time_stub <- "2020-03-14 12:51:02 CET" 52 | with_mock( 53 | Sys.time = function() time_stub, 54 | expect_warning( 55 | pocket_delete(item_ids = c("foo", "bar"), consumer_key = "fakekey", access_token = "faketoken"), 56 | regexp = "Action on bar failed with error: some error occurred" 57 | ) 58 | ) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_favorite.R: -------------------------------------------------------------------------------- 1 | context("pocket_favorite") 2 | 3 | 4 | test_that("missing consumer key causes error", { 5 | expect_error( 6 | pocket_favorite(item_ids = c("foobarid"), consumer_key = "", access_token = "faketoken"), 7 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable.", class = "usethis_error" 8 | ) 9 | }) 10 | 11 | test_that("missing access token causes error", { 12 | expect_error( 13 | pocket_favorite(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = ""), 14 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable.", class = "usethis_error" 15 | ) 16 | }) 17 | 18 | # send-055179-POST.json 19 | with_mock_api({ 20 | test_that("pocket_favorite - success generates message", { 21 | time_stub <- "2020-05-14 07:42:27 CEST" 22 | with_mock( 23 | Sys.time = function() time_stub, 24 | expect_message( 25 | pocket_favorite(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = "faketoken"), 26 | regexp = "Action was successful" 27 | ) 28 | ) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_get.R: -------------------------------------------------------------------------------- 1 | context("pocket_get") 2 | 3 | 4 | POCKET_TEST_CONSUMER_KEY <- "fakekey" 5 | POCKET_TEST_ACCESS_TOKEN <- "faketoken" 6 | 7 | 8 | test_that("empty consumer key causes error", { 9 | expect_error(pocket_get( 10 | access_token = POCKET_TEST_ACCESS_TOKEN, consumer_key = "" 11 | ), 12 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable. ", class = "usethis_error" 13 | ) 14 | }) 15 | 16 | test_that("empty access token causes error", { 17 | expect_error(pocket_get( 18 | access_token = "", consumer_key = POCKET_TEST_CONSUMER_KEY 19 | ), 20 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable", class = "usethis_error" 21 | ) 22 | }) 23 | 24 | test_that("invalid tag value causes error", { 25 | expect_error(pocket_get(access_token = POCKET_TEST_ACCESS_TOKEN, consumer_key = POCKET_TEST_CONSUMER_KEY, tag = c("more", "than", "one")), 26 | regexp = "^The tag argument can only be a character string.", class = "usethis_error" 27 | ) 28 | 29 | test_that("invalid favorite value causes error", { 30 | expect_error(pocket_get( 31 | access_token = POCKET_TEST_ACCESS_TOKEN, consumer_key = POCKET_TEST_CONSUMER_KEY, 32 | favorite = "stringisnotvalid" 33 | ), 34 | regexp = "^The favorite argument can only be", class = "usethis_error" 35 | ) 36 | }) 37 | }) 38 | 39 | test_that("invalid item_type value causes error", { 40 | expect_error(pocket_get( 41 | access_token = POCKET_TEST_ACCESS_TOKEN, consumer_key = POCKET_TEST_CONSUMER_KEY, 42 | item_type = "typenotexist" 43 | ), 44 | regexp = "^The item_type argument can only be", class = "usethis_error" 45 | ) 46 | }) 47 | 48 | ## 49 | 50 | test_that("invalid state value causes error", { 51 | expect_error(pocket_get(access_token = POCKET_TEST_ACCESS_TOKEN, consumer_key = POCKET_TEST_CONSUMER_KEY, state = c("more", "than", "one")), 52 | regexp = "^The state argument can only be one of the following:", class = "usethis_error" 53 | ) 54 | 55 | test_that("invalid state value causes error", { 56 | expect_error(pocket_get( 57 | access_token = POCKET_TEST_ACCESS_TOKEN, consumer_key = POCKET_TEST_CONSUMER_KEY, 58 | state = "stringisnotvalid" 59 | ), 60 | regexp = "^The state argument can only be one of the following:", class = "usethis_error" 61 | ) 62 | }) 63 | }) 64 | 65 | ## 66 | 67 | # get-86fba0-POST.R 68 | with_mock_api({ 69 | test_that("invalid access token causes error", { 70 | expect_error(pocket_get(access_token = "dsffkwejrl", consumer_key = POCKET_TEST_CONSUMER_KEY), 71 | regexp = "\n401 Unauthorized: A valid access token", class = "usethis_error" 72 | ) 73 | }) 74 | }) 75 | 76 | # get-40608f-POST.json 77 | with_mock_api({ 78 | test_that("return value is data frame", { 79 | return_value <- pocket_get(consumer_key = POCKET_TEST_CONSUMER_KEY, access_token = POCKET_TEST_ACCESS_TOKEN) 80 | expect_s3_class(return_value, "data.frame") 81 | expect_gt(nrow(return_value), 0) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_modify.R: -------------------------------------------------------------------------------- 1 | context("pocket_modify utils") 2 | 3 | testthat::test_that("successes and failures are extracted correctly from action_results", { 4 | content_stub <- list( 5 | action_results = list(TRUE, FALSE), 6 | action_errors = list(NULL, "some strange error"), 7 | status = 0 8 | ) 9 | mockery::stub(extract_action_results_, "httr::content", content_stub) 10 | action_results <- extract_action_results_(list(), c("a", "b")) 11 | testthat::expect_length(action_results$success_ids, 1) 12 | testthat::expect_length(action_results$failure_ids, 1) 13 | testthat::expect_length(action_results$failures, 1) 14 | }) 15 | 16 | 17 | testthat::test_that("warnings are generated for failures", { 18 | content_stub <- list( 19 | action_results = list(TRUE, FALSE), 20 | action_errors = list(NULL, "some strange error"), 21 | status = 0 22 | ) 23 | mockery::stub(extract_action_results_, "httr::content", content_stub) 24 | action_results <- extract_action_results_(list(), c("a", "b")) 25 | testthat::expect_warning(warn_for_failures_(action_results$failures), 26 | regexp = "Action on b failed with error: some strange error" 27 | ) 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_tag.R: -------------------------------------------------------------------------------- 1 | context("pocket_tag") 2 | 3 | 4 | test_that("empty consumer key causes error", { 5 | expect_error(pocket_tag("tags_remove", 6 | access_token = "myaccesstoken", consumer_key = "", 7 | ), 8 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable. ", class = "usethis_error" 9 | ) 10 | }) 11 | 12 | 13 | test_that("empty access token causes error", { 14 | expect_error(pocket_tag("tags_remove", 15 | access_token = "", consumer_key = "myconsumerkey", 16 | ), 17 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable", class = "usethis_error" 18 | ) 19 | }) 20 | 21 | test_that("pocket_tag throws error for invalid action type", { 22 | expect_error(pocket_tag("tag_remove", consumer_key = "myconsumerkey", access_token = "myaccesstoken"), # it's tags_remove 23 | regexp = "Tag actions can be only be: 'tags_add', 'tags_remove', 'tags_replace', 'tags_clear', 'tag_rename', or 'tag_delete'.", class = "usethis_error" 24 | ) 25 | }) 26 | 27 | # tags_add 28 | # send-44b5bc-POST.json 29 | with_mock_api({ 30 | test_that("pocket_tag tags_add - two successes", { 31 | time_stub <- "2020-04-14 12:51:02 CET" 32 | with_mock( 33 | Sys.time = function() time_stub, 34 | expect_message(pocket_tag("tags_add", c("3424323", "3423222"), tags = c("r", "hadley"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 35 | regexp = "Action was successful" 36 | ) 37 | ) 38 | }) 39 | }) 40 | 41 | # tags_remove 42 | # send-4dda31-POST.json 43 | with_mock_api({ 44 | test_that("pocket_tag tags_remove - two successes", { 45 | time_stub <- "2020-04-14 12:51:02 CET" 46 | with_mock( 47 | Sys.time = function() time_stub, 48 | expect_message(pocket_tag("tags_remove", c("3424323", "3423222"), tags = c("r", "hadley"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 49 | regexp = "Action was successful" 50 | ) 51 | ) 52 | }) 53 | }) 54 | 55 | # tags_clear 56 | test_that("pocket_tag tags_clear - throw error if tags are provided", { 57 | expect_error(pocket_tag("tags_clear", c("3424323", "3423222"), tags = c("foo", "hadley"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 58 | regexp = "If your action is 'tags_clear', you must not provide tags.", class = "usethis_error" 59 | ) 60 | }) 61 | 62 | # send-49cc63-POST.json 63 | with_mock_api({ 64 | test_that("pocket_tag tags_clear - two successes", { 65 | time_stub <- "2020-04-14 12:51:02 CET" 66 | with_mock( 67 | Sys.time = function() time_stub, 68 | expect_message(pocket_tag("tags_clear", c("3424323", "3423222"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 69 | regexp = "Action was successful" 70 | ) 71 | ) 72 | }) 73 | }) 74 | 75 | # tags_replace 76 | test_that("pocket_tag tags_replace - throw error if no tags provided", { 77 | expect_error(pocket_tag("tags_replace", c("3424323", "3423222"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 78 | regexp = "For 'tags_replace', you need to specify the tags argument.", class = "usethis_error" 79 | ) 80 | }) 81 | 82 | # send-53161a-POST.json 83 | with_mock_api({ 84 | test_that("pocket_tag tags_replace - two successes", { 85 | time_stub <- "2020-04-14 12:51:02 CET" 86 | with_mock( 87 | Sys.time = function() time_stub, 88 | expect_message(pocket_tag("tags_replace", c("3424323", "3423222"), tags = c("hadley"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 89 | regexp = "Action was successful for the items: 3424323, 3423222" 90 | ) 91 | ) 92 | }) 93 | }) 94 | 95 | 96 | # tag_delete 97 | test_that("pocket_tag tags_delete - throw error if more than one tag provided", { 98 | expect_error(pocket_tag("tag_delete", tag = c("jenny", "hadley"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 99 | regexp = "For 'tag_delete', you can only specify an atomic vector of one tag.", class = "usethis_error" 100 | ) 101 | }) 102 | 103 | # send-2c60b2-POST.json 104 | with_mock_api({ 105 | test_that("pocket_tag tag_delete - success", { 106 | time_stub <- "2020-04-14 12:51:02 CET" 107 | with_mock( 108 | Sys.time = function() time_stub, 109 | expect_message(pocket_tag("tag_delete", tags = c("hadley"), consumer_key = "myconsumerkey", access_token = "myaccesstoken"), 110 | regexp = "Successfully removed tag 'hadley'" 111 | ) 112 | ) 113 | }) 114 | }) 115 | -------------------------------------------------------------------------------- /tests/testthat/test_pocket_unfavorite.R: -------------------------------------------------------------------------------- 1 | context("pocket_unfavorite") 2 | 3 | 4 | test_that("missing consumer key causes error", { 5 | expect_error( 6 | pocket_unfavorite(item_ids = c("foobarid"), consumer_key = "", access_token = "faketoken"), 7 | regexp = "^POCKET_CONSUMER_KEY does not exist as environment variable.", class = "usethis_error" 8 | ) 9 | }) 10 | 11 | test_that("missing access token causes error", { 12 | expect_error( 13 | pocket_unfavorite(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = ""), 14 | regexp = "^POCKET_ACCESS_TOKEN does not exist as environment variable.", class = "usethis_error" 15 | ) 16 | }) 17 | 18 | # send-28a90b-POST.json 19 | with_mock_api({ 20 | test_that("pocket_unfavorite - success generates message", { 21 | time_stub <- "2020-05-14 07:43:26 CEST" 22 | with_mock( 23 | Sys.time = function() time_stub, 24 | expect_message( 25 | pocket_unfavorite(item_ids = c("foobarid"), consumer_key = "fakekey", access_token = "faketoken"), 26 | regexp = "Action was successful" 27 | ) 28 | ) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test_utils.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorrelAid/pocketapi/f071d1c722ca3c4ff54f0b218d3ca15cb124abf5/tests/testthat/test_utils.R -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/introduction_to_pocketapi/0/getpocket.com/v3/send-1c07b4-POST.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_results": [ 3 | { 4 | "item_id": "1520641742", 5 | "normal_url": "http://correlaid.org/blog", 6 | "resolved_id": "1520641742", 7 | "extended_item_id": "1520641742", 8 | "resolved_url": "http://correlaid.org/blog/", 9 | "domain_id": "26652124", 10 | "origin_domain_id": "26652124", 11 | "response_code": "200", 12 | "mime_type": "text/html", 13 | "content_length": "29261", 14 | "encoding": "utf-8", 15 | "date_resolved": "2016-12-16 04:22:52", 16 | "date_published": "0000-00-00 00:00:00", 17 | "title": "CorrelAid.", 18 | "excerpt": "Wir sind überzeugt, dass in der Arbeit mit Daten mehr Potential steckt als nur Werbung effektiver zu machen. Wir glauben, dass die Zivilgesellschaft von diesem Potential Gebrauch machen sollte. Wir möchten mit Datenanalyse Gutes tun.", 19 | "word_count": "163", 20 | "innerdomain_redirect": "1", 21 | "login_required": "0", 22 | "has_image": "1", 23 | "has_video": "0", 24 | "is_index": "1", 25 | "is_article": "1", 26 | "used_fallback": "0", 27 | "lang": "de", 28 | "time_first_parsed": "1481883772", 29 | "authors": { 30 | "60388584": { 31 | "author_id": "60388584", 32 | "name": "Damian Paderta", 33 | "url": "http://correlaid.org/author/damian/" 34 | } 35 | }, 36 | "images": { 37 | "1": { 38 | "item_id": "1520641742", 39 | "image_id": "1", 40 | "src": "https://i2.wp.com/correlaid.org/wp-content/uploads/2016/12/thijbbig.png?fit=830%2C440", 41 | "width": "830", 42 | "height": "440", 43 | "credit": "", 44 | "caption": "" 45 | }, 46 | "2": { 47 | "item_id": "1520641742", 48 | "image_id": "2", 49 | "src": "https://i1.wp.com/correlaid.org/wp-content/uploads/2016/12/IMG_9459.jpg?fit=830%2C553", 50 | "width": "830", 51 | "height": "553", 52 | "credit": "", 53 | "caption": "" 54 | }, 55 | "3": { 56 | "item_id": "1520641742", 57 | "image_id": "3", 58 | "src": "https://i2.wp.com/correlaid.org/wp-content/uploads/2016/12/20151019_191057.jpg?fit=830%2C623", 59 | "width": "830", 60 | "height": "623", 61 | "credit": "", 62 | "caption": "" 63 | }, 64 | "4": { 65 | "item_id": "1520641742", 66 | "image_id": "4", 67 | "src": "https://i2.wp.com/correlaid.org/wp-content/uploads/2016/02/ca_ms_politik.jpg?fit=830%2C466", 68 | "width": "830", 69 | "height": "466", 70 | "credit": "", 71 | "caption": "" 72 | }, 73 | "5": { 74 | "item_id": "1520641742", 75 | "image_id": "5", 76 | "src": "http://correlaid.org/wp-content/uploads/2015/11/cropped-cropped-YR89OQFMT1_klein1.jpg", 77 | "width": "1920", 78 | "height": "0", 79 | "credit": "", 80 | "caption": "" 81 | } 82 | }, 83 | "videos": [], 84 | "top_image_url": "https://s0.wp.com/i/blank.jpg", 85 | "resolved_normal_url": "http://correlaid.org/blog", 86 | "given_url": "https://www.correlaid.org/blog" 87 | }, 88 | { 89 | "item_id": "3119081882", 90 | "normal_url": "http://correlaid.org/about", 91 | "resolved_id": "0", 92 | "extended_item_id": "0", 93 | "resolved_url": "", 94 | "domain_id": "0", 95 | "origin_domain_id": "0", 96 | "response_code": "0", 97 | "mime_type": "", 98 | "content_length": "0", 99 | "encoding": "", 100 | "date_resolved": "0000-00-00 00:00:00", 101 | "date_published": "0000-00-00 00:00:00", 102 | "title": "", 103 | "excerpt": "", 104 | "word_count": "0", 105 | "innerdomain_redirect": "0", 106 | "login_required": "0", 107 | "has_image": "0", 108 | "has_video": "0", 109 | "is_index": "0", 110 | "is_article": "0", 111 | "used_fallback": "0", 112 | "lang": null, 113 | "time_first_parsed": null, 114 | "given_url": "https://correlaid.org/about" 115 | } 116 | ], 117 | "action_errors": [null, null], 118 | "status": 1 119 | } 120 | -------------------------------------------------------------------------------- /vignettes/pocketapi.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to {pocketapi}" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Introduction to pocketapi} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | %\VignetteDepends{ggplot2} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>" 15 | ) 16 | ``` 17 | 18 | ```{r, include=FALSE} 19 | library(httptest) 20 | start_vignette("introduction_to_pocketapi") 21 | Sys.setenv(POCKET_CONSUMER_KEY = "24256-xsadas") 22 | Sys.setenv(POCKET_ACCESS_TOKEN = "35435-badasdasd") 23 | ``` 24 | 25 | The website [Pocket](https://getpocket.com) is a well-known tool for storing things you find on the internet - in case you want to access them later. Be it a news article, a video on YouTube, an interesting Tweet or just any other URL that might prove useful. In short: Pocket is a tool that contributes to organising oneself. Pocket also offers an API whereby you can basically execute all actions that you can do when using Pocket in your web browser. This is where `pocketapi` comes into play. We have created a R wrapper for Pocket's API that allows you to organise, retrieve and change your items stored in your Pocket account through some convenient R functions. In the following, we explain how to connect to the API via `pocketapi`, how the key functions work and how the workflow looks like. 26 | 27 | ## Connecting to the API / Authentication 28 | 29 | ### Create a Pocket Application 30 | You need to create a Pocket *application* in Pocket's developer portal to access your Pocket data. Don't worry: this app will only be visible to you and only serves the purpose of acquiring the credentials for `pocketapi`. 31 | 32 | 1. Log in to your Pocket account and go to [https://getpocket.com/developer/apps/new](https://getpocket.com/developer/apps/new). 33 | 2. Click "Create New Application". You'll be presented with a form. Choose a sensible application *name* and a *description*. 34 | 3. Give your app *permissions*: Depending on the permissions of your app, you'll be able to use all or just a subset of the `pocketapi` functions: 35 | 36 | | `pocketapi` function | what it does | needed permission | 37 | | ------------------------------ | ---------------------------------- | ----------------- | 38 | | `pocket_get` | get data frame of all your pockets | Retrieve | 39 | | all other `pocket_*` functions | add new Pocket entries | Modify | 40 | 41 | 4. Check any *platform* for your app - this does not really matter but you need to check at least one box. 42 | 5. Accept the *terms of service* and click "Create Application". 43 | 44 | ### Get consumer key and token 45 | `Pocketapi` uses the *OAuth2* flow provided by the [Pocket Authentication API](https://getpocket.com/developer/docs/authentication) to get an access token for your App. Because Pocket does not closely follow the *OAuth* standard, we are not able to provide as smooth an experience as other packages do (e.g. [googlesheets4](https://github.com/tidyverse/googlesheets4)). Instead, the user has to follow the following instructions **once** to obtain an access token: 46 | 47 | 1. Request a request token: 48 | `req_token <- get_request_token(consumer_key)` 49 | 50 | 2. Authorize your app by entering the URL created by `create_authorize_url` **in your browser**: 51 | 52 | ```r 53 | create_authorize_url(req_token) 54 | ``` 55 | This step is critical: **Even if you have authorized your app before** and you want to get a new access token, you need to do the authorization in your browser again. Otherwise, the request token will not be authorized to generate an access token! 56 | 57 | 3. Get access token using the now authorized request token: 58 | 59 | ```r 60 | access_token <- get_access_token(consumer_key, request_token) 61 | ``` 62 | **Important**: Never make your `consumer_key` and `access_token` publicly available -- or anyone will be able to access your Pocket! 63 | 64 | ### Add the consumer key and access token to your environment 65 | It is common practice to set API keys in your R environment file so that every time you start R the key is loaded. 66 | 67 | All `pocketapi` functions access your `consumer_key` and `access_token` automatically by executing `Sys.getenv("POCKET_CONSUMER_KEY")` respectively `Sys.getenv("POCKET_ACCESS_TOKEN")`. Alternatively, you can provide an explicit definition of your `consumer_key` and `access_token` with each function call. 68 | 69 | In order to add your key to your environment file, you can use the function `edit_r_environ` from the `usethis` package: 70 | 71 | ```r 72 | usethis::edit_r_environ() 73 | ``` 74 | 75 | This will open your `.Renviron` file in the RStudio editor. Now, you can add the following lines to it: 76 | 77 | ``` 78 | POCKET_CONSUMER_KEY="yourkeygoeshere" 79 | POCKET_ACCESS_TOKEN="youraccesstokengoeshere" 80 | ``` 81 | Save the file and restart R for the changes to take effect. 82 | 83 | If your `.Renviron` lives at a non-conventional place, you can also edit it manually using RStudio or your favorite text editor. 84 | 85 | If you don't want to clutter your `.Renviron` file, you can also use an `.env` file in your project directory together with the [`dotenv`](https://github.com/gaborcsardi/dotenv) package. In this case, make sure to never share your `.env` file. 86 | 87 | 88 | ## The main functions of `pocketapi` 89 | 90 | `Pocketapi` offers seven functions to interact with items in your Pocket account. They can be grouped into three general buckets: 91 | 92 | 1. Adding items to your Pocket account: `pocket_add()` 93 | 2. Retrieving items from your Pocket account: `pocket_get()` 94 | 3. Modifying items that already exist in your Pocket account: `pocket_archive()`, `pocket_delete()`, `pocket_favorite()`, `pocket_unfavorite()`, `pocket_tag()` 95 | 96 | If you create an API token for a new Pocket app, you need to decide which permissions this app should have. The Pocket website offers three dimensions of permissions which match the bucket structure mentioned above. If you do not grant a specific permission to your app, it will not be possible to execute the corresponding functions in R. For instance, if you do not grant your app the permission to "modify", it will not be possible to archive, delete and favorite/unfavorite items as well as modifying the tags associated with your items. 97 | 98 | If you want to use the "modify" functions, you should also grant the app the permission to "retrieve" items: All "modify" functions are based on Pocket's internal item IDs, which you can only know when you retrieve the data first. 99 | 100 | ## Getting data from Pocket into R 101 | 102 | The main function to access Pocket data from R is `pocket_get()`. You can assign the output of the function to a new object to obtain a data frame where every row represents an object saved in Pocket. The default settings for `pocket_get()` are very broad, but it can be adjusted by using different arguments in the function. 103 | 104 | ### Input for the `pocket_get()` function 105 | 106 | The main arguments of `pocket_get()` are also explained in the function's help file, but below you find a quick overview of the main function arguments for adjusting which content is retrieved from Pocket. 107 | 108 | * **favorite** allows to filter on favorited items (`favorite = TRUE`) or on unfavorited items (`favorite = FALSE`). Using the default value (`favorite = NULL`) means that items are retrieved regardless if they are favorited or not. 109 | * **item_type** filters based on the format of the Pocket content, allowing to filter on "article", "image" or "video". This format classification is done automatically by Pocket and cannot be modified (and there are some instances of misclassification). Keeping the default (`item_type = NULL`) retrieves all items regardless of type. 110 | * **tag** selects only the items that have a certain tag, also making it possible to filter by any untagged items. At the moment, only one tag can be used for filtering at the same time. 111 | * **state** can select items depending on their reading status. It's possible to retrieve only items that have not yet been read and are still in the Pocket queue (`state = "unread"`) or to filter on items that are already read and archived (`state = "archive"`). By default, the function returns *all* items (`state = "all"`), regardless whether they are read or unread. Depending on your goals, you might prefer another option besides this default. 112 | 113 | The function arguments "consumer_key" and "access_token" are the mandatory account credentials for the API. Please see the previous section for an explanation how to set up your account. 114 | 115 | By default, the Pocket API has a limit of 5000 items per API call. This means that the output of `pocket_get()` is a data frame with up to 5000 rows, even if there might actually be more items in your Pocket. If you have more items saved in Pocket, you may use some tricks to combine multiple API calls to get a more complete picture (e.g., using the "state" parameter or playing around with favorited/tagged items), but it may not always be possible to retrieve all items if you have an extremely high number of items saved in your Pocket account. 116 | 117 | Like many other APIs, Pocket allows only a certain number of API calls per hour ([find details here](https://getpocket.com/developer/docs/rate-limits)), but usually this limit should not be an issue for `pocket_get()`. Note that *all* API calls, including adding or modifying Pocket items, count towards this hourly limit. 118 | 119 | ### Output of the pocket_get() function 120 | 121 | `pocket_get()` returns a data frame where each row is an item that has been saved in Pocket. For each item, the following information is saved: 122 | 123 | * **item_id** and **resolved_id**: internal Pocket item identifiers. If you want to modify an item (e.g. via `pocket_archive()` or `pocket_delete()`), you need to specify the *item_id* as an input for this function. 124 | * **given_url**: the web address of the item that has been saved. 125 | * **given_title** and **resolved_title**: the title of the item. "given_title" is the title that was saved along with the item (but can be blank), whereas "resolved_title" is coming from Pocket's attempt to parse it from the website (which is often more complete). 126 | * **favorite**: logical variable, indicating `TRUE` if an item is favorited. 127 | * **status**: numeric variable with three possible values: `0` if the item is unread, `1` if the item is archived, `2` if the item is about to be deleted. 128 | * **is_article**: logical variable, `TRUE` if Pocket has detected the item to be an article (and not an image or a video). The classification is provided by Pocket and not always 100 percent precise. 129 | * **has_image** and **has_video**: numeric variable, with three possible values: `0` if the item does not contain any image/video, `1` if the item *contains* an image/video, `2` if the item *is* an image/video. 130 | * **word_count**: numeric variable, indicating the number of words in the item. 131 | * **tags**: the user-defined tags associated with each item. If an item has several tags, they are separated via comma (e.g. "tag 1,tag 2"). 132 | * **authors**: information about the author of the item (generated by Pocket via parsing the website), consisting for each item of a list with four elements: "item_id" (should be identical to the information from the variable with the same name mentioned above), "author_id" (an identifier created by Pocket), "name" (the author's name in plain text) and "url" (the web address of the author at the website, usually showing all posts/articles written by the same author). 133 | * **images** and **image**: **image** contains some identifier for the image. **images** is a list with seven elements: "item_id" (should be identical to the information from the variable with the same name mentioned above), "image_id", "src" (the images' web address), "width" and "height" (supposedly showing the image's dimensions, but often mistakenly displaying "0" as a value even if the picture was larger than 0x0 pixels), "credit" (the person/organization credited for the picture, parsed by Pocket) and "caption" (the picture's caption as parsed by Pocket). 134 | 135 | If you need a more technical description of the data that is returned by the Pocket API, the [overview in the developer documentation](https://getpocket.com/developer/docs/v3/retrieve) might help. 136 | 137 | ### Applied example: obtaining data via `pocket_get()` and using it 138 | 139 | The following applied example illustrates how to extract data from Pocket via `pocket_get()` and use it thereafter in R. Please note that the example assumes that you have already connected a new Pocket application in your developer settings, created the necessary credentials and saved them in your `.renviron` file. All these steps are explained in the previous sections of this vignette. 140 | 141 | In our first code snippet, we call `pocket_get()`, keeping all options to the defaults, and assign its result to a new object `pocket_items`. Technically, `state = "all"` is also the default, but we specify it to make it easier to understand that we get data for both read *and* unread items. 142 | 143 | ```{r} 144 | library(pocketapi) 145 | 146 | pocket_items <- pocket_get(state = "all") 147 | ``` 148 | 149 | The resulting object `pocket_items` is now part of our R environment and can be used just like any standard data frame from other sources. It's a `data.frame` / `tibble`, with the latter allowing it to be integrated nicely into workflows based on the `tidyverse` packages ^[For more information about the `tibble` class, [see here](https://tibble.tidyverse.org). We can also see that we have `r nrow(pocket_items)` rows in our data frame, one for each item saved in Pocket. Tabulating the "status" variable shows us the reading status: There are `r nrow(pocket_items[pocket_items$status == 0,])` items which are unread (`status == 0`) and `r nrow(pocket_items[pocket_items$status == 1,])` items which that are archived (`status == 1`). (Note: The data is coming from an account that has been set up specifically for the development of this package. It's very likely that "real" users have many more items in their Pocket accounts.) 150 | 151 | ```{r} 152 | class(pocket_items) 153 | 154 | nrow(pocket_items) 155 | 156 | table(pocket_items$status) # reading status 157 | ``` 158 | 159 | The data can be used nicely as input for other R functions. In the code example below, we first show how the data is used as input for `tidyverse` packages: We use the `dplyr` package to calculate the mean and the standard deviation of the number of words per item, separately for unread and archived items. Then, we demonstrate how the data is used in a base R function - in this case, a t-test that allows us to see that there is no significant difference in the length of unread vs. archived articles. 160 | 161 | ```{r, warning=FALSE, message=FALSE} 162 | library(dplyr) 163 | 164 | # quick re-code: new "reading_status" variable with labels that are intuitive to understand 165 | pocket_items$reading_status <- recode(pocket_items$status, `0` = "unread", `1` = "archived") 166 | 167 | # using dplyr functions on the data: grouping the data and then summarizing it 168 | pocket_items %>% 169 | group_by(reading_status) %>% 170 | summarise(word_count_avg = mean(word_count), 171 | word_count_sd = sd(word_count), 172 | base_group = n()) 173 | 174 | # using base-R functions on the data: t-test 175 | t.test(word_count ~ reading_status, data = pocket_items) 176 | ``` 177 | 178 | Of course, the data can also serve as input for plotting functions. Below, we use the `ggplot2` package to show the distribution of the number of words per item for both unread and archived articles. As already shown in the t-test, the mean for both groups is very similar and the distributions largely overlap, but it seems that the distribution of the unread articles has slightly wider tails. 179 | 180 | ```{r, fig.width = 6} 181 | library(ggplot2) 182 | 183 | ggplot(data = pocket_items) + 184 | geom_density(aes(x = word_count, group = reading_status, fill = reading_status), alpha = 0.25) + 185 | labs(title = "Article Length of Read / Unread Items in Pocket", 186 | x = "Word Count", 187 | y = "Density", 188 | fill = "Reading Status", 189 | caption = "Data: Articles from an example account. 190 | \nn=20 unread and n=33 read/archived articles") + 191 | theme_minimal() + 192 | theme(legend.position = "top") 193 | ``` 194 | 195 | ## Getting content from R into Pocket 196 | 197 | With the function `pocket_add()`, `{pocketapi}` provides a convenient way of adding new links/items to your Pocket account. The function takes a character vector URLs as compulsory input; it will add these URLs to your account. Additionally, you can specify a variety of arguments. There are two main arguments: `tags` allows you to add tags to the items you want to add. *Attention: The tags specified in `tags` will be added to all elements in the vector!* The argument `success` (default: `TRUE`) outputs success or failure messages for each element (in order to use this argument, your App needs the rights for the GET endpoint, for it uses `pocket_get()` in the background). 198 | 199 | Here is an example of how this works: 200 | 201 | ```{r, warning=FALSE, message=FALSE} 202 | 203 | new_urls <- c("https://www.correlaid.org/blog", "https://correlaid.org/about") 204 | 205 | pocketapi::pocket_add(add_urls = new_urls, success = FALSE, tags = c("data4good", "correlaid")) 206 | ``` 207 | 208 | Before adding items using `pocket_add()`, it is crucial to beware of two things, since the Pocket API sometimes behaves oddly/unpredictably, especially when it comes to adding items: 209 | 210 | 1. The API only accepts URLs that start with *http* or *https*, including *://*; those starting with only *www.* will most likely *not* be added. 211 | 212 | 2. In some cases it might be that the URL, once it has been successfully added, is being changed from *https* to *http* internally. If you use the `success` argument of `pocket_add()`, this might lead to false failure messages (the URL will be successfully added, but the function cannot detect it due to the change in the URL). 213 | 214 | For further information on `pocket_add()` see the function's documentation. 215 | 216 | ## Manipulating content in Pocket from within R 217 | 218 | The `pocketapi` package also provides a range of functions for manipulating the items in your Pocket. These are: 219 | 220 | - `pocket_archive()` 221 | - `pocket_delete()` 222 | - `pocket_favorite()` 223 | - `pocket_unfavorite()` 224 | - `pocket_tag()` 225 | 226 | They usually do not need a lot of arguments, the most important one being the `item_ids`. Pocket assigns a unique ID to each item in your Pocket. You can retrive the IDs by using `pocket_get()`. The functions `pocket_archive`, `pocket_delete`, `pocket_unfavorite` and `pocket_favorite` only need the `item_ids` argument (which can be a single one or a vector). They perform the respective action on these items (e.g., delete or unfavorite them). 227 | 228 | ```{r, eval=FALSE, warning=FALSE, message=FALSE} 229 | 230 | # First perform pocket_get() to receive a data frame containing your items, including the items' IDs 231 | 232 | pocketapi::pocket_delete(item_ids = c("242234", "694834")) 233 | 234 | ``` 235 | 236 | `pocket_tag()` is slightly more complex since it can perform a range of actions on the specified items. These include: `tags_add`, `tags_remove`, `tags_replace`, `tags_clear`, `tag_rename`, and `tag_delete`. In addition to the `item_ids` argument, this function also needs one of six actions to be performed on the items. You can check out [Pocket's API documentation](https://getpocket.com/developer/docs/v3/modify#action_tags_add) for more info. 237 | 238 | - Two arguments, `tag_delete` and `tag_rename` do not need `item_ids` since they *change all tags present in your Pocket list* (delete them or rename them, respectively). They need the `tags` argument instead where you either specify the tags to be deleted or the (one!) tag to be renamed (specifying the old and the new name). 239 | 240 | - The action `tags_clear`, in turn, does not need `tags`, since it clears the items of `item_id` from *all* tags. 241 | 242 | - `tags_add` and `tags_remove` need both the `tags` and the `item_ids` arguments. They add (or remove) the tags specified in `tags` from the respective items. 243 | 244 | - `tags_replace` also needs `tags` and `item_ids`. However, it is used to *replace all tags of the item(s) with the newly specified* `tags`. 245 | 246 | Here are a few examples on how to manipulate your items in Pocket: 247 | 248 | ```{r, eval=FALSE, warning=FALSE, message=FALSE} 249 | 250 | # Adds four new tags to two items 251 | pocketapi::pocket_tag(action_name = "tags_add", 252 | item_ids = c("242234", "694834"), 253 | tags = c("boring", "done_reading", "newspaper", "german")) 254 | 255 | # Note: No tags needed, affects all items with tag "german" 256 | pocketapi::pocket_tag(action_name = "tag_delete", 257 | tags = "german") 258 | 259 | # Renames the tag "newspaper" into "politics" for all items 260 | pocketapi::pocket_tag(action_name = "tag_rename", 261 | tags = c("newspaper", "politics")) 262 | 263 | # Removes the tag "boring" from the two items 264 | pocketapi::pocket_tag(action_name = "tags_remove", 265 | item_ids = c("242234", "694834"), 266 | tags = "boring") 267 | 268 | # Replaces all existing tags with these three new ones 269 | pocketapi::pocket_tag(action_name = "tags_replace", 270 | item_ids = c("242234", "694834"), 271 | tags = c("interesting", "economics", "longread")) 272 | 273 | # Clears the two items from all tags we have assigned previously 274 | pocketapi::pocket_tag(action_name = "tags_clear", 275 | item_ids = c("242234", "694834")) 276 | 277 | 278 | ``` 279 | 280 | 281 | If you have any questions remaining regarding Pocket's API, refer to [its documentation](https://getpocket.com/developer/docs/overview). 282 | 283 | ```{r, include=FALSE} 284 | end_vignette() 285 | ``` 286 | --------------------------------------------------------------------------------