ropensci-archive/rtweet

`post_tweet()` with `rtweet_bot()` no longer works

Closed this issue · 15 comments

wurli commented

Previously I was using code like the following:

auth_as(rtweet_bot(
  api_key       = Sys.getenv("TWITTER_API_KEY"),  
  api_secret    = Sys.getenv("TWITTER_API_SECRET"),
  access_token  = Sys.getenv("TWITTER_ACCESS_TOKEN"),
  access_secret = Sys.getenv("TWITTER_ACCESS_SECRET")
))

post_tweet("whatever")

My bot's been offline for about 12 months for unrelated reasons. I'm working on it again and the same code which previously worked is now failing to post tweets.

Now I get this behaviour:

x <- post_tweet("whatever")
#> Your tweet has been posted!

x
#> Your message has not been posted! FALSETweet posted from 
#> Text:

Needless to say, having checked my bot's account, tweets are no longer being posted.

I've tried renewing my keys/tokens/secrets to no avail. I've also tried scouring the twitter API documentation for some insight, but my skill with APIs is unfortunately the limiting factor here.

Any help greatly appreciated.

Session info

─ Session info ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.2.1 (2022-06-23)
 os       macOS Ventura 13.2
 system   x86_64, darwin17.0
 ui       RStudio
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       Africa/Nairobi
 date     2023-11-20
 rstudio  2023.06.2+561 Mountain Hydrangea (desktop)
 pandoc   2.19.2 @ /usr/local/bin/pandoc

─ Packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 ! package           * version    date (UTC) lib source
 P askpass             1.2.0      2023-09-03 [?] CRAN (R 4.2.0)
 P brio                1.1.3      2021-11-30 [?] CRAN (R 4.2.0)
 P cachem              1.0.8      2023-05-01 [?] CRAN (R 4.2.0)
 P callr               3.7.3      2022-11-02 [?] CRAN (R 4.2.0)
 P cli                 3.6.1      2023-03-23 [?] CRAN (R 4.2.0)
 P colorspace          2.1-0      2023-01-23 [?] CRAN (R 4.2.0)
 P crayon              1.5.2      2022-09-29 [?] CRAN (R 4.2.0)
 P curl                5.1.0      2023-10-02 [?] CRAN (R 4.2.0)
 P desc                1.4.2      2022-09-08 [?] CRAN (R 4.2.0)
 P devtools            2.4.5      2022-10-11 [?] CRAN (R 4.2.0)
 P digest              0.6.33     2023-07-07 [?] CRAN (R 4.2.0)
 P dplyr             * 1.1.3      2023-09-03 [?] CRAN (R 4.2.0)
 P ellipsis            0.3.2      2021-04-29 [?] CRAN (R 4.2.0)
 P fansi               1.0.5      2023-10-08 [?] CRAN (R 4.2.0)
 P fastmap             1.1.1      2023-02-24 [?] CRAN (R 4.2.0)
 P forcats           * 1.0.0      2023-01-29 [?] CRAN (R 4.2.0)
 P fs                  1.6.3      2023-07-20 [?] CRAN (R 4.2.0)
 P generics            0.1.3      2022-07-05 [?] CRAN (R 4.2.0)
 P ggplot2           * 3.4.4      2023-10-12 [?] CRAN (R 4.2.0)
 P glue                1.6.2      2022-02-24 [?] CRAN (R 4.2.0)
 P gtable              0.3.4      2023-08-21 [?] CRAN (R 4.2.0)
 P hms                 1.1.3      2023-03-21 [?] CRAN (R 4.2.0)
 P htmltools           0.5.7      2023-11-03 [?] CRAN (R 4.2.1)
 P htmlwidgets         1.6.2      2023-03-17 [?] CRAN (R 4.2.0)
 P httpuv              1.6.12     2023-10-23 [?] CRAN (R 4.2.1)
 P httr                1.4.7      2023-08-15 [?] CRAN (R 4.2.0)
 P httr2             * 1.0.0      2023-11-14 [?] CRAN (R 4.2.1)
 P jose                1.2.0      2021-11-06 [?] CRAN (R 4.2.0)
 P jsonlite            1.8.7      2023-06-29 [?] CRAN (R 4.2.0)
 P later               1.3.1      2023-05-02 [?] CRAN (R 4.2.0)
 P lifecycle           1.0.4      2023-11-07 [?] CRAN (R 4.2.1)
 P lubridate         * 1.9.3      2023-09-27 [?] CRAN (R 4.2.0)
 P magrittr            2.0.3      2022-03-30 [?] CRAN (R 4.2.0)
 P memoise             2.0.1      2021-11-26 [?] CRAN (R 4.2.0)
 P mime                0.12       2021-09-28 [?] CRAN (R 4.2.0)
 P miniUI              0.1.1.1    2018-05-18 [?] CRAN (R 4.2.0)
 P munsell             0.5.0      2018-06-12 [?] CRAN (R 4.2.0)
 P openssl             2.1.1      2023-09-25 [?] CRAN (R 4.2.0)
 P pillar              1.9.0      2023-03-22 [?] CRAN (R 4.2.0)
 P pkgbuild            1.4.2      2023-06-26 [?] CRAN (R 4.2.0)
 P pkgconfig           2.0.3      2019-09-22 [?] CRAN (R 4.2.0)
 P pkgload             1.3.3      2023-09-22 [?] CRAN (R 4.2.0)
 P prettyunits         1.2.0      2023-09-24 [?] CRAN (R 4.2.0)
 P processx            3.8.2      2023-06-30 [?] CRAN (R 4.2.0)
 P profvis             0.3.8      2023-05-02 [?] CRAN (R 4.2.0)
 P promises            1.2.1      2023-08-10 [?] CRAN (R 4.2.0)
 P ps                  1.7.5      2023-04-18 [?] CRAN (R 4.2.0)
 P purrr             * 1.0.2      2023-08-10 [?] CRAN (R 4.2.0)
 P R6                  2.5.1      2021-08-19 [?] CRAN (R 4.2.0)
 P rappdirs            0.3.3      2021-01-31 [?] CRAN (R 4.2.0)
 P Rcpp                1.0.11     2023-07-06 [?] CRAN (R 4.2.0)
 P readr             * 2.1.4      2023-02-10 [?] CRAN (R 4.2.0)
 P remotes             2.4.2.1    2023-07-18 [?] CRAN (R 4.2.0)
 P renv                1.0.3      2023-09-19 [?] CRAN (R 4.2.0)
 P rlang               1.1.2      2023-11-04 [?] CRAN (R 4.2.1)
 P rprojroot           2.0.4      2023-11-05 [?] CRAN (R 4.2.1)
 P rstudioapi          0.15.0     2023-07-07 [?] CRAN (R 4.2.0)
 P rtweet              1.2.1      2023-10-16 [?] CRAN (R 4.2.0)
 P scales              1.2.1      2022-08-20 [?] CRAN (R 4.2.0)
 P sessioninfo         1.2.2      2021-12-06 [?] CRAN (R 4.2.0)
 P shiny               1.7.4      2022-12-15 [?] CRAN (R 4.2.0)
 P stringdist          0.9.10     2022-11-07 [?] CRAN (R 4.2.0)
 P stringi             1.7.12     2023-01-11 [?] CRAN (R 4.2.0)
 P stringr           * 1.5.0      2022-12-02 [?] CRAN (R 4.2.0)
 P testthat          * 3.2.0      2023-10-06 [?] CRAN (R 4.2.0)
 P tibble            * 3.2.1      2023-03-20 [?] CRAN (R 4.2.0)
 P tidyr             * 1.3.0      2023-01-24 [?] CRAN (R 4.2.0)
 P tidyselect          1.2.0      2022-10-10 [?] CRAN (R 4.2.0)
 P tidyverse         * 2.0.0      2023-02-22 [?] CRAN (R 4.2.0)
 R tidyverse-dev-bot * 0.0.0.9000 <NA>       [?] <NA>
 P timechange          0.2.0      2023-01-11 [?] CRAN (R 4.2.0)
 P tzdb                0.4.0      2023-05-12 [?] CRAN (R 4.2.0)
 P urlchecker          1.0.1      2021-11-30 [?] CRAN (R 4.2.0)
 P usethis             2.2.2.9000 2023-11-11 [?] Github (r-lib/usethis@52b45dc)
 P utf8                1.2.4      2023-10-22 [?] CRAN (R 4.2.1)
 P V8                  4.4.0      2023-10-09 [?] CRAN (R 4.2.0)
 P vctrs               0.6.4      2023-10-12 [?] CRAN (R 4.2.0)
 P withr               2.5.2      2023-10-30 [?] CRAN (R 4.2.1)
 P xtable              1.8-4      2019-04-21 [?] CRAN (R 4.2.0)

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
llrs commented

