I want to update my Twitter banner profle (the header image) using the Twitter API v1.1 from within R. I don’t care how it’s done, it can be a function from an existing R package (the ones I know of, do not have this kind of functionality) or it can be a call to system2("curl", args = args_strng)
or any other approach - as long as it is done in R.
Below I will explain in detail what approaches I have tried so far.
Approach 1: Using {rtweet} (v0.7.0)
{rtweet} is the most popular R package used to interact with Twitter API v1.1. It comes with a lot of functions, but updating the banner profile is not supported yet.
The idea was create a function building on {rtweet} that allows linking a file path to an image of appropriate size (1500x500 px) to upload it as new Twitter banner.
update_profile_banner <- function (banner_file, token = NULL) {
token <- rtweet:::check_token(token)
banner_uri <- base64enc::base64encode(banner_file)
query <- "account/update_profile_banner"
# build params
params <- list(banner = banner_uri)
# make URL
url <- rtweet:::make_url(query = query, param = params)
# send
r <- rtweet:::TWIT(get = FALSE, url, token)
if (!r$status_code %in% c(200,201,202)) {
return(httr::content(r))
}
message("your profile banner image has been updated!")
}
# Now all we need is a new banner image (see `test_png` below) and a valid Twitter token:
library(rtweet) # v.0.7.0
library(httr) # v.1.4.2
# Create a token containing Twitter keys
mytoken <- rtweet::create_token(
app = "the name of the Twitter app",
consumer_key = Sys.getenv("MY_CONSUMER_API_KEY"),
consumer_secret = Sys.getenv("MY_CONSUMER_API_SECRET"),
access_token = Sys.getenv("MY_ACCESS_TOKEN"),
access_secret = Sys.getenv("MY_ACCESS_TOKEN_SECRET"),
set_renv = FALSE
)
test_png <- "https://raw.githubusercontent.com/TimTeaFan/dynamicTwitterHeader/main/data/test.png"
update_profile_banner(test_png, mytoken)
#> Error in curl::curl_fetch_memory(url, handle = handle): HTTP/2 stream 0 was not closed cleanly: INTERNAL_ERROR (err 2)
The error message isn’t very helpful but looking it up and changing HTTP version 2 to 1.1 throws a more meaningful message telling us that the header is too long.
I suspect that this is due to the fact that under the hood {httr} calls the following curl
command:
POST https://api.twitter.com/1.1/account/update_profile_banner.json?banner=super_long_image_string_goes_here+oauth_credentials_come_last
It's just a simple POST
command not differentiating between the header and the body, which (my guess) makes the header too long due to the image string.
To check whether this assumption is true (or at least not wrong), and to exclude the possibility that the approach itself is faulty, I created a similar function to get a banner image:
get_profile_banner <- function (screen_name, token) {
token <- rtweet:::check_token(token)
query <- "users/profile_banner"
# build params
params <- list(screen_name = screen_name)
# make URL
url <- rtweet:::make_url(query = query, param = params)
r <- rtweet:::TWIT(get = TRUE, url, token)
if (!r$status_code %in% c(200,201,202)) {
return(httr::content(r))
}
rtweet:::from_js(r)
}
get_profile_banner("timteafan", mytoken)
#> $sizes
#> $sizes$ipad
#> $sizes$ipad$h
#> [1] 313
#>
#> $sizes$ipad$w
#> [1] 626
#>
#> $sizes$ipad$url
#> [1] "https://pbs.twimg.com/profile_banners/188839854/1653511325/ipad"
#>
#> ...
#> [truncated]
Since get_profile_banner()
is working fine, I suspect that the problem is indeed due to the long base64 encoded image string we are sending to the Twitter API in a simple call to curl POST
.
Approach 2: Using system2("curl")
So a possible workaround might be to just call curl
directly from R using system2()
by building a more sophisticated call separating the header containing the authorization from the body containing the image string.
The only problem here is that we’d have to create our own signature. I tried this, but failed, so I decided to use {httr} to create the signature and all oauth credentials and then use those as header.
Let’s first start with get_banner_profile()
to see whether this approach works.
For this we need some functions and helper functions:
This is the main function
get_banner <- function(screen_name, token) {
query <- "users/profile_banner"
url <- rtweet:::make_url(query = query,
param = list(screen_name = screen_name))
# lets create the oauth credentials
oauth <- oauth_GET(url, token)
oauth <- oauth[[which(names(oauth) == "Authorization")]]
# this is the initial curl command
req <- "--get 'https://api.twitter.com/1.1/users/profile_banner.json?'"
# this is the data
data <- paste0("--data 'screen_name=", screen_name, "'")
# and here goes the header
header <- paste0("--header 'Authorization:", oauth, "'")
# lets collapse everything into one string
args <- paste(req, data, header, collapse = " ")
system2("curl", args = args)
}
Some helper functions:
oauth_GET <- function (url = NULL, config = list(), ..., handle = NULL) {
hu <- httr:::handle_url(handle, url, ...)
req <- httr:::request_build("GET", hu$url, as.token.request(config), ...)
oauth_req(req, hu$handle$handle)
}
oauth_req <- function (req, handle) {
stopifnot(httr:::is.request(req), inherits(handle, "curl_handle"))
req <- httr:::request_prepare(req)
req$headers
}
# httr methods
as.token.request <- function(x) auth_req(auth_token = x)
auth_req <- function (auth_token) {
structure(list(auth_token = auth_token), class = "request")
}
library(httr)
get_banner("timteafan", mytoken)
#> {"sizes":{"ipad":{"h":313,"w":626,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/ipad"},"ipad_retina":{"h":626,"w":1252,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/ipad_retina"},"web":{"h":260,"w":520,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/web"},"web_retina":{"h":520,"w":1040,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/web_retina"},"mobile":{"h":160,"w":320,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/mobile"},"mobile_retina":{"h":320,"w":640,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/mobile_retina"},"300x100":{"h":100,"w":300,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/300x100"},"600x200":{"h":200,"w":600,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/600x200"},"1500x500":{"h":500,"w":1500,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/1500x500"},"1080x360":{"h":360,"w":1080,"url":"https:\/\/pbs.twimg.com\/profile_banners\/188839854\/1653511325\/1080x360"}}}
Since this is working, lets try the same with update_banner()
:
upload_banner <- function(banner_file, token) {
query <- "account/update_profile_banner"
banner_uri <- base64enc::base64encode(banner_file)
url <- rtweet:::make_url(query = query,
param = list(banner = banner_uri))
oauth <- oauth_POST(url, token)
oauth <- oauth[names(oauth) == "Authorization"]
req <- "--request POST 'https://api.twitter.com/1.1/account/update_profile_banner.json?'"
header <- paste0("--header 'Authorization: ", oauth, "'")
header2 <- paste0("--header 'Content-Type: application/json'")
header3 <- paste0("--header 'Accept: application/json, text/xml, application/xml, */*'")
data <- paste0('--data "banner=', banner_uri,'"')
myargs <- paste(req, header2, header3, header, data, collapse = " ")
system2("curl", args = myargs)
}
oauth_POST <- function (url = NULL, config = list(), ...,
body = NULL, encode = c("multipart", "form", "json", "raw"),
handle = NULL) {
encode <- match.arg(encode)
hu <- httr:::handle_url(handle, url, ...)
req <- httr:::request_build("POST", hu$url, httr:::body_config(body, match.arg(encode)),
auth_req(config), ...)
oauth_req(req, hu$handle$handle)
}
upload_banner(test_png, mytoken)
#> % Total % Received % Xferd Average Speed Time Time Time Current
#> Dload Upload Total Spent Left Speed
#> 100 16279 100 64 100 16215 97 24728 --:--:-- --:--:-- --:--:-- 25083
#> {"errors":[{"message":"Could not authenticate you","code":32}]}
Created on 2022-06-16 by the reprex package (v0.3.0)
Since this is failing, I suspect that the problem stems from the authorization via {httr} and my guess is that the long image string is the cause.
What I have not tried yet:
The development version of {rtweet}: From what I've seen on Github the underyling calls to {httr} haven't changed much, which is why I expect the problem to persist.
The {httr2} package: I think I read somewhere that it doesn't work with OAUTH 1.0
Other R packages such as using only {curl} or {Rcurl} or any other package that works with the Twitter API in R.
Is this endpoint still working?
As @llrs points out the user endpoints are on the migration map and will be available in Twitter's API 2.0 some time down the road. However, the endpoints are still open under API 1.1, we can use the folllowing code to update the profile banner in python:
import os
import tweepy
# Get environment variables
CONKEY = os.getenv('MYTWITTER_CONSUMER_API_KEY')
CONSEC = os.environ.get('MYTWITTER_CONSUMER_API_SECRET')
ACCKEY = os.getenv('MYTWITTER_ACCESS_TOKEN')
ACCSEC = os.environ.get('MYTWITTER_ACCESS_TOKEN_SECRET')
auth = tweepy.OAuth1UserHandler(
CONKEY,
CONSEC,
ACCKEY,
ACCSEC
)
api = tweepy.API(auth)
image_url = 'https://raw.githubusercontent.com/TimTeaFan/dynamicTwitterHeader/main/data/test.png'
api.update_profile_banner(image_url)