├── .github └── workflows │ ├── deploy-shinyapps.yml │ └── update-data.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── deploy.R ├── likes.rds ├── make_data.R ├── server.R ├── shinytweet.Rproj ├── ui.R └── update_data.R /.github/workflows/deploy-shinyapps.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Run on push master, main 3 | 4 | # Controls when the action will run. 5 | on: 6 | # Triggers the workflow on push or pull request events but only for the main branch 7 | push: 8 | branches: [ main, master ] 9 | 10 | workflow_run: 11 | workflows: ["Update data"] 12 | types: 13 | - completed 14 | 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | 18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 19 | jobs: 20 | # This workflow contains a single job called "build" 21 | build: 22 | # The type of runner that the job will run on 23 | runs-on: Ubuntu-20.04 24 | 25 | # Steps represent a sequence of tasks that will be executed as part of the job 26 | steps: 27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 28 | - uses: actions/checkout@v2 29 | 30 | # build the docker image and give it the name main 31 | - name: Build image 32 | run: docker build -t main . 33 | # run the docker image supply the secrets from the github secrets store. 34 | - name: execute 35 | run: > 36 | docker run 37 | -e SHINY_ACC_NAME=${{ secrets.SHINY_ACC_NAME }} 38 | -e TOKEN=${{secrets.TOKEN}} 39 | -e SECRET=${{secrets.SECRET}} 40 | main 41 | -------------------------------------------------------------------------------- /.github/workflows/update-data.yml: -------------------------------------------------------------------------------- 1 | name: Update data 2 | 3 | on: 4 | # schedule: 5 | # - cron: "0 8 * * *" 6 | # Allows you to run this workflow manually from the Actions tab 7 | workflow_dispatch: 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | update: 12 | runs-on: ubuntu-latest 13 | env: 14 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - uses: r-lib/actions/setup-r@v2 19 | with: 20 | use-public-rspm: true 21 | 22 | - name: Install dependencies 23 | run: | 24 | install.packages(c("rtweet", "dplyr", "tibble", "stringr", "lubridate", "readr")) 25 | shell: Rscript {0} 26 | 27 | - name: Update data 28 | run: | 29 | source("update_data.R") 30 | shell: Rscript {0} 31 | env: 32 | BEARER: ${{ secrets.TWITTER_BEARER }} 33 | 34 | - name: Commit files 35 | run: | 36 | git config --local user.email "actions@github.com" 37 | git config --local user.name "GitHub Actions" 38 | git add --all 39 | git commit -am "add data" 40 | git push -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .Renviron 6 | rsconnect/ 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rocker/shiny:4.2.1 2 | RUN install2.r rsconnect tibble dplyr stringr rtweet htmltools lubridate bslib reactable 3 | WORKDIR /home/shinytweet 4 | COPY ui.R ui.R 5 | COPY server.R server.R 6 | COPY likes.rds likes.rds 7 | COPY deploy.R deploy.R 8 | CMD Rscript deploy.R -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nicola Rennie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shinytweet 2 | 3 | Dashboard to explore my recently liked tweets, and give links to resources. 4 | -------------------------------------------------------------------------------- /deploy.R: -------------------------------------------------------------------------------- 1 | library(rsconnect) 2 | 3 | # a function to stop the script when one of the variables cannot be found 4 | # and to strip quotation marks from the secrets when you supplied them 5 | error_on_missing_name <- function(name) { 6 | var <- Sys.getenv(name, unset = NA) 7 | if(is.na(var)) { 8 | stop(paste0("cannot find ", name, " !"), call. = FALSE) 9 | } 10 | gsub("\"", "", var) 11 | } 12 | 13 | # Authenticate 14 | setAccountInfo(name = error_on_missing_name("SHINY_ACC_NAME"), 15 | token = error_on_missing_name("TOKEN"), 16 | secret = error_on_missing_name("SECRET")) 17 | 18 | # Deploy the application. 19 | deployApp(appFiles = c("ui.R", "server.R", "likes.rds")) -------------------------------------------------------------------------------- /likes.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/shinytweet/b0189aec743609ede744e57bf2e321d22bcc3830/likes.rds -------------------------------------------------------------------------------- /make_data.R: -------------------------------------------------------------------------------- 1 | library(rtweet) 2 | library(dplyr) 3 | library(stringr) 4 | library(tibble) 5 | 6 | # function to grab original data from twitter 7 | 8 | start_data <- function(n = 3000) { 9 | # define parameters 10 | username <- "nrennie35" 11 | words_to_keep <- c("rstats", " r ", "rstudio", "tidyverse", 12 | "python", "shiny", "ggplot2", 13 | "tableau", "rladies", "dataviz", "vizualisation", 14 | "visualisation") 15 | 16 | # get likes 17 | likes <- get_favorites(username, n = n) 18 | 19 | # get users data 20 | users <- users_data(likes) 21 | likes$user <- users$screen_name 22 | likes$protected <- users$protected 23 | likes <- as_tibble(likes) 24 | 25 | # get urls 26 | entity <- likes$entities 27 | urls <- lapply(seq_len(length(entity)), function(x) entity[[x]]$urls$expanded_url[1]) 28 | likes$content_url <- urls 29 | 30 | # data wrangling to filter likes 31 | likes <- likes %>% 32 | filter(protected == FALSE) %>% 33 | mutate(lower_text = tolower(full_text)) %>% 34 | filter(str_detect(lower_text, str_c(words_to_keep, collapse = "|"))) %>% 35 | filter(!is.na(content_url)) %>% 36 | mutate(url = paste0("", "LINK", "")) %>% 37 | mutate(tweet_link = paste0("https://twitter.com/", user, "/status/", id_str), 38 | user_link = paste0("https://twitter.com/", user)) 39 | 40 | # more data wrangling for table 41 | likes <- likes %>% 42 | select(created_at, user, full_text, tweet_link, content_url) %>% 43 | rename(Date = created_at, 44 | User = user, 45 | Tweet = full_text, 46 | URL = tweet_link, 47 | Link = content_url) %>% 48 | mutate(Date = rtweet:::format_date(Date), 49 | Date = as.Date(Date)) 50 | 51 | # save file 52 | readr::write_rds(likes, 'likes.rds') 53 | } 54 | -------------------------------------------------------------------------------- /server.R: -------------------------------------------------------------------------------- 1 | # read in data 2 | likes <- readRDS('likes.rds') 3 | 4 | # Define server logic ---- 5 | server <- function(input, output) { 6 | 7 | output$table_output = reactable::renderReactable({ 8 | 9 | # create table 10 | reactable::reactable(likes, 11 | columns = list( 12 | # define date 13 | Date = colDef( 14 | align = "center", 15 | minWidth = 60), 16 | # define user 17 | User = colDef( 18 | cell = function(User) { 19 | htmltools::tags$a(href = paste0("https://twitter.com/", as.character(User)), 20 | target = "_blank", paste0("@",User))}, 21 | minWidth = 60), 22 | # define tweet text 23 | Tweet = colDef( 24 | align = "left", 25 | minWidth = 120), 26 | # define tweet url 27 | URL = colDef( 28 | cell = function(URL) { 29 | htmltools::tags$a(href = as.character(URL), 30 | target = "_blank", as.character(URL))}), 31 | # define external link 32 | Link = colDef( 33 | cell = function(Link) { 34 | htmltools::tags$a(href = as.character(Link), 35 | target = "_blank", as.character(Link))}) 36 | ), 37 | # additional table options 38 | searchable = TRUE, 39 | striped = TRUE, 40 | defaultPageSize = 8) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /shinytweet.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /ui.R: -------------------------------------------------------------------------------- 1 | # load packages for shiny app 2 | library(shiny) 3 | library(dplyr) 4 | library(reactable) 5 | library(bslib) 6 | library(htmltools) 7 | library(lubridate) 8 | 9 | # Define UI ---- 10 | ui <- navbarPage( 11 | 12 | # define title 13 | title = "{shinytweet}", 14 | 15 | # add theme 16 | theme = bs_theme(version = 4, 17 | bootswatch = "minty", 18 | primary = "#12a79d"), 19 | 20 | # add in table 21 | tabPanel("Favourite Tweets", reactable::reactableOutput("table_output")) 22 | 23 | ) -------------------------------------------------------------------------------- /update_data.R: -------------------------------------------------------------------------------- 1 | library(rtweet) 2 | library(dplyr) 3 | library(tibble) 4 | library(stringr) 5 | library(readr) 6 | 7 | # function to grab new data 8 | new_data <- function(n = 30) { 9 | # define parameters 10 | username <- "nrennie35" 11 | words_to_keep <- c("rstats", " r ", "rstudio", "tidyverse", 12 | "python", "shiny", "ggplot2", 13 | "tableau", "rladies", "dataviz", 14 | "data science", 15 | "visualization", 16 | "quarto", "visualisation") 17 | url_pattern <- "http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" 18 | 19 | # get likes data from twitter 20 | likes <- get_favorites(username, n = n) 21 | 22 | # add user data 23 | users <- users_data(likes) 24 | likes$user <- users$screen_name 25 | likes$protected <- users$protected 26 | likes <- as_tibble(likes) 27 | 28 | # get urls 29 | entity <- likes$entities 30 | urls <- lapply(seq_len(length(entity)), function(x) entity[[x]]$urls$expanded_url[1]) 31 | likes$content_url <- urls 32 | 33 | # data wrangling to filter likes 34 | likes <- likes %>% 35 | filter(protected == FALSE) %>% 36 | mutate(lower_text = tolower(full_text)) %>% 37 | filter(str_detect(lower_text, str_c(words_to_keep, collapse = "|"))) %>% 38 | filter(!is.na(content_url)) %>% 39 | mutate(url = paste0("", "LINK", "")) %>% 40 | mutate(tweet_link = paste0("https://twitter.com/", user, "/status/", id_str), 41 | user_link = paste0("https://twitter.com/", user)) 42 | 43 | # more data wrangling for table 44 | likes <- likes %>% 45 | select(created_at, user, full_text, tweet_link, content_url) %>% 46 | rename(Date = created_at, 47 | User = user, 48 | Tweet = full_text, 49 | URL = tweet_link, 50 | Link = content_url) %>% 51 | mutate(Date = rtweet:::format_date(Date), 52 | Date = as.Date(Date)) 53 | 54 | # return data 55 | return(likes) 56 | } 57 | 58 | # authenticate 59 | my_app <- rtweet_app(bearer_token = Sys.getenv("BEARER", unset = NA)) 60 | auth_as(my_app) 61 | 62 | # function to update data set 63 | update_data <- function(since_id) { 64 | new_data_df <- new_data() 65 | likes <- readr::read_rds('likes.rds') 66 | likes <- rbind(new_data_df, likes) 67 | likes <- distinct(likes) 68 | readr::write_rds(likes, 'likes.rds') 69 | } 70 | 71 | # run update 72 | update_data() 73 | --------------------------------------------------------------------------------