The following script uses RSelenium
to post all
athletes’ logged activities to FSurge’s Social Wall.
It can be imported in Windows Task scheduler to automatically upload
activities on specific timepoints (e.g., every 10 minutes; see this R-bloggers
post). This may also be scheduled from R itself (see here).
#clean environment
rm(list=ls())
#garbage collect
gc()
#kill java instances within Rstudio
system("taskkill /im java.exe /f", intern=FALSE, ignore.stdout=FALSE)
#setwd to folder containing script
wd <- "C:/Users/Rob/Documents/GitHub/FinalSurge_experiment"
setwd(wd)
#load packages
#install.packages("RSelenium")
#install.packages("netstat")
library(RSelenium)
library(rvest)
library(tidyverse)
library(netstat)
library(pingr)
#find a free port
port <- netstat::free_port(random=T)
#ping port for confirmation
#pingr::ping_port("localhost", port)
#set up selenium server and browser
#rsDriver() tries to load latest Chrome driveres; so set to NULL!!!
rD <- rsDriver(browser="firefox", port=port, verbose=F, chromever=NULL)
remDr <- rD[["client"]]
#pingr::ping_port("localhost", port) #check
#load login attributes from `secret`-folder, containing attributes required for login:
#email, password,
load("./local/accounts.R") # all pre-set accounts
#only take accounts that are assigned a social wall
df <- accounts[which(accounts$socialwall==1),] #social wall participants
#and only take accounts that are occupied
df <- df[which(df$occupied==T),]
#shuffle rows...
df <- df[sample(1:nrow(df)), ]
#no. of athletes n
(n <- nrow(df))
for (i in 1:n) {
try = 0
success <- FALSE
while (!success & try<5) {
try=try+1
tryCatch( {
#login
remDr$navigate("https://log.finalsurge.com/login.cshtml")
remDr$findElement(using = "id", value = "login_name")$sendKeysToElement(list(df$email[i]))
remDr$findElement(using = "id", value = "login_password")$sendKeysToElement(list(df$paswoord[i]))
remDr$findElements("class name", "btn")[[1]]$clickElement()
#navigate to workout report
remDr$navigate("https://log.finalsurge.com/WorkoutReport.cshtml")
#activities performed in last 5 days can be posted to social wall
today <- as.Date(format(Sys.Date()))
mydate <- today-5
myFSdate <- format(mydate, "%m/%d/%Y") #convert to final surge format
remDr$findElement(using = "id", value = "WorkoutDate")$clearElement()
remDr$findElement(using = "id", value = "WorkoutDate")$sendKeysToElement(list(myFSdate, key="enter"))
Sys.sleep(3)
#get the activities performed
#remDr$findElement("class", "table")$highlightElement()
page <- remDr$getPageSource() #get source
###page[[2]]#this is a test: this will give an error!!
#and put in dataframe
fs_table <- page[[1]] %>%
read_html() %>%
html_nodes('table') %>%
.[[1]] %>%
html_table() %>%
as.data.frame(.) %>%
#remove last row (total)
.[-nrow(.),]
#exclude activities that cannot be posted on the wall
#i.e., with no time
fs_table <- fs_table[fs_table$Werkelijk!="",]
#also exclude warmup and cooldown
fs_table <- fs_table[fs_table$Activiteit!="Warming-up" & fs_table$Activiteit!="Cooldown",]
if("NA" %in% rownames(fs_table)) {
fs_table <- fs_table[-nrow(fs_table),]
}
#the workout report does not contain the full workout description...
#that is, the name combined with the description.
#because there is a limit on the number of characters in the workout report....
#scrape full description by navigating to 'workout details' link
#the description may also contain substring "updated to social wall" if activities
#were already shared (i add this later on); these activities should of course not be
#shared again...
if(nrow(fs_table)>0) { # only if athlete has shareable workouts...
#first create a new variable 'workout description'
fs_table$description <- NA
#also add a variable indicating whether activity is already posted or not
fs_table$posted <- 0
#get inner html of workout report
#remDr$findElement("class", "table")$highlightElement()
inner <- remDr$findElement("class", "table")$getElementAttribute("innerHTML")
#get urls corresponding to activities in workout report
#and put in vector
url <- read_html(inner[[1]]) %>% #read html
html_nodes("td") %>% #get data cells (td tags)
.[grepl( "WorkoutDetails", ., fixed = TRUE)] %>% #extract "WorkoutDetails" urls
str_extract_all(.,"(?<=a href=).+(?=target=)") %>%
unlist(.) %>%
substring(.,16) %>%
substring(.,1,nchar(.)-2)
for (x in 1:nrow(fs_table)) { #for activity x
#get full link
link <- paste0("https://log.finalsurge.com/WorkoutAdd", url[as.numeric(rownames(fs_table))][x])
#navigate
remDr$navigate(link)
Sys.sleep(1)
#scrape workout description
desc <- remDr$findElement(using = "id", value = "Desc")$getElementAttribute("value")[[1]]
fs_table$description[x] <- desc
#and name
name <- remDr$findElement(using = "id", value = "Name")$getElementAttribute("value")[[1]]
fs_table$Workout[x] <- name
#and notes
#remDr$findElements("class name", "w-box-header")[[2]]$highlightElement()
#for some reason, the textbox may already be opened. So, click only if that is not the case
if(remDr$findElements("class name", "w-box-content")[[2]]$getElementText()[[1]] == "") {
remDr$findElements("class name", "w-box-header")[[2]]$clickElement() }
note <- remDr$findElement(using = "id", value = "PostDesc")$getElementAttribute("value")[[1]]
fs_table$Notities[x] <- note
#if the description contains "posted to social wall", posted=1
fs_table$posted[x] <- ifelse(T %in% grepl("Gepost op de Social Wall!", fs_table$description[x], fixed = TRUE), 1, fs_table$posted[x])
}
#remove empty strings with NA
fs_table$Workout <- ifelse(fs_table$Workout=="", NA, fs_table$Workout)
fs_table$description <- ifelse(fs_table$description=="", NA, fs_table$description)
fs_table$Gevoel <- ifelse(fs_table$Gevoel=="", NA, fs_table$Gevoel)
fs_table$Notities <- ifelse(fs_table$Notities=="", NA, fs_table$Notities)
#if new activities to share...
share <- which(fs_table$posted==0)
if(length(share)>0) {
Sys.sleep(1)
#to the social wall!
remDr$navigate("https://log.finalsurge.com/SocialWall/")
Sys.sleep(.5)
#agree on "advisory on sharing"
remDr$findElements("class name", "btn")[[1]]$clickElement()
for (j in unique(share)) {
remDr$navigate("https://log.finalsurge.com/SocialWall/")
Sys.sleep(.5)
remDr$findElement(using = 'class','social-workout')$clickElement()
Sys.sleep(.5)
remDr$findElements("class", "ShareWorkout")[[j]]$clickElement()
#before publishing, add a description (workout note), based on `fs_table`/workoutreport
#get activity from fs_table corresponding to this workout
activity <- fs_table[j,]
if(!is.na(activity$Workout)) {
remDr$findElement(using="id", value="socialpost")$sendKeysToElement(list(paste0("Activiteit: ", activity$Workout, "\n\n")))
}
if(!is.na(activity$description)) {
remDr$findElement(using="id", value="socialpost")$sendKeysToElement(list(paste0("Beschrijving: ", activity$description, "\n\n")))
}
if(!is.na(activity$Notities)) {
remDr$findElement(using="id", value="socialpost")$sendKeysToElement(list(paste0("Notities: ", activity$Notities)))
}
#publish
remDr$findElements("class name", "btn")[[5]]$clickElement()
Sys.sleep(3)
#now go back to the workout details to add substring "posted to social wall"
#to description to prevent activity from being shared again!
remDr$navigate(paste0("https://log.finalsurge.com/WorkoutAdd/", url[as.numeric(rownames(fs_table))[j]]))
Sys.sleep(1)
des <- remDr$findElement("id", "Desc")$getElementText()
if(!grepl("Gepost op de Social Wall!", des)) {
remDr$findElement(using="id", value="Desc")$sendKeysToElement(list("\n\nGepost op de Social Wall!"))
Sys.sleep(.2)
remDr$findElement(using = "id", value = "saveButton")$clickElement()
}
}
}
}
#logout
remDr$findElement(using = 'link text','Uitloggen')$clickElement()
Sys.sleep(.5)
# and re-log in with new account...)
remDr$findElement(using = 'link text','Log in account')$clickElement()
Sys.sleep(1)
#if the script succeeded, we're at the login page again!
if(remDr$getTitle()[[1]]=="Final Surge - Inloggen") success <- TRUE
},
error = function(e) { #if an error is caught, logout
remDr$findElement(using = 'link text','Uitloggen')$clickElement()
})
}
}
#terminate processes
pid <- rD$server$process$get_pid()#get process id
system(paste0("Taskkill /F /T" ," /PID ", pid))
#pingr::ping_port("localhost", port) #check
rm(list=ls())
gc()
quit()
n
LS0tDQp0aXRsZTogIlNvY2lhbCBXYWxsIFVwZGF0ZXIiDQpkYXRlOiAiTGFzdCBjb21waWxlZCBvbiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiB0d2Vha3MuY3NzDQogICAgdG9jOiAgZmFsc2UNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCi0tLQ0KDQpgYGB7ciwgZ2xvYmFsc2V0dGluZ3MsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KbGlicmFyeShrbml0cikNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCm9wdHNfY2h1bmskc2V0KHRpZHkub3B0cz1saXN0KHdpZHRoLmN1dG9mZj0xMDApLHRpZHk9VFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsY29tbWVudCA9ICIjPiIsIGNhY2hlPVRSVUUsIGNsYXNzLnNvdXJjZT1jKCJ0ZXN0IiksIGNsYXNzLm91dHB1dD1jKCJ0ZXN0MiIpKQ0Kb3B0aW9ucyh3aWR0aCA9IDEwMCkNCnJnbDo6c2V0dXBLbml0cigpDQoNCg0KY29sb3JpemUgPC0gZnVuY3Rpb24oeCwgY29sb3IpIHtzcHJpbnRmKCI8c3BhbiBzdHlsZT0nY29sb3I6ICVzOyc+JXM8L3NwYW4+IiwgY29sb3IsIHgpIH0NCmBgYA0KDQoNCmBgYHtyIGtsaXBweSwgZWNobz1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0Ka2xpcHB5OjprbGlwcHkocG9zaXRpb24gPSBjKCd0b3AnLCAncmlnaHQnKSkNCiNrbGlwcHk6OmtsaXBweShjb2xvciA9ICdkYXJrcmVkJykNCiNrbGlwcHk6OmtsaXBweSh0b29sdGlwX21lc3NhZ2UgPSAnQ2xpY2sgdG8gY29weScsIHRvb2x0aXBfc3VjY2VzcyA9ICdEb25lJykNCmBgYA0KDQoNCi0tLSAgDQoNClRoZSBmb2xsb3dpbmcgc2NyaXB0IHVzZXMgYFJTZWxlbml1bWAgdG8gcG9zdCBhbGwgYXRobGV0ZXMnIGxvZ2dlZCBhY3Rpdml0aWVzIHRvIEZTdXJnZSdzIFNvY2lhbCBXYWxsLiANCg0KSXQgY2FuIGJlIGltcG9ydGVkIGluIFdpbmRvd3MgVGFzayBzY2hlZHVsZXIgdG8gYXV0b21hdGljYWxseSB1cGxvYWQgYWN0aXZpdGllcyBvbiBzcGVjaWZpYyB0aW1lcG9pbnRzIChlLmcuLCBldmVyeSAxMCBtaW51dGVzOyBzZWUgdGhpcyBbUi1ibG9nZ2VycyBwb3N0XShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8yMDE4LzEwL2hvdy10by1ydW4tci1mcm9tLXRoZS10YXNrLXNjaGVkdWxlci8jOn46dGV4dD1SdW5uaW5nJTIwUiUyMGZyb20lMjB0aGUlMjBUYXNrJTIwU2NoZWR1bGVyJnRleHQ9UHJlc3NpbmclMjB0aGUlMjB3aW5kb3dzJTIwa2V5JTJDJTIwZm9sbG93ZWQsdGhlJTIwbmFtZSUyMG9mJTIweW91ciUyMHRhc2spKS4gVGhpcyBtYXkgYWxzbyBiZSBzY2hlZHVsZWQgZnJvbSBSIGl0c2VsZiAoc2VlIFtoZXJlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdGFza3NjaGVkdWxlUi92aWduZXR0ZXMvdGFza3NjaGVkdWxlUi5odG1sKSkuDQoNCmBgYHtyLCBldmFsPUZ9DQojY2xlYW4gZW52aXJvbm1lbnQNCnJtKGxpc3Q9bHMoKSkNCiNnYXJiYWdlIGNvbGxlY3QNCmdjKCkNCiNraWxsIGphdmEgaW5zdGFuY2VzIHdpdGhpbiBSc3R1ZGlvDQpzeXN0ZW0oInRhc2traWxsIC9pbSBqYXZhLmV4ZSAvZiIsIGludGVybj1GQUxTRSwgaWdub3JlLnN0ZG91dD1GQUxTRSkNCg0KI3NldHdkIHRvIGZvbGRlciBjb250YWluaW5nIHNjcmlwdA0Kd2QgPC0gIkM6L1VzZXJzL1JvYi9Eb2N1bWVudHMvR2l0SHViL0ZpbmFsU3VyZ2VfZXhwZXJpbWVudCIgDQpzZXR3ZCh3ZCkNCg0KI2xvYWQgcGFja2FnZXMNCiNpbnN0YWxsLnBhY2thZ2VzKCJSU2VsZW5pdW0iKQ0KI2luc3RhbGwucGFja2FnZXMoIm5ldHN0YXQiKQ0KbGlicmFyeShSU2VsZW5pdW0pDQpsaWJyYXJ5KHJ2ZXN0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KG5ldHN0YXQpDQpsaWJyYXJ5KHBpbmdyKQ0KDQojZmluZCBhIGZyZWUgcG9ydA0KcG9ydCA8LSBuZXRzdGF0OjpmcmVlX3BvcnQocmFuZG9tPVQpDQojcGluZyBwb3J0IGZvciBjb25maXJtYXRpb24NCiNwaW5ncjo6cGluZ19wb3J0KCJsb2NhbGhvc3QiLCBwb3J0KQ0KDQojc2V0IHVwIHNlbGVuaXVtIHNlcnZlciBhbmQgYnJvd3Nlcg0KI3JzRHJpdmVyKCkgdHJpZXMgdG8gbG9hZCBsYXRlc3QgQ2hyb21lIGRyaXZlcmVzOyBzbyBzZXQgdG8gTlVMTCEhIQ0KckQgPC0gcnNEcml2ZXIoYnJvd3Nlcj0iZmlyZWZveCIsIHBvcnQ9cG9ydCwgdmVyYm9zZT1GLCBjaHJvbWV2ZXI9TlVMTCkNCnJlbURyIDwtIHJEW1siY2xpZW50Il1dDQojcGluZ3I6OnBpbmdfcG9ydCgibG9jYWxob3N0IiwgcG9ydCkgI2NoZWNrDQoNCiNsb2FkIGxvZ2luIGF0dHJpYnV0ZXMgZnJvbSBgc2VjcmV0YC1mb2xkZXIsIGNvbnRhaW5pbmcgYXR0cmlidXRlcyByZXF1aXJlZCBmb3IgbG9naW46DQojZW1haWwsIHBhc3N3b3JkLCANCmxvYWQoIi4vbG9jYWwvYWNjb3VudHMuUiIpICMgYWxsIHByZS1zZXQgYWNjb3VudHMNCg0KI29ubHkgdGFrZSBhY2NvdW50cyB0aGF0IGFyZSBhc3NpZ25lZCBhIHNvY2lhbCB3YWxsDQpkZiA8LSBhY2NvdW50c1t3aGljaChhY2NvdW50cyRzb2NpYWx3YWxsPT0xKSxdICNzb2NpYWwgd2FsbCBwYXJ0aWNpcGFudHMNCg0KI2FuZCBvbmx5IHRha2UgYWNjb3VudHMgdGhhdCBhcmUgb2NjdXBpZWQNCmRmIDwtIGRmW3doaWNoKGRmJG9jY3VwaWVkPT1UKSxdDQoNCiNzaHVmZmxlIHJvd3MuLi4NCmRmIDwtIGRmW3NhbXBsZSgxOm5yb3coZGYpKSwgXQ0KDQojbm8uIG9mIGF0aGxldGVzIG4NCihuIDwtIG5yb3coZGYpKQ0KDQpmb3IgKGkgaW4gMTpuKSB7DQoNCiAgdHJ5ID0gMA0KICBzdWNjZXNzIDwtIEZBTFNFDQogIA0KICB3aGlsZSAoIXN1Y2Nlc3MgJiB0cnk8NSkgew0KICAgIA0KICAgIHRyeT10cnkrMQ0KICAgIA0KICAgIHRyeUNhdGNoKCB7DQogICAgICANCiAgICAgICNsb2dpbg0KICAgICAgcmVtRHIkbmF2aWdhdGUoImh0dHBzOi8vbG9nLmZpbmFsc3VyZ2UuY29tL2xvZ2luLmNzaHRtbCIpDQogICAgICByZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICJpZCIsIHZhbHVlID0gImxvZ2luX25hbWUiKSRzZW5kS2V5c1RvRWxlbWVudChsaXN0KGRmJGVtYWlsW2ldKSkNCiAgICAgIHJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImlkIiwgdmFsdWUgPSAibG9naW5fcGFzc3dvcmQiKSRzZW5kS2V5c1RvRWxlbWVudChsaXN0KGRmJHBhc3dvb3JkW2ldKSkNCiAgICAgIHJlbURyJGZpbmRFbGVtZW50cygiY2xhc3MgbmFtZSIsICJidG4iKVtbMV1dJGNsaWNrRWxlbWVudCgpDQogICAgICANCiAgICAgICNuYXZpZ2F0ZSB0byB3b3Jrb3V0IHJlcG9ydA0KICAgICAgcmVtRHIkbmF2aWdhdGUoImh0dHBzOi8vbG9nLmZpbmFsc3VyZ2UuY29tL1dvcmtvdXRSZXBvcnQuY3NodG1sIikNCiAgICAgIA0KICAgICAgI2FjdGl2aXRpZXMgcGVyZm9ybWVkIGluIGxhc3QgNSBkYXlzIGNhbiBiZSBwb3N0ZWQgdG8gc29jaWFsIHdhbGwNCiAgICAgIHRvZGF5IDwtIGFzLkRhdGUoZm9ybWF0KFN5cy5EYXRlKCkpKQ0KICAgICAgbXlkYXRlIDwtIHRvZGF5LTUNCiAgICAgIG15RlNkYXRlIDwtIGZvcm1hdChteWRhdGUsICIlbS8lZC8lWSIpICNjb252ZXJ0IHRvIGZpbmFsIHN1cmdlIGZvcm1hdA0KICAgICAgDQogICAgICByZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICJpZCIsIHZhbHVlID0gIldvcmtvdXREYXRlIikkY2xlYXJFbGVtZW50KCkNCiAgICAgIHJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImlkIiwgdmFsdWUgPSAiV29ya291dERhdGUiKSRzZW5kS2V5c1RvRWxlbWVudChsaXN0KG15RlNkYXRlLCBrZXk9ImVudGVyIikpDQogICAgICBTeXMuc2xlZXAoMykNCiAgICAgICNnZXQgdGhlIGFjdGl2aXRpZXMgcGVyZm9ybWVkDQogICAgICAjcmVtRHIkZmluZEVsZW1lbnQoImNsYXNzIiwgInRhYmxlIikkaGlnaGxpZ2h0RWxlbWVudCgpDQogICAgICBwYWdlIDwtIHJlbURyJGdldFBhZ2VTb3VyY2UoKSAjZ2V0IHNvdXJjZQ0KICAgICAgDQogICAgICAjIyNwYWdlW1syXV0jdGhpcyBpcyBhIHRlc3Q6IHRoaXMgd2lsbCBnaXZlIGFuIGVycm9yISENCiAgICAgIA0KICAgICAgI2FuZCBwdXQgaW4gZGF0YWZyYW1lDQogICAgICBmc190YWJsZSA8LSBwYWdlW1sxXV0gJT4lDQogICAgICAgIHJlYWRfaHRtbCgpICU+JQ0KICAgICAgICBodG1sX25vZGVzKCd0YWJsZScpICU+JSANCiAgICAgICAgLltbMV1dICU+JQ0KICAgICAgICBodG1sX3RhYmxlKCkgJT4lDQogICAgICAgIGFzLmRhdGEuZnJhbWUoLikgJT4lIA0KICAgICAgICAjcmVtb3ZlIGxhc3Qgcm93ICh0b3RhbCkNCiAgICAgICAgLlstbnJvdyguKSxdDQogICAgICANCiAgICAgICNleGNsdWRlIGFjdGl2aXRpZXMgdGhhdCBjYW5ub3QgYmUgcG9zdGVkIG9uIHRoZSB3YWxsDQogICAgICAjaS5lLiwgd2l0aCBubyB0aW1lDQogICAgICBmc190YWJsZSA8LSBmc190YWJsZVtmc190YWJsZSRXZXJrZWxpamshPSIiLF0NCiAgICAgIA0KICAgICAgI2Fsc28gZXhjbHVkZSB3YXJtdXAgYW5kIGNvb2xkb3duDQogICAgICBmc190YWJsZSA8LSBmc190YWJsZVtmc190YWJsZSRBY3Rpdml0ZWl0IT0iV2FybWluZy11cCIgJiBmc190YWJsZSRBY3Rpdml0ZWl0IT0iQ29vbGRvd24iLF0NCiAgICAgIA0KICAgICAgaWYoIk5BIiAlaW4lIHJvd25hbWVzKGZzX3RhYmxlKSkgew0KICAgICAgICBmc190YWJsZSA8LSBmc190YWJsZVstbnJvdyhmc190YWJsZSksXQ0KICAgICAgfQ0KICAgICAgDQogICAgICAjdGhlIHdvcmtvdXQgcmVwb3J0IGRvZXMgbm90IGNvbnRhaW4gdGhlIGZ1bGwgd29ya291dCBkZXNjcmlwdGlvbi4uLg0KICAgICAgI3RoYXQgaXMsIHRoZSBuYW1lIGNvbWJpbmVkIHdpdGggdGhlIGRlc2NyaXB0aW9uLg0KICAgICAgI2JlY2F1c2UgdGhlcmUgaXMgYSBsaW1pdCBvbiB0aGUgbnVtYmVyIG9mIGNoYXJhY3RlcnMgaW4gdGhlIHdvcmtvdXQgcmVwb3J0Li4uLg0KICAgICAgI3NjcmFwZSBmdWxsIGRlc2NyaXB0aW9uIGJ5IG5hdmlnYXRpbmcgdG8gJ3dvcmtvdXQgZGV0YWlscycgbGluaw0KICAgICAgDQogICAgICAjdGhlIGRlc2NyaXB0aW9uIG1heSBhbHNvIGNvbnRhaW4gc3Vic3RyaW5nICJ1cGRhdGVkIHRvIHNvY2lhbCB3YWxsIiBpZiBhY3Rpdml0aWVzDQogICAgICAjd2VyZSBhbHJlYWR5IHNoYXJlZCAoaSBhZGQgdGhpcyBsYXRlciBvbik7IHRoZXNlIGFjdGl2aXRpZXMgc2hvdWxkIG9mIGNvdXJzZSBub3QgYmUgDQogICAgICAjc2hhcmVkIGFnYWluLi4uDQogICAgICANCiAgICAgIGlmKG5yb3coZnNfdGFibGUpPjApIHsgIyBvbmx5IGlmIGF0aGxldGUgaGFzIHNoYXJlYWJsZSB3b3Jrb3V0cy4uLg0KICAgICAgICANCiAgICAgICAgI2ZpcnN0IGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSAnd29ya291dCBkZXNjcmlwdGlvbicNCiAgICAgICAgZnNfdGFibGUkZGVzY3JpcHRpb24gPC0gTkENCiAgICAgICAgDQogICAgICAgICNhbHNvIGFkZCBhIHZhcmlhYmxlIGluZGljYXRpbmcgd2hldGhlciBhY3Rpdml0eSBpcyBhbHJlYWR5IHBvc3RlZCBvciBub3QNCiAgICAgICAgZnNfdGFibGUkcG9zdGVkIDwtIDANCiAgICAgICAgDQogICAgICAgICNnZXQgaW5uZXIgaHRtbCBvZiB3b3Jrb3V0IHJlcG9ydA0KICAgICAgICAjcmVtRHIkZmluZEVsZW1lbnQoImNsYXNzIiwgInRhYmxlIikkaGlnaGxpZ2h0RWxlbWVudCgpDQogICAgICAgIGlubmVyIDwtIHJlbURyJGZpbmRFbGVtZW50KCJjbGFzcyIsICJ0YWJsZSIpJGdldEVsZW1lbnRBdHRyaWJ1dGUoImlubmVySFRNTCIpDQogICAgICAgIA0KICAgICAgICAjZ2V0IHVybHMgY29ycmVzcG9uZGluZyB0byBhY3Rpdml0aWVzIGluIHdvcmtvdXQgcmVwb3J0DQogICAgICAgICNhbmQgcHV0IGluIHZlY3Rvcg0KICAgICAgICB1cmwgPC0gcmVhZF9odG1sKGlubmVyW1sxXV0pICU+JSAjcmVhZCBodG1sDQogICAgICAgICAgaHRtbF9ub2RlcygidGQiKSAlPiUgI2dldCBkYXRhIGNlbGxzICh0ZCB0YWdzKQ0KICAgICAgICAgIC5bZ3JlcGwoICJXb3Jrb3V0RGV0YWlscyIsIC4sIGZpeGVkID0gVFJVRSldICU+JSAjZXh0cmFjdCAiV29ya291dERldGFpbHMiIHVybHMNCiAgICAgICAgICBzdHJfZXh0cmFjdF9hbGwoLiwiKD88PWEgaHJlZj0pLisoPz10YXJnZXQ9KSIpICU+JQ0KICAgICAgICAgIHVubGlzdCguKSAlPiUNCiAgICAgICAgICBzdWJzdHJpbmcoLiwxNikgJT4lDQogICAgICAgICAgc3Vic3RyaW5nKC4sMSxuY2hhciguKS0yKQ0KICAgICAgICANCiAgICAgICAgZm9yICh4IGluIDE6bnJvdyhmc190YWJsZSkpIHsgI2ZvciBhY3Rpdml0eSB4DQogIA0KICAgICAgICAgICNnZXQgZnVsbCBsaW5rDQogICAgICAgICAgbGluayA8LSBwYXN0ZTAoImh0dHBzOi8vbG9nLmZpbmFsc3VyZ2UuY29tL1dvcmtvdXRBZGQiLCB1cmxbYXMubnVtZXJpYyhyb3duYW1lcyhmc190YWJsZSkpXVt4XSkNCiAgICAgICAgICANCiAgICAgICAgICAjbmF2aWdhdGUNCiAgICAgICAgICByZW1EciRuYXZpZ2F0ZShsaW5rKQ0KICAgICAgICAgIFN5cy5zbGVlcCgxKQ0KICAgICAgICAgIA0KICAgICAgICAgICNzY3JhcGUgd29ya291dCBkZXNjcmlwdGlvbg0KICAgICAgICAgIGRlc2MgPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAiaWQiLCB2YWx1ZSA9ICJEZXNjIikkZ2V0RWxlbWVudEF0dHJpYnV0ZSgidmFsdWUiKVtbMV1dDQogICAgICAgICAgZnNfdGFibGUkZGVzY3JpcHRpb25beF0gPC0gZGVzYw0KICAgICAgICAgIA0KICAgICAgICAgICNhbmQgbmFtZQ0KICAgICAgICAgIG5hbWUgPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAiaWQiLCB2YWx1ZSA9ICJOYW1lIikkZ2V0RWxlbWVudEF0dHJpYnV0ZSgidmFsdWUiKVtbMV1dDQogICAgICAgICAgZnNfdGFibGUkV29ya291dFt4XSA8LSBuYW1lDQogICAgICAgICAgDQogICAgICAgICAgI2FuZCBub3Rlcw0KICAgICAgICAgICNyZW1EciRmaW5kRWxlbWVudHMoImNsYXNzIG5hbWUiLCAidy1ib3gtaGVhZGVyIilbWzJdXSRoaWdobGlnaHRFbGVtZW50KCkNCiAgICAgICAgICAjZm9yIHNvbWUgcmVhc29uLCB0aGUgdGV4dGJveCBtYXkgYWxyZWFkeSBiZSBvcGVuZWQuIFNvLCBjbGljayBvbmx5IGlmIHRoYXQgaXMgbm90IHRoZSBjYXNlDQogICAgICAgICAgaWYocmVtRHIkZmluZEVsZW1lbnRzKCJjbGFzcyBuYW1lIiwgInctYm94LWNvbnRlbnQiKVtbMl1dJGdldEVsZW1lbnRUZXh0KClbWzFdXSA9PSAiIikgew0KICAgICAgICAgICAgcmVtRHIkZmluZEVsZW1lbnRzKCJjbGFzcyBuYW1lIiwgInctYm94LWhlYWRlciIpW1syXV0kY2xpY2tFbGVtZW50KCkgfQ0KICAgICAgICAgIA0KICAgICAgICAgIG5vdGUgPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAiaWQiLCB2YWx1ZSA9ICJQb3N0RGVzYyIpJGdldEVsZW1lbnRBdHRyaWJ1dGUoInZhbHVlIilbWzFdXQ0KICAgICAgICAgIGZzX3RhYmxlJE5vdGl0aWVzW3hdIDwtIG5vdGUNCiAgICAgICAgICANCiAgICAgICAgICAjaWYgdGhlIGRlc2NyaXB0aW9uIGNvbnRhaW5zICJwb3N0ZWQgdG8gc29jaWFsIHdhbGwiLCBwb3N0ZWQ9MQ0KICAgICAgICAgIGZzX3RhYmxlJHBvc3RlZFt4XSA8LSBpZmVsc2UoVCAlaW4lIGdyZXBsKCJHZXBvc3Qgb3AgZGUgU29jaWFsIFdhbGwhIiwgZnNfdGFibGUkZGVzY3JpcHRpb25beF0sIGZpeGVkID0gVFJVRSksIDEsIGZzX3RhYmxlJHBvc3RlZFt4XSkNCiAgICAgICAgfSANCiAgICAgICANCiAgICAgICAgI3JlbW92ZSBlbXB0eSBzdHJpbmdzIHdpdGggTkENCiAgICAgICAgZnNfdGFibGUkV29ya291dCA8LSBpZmVsc2UoZnNfdGFibGUkV29ya291dD09IiIsIE5BLCBmc190YWJsZSRXb3Jrb3V0KQ0KICAgICAgICBmc190YWJsZSRkZXNjcmlwdGlvbiA8LSBpZmVsc2UoZnNfdGFibGUkZGVzY3JpcHRpb249PSIiLCBOQSwgZnNfdGFibGUkZGVzY3JpcHRpb24pDQogICAgICAgIGZzX3RhYmxlJEdldm9lbCA8LSBpZmVsc2UoZnNfdGFibGUkR2V2b2VsPT0iIiwgTkEsIGZzX3RhYmxlJEdldm9lbCkNCiAgICAgICAgZnNfdGFibGUkTm90aXRpZXMgPC0gaWZlbHNlKGZzX3RhYmxlJE5vdGl0aWVzPT0iIiwgTkEsIGZzX3RhYmxlJE5vdGl0aWVzKQ0KICAgICAgICANCiAgICAgICAgI2lmIG5ldyBhY3Rpdml0aWVzIHRvIHNoYXJlLi4uDQogICAgICAgIHNoYXJlIDwtIHdoaWNoKGZzX3RhYmxlJHBvc3RlZD09MCkNCiAgICAgICAgDQogICAgICAgIGlmKGxlbmd0aChzaGFyZSk+MCkgew0KICAgICAgICAgIFN5cy5zbGVlcCgxKQ0KICAgICAgICAgICN0byB0aGUgc29jaWFsIHdhbGwhDQogICAgICAgICAgcmVtRHIkbmF2aWdhdGUoImh0dHBzOi8vbG9nLmZpbmFsc3VyZ2UuY29tL1NvY2lhbFdhbGwvIikNCiAgICAgICAgICBTeXMuc2xlZXAoLjUpDQogICAgICAgICAgI2FncmVlICBvbiAiYWR2aXNvcnkgb24gc2hhcmluZyINCiAgICAgICAgICByZW1EciRmaW5kRWxlbWVudHMoImNsYXNzIG5hbWUiLCAiYnRuIilbWzFdXSRjbGlja0VsZW1lbnQoKQ0KICAgICAgICAgIA0KICAgICAgICAgIGZvciAoaiBpbiB1bmlxdWUoc2hhcmUpKSB7DQogICAgICAgICAgICByZW1EciRuYXZpZ2F0ZSgiaHR0cHM6Ly9sb2cuZmluYWxzdXJnZS5jb20vU29jaWFsV2FsbC8iKQ0KICAgICAgICAgICAgU3lzLnNsZWVwKC41KQ0KICAgICAgICAgICAgcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAnY2xhc3MnLCdzb2NpYWwtd29ya291dCcpJGNsaWNrRWxlbWVudCgpDQogICAgICAgICAgICBTeXMuc2xlZXAoLjUpDQogICAgICAgICAgICByZW1EciRmaW5kRWxlbWVudHMoImNsYXNzIiwgIlNoYXJlV29ya291dCIpW1tqXV0kY2xpY2tFbGVtZW50KCkNCiAgICAgICAgICANCiAgICAgICAgICAgICNiZWZvcmUgcHVibGlzaGluZywgYWRkIGEgZGVzY3JpcHRpb24gKHdvcmtvdXQgbm90ZSksIGJhc2VkIG9uIGBmc190YWJsZWAvd29ya291dHJlcG9ydA0KICAgICAgICAgICAgDQogICAgICAgICAgICAjZ2V0IGFjdGl2aXR5IGZyb20gZnNfdGFibGUgY29ycmVzcG9uZGluZyB0byB0aGlzIHdvcmtvdXQNCiAgICAgICAgICAgIGFjdGl2aXR5IDwtIGZzX3RhYmxlW2osXQ0KICAgICAgICAgICAgDQogICAgICAgICAgICBpZighaXMubmEoYWN0aXZpdHkkV29ya291dCkpIHsNCiAgICAgICAgICAgICAgcmVtRHIkZmluZEVsZW1lbnQodXNpbmc9ImlkIiwgdmFsdWU9InNvY2lhbHBvc3QiKSRzZW5kS2V5c1RvRWxlbWVudChsaXN0KHBhc3RlMCgiQWN0aXZpdGVpdDogIiwgYWN0aXZpdHkkV29ya291dCwgIlxuXG4iKSkpDQogICAgICAgICAgICB9DQogICAgICAgICAgICBpZighaXMubmEoYWN0aXZpdHkkZGVzY3JpcHRpb24pKSB7DQogICAgICAgICAgICAgIHJlbURyJGZpbmRFbGVtZW50KHVzaW5nPSJpZCIsIHZhbHVlPSJzb2NpYWxwb3N0Iikkc2VuZEtleXNUb0VsZW1lbnQobGlzdChwYXN0ZTAoIkJlc2NocmlqdmluZzogIiwgYWN0aXZpdHkkZGVzY3JpcHRpb24sICJcblxuIikpKQ0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgaWYoIWlzLm5hKGFjdGl2aXR5JE5vdGl0aWVzKSkgew0KICAgICAgICAgICAgICByZW1EciRmaW5kRWxlbWVudCh1c2luZz0iaWQiLCB2YWx1ZT0ic29jaWFscG9zdCIpJHNlbmRLZXlzVG9FbGVtZW50KGxpc3QocGFzdGUwKCJOb3RpdGllczogIiwgYWN0aXZpdHkkTm90aXRpZXMpKSkNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgICNwdWJsaXNoDQogICAgICAgICAgICByZW1EciRmaW5kRWxlbWVudHMoImNsYXNzIG5hbWUiLCAiYnRuIilbWzVdXSRjbGlja0VsZW1lbnQoKQ0KICAgICAgICAgICAgDQogICAgICAgICAgICBTeXMuc2xlZXAoMykNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgI25vdyBnbyBiYWNrIHRvIHRoZSB3b3Jrb3V0IGRldGFpbHMgdG8gYWRkIHN1YnN0cmluZyAicG9zdGVkIHRvIHNvY2lhbCB3YWxsIg0KICAgICAgICAgICAgI3RvIGRlc2NyaXB0aW9uIHRvIHByZXZlbnQgYWN0aXZpdHkgZnJvbSBiZWluZyBzaGFyZWQgYWdhaW4hDQogICAgICAgICAgICByZW1EciRuYXZpZ2F0ZShwYXN0ZTAoImh0dHBzOi8vbG9nLmZpbmFsc3VyZ2UuY29tL1dvcmtvdXRBZGQvIiwgIHVybFthcy5udW1lcmljKHJvd25hbWVzKGZzX3RhYmxlKSlbal1dKSkNCiAgICAgICAgICAgIFN5cy5zbGVlcCgxKQ0KICAgICAgICAgICAgZGVzIDwtIHJlbURyJGZpbmRFbGVtZW50KCJpZCIsICJEZXNjIikkZ2V0RWxlbWVudFRleHQoKQ0KICAgICAgICAgICAgaWYoIWdyZXBsKCJHZXBvc3Qgb3AgZGUgU29jaWFsIFdhbGwhIiwgZGVzKSkgew0KICAgICAgICAgICAgICByZW1EciRmaW5kRWxlbWVudCh1c2luZz0iaWQiLCB2YWx1ZT0iRGVzYyIpJHNlbmRLZXlzVG9FbGVtZW50KGxpc3QoIlxuXG5HZXBvc3Qgb3AgZGUgU29jaWFsIFdhbGwhIikpDQogICAgICAgICAgICAgIFN5cy5zbGVlcCguMikNCiAgICAgICAgICAgICAgcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAiaWQiLCB2YWx1ZSA9ICJzYXZlQnV0dG9uIikkY2xpY2tFbGVtZW50KCkNCiAgICAgICAgICAgIH0NCiAgICANCiAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgIH0NCiAgICAgICNsb2dvdXQNCiAgICAgIHJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gJ2xpbmsgdGV4dCcsJ1VpdGxvZ2dlbicpJGNsaWNrRWxlbWVudCgpDQogICAgICBTeXMuc2xlZXAoLjUpDQogICAgICANCiAgICAgICMgYW5kIHJlLWxvZyBpbiB3aXRoIG5ldyBhY2NvdW50Li4uKQ0KICAgICAgcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAnbGluayB0ZXh0JywnTG9nIGluIGFjY291bnQnKSRjbGlja0VsZW1lbnQoKQ0KICAgICAgU3lzLnNsZWVwKDEpDQogICAgICANCiAgICAgICNpZiB0aGUgc2NyaXB0IHN1Y2NlZWRlZCwgd2UncmUgYXQgdGhlIGxvZ2luIHBhZ2UgYWdhaW4hDQogICAgICBpZihyZW1EciRnZXRUaXRsZSgpW1sxXV09PSJGaW5hbCBTdXJnZSAtIElubG9nZ2VuIikgc3VjY2VzcyA8LSBUUlVFDQogICAgfSwgDQogICAgZXJyb3IgPSBmdW5jdGlvbihlKSB7ICNpZiBhbiBlcnJvciBpcyBjYXVnaHQsIGxvZ291dCANCiAgICAgIHJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gJ2xpbmsgdGV4dCcsJ1VpdGxvZ2dlbicpJGNsaWNrRWxlbWVudCgpDQogICAgfSkgDQogIH0NCiAgDQp9DQoNCiN0ZXJtaW5hdGUgcHJvY2Vzc2VzDQpwaWQgPC0gckQkc2VydmVyJHByb2Nlc3MkZ2V0X3BpZCgpI2dldCBwcm9jZXNzIGlkDQpzeXN0ZW0ocGFzdGUwKCJUYXNra2lsbCAvRiAvVCIgLCIgL1BJRCAiLCBwaWQpKQ0KI3BpbmdyOjpwaW5nX3BvcnQoImxvY2FsaG9zdCIsIHBvcnQpICNjaGVjaw0KDQpybShsaXN0PWxzKCkpDQpnYygpDQpxdWl0KCkNCm4NCmBgYA0K
Copyright © 2023 Rob Franken