├── _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 | []()
4 | [](https://travis-ci.org/jonocarroll/starryeyes)
5 |
6 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------