You need to use the function using the API v2, tweet_post(). I didn't deprecate the function because apparently some (random?) users can still work with some API v1 endpoints. Also the API v2 don't allow the "old" rtweet_bot to work (for the same reasons I haven't deprecated (but I am reconsidering this as I need to make a new release soon)

To use the new API v2 you'll need to authenticate via the new OAuth2 mechanism, as described in the vignette. There might be some problems with the recent update on httr2. Please open a new issue with the details too (as nicely done here). Thanks

wurli commented

Thanks for the (very!) quick response. Previously I've been using what I believe is a 3-legged oauth flow, because this bot posts using GitHub actions and needs a persistent authorisation which doesn't rely on a web browser. My understanding is that there's no solution that fits this use-case with the OAuth2 mechanism. Am I mistaken?

Very grateful for your help!

llrs commented

AFAIK no, I think there might be a way but I am not sure it works or how and it is not implemented on the released rtweet.
However, you can authenticate locally and move your credential to a GHA (encrypting the file with a password or removing the user token and adding it on the GHA or some similar methods).

wurli commented

After a bit of pain, I've now got my bot back up and working again. I strongly suspect I've not strictly followed best practice, but the bot seems to be working and my credentials are all secure.

For anyone facing the same predicament, here's what I needed to do:

  1. Update my app in line with the latest rtweet documentation. In particular, this meant changing the type of my app from "Web App, Automated App or Bot" to "Native App", and updating the callback URI to http://127.0.0.1:1410/.

  2. As directed above, ditch the rtweet_bot() with post_tweet() approach in favour of rtweet_client()/rtweet_oauth2() with tweet_post(). As an aside, I find the naming of post_tweet() vs tweet_post() quite distressing - it felt like a real gotcha when I realised there were two functions, neither of which reference the other in their documentation. I'd suggest updating the docs to explain the difference, and perhaps renaming tweet_post() to post_tweet2() so intellisense can tell folks there's a new approach.

  3. This posed a challenge in that rtweet_oauth2() returns a token which expires within 2 hours, after which time a user must manually confirm in the browser that they're happy for rtweet to continue to use their account. My project is an automated bot running hourly using GitHub actions, so this wasn't an option. Here's the workaround I used:

    • After some digging, I found that rtweet_oauth2() returns an object with a refresh_token which can be used to keep the authorisation active without needing to confirm in a browser.

    • The issue is that, this refresh token can only be used once, extending authorisation for 2 more hours. When it's used, the request returns a new refresh token which can be used in the same way, and at the same time invalidates the original token. httr2 errors in such cases to prevent this sort of usage, so I had to use an internal function httr2:::token_refresh() to bypass the error (NB httr2 1.0 merely warns in such cases, but at the time of writing, rtweet doesn't seem to work with versions later than 0.2.2).

    • The refresh token is obviously sensitive, so to avoid exposing it in my source code, I used httr2::secret_read_rds() and httr2::secret_write_rds() to keep it saved in its own file, which could then be continuously updated as and when the refresh token is used and regenerated (my bot runs on GitHub actions and saves changes to files by simply committing them to the repo). This approach allowed me add an encryption key as a secret environmental variable to encrypt/decrypt the refresh token in my GA workflow.

  4. I also ran into an issue in that, unlike post_tweet(), tweet_post() doesn't make it obvious how to tweet in reply to another tweet. Reading the twitter API docs I found I could do this using, e.g. tweet_post("text", reply = list(in_reply_to_tweet_id = 12345)).

Anyone interested in seeing the full approach can check out the source code at wurli/tidyverse-dev-bot. In particular, see the source for tidyverse_dev_bot_twitter_token().

llrs commented

After a bit of pain, I've now got my bot back up and working again. I strongly suspect I've not strictly followed best practice, but the bot seems to be working and my credentials are all secure.

For anyone facing the same predicament, here's what I needed to do:

