├── _config.yml ├── .gitignore ├── .Rbuildignore ├── tests ├── testthat.R └── testthat │ └── test_feed.R ├── man ├── figures │ ├── fullofstars.gif │ ├── githubstars.png │ ├── shinydashboard.png │ └── starsinhereyes_banner.jpg ├── pipe.Rd ├── extract_feed.Rd ├── obtain_feed.Rd └── starryeyes.Rd ├── .travis.yml ├── NAMESPACE ├── starryeyes.Rproj ├── DESCRIPTION ├── R ├── process.R ├── dashboard.R └── connect.R └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(starryeyes) 3 | 4 | test_check("starryeyes") 5 | -------------------------------------------------------------------------------- /man/figures/fullofstars.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonocarroll/starryeyes/HEAD/man/figures/fullofstars.gif -------------------------------------------------------------------------------- /man/figures/githubstars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonocarroll/starryeyes/HEAD/man/figures/githubstars.png -------------------------------------------------------------------------------- /man/figures/shinydashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonocarroll/starryeyes/HEAD/man/figures/shinydashboard.png -------------------------------------------------------------------------------- /man/figures/starsinhereyes_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonocarroll/starryeyes/HEAD/man/figures/starsinhereyes_banner.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | sudo: false 5 | cache: packages 6 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(extract_feed) 5 | export(obtain_feed) 6 | export(starryeyes) 7 | importFrom(magrittr,"%>%") 8 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/process.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \description{ 10 | Pipe operator 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/extract_feed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/process.R 3 | \name{extract_feed} 4 | \alias{extract_feed} 5 | \title{Extract GitHub RSS feed} 6 | \usage{ 7 | extract_feed(feed) 8 | } 9 | \arguments{ 10 | \item{feed}{a list containing two elements: a username and a RSS atom link} 11 | } 12 | \value{ 13 | a data.frame of the RSS feed contents 14 | } 15 | \description{ 16 | Extract GitHub RSS feed 17 | } 18 | -------------------------------------------------------------------------------- /starryeyes.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 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 | -------------------------------------------------------------------------------- /man/obtain_feed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/connect.R 3 | \name{obtain_feed} 4 | \alias{obtain_feed} 5 | \title{Obtain RSS feed URL} 6 | \usage{ 7 | obtain_feed(username, password) 8 | } 9 | \arguments{ 10 | \item{username}{GitHub username. If missing, will attempt to use GITHUB_PAT to determine it.} 11 | 12 | \item{password}{GitHub password. If missing, will be prompted to enter is securely.} 13 | } 14 | \value{ 15 | a two-element list containing the username and RSS atom feed URL 16 | } 17 | \description{ 18 | Obtain RSS feed URL 19 | } 20 | -------------------------------------------------------------------------------- /man/starryeyes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/dashboard.R 3 | \name{starryeyes} 4 | \alias{starryeyes} 5 | \title{Create a shinydashboard of the recent activity} 6 | \usage{ 7 | starryeyes(username, password, feedURL) 8 | } 9 | \arguments{ 10 | \item{username}{GitHub username. If missing, will attempt to use GITHUB_PAT to determine it.} 11 | 12 | \item{password}{GitHub password. If missing, will be prompted to enter is securely.} 13 | 14 | \item{feedURL}{(optionally) a GitHub RSS feed URL (with token) to use} 15 | } 16 | \value{ 17 | produces a shinydashboard of recent activity. 18 | } 19 | \description{ 20 | Create a shinydashboard of the recent activity 21 | } 22 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: starryeyes 2 | Title: Gather together stars from people you follow 3 | Version: 0.0.0.9001 4 | Authors@R: person("Jonathan", "Carroll", email = "rpkg@jcarroll.com.au", role = c("aut", "cre")) 5 | Description: "Oh my God! — it's full of stars!" Discover new packages by leveraging the stars your followers provide. 6 | Depends: R (>= 3.4.0) 7 | License: GPL (>=2) 8 | Encoding: UTF-8 9 | LazyData: true 10 | Imports: getPass, 11 | httr, 12 | xml2, 13 | magrittr, 14 | rvest, 15 | jsonlite 16 | Suggests: testthat, 17 | shiny, 18 | shinydashboard, 19 | DT 20 | URL: https://github.com/jonocarroll/starryeyes 21 | BugReports: https://github.com/jonocarroll/starryeyes/issues 22 | RoxygenNote: 6.0.1 23 | -------------------------------------------------------------------------------- /tests/testthat/test_feed.R: -------------------------------------------------------------------------------- 1 | context("RSS feed") 2 | 3 | ## store the GITHUB_PAT 4 | old_pat <- Sys.getenv("GITHUB_PAT") 5 | Sys.setenv(GITHUB_PAT = "") 6 | test_that("feed can be returned when user does NOT have a valid GITHUB_PAT set", 7 | expect_error(obtain_feed()) 8 | ) 9 | 10 | ## use the bundled pat to at least retrieve something 11 | bundled <- paste0("b2b7441d", 12 | "aeeb010b", 13 | "1df26f1f6", 14 | "0a7f1ed", 15 | "c485e443") 16 | Sys.setenv(GITHUB_PAT=bundled) 17 | test_that("feed can be returned when a user DOES have a valid GITHUB_PAT set", { 18 | expect_warning(obtain_feed(password = "")) 19 | expect_equivalent(suppressWarnings(is.null(obtain_feed(password = "")[[2]])), TRUE) 20 | }) 21 | 22 | ## return the GITHUB_PAT to normal 23 | Sys.setenv(GITHUB_PAT=old_pat) 24 | -------------------------------------------------------------------------------- /R/process.R: -------------------------------------------------------------------------------- 1 | #' Extract GitHub RSS feed 2 | #' 3 | #' @param feed a list containing two elements: a username and a RSS atom link 4 | #' 5 | #' @return a data.frame of the RSS feed contents 6 | #' @export 7 | extract_feed <- function(feed) { 8 | 9 | entries <- httr::GET(feed) %>% 10 | xml2::read_html() %>% 11 | rvest::html_nodes("entry") 12 | 13 | id <- rvest::html_nodes(entries, "id") %>% 14 | rvest::html_text() %>% 15 | gsub("^.*:", "", x = .) %>% 16 | gsub("Event.*$", "", x = .) 17 | title <- rvest::html_nodes(entries, "title") %>% 18 | rvest::html_text() 19 | user <- sub("^(.*?) .*", "\\1", title) 20 | action <- sub("^(.*?) (.*) .*$", "\\2", title) 21 | target <- sub("^.* (.*?)$", "\\1", title) 22 | published <- rvest::html_nodes(entries, "published") %>% 23 | rvest::html_text() 24 | links <- rvest::html_nodes(entries, "link") %>% 25 | rvest::html_attr("href") 26 | thumb <- rvest::html_nodes(entries, "thumbnail") %>% 27 | rvest::html_attr("url") 28 | 29 | tidyfeed <- data.frame( 30 | thumb, user, action, target, published, id, links, stringsAsFactors = FALSE 31 | ) 32 | 33 | return(tidyfeed) 34 | 35 | } 36 | 37 | #' Pipe operator 38 | #' 39 | #' @name %>% 40 | #' @rdname pipe 41 | #' @keywords internal 42 | #' @export 43 | #' @importFrom magrittr %>% 44 | #' @usage lhs \%>\% rhs 45 | NULL 46 | 47 | globalVariables(".") 48 | -------------------------------------------------------------------------------- /R/dashboard.R: -------------------------------------------------------------------------------- 1 | #' Create a shinydashboard of the recent activity 2 | #' 3 | #' @inheritParams obtain_feed 4 | #' @param feedURL (optionally) a GitHub RSS feed URL (with token) to use 5 | #' @return produces a shinydashboard of recent activity. 6 | #' @export 7 | starryeyes <- function(username, password, feedURL) { 8 | 9 | # loadNamespace("shiny") 10 | # loadNamespace("DT") 11 | # loadNamespace("shinydashboard") 12 | 13 | if (!missing(feedURL)) { 14 | my_feed_list <- list(user = "", 15 | feed = feedURL) 16 | } else { 17 | my_feed_list <- obtain_feed(username, password) 18 | } 19 | my_table <- extract_feed(my_feed_list$feed) 20 | my_table$thumb <- paste0("") 21 | my_table$links <- paste0("", my_table$target, "") 22 | my_table$published <- paste(format(Sys.time() - as.POSIXct(my_table$published, format = "%Y-%m-%dT%TZ"), digits = 3), "ago") 23 | 24 | ui <- shinydashboard::dashboardPage( 25 | shinydashboard::dashboardHeader(title = my_feed_list$user), 26 | shinydashboard::dashboardSidebar(disable = TRUE), 27 | shinydashboard::dashboardBody( 28 | shiny::fluidRow( 29 | shinydashboard::box(DT::dataTableOutput("table1"), width = 12) 30 | ) 31 | ) 32 | ) 33 | 34 | server <- function(input, output) { 35 | output$table1 <- DT::renderDataTable({ 36 | DT::datatable(my_table[ , c("thumb", "user", "action", "target", "published", "links")], 37 | escape = FALSE, 38 | options = list(pageLength = 100)) 39 | }) 40 | } 41 | 42 | shiny::shinyApp(ui, server) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # starryeyes 2 | 3 | [![CRAN](https://www.r-pkg.org/badges/version/starryeyes)]() 4 | [![Travis-CI Build Status](https://travis-ci.org/jonocarroll/starryeyes.svg?branch=master)](https://travis-ci.org/jonocarroll/starryeyes) 5 | 6 | ![](./man/figures/starsinhereyes_banner.jpg) 7 | 8 | Discover new packages through social delegation! 9 | 10 | ## Installation 11 | 12 | You can install starryeyes from github with: 13 | 14 | ``` r 15 | # install.packages("devtools") 16 | devtools::install_github("jonocarroll/starryeyes") 17 | ``` 18 | 19 | ## Motivation 20 | 21 | GitHub doesn't officially have an API endpoint for "_which of the people I follow starred something?_" but I wanted a way to do this. Originally I set up an IFTTT action to watch my private GitHub RSS feed and send me an email when a new star appeared, but this quickly filled my inbox. 22 | 23 | ![](./man/figures/githubstars.png) 24 | 25 | This way, I can check-in when I like. 26 | 27 | 28 | ## Usage: 29 | 30 | 1. Follow some trendsetters on GitHub. 31 | 2. Wait until they star a heap of cool repos 32 | 3. `starryeyes()` to produce a `shinydashboard` of the activity. 33 | 34 | This will work out your GitHub username by itself if you're using a `GITHUB_PAT` environment variable already. If not, you can supply your GitHub `username`. You can enter your `password` or be prompted (securely) for it. You can just provide the `feedURL` if you rather skip all of that (see `github.com` if you're logged in). 35 | 36 | The final product looks something like this: 37 | 38 | ![](./man/figures/shinydashboard.png) 39 | 40 | ## Future work: 41 | 42 | - Cache the results so they accumulate over time 43 | - Remove duplicates (spread them out over a unique starring) 44 | - shinyapps.io (?) 45 | 46 | ![](./man/figures/fullofstars.gif) 47 | 48 | _"Oh my God! — it's full of stars!"_ 49 | -------------------------------------------------------------------------------- /R/connect.R: -------------------------------------------------------------------------------- 1 | #' Obtain RSS feed URL 2 | #' 3 | #' @param username GitHub username. If missing, will attempt to use GITHUB_PAT to determine it. 4 | #' @param password GitHub password. If missing, will be prompted to enter is securely. 5 | #' 6 | #' @return a two-element list containing the username and RSS atom feed URL 7 | #' @export 8 | obtain_feed <- function(username, password) { 9 | 10 | ## see if the user already has a GITHUB_PAT set 11 | ## if so, take the username from that 12 | user <- github_GET("/user") 13 | if (is.null(user$login) && missing(username)) stop("Requires a username") 14 | 15 | ## get the list of user items from api.github.com/feeds 16 | ## ask the user for their password, securely 17 | feeds <- httr::GET("https://api.github.com/feeds", 18 | httr::authenticate(user = user$login, 19 | password = ifelse(missing(password), 20 | getPass::getPass("Please enter your Github password"), 21 | password 22 | ))) 23 | 24 | if (identical(httr::status_code(feeds), 200L)) { 25 | return(list(user = user$login, 26 | feed = httr::content(feeds)$current_user_url) 27 | ) 28 | } else { 29 | message(httr::warn_for_status(feeds)) 30 | return(list(user = NULL, 31 | feed = NULL) 32 | ) 33 | } 34 | } 35 | 36 | 37 | github_auth <- function(token) { 38 | if (is.null(token)) { 39 | NULL 40 | } else { 41 | httr::authenticate(token, "x-oauth-basic", "basic") 42 | } 43 | } 44 | 45 | github_response <- function(req) { 46 | text <- httr::content(req, as = "text") 47 | parsed <- jsonlite::fromJSON(text, simplifyVector = FALSE) 48 | 49 | if (httr::status_code(req) >= 400) { 50 | stop(github_error(req)) 51 | } 52 | 53 | parsed 54 | } 55 | 56 | github_error <- function(req) { 57 | text <- httr::content(req, as = "text", encoding = "UTF-8") 58 | parsed <- tryCatch(jsonlite::fromJSON(text, simplifyVector = FALSE), 59 | error = function(e) { 60 | list(message = text) 61 | }) 62 | errors <- vapply(parsed$errors, `[[`, "message", FUN.VALUE = character(1)) 63 | 64 | structure( 65 | list( 66 | call = sys.call(-1), 67 | message = paste0(parsed$message, " (", httr::status_code(req), ")\n", 68 | if (length(errors) > 0) { 69 | paste("* ", errors, collapse = "\n") 70 | }) 71 | ), class = c("condition", "error", "github_error")) 72 | } 73 | 74 | ## stolen from hadley/devtools/github.R 75 | github_GET <- function(path, ..., pat = github_pat(), 76 | host = "https://api.github.com") { 77 | 78 | url <- httr::parse_url(host) 79 | url$path <- paste(url$path, path, sep = "/") 80 | ## May remove line below at release of httr > 1.1.0 81 | url$path <- gsub("^/", "", url$path) 82 | ## 83 | req <- httr::GET(url, github_auth(pat), ...) 84 | github_response(req) 85 | } 86 | 87 | github_pat <- function(quiet = FALSE) { 88 | pat <- Sys.getenv("GITHUB_PAT") 89 | if (nzchar(pat)) { 90 | if (!quiet) { 91 | message("Using GitHub PAT from envvar GITHUB_PAT") 92 | } 93 | return(pat) 94 | } 95 | message("No GITHUB_PAT environment variable set.\nConsider setting one?\nInstructions: https://itsalocke.com/using-travis-make-sure-use-github-pat/") 96 | return(NULL) 97 | } 98 | 99 | in_ci <- function() { 100 | nzchar(Sys.getenv("CI")) 101 | } 102 | --------------------------------------------------------------------------------