From 997b3dacbf7fe494acc7b6cd4bc5b83a2b638f9d Mon Sep 17 00:00:00 2001 From: boB Rudis Date: Mon, 1 Jan 2018 07:58:57 -0500 Subject: [PATCH] tweet shot + other cleanup --- DESCRIPTION | 6 ++- NAMESPACE | 1 + R/get-my-timeline.R | 7 +--- R/next_cursor.R | 1 + R/tweet_shot.R | 84 ++++++++++++++++++++++++++++++++++++++++++ R/utils.R | 13 +++++++ man/get_my_timeline.Rd | 8 +--- man/next_cursor.Rd | 2 + man/tweet_shot.Rd | 39 ++++++++++++++++++++ 9 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 R/tweet_shot.R create mode 100644 man/tweet_shot.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 4c486bd3..d1e3683d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,7 +26,11 @@ Suggests: ggplot2, knitr, rmarkdown, - testthat + testthat, + glue, + magick, + webshot, + scales VignetteBuilder: knitr LazyData: yes RoxygenNote: 6.0.1.9000 diff --git a/NAMESPACE b/NAMESPACE index b3c0c2cf..b20d0943 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -85,6 +85,7 @@ export(suggested_users) export(trends_available) export(ts_data) export(ts_plot) +export(tweet_shot) export(tweets_data) export(tweets_with_users) export(uq_rtweet) diff --git a/R/get-my-timeline.R b/R/get-my-timeline.R index 3043f570..027abba7 100644 --- a/R/get-my-timeline.R +++ b/R/get-my-timeline.R @@ -11,14 +11,9 @@ #' Must be of length 1 or equal to length of user. #' @param max_id Character, status_id from which returned tweets #' should be older than. -#' @param home Logical, indicating whether to return a user-timeline -#' or home-timeline. By default, home is set to FALSE, which means -#' \code{get_timeline} returns tweets posted by the given user. To -#' return a user's home timeline feed, that is, the tweets posted by -#' accounts followed by a user, set the home to false. #' @param parse Logical, indicating whether to return parsed #' (data.frames) or nested list object. By default, \code{parse = -#' TRUE} saves users from the time [and frustrations] associated +#' TRUE} saves users from the time (and frustrations) associated #' with disentangling the Twitter API return objects. #' @param check Logical indicating whether to remove check available #' rate limit. Ensures the request does not exceed the maximum diff --git a/R/next_cursor.R b/R/next_cursor.R index 2afa49ca..53044109 100644 --- a/R/next_cursor.R +++ b/R/next_cursor.R @@ -134,6 +134,7 @@ get_max_id <- function(x) { #' @rdname next_cursor +#' @param .x id #' @export max_id <- function(.x) UseMethod("max_id") diff --git a/R/tweet_shot.R b/R/tweet_shot.R new file mode 100644 index 00000000..96b0d133 --- /dev/null +++ b/R/tweet_shot.R @@ -0,0 +1,84 @@ +#' Capture an image of a tweet/thread +#' +#' Provide a status id or a full Twitter link to a tweet and this function +#' will capture an image of the tweet --- or tweet + thread (if there are +#' Twitter-linked replies) --- from the mobile version of said tweet/thread. +#' +#' For this to work, you will need to ensure the packages in `Suggests:` are +#' installed as they will be loaded upon the first invocation of this function. +#' +#' Use the `zoom` factor to get more pixels which may improve the text rendering +#' of the tweet/thread. +#' +#' @md +#' @param statusid_or_url a valid Twitter status id (e.g. "`947082036019388416`") or +#' a valid Twitter status URL (e.g. "`https://twitter.com/jhollist/status/947082036019388416`"). +#' @param zoom a positive number >= 1. See the help for `[webshot::webshot()]` for more information. +#' @param scale auto-scale the image back to 1:1? Default it `TRUE`, which means `magick` +#' will be used to return a "normal" sized tweet. Set it to `FALSE` to perform your +#' own image manipulation. +#' @return `magick` object +#' @export +#' @examples \dontrun{ +#' tweet_shot("947082036019388416") +#' tweet_shot("https://twitter.com/jhollist/status/947082036019388416") +#' } +tweet_shot <- function(statusid_or_url, zoom=3, scale=TRUE) { + + statusid_or_url <- statusid_or_url[1] + zoom <- zoom[1] + scale <- scale[1] + + if (zoom <= 1) stop("zoom must be a positive number, >= 1", call.=FALSE) + if (!is.logical(scale)) stop("scale must be TRUE/FALSE", call.=FALSE) + + # can we do it? + try_require("glue", "glue_data") + try_require("magick", "image_read") + try_require("webshot", "webshot") + try_require("scales", "percent") + + # yes we can! (if the function got to this point) + + x <- statusid_or_url + + # first test if we have a Twitter URL + is_url <- grepl("^http[s]://", x) + + if (is_url) { # mebbe, let's look further + + is_twitter <- grepl("twitter", x) # shld have "twitter" in it + if (!is_twitter) stop("statusid_or_url must be a valid Twitter status id or URL", call.=FALSE) + + is_status <- grepl("status", x) # shld also have "status" in it + if (!is_status) stop("statusid_or_url must be a valid Twitter status id or URL", call.=FALSE) + + already_mobile <- grepl("://mobile\\.", x) # if it's not a mobile status, make it one + if (!already_mobile) x <- sub("://twi", "://mobile.twi", x) + + } else { # let's see if it's a status id + + x <- rtweet::lookup_tweets(x) + if (!(nrow(x) > 0)) stop("Twitter status not found", call.=FALSE) # nope + + # make a mobile URL + x <- glue::glue_data(x, "https://mobile.twitter.com/{screen_name}/status/{status_id}") + + } + + # keep the filesystem clean + tf <- tempfile(fileext = ".png") + on.exit(unlink(tf), add=TRUE) # it'll clean up for us + + # capture the tweet + webshot::webshot(url=x, file=tf, zoom=zoom) + + img <- magick::image_read(tf) # read the image in + img <- magick::image_trim(img) # remove the extraneous border + + # scale if we want to + if ((zoom > 1) && (scale)) img <- magick::image_scale(img, scales::percent(1/zoom)) + + img + +} diff --git a/R/utils.R b/R/utils.R index 702786d8..254dec5f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -563,3 +563,16 @@ obs2string <- function(x, sep) { x[is.na(x)] <- "" paste(x, collapse = sep) } + +# Enables loading packages when necessary vs import + +try_require <- function(pkg, f) { + + if (requireNamespace(pkg, quietly = TRUE)) { + library(pkg, character.only = TRUE) + return(invisible()) + } + + stop("Package `", pkg, "` required for `", f , "`.\n", + "Please install and try again.", call. = FALSE) +} \ No newline at end of file diff --git a/man/get_my_timeline.Rd b/man/get_my_timeline.Rd index 150cf7a4..5220ef30 100644 --- a/man/get_my_timeline.Rd +++ b/man/get_my_timeline.Rd @@ -16,7 +16,7 @@ should be older than.} \item{parse}{Logical, indicating whether to return parsed (data.frames) or nested list object. By default, \code{parse = - TRUE} saves users from the time \link{and frustrations} associated + TRUE} saves users from the time (and frustrations) associated with disentangling the Twitter API return objects.} \item{check}{Logical indicating whether to remove check available @@ -30,12 +30,6 @@ variable in the tokens vignette (in r, send \code{?tokens} to console).} \item{...}{Further arguments passed on as parameters in API query.} - -\item{home}{Logical, indicating whether to return a user-timeline -or home-timeline. By default, home is set to FALSE, which means -\code{get_timeline} returns tweets posted by the given user. To -return a user's home timeline feed, that is, the tweets posted by -accounts followed by a user, set the home to false.} } \value{ A tbl data frame of tweets data with users data attribute. diff --git a/man/next_cursor.Rd b/man/next_cursor.Rd index b357bfaf..35319c32 100644 --- a/man/next_cursor.Rd +++ b/man/next_cursor.Rd @@ -11,6 +11,8 @@ max_id(.x) } \arguments{ \item{x}{Data object returned by Twitter API.} + +\item{.x}{id} } \value{ Character string of next cursor value used to retrieved diff --git a/man/tweet_shot.Rd b/man/tweet_shot.Rd new file mode 100644 index 00000000..a5662d5c --- /dev/null +++ b/man/tweet_shot.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tweet_shot.R +\name{tweet_shot} +\alias{tweet_shot} +\title{Capture an image of a tweet/thread} +\usage{ +tweet_shot(statusid_or_url, zoom = 3, scale = TRUE) +} +\arguments{ +\item{statusid_or_url}{a valid Twitter status id (e.g. "\code{947082036019388416}") or +a valid Twitter status URL (e.g. "\code{https://twitter.com/jhollist/status/947082036019388416}").} + +\item{zoom}{a positive number >= 1. See the help for \code{[webshot::webshot()]} for more information.} + +\item{scale}{auto-scale the image back to 1:1? Default it \code{TRUE}, which means \code{magick} +will be used to return a "normal" sized tweet. Set it to \code{FALSE} to perform your +own image manipulation.} +} +\value{ +\code{magick} object +} +\description{ +Provide a status id or a full Twitter link to a tweet and this function +will capture an image of the tweet --- or tweet + thread (if there are +Twitter-linked replies) --- from the mobile version of said tweet/thread. +} +\details{ +For this to work, you will need to ensure the packages in \code{Suggests:} are +installed as they will be loaded upon the first invocation of this function. + +Use the \code{zoom} factor to get more pixels which may improve the text rendering +of the tweet/thread. +} +\examples{ +\dontrun{ +tweet_shot("947082036019388416") +tweet_shot("https://twitter.com/jhollist/status/947082036019388416") +} +}