├── .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 |
--------------------------------------------------------------------------------