1. Update my app in line with the latest [rtweet documentation](https://docs.ropensci.org/rtweet/articles/auth.html#oauth2-protocol). In particular, this meant changing the type of my app from "_Web App, Automated App or Bot_" to "_Native App_", and updating the callback URI to [http://127.0.0.1:1410](http://127.0.0.1:1410/).

I'm glad the documentation helped!

2. As directed above, ditch the `rtweet_bot()` with `post_tweet()` approach in favour of `rtweet_client()`/`rtweet_oauth2()` with `tweet_post()`. As an aside, I find the naming of `post_tweet()` vs `tweet_post()` quite distressing - it felt like a real gotcha when I realised there were two functions, neither of which reference the other in their documentation. I'd suggest updating the docs to explain the difference, and perhaps renaming `tweet_post()` to `post_tweet2()` so intellisense can tell folks there's a new approach.

Yes, I will ditch post_tweet() but for some users it might still work.
I'll update the docs and in the warning I'll provide the new replacement function.

3. This posed a challenge in that `rtweet_oauth2()` returns a token which expires within 2 hours, after which time a user must manually confirm in the browser that they're happy for rtweet to continue to use their account. My project is an automated bot running hourly using GitHub actions, so this wasn't an option. Here's the workaround I used:

This workaround exists only because before httr2 1.0.0 there was no way for httr to handle this scenario. I had to roll my own solution. Which stayed mostly on Github until I released the 1.2.1 on the 2023-10-16 after a prompt from CRAN.

   * After some digging, I found that `rtweet_oauth2()` returns an object with a _refresh_token_ which can be used to keep the authorisation active without needing to confirm in a browser.
   * The issue is that, this refresh token can only be used once, extending authorisation for 2 more hours. When it's used, the request returns a new refresh token which can be used in the same way, and at the same time invalidates the original token. httr2 errors in such cases to prevent this sort of usage, so I had to use an internal function `httr2:::token_refresh()` to bypass the error (NB httr2 1.0 [merely warns](https://httr2.r-lib.org/reference/req_oauth_refresh.html?q=oauth_flow_refre) in such cases, but at the time of writing, rtweet doesn't seem to work with versions later than 0.2.2).

I made compulsory to use 0.2.3 because rtweet depends on a fix on that version in order to manage the token.

   * The refresh token is obviously sensitive, so to avoid exposing it in my source code, I used `httr2::secret_read_rds()` and `httr2::secret_write_rds()` to keep it saved in its own file, which could then be continuously updated as and when the refresh token is used and regenerated (my bot runs on GitHub actions and saves changes to files by simply committing them to the repo). This approach allowed me add an encryption key as a secret environmental variable to encrypt/decrypt the refresh token in my GA workflow.

4. I also ran into an issue in that, unlike `post_tweet()`, `tweet_post()` doesn't make it obvious how to tweet in reply to another tweet. Reading the [twitter API docs](https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets) I found I could do this using, e.g. `tweet_post("text", reply = list(in_reply_to_tweet_id = 12345))`.

Yes, in the functions using the API v2 all documentations link to the API docs so users can make an informed decision. If people use rtweet I might write more example or ease some steps (like #778) but at this moment, my motivation is low.

Anyone interested in seeing the full approach can check out the source code at wurli/tidyverse-dev-bot. In particular, see the source for tidyverse_dev_bot_twitter_token().

Thanks for adding the link.

As a final note as now httr2 1.0.0 has been released, I might update again rtweet to make it easier to work with the refresh token and deprecate all the old functions (there is also an error in CRAN related to old token in rtweet < 1.0.0).

Hi all. I just tried the changes recommended above but it did not authenticate so I had to abort it and I got the following error. Perhaps they have changed something in the API again?

client_id <- 'acbdefg'
client_secret <- 'ghijklm'

client <- rtweet::rtweet_client(client_id = client_id,
                                app = 'myapp',
                                client_secret = client_secret)

user_oauth2 <- rtweet::rtweet_oauth2(client = client)

Warning messages:
1: The host_name argument of oauth_flow_auth_code() is deprecated as
of httr2 1.0.0.
ℹ Please use the redirect_uri argument instead.
ℹ The deprecated feature was likely used in the httr2 package.
Please report the issue at https://github.com/r-lib/httr2/issues.
This warning is displayed once every 8 hours.
Call lifecycle::last_lifecycle_warnings() to see where this warning
was generated.

llrs commented

@rafapereirabr This is a warning, not an error, and comes from changes in httr2 1.0.0. Do you have the error message?

you're right, @llrs. The function only throws a warning message. The error occurs on the Twitter page. See image below. That's why I asked if perhaps they changed somehting about their api again.

Screenshot 2024-02-12 200655

Could you share the output of auth_sitrep(), the version of rtweet you are using and the specific code (and output) used until you get to this screen? Thanks

Hi @llrs . When I run auth_sitrep(), there is no output. I'm using {rtweet} v1.2.1, and here is the code I'm using:


library(rtweet)

###### 0. Authenticate Tweeter API --------------------------------

client_id <- 'blablabla'
client_secret <- 'blablabla'

client <- rtweet::rtweet_client(client_id = client_id,
                                app = 'cadacantodobrasil',
                                client_secret = client_secret)
# Authenticate
user_oauth2 <- rtweet::rtweet_oauth2(client = client)

auth_as(user_oauth2)
# auth_save(user_oauth2, "oauth2_authentication")


auth_sitrep()

Ok, you don't seem to have any credential stored not from rtweet < 1.0 or from rtweet > 1.0. The rtweet_oauth2 will be deprecated in the next version, because I won't maintain a custom system to handle authorization for the API and will be directly handled by httr2.

You will need to first save the client client_save(client) just once! and use it in all the other sessions with client_as("cadacantodobrasil"). If the requests to the API v2 (aka tweet_post fail, then there might still be a problem of rtweet: please open a new issue with all the information.

Hi @llrs. thanks so much for the work you've put updating the package! Sorry for my slow response here. I've now updated to the new stable version of rtweet on CRAN v2.0.0 but I still had no success. Here's a bit of context before sharing my code below.

Context: I'm using rtweet to automate on a bot that tweets a satellite image of a Brazilian census tract every 30 minutes. This is the bot. The bot is automated using github actions and all packges are managed using {renv}. The bot used to work well but it was broken after Twitter changed the authentication process, and now I'm trying to put it live back again. As a start, I'm not trying to automate the tweets yet. I'm simply trying to manually authentucate and post a tweet from within R, but I still have no success.

Attempt 1

First I tried this from a fresh R session:

library(renv)
library(rtweet)

# set up your client app
client <- rtweet::rtweet_client(app = "cadacantodobrasil")
#> I manually insert client id and secret on the pop-up window

# save client (JUST ONCE)
#client_save(client)
#> Saving client to 'C:\Users\user\AppData\Roaming/R/config/R/rtweet/clients/.rds'

client_as(client)
#> Using client cadacantodobrasil

# this generates no output
auth_sitrep()

# post tweet
tweet_post(paste0("test ", Sys.time()))

Error in httr2::req_perform():
! HTTP 403 Forbidden.
Run rlang::last_trace() to see where the error occurred.

Attempt 2

Next, I tried this from a fresh R session:

library(renv)
library(rtweet)

client_as('cadacantodobrasil')

Error in load_client():
! Can't find saved client with name 'cadacantodobrasil'
Run rlang::last_trace() to see where the error occurred.

Attempt 3

Finally, I tried this from a fresh R session:

app <- rtweet_app(bearer_token = my_bearer_token )
auth_as(app)

# post tweet
tweet_post(paste0("test ", Sys.time()))

Error in httr2::req_perform():
! HTTP 403 Forbidden.
Run rlang::last_trace() to see where the error occurred.

Thanks for your guidance here!

and here's my

> sessionInfo()
R version 4.3.1 (2023-06-16 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 11 x64 (build 22621)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/Sao_Paulo
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
[1] rtweet_2.0.0 renv_1.0.5  

loaded via a namespace (and not attached):
 [1] later_1.3.2     R6_2.5.1        httpuv_1.6.15   magrittr_2.0.3 
 [5] rappdirs_0.3.3  glue_1.7.0      lifecycle_1.0.4 promises_1.2.1 
 [9] cli_3.6.2       askpass_1.2.0   openssl_2.1.1   withr_3.0.0    
[13] compiler_4.3.1  tools_4.3.1     curl_5.2.1      Rcpp_1.0.12    
[17] httr2_1.0.1     jsonlite_1.8.8  rlang_1.1.3 
llrs commented

@rafapereirabr Please open a separate issue with the detailed information: I'll need to know the client_list() after each step with the client. It is weird that saving the client via client_save results in a file with no name. There might be a problem there.

Note that this repository will be archived soon to signal that there won't be further updates as I stepped down of maintaining it.
I have emailed CRAN about orphaning the package but unfortunately they haven't marked it on CRAN.
If anyone is willing to maintain it feel free to comment.

Thanks @llrs , I will open a separate issue. I'm sorry to hear you'll be stepping down as maintainer of {rtweet}, it's great package. Nonetheless, I understand priorities changes, and maitaining a package can be very demanding, specially with a very popular package. I hope the R community and rOpenSci will find someone to continue your work.