Skip navigation

Many R package authors (including myself) lump a collection of small, useful functions into some type of utils.R file and usually do not export the functions since they are (generally) designed to work on package internals rather than expose their functionality via the exported package API. Just like Batman’s utility belt, which can be customized for any mission, any set of utilities in a given R package will also likely be different from those in other packages.

I thought it would be neat to take a look at:

  • just how many packages have one or more util*.R files and what the most common file names are for them;
  • utility function naming preferences — specifically snake-case, camel-case or dot-case
  • what the most common “utility” functions names are across the packages
  • coding style — specifically compare ratios of white space, full-line comments to code size

for all the published packages on CRAN.

There are many more questions one can ask and then use this corpus to answer, so we’ll close out the post with a link to it so any intrepid readers can do just that, especially since reproducing the first bit of this post would require a local CRAN mirror (which most folks — rightly so — do not have handy).

Acquiring and Transforming the Data We Need

Since I have local CRAN mirror, it’s just a matter of iterating through all the package tar.gz files in src/contrib and grepping through a tar listing of each for a pattern like "R/util.*$. That pattern isn’t perfect but it’s quick and we’ll be able to filter out any files it catches that don’t belong. I chose to use a small bash script for this but it’s possible to do this with R as well (an exercise left to the reader). The resultant data file looks a bit like the output from an ls -l (linux-ish) directory listing:

-rw-r--r--  0 hornik users    1658 Jun  5  2016 AHR/R/util.R
-rw-r--r--  0 ligges users   12609 Dec 13  2016 ALA4R/R/utilities_internal.R
-rw-r--r--  0 hornik users       0 Feb 24  2017 AWR.Kinesis/R/utils.R
-rw-r--r--  0 ligges users    4127 Aug 30  2017 AlphaVantageClient/R/utils.R
-rw-r--r--  0 ligges users     121 Jan 19  2017 AmyloGram/R/utils.R
-rw-r--r--  0 ligges users    2873 Jan 17 23:04 DT/R/utils.R
-rw-r--r--  0 ligges users    3055 Jan 17  2017 cleanr/inst/source/R/utils.R
drwxr-xr-x  0 ligges users       0 Sep 24  2017 JGR/java/org/rosuda/JGR/util/

I made sure to show a few examples of where a better search pattern would have helped ensure lines like the three at the bottom of that listings aren’t included. But, we all often have to deal with imperfect data, so we’ll make sure to deal with that during the ingestion & cleanup process.

library(stringi)
library(hrbrthemes)
library(archive) # devtools::install_github("jimhester", "archive")
library(tidyverse)

# I ran readr::type_convert() once and it returns this column type spec. By using it 
# for subsequent conversions, we'll gain reproducibility and data format change 
# detection capabilities "for free"

cols(
  permsissions = col_character(),
  links = col_integer(),
  owner = col_character(),
  group = col_character(),
  size = col_integer(),
  month = col_character(),
  day = col_integer(),
  year_hr = col_character(),
  path = col_character()
) -> tar_cols

# Now, we parse the tar verbose ('ls -l') listing

stri_read_lines("~/Data/pkutils.txt") %>% # stringi was loaded so might as well use it
  stri_split_regex(" +", 9, simplify = TRUE) %>% # split input into 9 columns
  as_data_frame() %>% # ^^ returns a matrix but data frames are more useful for our work
  set_names(names(tar_cols$cols)) %>% # column names are useful and we can use our colspec for it
  type_convert(col_types = tar_cols) %>% # see comment block before cols()
  mutate(day = sprintf("%02d", day)) %>% # now we'll work on getting the date pieces to be a Date
  mutate(year_hr = case_when( # the year_hr field can be either %Y or %H:%M depending on file 'recency'
    stri_detect_fixed(year_hr, ":") &
      (month %in% c("Jan", "Feb", "Mar", "Apr")) ~ "2018", # if %H:%M but 'starter' months it's 2018
    stri_detect_fixed(year_hr, ":") &
      (month %in% c("Dec", "Nov", "Oct", "Sep", "Aug", "Jul", "Jun")) ~ "2017", # %H:%M & 'end' months
    TRUE ~ year_hr # already in %Y format
  )) %>%
  mutate(date= lubridate::mdy(sprintf("%s %s, %s", month, day, year_hr))) %>% # get a Date
  mutate(pkg = stri_match_first_regex(path, "^(.*)/R/")[,2]) %>% # extract package name (stri_extract is also usable here)
  mutate(fil = basename(path)) %>% # extrafct just the file name
  filter(!is.na(pkg)) %>% # handle one type of wrongly included file
  filter(!stri_detect_fixed(pkg, "/")) %>% # ande another
  filter(!is.na(path)) -> xdf # and another; but we're done so we close with an assignment

glimpse(xdf)
## Observations: 1,746
## Variables: 12
## $ permsissions <chr> "-rw-r--r--", "-rw-r--r--", "-rw-r--r--", "-rw-r-...
## $ links        <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
## $ owner        <chr> "hornik", "ligges", "hornik", "ligges", "ligges",...
## $ group        <chr> "users", "users", "users", "users", "users", "her...
## $ size         <int> 1658, 12609, 0, 4127, 121, 52, 36977, 34198, 3676...
## $ month        <chr> "Jun", "Dec", "Feb", "Aug", "Jan", "Aug", "Jan", ...
## $ day          <chr> "05", "13", "24", "30", "19", "10", "06", "10", "...
## $ year_hr      <chr> "2016", "2016", "2017", "2017", "2017", "2017", "...
## $ path         <chr> "AHR/R/util.R", "ALA4R/R/utilities_internal.R", "...
## $ date         <date> 2016-06-05, 2016-12-13, 2017-02-24, 2017-08-30, ...
## $ pkg          <chr> "AHR", "ALA4R", "AWR.Kinesis", "AlphaVantageClien...
## $ fil          <chr> "util.R", "utilities_internal.R", "utils.R", "uti...

To the analysis!

Finding the Utility of ‘util’s

A careful look at the glimpse() listing shows we have 1,745 files that begin with util, but how many packages have at least one util files?

nrow(distinct(xdf, pkg))
## [1] 1397

That’s roughly 10% of CRAN, but doesn’t mean other packages do not have “utility belt” functions since other authors may have just been more creative or deliberate with their file naming conventions.

Readers with keen eyes may have noticed we spent some deliberate CPU cycles to get a Date column. Part of that was to show how to do that (mostly as an example for folks new to R) but we also did it to ask temporal questions, such as “Are package ‘utility belts’ a “new” thing?”. The data suggests that utility belts are products/attributes of more recently published or updated packages:

distinct(xdf, pkg, date) %>%
  mutate(yr = as.integer(lubridate::year(date))) %>%
  count(yr) %>%
  complete(yr, fill=list(n=0)) %>%
  ggplot(aes(yr, n)) +
  geom_col(fill="lightslategray", width=0.65) +
  labs(
    x = NULL, y = "Package count",
    title = "Recently published or updated packages tend to have more 'util'\nthan older/less actively-maintained ones",
    subtitle = "Count of packages (by year) with 'util's"
  ) +
  theme_ipsum_rc(grid="Y")

We could answer this more completely by going through the CRAN archives for all these packages, but for now we’ll just see which packages might have helped set this trend going:

distinct(xdf, pkg, date) %>%
      arrange(date) %>% 
      print(n=20)
    ## # A tibble: 1,540 x 2
    ##    date       pkg       
    ##  1 1980-01-01 bsts      
    ##  2 2006-06-28 evdbayes  
    ##  3 2006-11-29 hexView   
    ##  4 2006-12-17 StatDataML
    ##  5 2007-10-05 tpr       
    ##  6 2007-11-07 seqinr    
    ##  7 2007-11-26 registry  
    ##  8 2008-07-25 ramps     
    ##  9 2008-10-23 RobAStBase
    ## 10 2009-02-23 vcd       
    ## 11 2009-06-26 ttutils   
    ## 12 2009-07-03 histogram 
    ## 13 2009-11-27 polynom   
    ## 14 2009-11-27 tau       
    ## 15 2010-01-05 itertools 
    ## 16 2010-01-22 tableplot 
    ## 17 2010-06-09 rbugs     
    ## 18 2011-03-17 playwith  
    ## 19 2011-05-11 marelac   
    ## 20 2011-10-11 timeSeries
    ## # ... with 1,520 more rows

Going back to our corpus, what are the most common names for these utility belt files?

## count(xdf, fil, sort=TRUE) %>% 
    ##   mutate(pct = scales::percent(n/sum(n))) %>% 
    ##   print(n=20)
    ## # A tibble: 409 x 3
    ##    fil                      n pct  
    ##  1 utils.R                865 49.5%
    ##  2 utilities.R            145 8.3% 
    ##  3 util.R                 134 7.7% 
    ##  4 utils.r                 68 3.9% 
    ##  5 utility.R               47 2.7% 
    ##  6 utility_functions.R     25 1.4% 
    ##  7 util.r                  16 0.9% 
    ##  8 utilities.r             14 0.8% 
    ##  9 utils-pipe.R             9 0.5% 
    ## 10 utilityFunctions.R       6 0.3% 
    ## 11 utils-format.r           3 0.2% 
    ## 12 util_functions.R         2 0.1% 
    ## 13 util_rescale.R           2 0.1% 
    ## 14 util-aux.R               2 0.1% 
    ## 15 util-checkparam.R        2 0.1% 
    ## 16 util-startarg.R          2 0.1% 
    ## 17 utilcmst.R               2 0.1% 
    ## 18 utilhot.R                2 0.1% 
    ## 19 utilities_internal.R     2 0.1% 
    ## 20 utility-functions.R      2 0.1% 
    ## # ... with 389 more rows

Over 50% of other CRAN packages are as “un-creative” as I am when it comes to naming these files.

Let’s see how packed these belts are:

ggplot(xdf, aes(x="", size)) +
  ggbeeswarm::geom_quasirandom(
    fill="lightslategray", color="white",
    alpha=1/2, stroke=0.25, size=3, shape=21
  ) +
  geom_boxplot(fill="#00000000", outlier.colour = "#00000000") +
  geom_text(
    data=data_frame(), aes(x=-Inf, y=median(xdf$size), label="Median:\n2,717"),
    hjust = 0, family = font_rc, size = 3, color = "lightslateblue"
  ) +
  scale_y_comma(
    name = "File size", trans="log10", limits=c(NA, 200000),
    breaks = c(10, 100, 1000, 10000, 100000) 
  ) +
  labs(
    x = NULL, 
    title = "Most 'util' files are between 1K and 10K in size",
    caption = "Note y-axis log10 scale"
  ) +
  theme_ipsum_rc(grid="Y")

We’ll need to do a bit more data collection to answer the last two questions.

Focus on Functions

To examine function names and source code statistics, we’ll need to read in the contents of each file and parse them. Let’s do that first bit with some help from the archive package which will help us open up these compressed tar files and pull out the file(s) we need from them vs have to code this up more manually.

Again, this code is only reproducible if you have CRAN handy, but soon (promise!) you’ll have a file you can work with for the remainder of the post:

extract_source <- function(pkg, fil, .pb = NULL) {

  if (!is.null(.pb)) .pb$tick()$print()

  list.files(
    path = "/cran/src/contrib", # my path to local CRAN
    pattern = sprintf("^%s_.*gz", pkg), # rough pattern for the package archive filename
    recursive = FALSE,
    full.names = TRUE
  ) -> tgt

  con <- archive_read(tgt[1], fil)
  src <- readLines(con, warn = FALSE)
  close(con)

  paste0(src, collapse="\n")

}

pb <- progress_estimated(nrow(xdf))
xdf <- mutate(xdf, file_src = map2_chr(pkg, path, extract_source, .pb=pb))

That (on-drive) ~10MB data frame is in https://rud.is/dl/utility-belt.rds. The rest of the post builds off of it so you can start coding along at home now.

Let’s extract the function names:

# we'll use these two functions to help test whether bits 
# of our parsed code are, indeed, functions. 
#
# Alternately: "I heard you liked functions so I made
# functions to help you find functions"
#
# we could have used `rlang` helpers here, but I had these
# handy from pre-`rlang` days.

is_assign <- function(x) {
  as.character(x) %in% c('<-', '=', '<<-', 'assign')
}

is_func <- function(x) {
  is.call(x) &&
    is_assign(x[[1]]) &&
    is.call(x[[3]]) &&
    (x[[3]][[1]] == quote(`function`))
}

read_rds("~/Data/utility-belt.rds") %>% # I have this file in ~/Data; change this for your location
  mutate(parsed = map(file_src, ~parse(text = .x, keep.source = TRUE))) %>% # parse each file
  mutate(func_names = map(parsed, ~{ # go through parsed file
    keep(.x, is_func) %>% # and only keep functions
      map(~as.character(.x[[2]])) %>% # extract the function name
      flatten_chr() # return a character vector
  })) -> xdf

With those handy, we can see if there are any commonalities across all these packages:

select(xdf, pkg, fil, func_names) %>%
  unnest() %>%
  count(func_names, sort=TRUE) %>%
  print(n=20)
##    func_names                 n
##  1 %||%                      84
##  2 compact                   19
##  3 isFALSE                   19
##  4 assertthat::on_failure    16
##  5 is_windows                16
##  6 trim                      14
##  7 .on Load                   13 # (IRL there's no space here but the WP input sanitizer hates this word due to js abuse
##  8 names2                    12
##  9 dots                      11
## 10 is_string                 11
## 11 vlapply                   11
## 12 .onAttach                 10
## 13 error.bars                10
## 14 normalize                 10
## 15 vcapply                   10
## 16 cat0                       9
## 17 collapse                   9
## 18 err                        9
## 19 getmin                     9
## 20 is_dir                     9
## # ... with 1.252e+04 more rows

We can also see if there are common case conventions:

select(xdf, pkg, fil, func_names) %>%
  unnest() %>%
  mutate(is_camel = (!stri_detect_fixed(func_names, "_")) &
           (!stri_detect_regex(func_names, "[[:alpha:]]\\.[[:alpha:]]")) &
           (stri_detect_regex(func_names, "[A-Z]"))) %>%
  mutate(is_dotcase = stri_detect_regex(func_names, "[[:alpha:]]\\.[[:alpha:]]")) %>%
  mutate(is_snake = stri_detect_fixed(func_names, "_") &
           (!stri_detect_regex(func_names, "[[:alpha:]]\\.[[:alpha:]]"))) -> case_hunt

count(case_hunt, is_camel, is_dotcase, is_snake) %>% 
  mutate(pct = scales::percent(n/sum(n))) %>% 
  mutate(description = c(
    "one-'word' names",
    "snake_case",
    "dot.case",
    "camelCase"
  )) %>% 
  arrange(n) %>% 
  mutate(description = factor(description, description)) %>% 
  ggplot(aes(description, n)) +
  geom_col(fill="lightslategray", width=0.65) +
  geom_label(aes(y = n, label=pct), label.size=0, family=font_rc, nudge_y=150) +
  scale_y_comma("Number of functions") +
  labs(
    x=NULL,
    title = "dot.case does not seem to be en-vogue for utility belt functions"
  ) +
  theme_ipsum_rc(grid="Y")

I had a hunch that isX…()/is_x…() could be likely names for utility belt functions, so let’s normalize the function names to snake_case and see if that’s true:

select(xdf, pkg, fil, func_names) %>%
  unnest() %>%
  filter(stri_detect_regex(func_names, "^(\\.is|is)")) %>%
  mutate(func_names = snakecase::to_snake_case(func_names)) %>%
  count(func_names, sort=TRUE)
## # A tibble: 547 x 2
##    func_names       n
##  1 is_false        24
##  2 is_windows      19
##  3 is_string       18
##  4 is_empty        13
##  5 is_dir          11
##  6 is_formula      11
##  7 is_installed    11
##  8 is_linux         9
##  9 is_na            9
## 10 is_error         8
## # ... with 537 more rows

Only 5% (819) out of 14,123 extracted function names are is_; not overwhelming, but a respectable slice.

There are more questions we could ask of function names and styles, but we’ll leave some work for y’all to do on your own.

Let’s head over to the final rooftop exercise.

Code, Comment & Blank Line Density

Since we have the raw source, we can also take a look at coding style. There are many questions we could ask here and more than a few packages we could draw on to help answer them. For now, we’ll just take a look at the mean ratios of comments and blank lines to code across the packages in this utility belt corpus and give you the opportunity to tease out other interesting tidbits such as “what base R and other package functions are most often used in utility belt functions?” or “are package authors using evil = for assignment or proper <-?”.

xdf %>%
  mutate(
    num_lines = stri_count_fixed(xdf$file_src, "\n"),
    num_blank_lines = stri_count_regex(xdf$file_src, "^[[:space:]]*$", opts_regex = stri_opts_regex(multiline=TRUE)),
    num_whole_line_comments = lengths(cmnt_df$comments),
    comment_density = num_whole_line_comments / (num_lines - num_blank_lines - num_whole_line_comments),
    blank_density = num_blank_lines / (num_lines - num_whole_line_comments)
  ) %>%
  select(-permsissions, -links, -owner, -group, month, -day, -year_hr) -> xdf

# now compute mean ratios
group_by(xdf, pkg) %>%
  summarise(
    `Comment-to-code Ratio` = mean(comment_density),
    `Blank lines-to-code Ratio` = mean(blank_density)
  ) %>%
  ungroup() %>%
  filter(!is.infinite(`Comment-to-code Ratio`)) %>%
  filter(!is.nan(`Comment-to-code Ratio`)) %>%
  filter(!is.infinite(`Blank lines-to-code Ratio`)) %>%
  filter(!is.nan(`Blank lines-to-code Ratio`)) %>%
  gather(measure, value, -pkg) -> code_ratios

# we want to label the median values
group_by(code_ratios, measure) %>%
  summarise(median = median(value)) -> code_ratio_meds

ggplot(code_ratios, aes(measure, value, group=measure)) +
  ggbeeswarm::geom_quasirandom(
    fill="lightslategray", color="#2b2b2b", alpha=1/2,
    stroke=0.25, size=3, shape=21
  ) +
  geom_boxplot(fill="#00000000", outlier.colour = "#00000000") +
  geom_label(
    data = code_ratio_meds,
    aes(-Inf, c(0.3, 5), label=sprintf("Median:\n%s", round(median, 2)), group=measure),
    family = font_rc, size=3, color="lightslateblue", hjust = 0, label.size=0
  ) +
  scale_y_continuous() +
  labs(
    x = NULL, y = NULL,
    caption = "Note free y scale"
  ) +
  facet_wrap(~measure, scales="free") +
  theme_ipsum_rc(grid="Y", strip_text_face = "bold") +
  theme(axis.text.x=element_blank())

FIN

You can find the (on-drive) ~10MB data frame is at: https://rud.is/dl/utility-belt.rds.

All the above code in this gist: https://gist.github.com/hrbrmstr/33d29bb39eaa7f2f1e95308038f85b59.

If you do your own ‘utility belt’ analyses, drop a note in the comments with a link to your findings!

Modern MacBook Pros have a fairly useless (c’mon, admit it!) “Touch Bar” that did little more than cause severe ire in the developer community after turning a full-fledged, tactile Escape key into a hollow version if its former self.

Having said, that, some apps do make OK use of it, with Fantastical and Omnigraffle being two of the better implementations.

While I don’t need Touch Bar support in RStudio (I prefer developing cross-device keyboard shortcut muscle memory), I do have a Touch Bar Mac and came across this blog post which caused me to create this:

using BetterTouchTool (BTT).

This is a clearer, “proper screenshot” (Cmd-Shift-6) of it:

The rightmost two Touch Bar icons are technically not RStudio components, but I prefer iTerm to the built-in RStudio javascript terminal and felt compelled to write a curl … | jq … “one-liner” to get local weather data from the Dark Sky API, since there’s amazing support for scheduled actions to update the button display.

BTT is not free (~$7.00 USD) and, so far, I’ve only wired up basic keyboard shortcuts for RStudio with it. I have setup other BTT configurations for general and app-specific use for other apps and even setup a few “monitoring” ones that let me see the status off various external web services that I usually monitor in other ways. And, I plan on exploring ways to have it be a bit more dynamic with regard to the RStudio context (i.e. package, project or one of those “37 Untitled Document” type of session days).

The config (minus the weather and iTerm) is below and can be added into any BTT config file pretty easily. If you come up with some creative RStudio or other data-sci workflows with BTT, drop a link to the in the comments.

{
      "BTTAppBundleIdentifier" : "org.rstudio.RStudio",
      "BTTAppName" : "RStudio",
      "BTTAppProcessMatchMode" : 2,
      "BTTAppProcessName" : "RStudio",
      "BTTAppSpecificSettings" : {

      },
      "BTTTriggers" : [
        {
          "BTTTouchBarButtonName" : "New",
          "BTTTriggerType" : 629,
          "BTTTriggerClass" : "BTTTriggerTypeTouchBar",
          "BTTPredefinedActionType" : -1,
          "BTTPredefinedActionName" : "No Action",
          "BTTShortcutToSend" : "56,55,45",
          "BTTEnabled" : 1,
          "BTTOrder" : 1,
          "BTTIconData" : "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSCAktEAEpoTdBepUaepcONkISIJSICUHEji4q2AuiWNFVEEXXAsiiImJnUbDXhyIqyrpYsKHyJgV0fe175\/vmzp8z55z5z9wzk7kAKNuy8\/JyUBUAcgX5wpggP2ZScgqT9BigQAuQgQcgsTmiPN\/o6HAAZaT\/u7y\/CRBJf81aEutfx\/+rqHJ5Ig4ASDTEaVwRJxfiowDgmpw8YT4AhHaoN5qZnyfBAxCrCyFBAIi4BGfIsKYEp8nwOKlNXAwLYh8AyFQ2W5gBgJKEN7OAkwHjKEk42gq4fAHEWyD24mSyuRDfh3hcbu50iJXJEJun\/RAn428x00ZjstkZo1iWi1TI\/nxRXg571v+5HP9bcnPEI3MYwkbNFAbHSHKG61adPT1MgqkQNwvSIqMgVoP4Ap8rtZfgu5ni4Hi5fT9HxIJrBhgAvm4u2z8MYh2IGeLseF85tmcLpb7QHo3k54fEyXGacHqMPD5aIMiJDJfHWZrJCxnB23iigNgRm3R+YAjEsNLQo0WZcYkynmhbAT8hEmIliK+KsmPD5L4PizJZkSM2QnGMhLMxxO\/ShYExMhtMM1c0khdmw2FL54K1gPnkZ8YFy3yxJJ4oKXyEA5fnHyDjgHF5gng5NwxWl1+M3LckLydabo9t4+UExcjWGTskKogd8e3KhwUmWwfscRY7NFo+1\/u8\/Og4GTccBeGABfwBE4hhSwPTQRbgd\/Q39MNfspFAwAZCkAF4wFquGfFIlI4I4DMWFIE\/IeIB0aifn3SUBwqg\/uuoVva0BunS0QKpRzZ4CnEuro174R54OHz6wGaPu+JuI35M5ZFZiQFEf2IwMZBoMcqDA1nnwCYE\/H+jC4M9D2Yn4SIYyeF7PMJTQifhMeEGoZtwBySAJ9Iocqtp\/GLhT8yZIAJ0w2iB8uzSfswON4WsnXA\/3BPyh9xxBq4NrHFHmIkv7g1zc4LaHxmKR7l9X8uf55Ow\/jEfuV7JUslJziJt9M2wRq1+jsL6YY24sA\/72RJbih3BzmOnsYtYM9YAmNgprBFrx05I8GglPJFWwshsMVJu2TAOf8TGtta2z\/bLT3Oz5fNL1kuUzyvMl2wG1vS8WUJ+RmY+0xeexjxmiIBjM45pb2vnCoDkbJcdHW8Z0jMbYVz6riteAYCn4\/DwcPN3XbgyAEdhTVN6vuvM3eF2LQTgwkqOWFgg00mOY0AAFKAMd4UW0ANGwBzmYw+c4X+IDwgAoSAKxIFkMBWueCbIhZxngjlgISgBZWA12AA2g+1gF6gGB8Bh0ACawWlwDlwGV8ENcA\/WRS94CQbAezCEIAgJoSF0RAvRR0wQK8QecUW8kAAkHIlBkpFUJAMRIGJkDrIIKUPWIpuRnUgN8htyHDmNXEQ6kTvII6QPeYN8RjGUiqqjuqgpOh51RX3RMDQOnYJmoDPQInQxuhKtQKvQ\/Wg9ehq9jN5Au9GX6CAGMEWMgRlg1pgrxsKisBQsHRNi87BSrByrwuqwJvier2HdWD\/2CSfidJyJW8PaDMbjcQ4+A5+HL8c349V4Pd6GX8Mf4QP4NwKNoEOwIrgTQghJhAzCTEIJoZywh3CMcBbum17CeyKRyCCaEV3gvkwmZhFnE5cTtxIPEluIncQe4iCJRNIiWZE8SVEkNimfVELaRNpPOkXqIvWSPpIVyfpke3IgOYUsIBeTy8n7yCfJXeRn5CEFFQUTBXeFKAWuwiyFVQq7FZoUrij0KgxRVClmFE9KHCWLspBSQamjnKXcp7xVVFQ0VHRTnKjIV1ygWKF4SPGC4iPFT1Q1qiWVRZ1MFVNXUvdSW6h3qG9pNJopzYeWQsunraTV0M7QHtI+KtGVbJRClLhK85UqleqVupReKSsomyj7Kk9VLlIuVz6ifEW5X0VBxVSFpcJWmadSqXJc5ZbKoCpd1U41SjVXdbnqPtWLqs\/VSGqmagFqXLXFarvUzqj10DG6EZ1F59AX0XfTz9J71YnqZuoh6lnqZeoH1DvUBzTUNBw1EjQKNSo1Tmh0MzCGKSOEkcNYxTjMuMn4PEZ3jO8Y3phlY+rGdI35oDlW00eTp1mqeVDzhuZnLaZWgFa21hqtBq0H2ri2pfZE7Zna27TPavePVR\/rMZYztnTs4bF3dVAdS50Yndk6u3TadQZ19XSDdPN0N+me0e3XY+j56GXprdc7qdenT9f30ufrr9c\/pf+CqcH0ZeYwK5htzAEDHYNgA7HBToMOgyFDM8N4w2LDg4YPjChGrkbpRuuNWo0GjPWNI4znGNca3zVRMHE1yTTZaHLe5IOpmWmi6RLTBtPnZppmIWZFZrVm981p5t7mM8yrzK9bEC1cLbIttlpctUQtnSwzLSstr1ihVs5WfKutVp3jCOPcxgnGVY27ZU219rUusK61fmTDsAm3KbZpsHk13nh8yvg148+P\/2brZJtju9v2np2aXahdsV2T3Rt7S3uOfaX9dQeaQ6DDfIdGh9eOVo48x22Ot53oThFOS5xanb46uzgLneuc+1yMXVJdtrjcclV3jXZd7nrBjeDm5zbfrdntk7uze777Yfe\/PKw9sj32eTyfYDaBN2H3hB5PQ0+2507Pbi+mV6rXDq9ubwNvtneV92MfIx+uzx6fZ74Wvlm++31f+dn6Cf2O+X1gubPmslr8Mf8g\/1L\/jgC1gPiAzQEPAw0DMwJrAweCnIJmB7UEE4LDgtcE3wrRDeGE1IQMhLqEzg1tC6OGxYZtDnscbhkuDG+KQCNCI9ZF3I80iRRENkSBqJCodVEPos2iZ0T\/PpE4MXpi5cSnMXYxc2LOx9Jjp8Xui30f5xe3Ku5evHm8OL41QTlhckJNwodE\/8S1id1J45PmJl1O1k7mJzemkFISUvakDE4KmLRhUu9kp8klk29OMZtSOOXiVO2pOVNPTFOexp52JJWQmpi6L\/ULO4pdxR5MC0nbkjbAYXE2cl5yfbjruX08T95a3rN0z\/S16c8zPDPWZfRlemeWZ\/bzWfzN\/NdZwVnbsz5kR2XvzR7OScw5mEvOTc09LlATZAvaputNL5zemWeVV5LXPcN9xoYZA8Iw4R4RIpoiasxXh9ecdrG5+BfxowKvgsqCjzMTZh4pVC0UFLbPspy1bNazosCiX2fjszmzW+cYzFk459Fc37k75yHz0ua1zjeav3h+74KgBdULKQuzF\/5RbFu8tvjdosRFTYt1Fy9Y3PNL0C+1JUolwpJbSzyWbF+KL+Uv7VjmsGzTsm+l3NJLZbZl5WVflnOWX1pht6JixfDK9JUdq5xXbVtNXC1YfXON95rqtapri9b2rItYV7+eub50\/bsN0zZcLHcs376RslG8sbsivKJxk\/Gm1Zu+bM7cfKPSr\/LgFp0ty7Z82Mrd2rXNZ1vddt3tZds\/7+DvuL0zaGd9lWlV+S7iroJdT3cn7D7\/q+uvNXu095Tt+bpXsLe7Oqa6rcalpmafzr5VtWituLZv\/+T9Vw\/4H2iss67beZBxsOwQOCQ+9OK31N9uHg473HrE9UjdUZOjW47Rj5XWI\/Wz6gcaMhu6G5MbO4+HHm9t8mg69rvN73ubDZorT2icWHWScnLxyeFTRacGW\/Ja+k9nnO5pndZ670zSmettE9s6zoadvXAu8NyZ877nT13wvNB80f3i8UuulxouO1+ub3dqP\/aH0x\/HOpw76q+4XGm86na1qXNC58ku767T1\/yvnbsecv3yjcgbnTfjb96+NflW923u7ed3cu68vltwd+jegvuE+6UPVB6UP9R5WPUPi38c7HbuPvHI\/1H749jH93o4PS+fiJ586V38lPa0\/Jn+s5rn9s+b+wL7rr6Y9KL3Zd7Lof6SP1X\/3PLK\/NXRv3z+ah9IGuh9LXw9\/Gb5W623e985vmsdjB58+D73\/dCH0o9aH6s\/uX46\/znx87OhmV9IXyq+Wnxt+hb27f5w7vBwHlvIll4FMNjQ9HQA3uwFgJYMAP0qvD8oyb69pILIvhelCPwnLPs+k4ozAHWwk1y5WS0AHILNFDbaAgAkV+84H4A6OIw2uYjSHexlsajwC4bwcXj4rS4ApCYAvgqHh4e2Dg9\/3Q3J3gGgZYbsm08iRHi\/3yGN0cUoXAB+kn8CCCdsu7q1Xy8AAAAJcEhZcwAAFiUAABYlAUlSJPAAAAYMSURBVHgB7ZzLbhU3HMYJLRTKJYXQcgtpxKotIIHEusmyD4DEDsEL8AYgwUOwZovEA7CEBZuqUpEqddEdCRJRUiil9wuX7zeM0bmNL8dn4uOJ\/9Inn5nxeOxvvrH9\/9vJzLZ39omSywLpNNv\/qtya8EK4J\/wuJLUP66fPKN0vzCatjfvhr5TlX+EDYU7YIfwqvBaSmCFwn55+QVhIUgv\/h75RVsj6TTgnrAq3hF+EJGYIzEWBhqRd+nGiPvi0TpMo0RBoKpZLulsVXRJeCrz8FSGJEnMlENL21OTNK+XTTqLEXAkUX5UlV2LuBCZXYu4EJldiKIH0NX8KpMzJ2jYU9pFAutPysGRKDCXwLzXigcDo97OAZ9Cm0cedF5jkLwpMnG226X1iKIGoDuKYuD4RNoPAw3rOAYEU5U+VEkMJRIH3BTyAH4R\/hDZtuwq\/KxwRbgrHhUVhapQYSqDpA3Himfn\/LbRp9G3\/CTyXl8bxuErkPl6I8Vgm4j\/jlGNEYa7UKcdN9ocuEAWhEkRF2v6E9YjqGfi+3wvfCV8INJ5+0dRfP0caAkG1JwVcv9PCTwL15sVEW6gCox84RgGQRWPXBRQ4jhIher6+\/5hSjul+IDJKieYNTrMC1cbKaOy4SqSdfMKLwinhnPBIIDQWpcQcFKg2VharRKY45pOmwKMCfWuUEnNSII3GYpVI7JPAw5dCtBJzUqDaW1mMEimAeSR9KX0hCoxSYo4KVJsrmwol5qhAQ+AoJXLtM4GItVEa50aZuT6oRJwFXo6X5axA08BeJX6rkwsCA8NBwSUQ2t\/bJ57V8UOB+a6XuR7gVUjiTL1KpE97LHBuXvi4Bn1ek\/UqkSmNEVVT\/r7zXSCQBkEcquHzuyEwyl4V8D6WBYi0Gb7153UGl5\/dV05XCKRRqA5sCHzWqwLKw+2EYEh0KRE1BlmXCOxtOKSxSocSIc9XicoaZl0lECUSs0Rx6wJeCLHMiRvhnS4bfeL9GvyeuHWdQD5f1nDMOk4hcOIMRBbYdQVG0uO+vRDo5siaoxBopcd9sRDo5siaoxBopcd9sRDo5siaoxBopcd9sRDo5siao6u+sGk0nggxPgKs+MQuI5KDH+1tXScQ8h4LEHNNINRvM1y+Z7YMg9e6TiAKRH0Y8UFXvI+8QVGbrUAgkWqUxa4GW0BVl6vYIbsVvK3rBEIEAyVgp6tr0KT\/47NHuV7WdQJZICIqzU6ur4U9gs3Yect+RFIv6zqBfLIoj\/UQwvosYdqMKPaWXJVrIsUocEEZLgqHmzLW51eU3hYg0su2ggIhkXbO1rARw6fuGmj67nd1qn2Zy8EwA4XAYU6CzhQCg+gazlwIHOYk6EwhMIiu4cw5j8L4tQiAuV2TEAgeHBAYgZvy6NJ7Iw8Tb9ffv+CxEHR4nSuBZjcVjb0k8FcGowxC2CfIRNo1ieb+OeG6gO9sM6I7N4WNXAkc9DAgaZRBIOThjfh4GORhX6GJ4OjnSHtfXs4E4tfyeZ4SmjwMMykm9dn3R7fwleAKJqwoTxUay5lASEFh9HOuQKmyeJlRtiszCqxeDhUoFsFAITCCPG4tBBYCIxmIvL0oMJLAXEdhphmsnrEbnw3ltlGYuR0jJvPBauRU2mSUy+KTaxpDyL\/KkyuBkIc3AIF3hCYvg\/YdEgiULguQaDPI81kTIWJNXuefQpFnGo23j7eAy7UqMKkeZRAI2SxVkrqMPE8EV0gfBVbl5a5AHPqnQlNfzoT3jHBC+EZoUqouVcY\/00CBvBSbEUwgb7ACTV+yV\/cS4bD1PZTftpmGkI4y6kdDUaqrX+N+ykFdLzjwsVAF8gcrywIS5y8b6YNSGo316bNaq2MogYxodMo40vxOTSAvknoks1ACUeCSwOfg0ym33bAVPeC24Or0W6tHKIH0gU0jXmuVtBQcvI5rKWusS02j11iFbcWbQhWYG0d0NWWHasRbKztUI8jjVhRo1jeYHFdheC40WNmhOkAMBG7KDlXzplzroQP1S36IYqi7zYyX0sq0ywwiONs\/Cs9tNZnCa2uqU9Ce5km3wRDYZQVOmrO+8t4CdZ1lQ7Sx3EwAAAAASUVORK5CYII=",
          "BTTTriggerConfig" : {
            "BTTTouchBarItemIconHeight" : 22,
            "BTTTouchBarItemIconWidth" : 22,
            "BTTTouchBarItemPadding" : 0,
            "BTTTouchBarFreeSpaceAfterButton" : "15.000000",
            "BTTTouchBarButtonColor" : "58.650001, 58.650001, 58.650001, 255.000000",
            "BTTTouchBarAlwaysShowButton" : "0",
            "BTTTouchBarAlternateBackgroundColor" : "0.000000, 0.000000, 0.000000, 0.000000"
          }
        },
        {
          "BTTTouchBarButtonName" : "Build",
          "BTTTriggerType" : 629,
          "BTTTriggerClass" : "BTTTriggerTypeTouchBar",
          "BTTPredefinedActionType" : -1,
          "BTTPredefinedActionName" : "No Action",
          "BTTShortcutToSend" : "56,55,11",
          "BTTEnabled" : 1,
          "BTTOrder" : 3,
          "BTTIconData" : "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSCAktEAEpoTdBepUaepcONkISIJSICUHEji4q2AuiWNFVEEXXAsiiImJnUbDXhyIqyrpYsKHyJgV0fe175\/vmzp8z55z5z9wzk7kAKNuy8\/JyUBUAcgX5wpggP2ZScgqT9BigQAuQgQcgsTmiPN\/o6HAAZaT\/u7y\/CRBJf81aEutfx\/+rqHJ5Ig4ASDTEaVwRJxfiowDgmpw8YT4AhHaoN5qZnyfBAxCrCyFBAIi4BGfIsKYEp8nwOKlNXAwLYh8AyFQ2W5gBgJKEN7OAkwHjKEk42gq4fAHEWyD24mSyuRDfh3hcbu50iJXJEJun\/RAn428x00ZjstkZo1iWi1TI\/nxRXg571v+5HP9bcnPEI3MYwkbNFAbHSHKG61adPT1MgqkQNwvSIqMgVoP4Ap8rtZfgu5ni4Hi5fT9HxIJrBhgAvm4u2z8MYh2IGeLseF85tmcLpb7QHo3k54fEyXGacHqMPD5aIMiJDJfHWZrJCxnB23iigNgRm3R+YAjEsNLQo0WZcYkynmhbAT8hEmIliK+KsmPD5L4PizJZkSM2QnGMhLMxxO\/ShYExMhtMM1c0khdmw2FL54K1gPnkZ8YFy3yxJJ4oKXyEA5fnHyDjgHF5gng5NwxWl1+M3LckLydabo9t4+UExcjWGTskKogd8e3KhwUmWwfscRY7NFo+1\/u8\/Og4GTccBeGABfwBE4hhSwPTQRbgd\/Q39MNfspFAwAZCkAF4wFquGfFIlI4I4DMWFIE\/IeIB0aifn3SUBwqg\/uuoVva0BunS0QKpRzZ4CnEuro174R54OHz6wGaPu+JuI35M5ZFZiQFEf2IwMZBoMcqDA1nnwCYE\/H+jC4M9D2Yn4SIYyeF7PMJTQifhMeEGoZtwBySAJ9Iocqtp\/GLhT8yZIAJ0w2iB8uzSfswON4WsnXA\/3BPyh9xxBq4NrHFHmIkv7g1zc4LaHxmKR7l9X8uf55Ow\/jEfuV7JUslJziJt9M2wRq1+jsL6YY24sA\/72RJbih3BzmOnsYtYM9YAmNgprBFrx05I8GglPJFWwshsMVJu2TAOf8TGtta2z\/bLT3Oz5fNL1kuUzyvMl2wG1vS8WUJ+RmY+0xeexjxmiIBjM45pb2vnCoDkbJcdHW8Z0jMbYVz6riteAYCn4\/DwcPN3XbgyAEdhTVN6vuvM3eF2LQTgwkqOWFgg00mOY0AAFKAMd4UW0ANGwBzmYw+c4X+IDwgAoSAKxIFkMBWueCbIhZxngjlgISgBZWA12AA2g+1gF6gGB8Bh0ACawWlwDlwGV8ENcA\/WRS94CQbAezCEIAgJoSF0RAvRR0wQK8QecUW8kAAkHIlBkpFUJAMRIGJkDrIIKUPWIpuRnUgN8htyHDmNXEQ6kTvII6QPeYN8RjGUiqqjuqgpOh51RX3RMDQOnYJmoDPQInQxuhKtQKvQ\/Wg9ehq9jN5Au9GX6CAGMEWMgRlg1pgrxsKisBQsHRNi87BSrByrwuqwJvier2HdWD\/2CSfidJyJW8PaDMbjcQ4+A5+HL8c349V4Pd6GX8Mf4QP4NwKNoEOwIrgTQghJhAzCTEIJoZywh3CMcBbum17CeyKRyCCaEV3gvkwmZhFnE5cTtxIPEluIncQe4iCJRNIiWZE8SVEkNimfVELaRNpPOkXqIvWSPpIVyfpke3IgOYUsIBeTy8n7yCfJXeRn5CEFFQUTBXeFKAWuwiyFVQq7FZoUrij0KgxRVClmFE9KHCWLspBSQamjnKXcp7xVVFQ0VHRTnKjIV1ygWKF4SPGC4iPFT1Q1qiWVRZ1MFVNXUvdSW6h3qG9pNJopzYeWQsunraTV0M7QHtI+KtGVbJRClLhK85UqleqVupReKSsomyj7Kk9VLlIuVz6ifEW5X0VBxVSFpcJWmadSqXJc5ZbKoCpd1U41SjVXdbnqPtWLqs\/VSGqmagFqXLXFarvUzqj10DG6EZ1F59AX0XfTz9J71YnqZuoh6lnqZeoH1DvUBzTUNBw1EjQKNSo1Tmh0MzCGKSOEkcNYxTjMuMn4PEZ3jO8Y3phlY+rGdI35oDlW00eTp1mqeVDzhuZnLaZWgFa21hqtBq0H2ri2pfZE7Zna27TPavePVR\/rMZYztnTs4bF3dVAdS50Yndk6u3TadQZ19XSDdPN0N+me0e3XY+j56GXprdc7qdenT9f30ufrr9c\/pf+CqcH0ZeYwK5htzAEDHYNgA7HBToMOgyFDM8N4w2LDg4YPjChGrkbpRuuNWo0GjPWNI4znGNca3zVRMHE1yTTZaHLe5IOpmWmi6RLTBtPnZppmIWZFZrVm981p5t7mM8yrzK9bEC1cLbIttlpctUQtnSwzLSstr1ihVs5WfKutVp3jCOPcxgnGVY27ZU219rUusK61fmTDsAm3KbZpsHk13nh8yvg148+P\/2brZJtju9v2np2aXahdsV2T3Rt7S3uOfaX9dQeaQ6DDfIdGh9eOVo48x22Ot53oThFOS5xanb46uzgLneuc+1yMXVJdtrjcclV3jXZd7nrBjeDm5zbfrdntk7uze777Yfe\/PKw9sj32eTyfYDaBN2H3hB5PQ0+2507Pbi+mV6rXDq9ubwNvtneV92MfIx+uzx6fZ74Wvlm++31f+dn6Cf2O+X1gubPmslr8Mf8g\/1L\/jgC1gPiAzQEPAw0DMwJrAweCnIJmB7UEE4LDgtcE3wrRDeGE1IQMhLqEzg1tC6OGxYZtDnscbhkuDG+KQCNCI9ZF3I80iRRENkSBqJCodVEPos2iZ0T\/PpE4MXpi5cSnMXYxc2LOx9Jjp8Xui30f5xe3Ku5evHm8OL41QTlhckJNwodE\/8S1id1J45PmJl1O1k7mJzemkFISUvakDE4KmLRhUu9kp8klk29OMZtSOOXiVO2pOVNPTFOexp52JJWQmpi6L\/ULO4pdxR5MC0nbkjbAYXE2cl5yfbjruX08T95a3rN0z\/S16c8zPDPWZfRlemeWZ\/bzWfzN\/NdZwVnbsz5kR2XvzR7OScw5mEvOTc09LlATZAvaputNL5zemWeVV5LXPcN9xoYZA8Iw4R4RIpoiasxXh9ecdrG5+BfxowKvgsqCjzMTZh4pVC0UFLbPspy1bNazosCiX2fjszmzW+cYzFk459Fc37k75yHz0ua1zjeav3h+74KgBdULKQuzF\/5RbFu8tvjdosRFTYt1Fy9Y3PNL0C+1JUolwpJbSzyWbF+KL+Uv7VjmsGzTsm+l3NJLZbZl5WVflnOWX1pht6JixfDK9JUdq5xXbVtNXC1YfXON95rqtapri9b2rItYV7+eub50\/bsN0zZcLHcs376RslG8sbsivKJxk\/Gm1Zu+bM7cfKPSr\/LgFp0ty7Z82Mrd2rXNZ1vddt3tZds\/7+DvuL0zaGd9lWlV+S7iroJdT3cn7D7\/q+uvNXu095Tt+bpXsLe7Oqa6rcalpmafzr5VtWituLZv\/+T9Vw\/4H2iss67beZBxsOwQOCQ+9OK31N9uHg473HrE9UjdUZOjW47Rj5XWI\/Wz6gcaMhu6G5MbO4+HHm9t8mg69rvN73ubDZorT2icWHWScnLxyeFTRacGW\/Ja+k9nnO5pndZ670zSmettE9s6zoadvXAu8NyZ877nT13wvNB80f3i8UuulxouO1+ub3dqP\/aH0x\/HOpw76q+4XGm86na1qXNC58ku767T1\/yvnbsecv3yjcgbnTfjb96+NflW923u7ed3cu68vltwd+jegvuE+6UPVB6UP9R5WPUPi38c7HbuPvHI\/1H749jH93o4PS+fiJ586V38lPa0\/Jn+s5rn9s+b+wL7rr6Y9KL3Zd7Lof6SP1X\/3PLK\/NXRv3z+ah9IGuh9LXw9\/Gb5W623e985vmsdjB58+D73\/dCH0o9aH6s\/uX46\/znx87OhmV9IXyq+Wnxt+hb27f5w7vBwHlvIll4FMNjQ9HQA3uwFgJYMAP0qvD8oyb69pILIvhelCPwnLPs+k4ozAHWwk1y5WS0AHILNFDbaAgAkV+84H4A6OIw2uYjSHexlsajwC4bwcXj4rS4ApCYAvgqHh4e2Dg9\/3Q3J3gGgZYbsm08iRHi\/3yGN0cUoXAB+kn8CCCdsu7q1Xy8AAAAJcEhZcwAAFiUAABYlAUlSJPAAAAbCSURBVHgB7ZxPjxRFGIcRdf2D4l+WuAKLcd0YDnpCT4ZN\/Ape9ehBE41697DRK0fima9g0IuR1S9AjIkH8IDLsuIaRBQVRNHn6UwltbM90z3TtTvVO\/0mP2u2p7tm6tdPVb1VPbJnTxeNHLir0dU7e\/EMHxe+byjrfoP\/OPFvZJk07kla2\/ZVdi9Vz6P70INoLxolbnHyd8gyaeRuoKRp2APoUK98iHJUAm9yzQb6E11Hd1CSyN1AjTuBNO8d9AQyRjXwBtd8gS6hU+gaShK5GhjI208rNe8ImkOPoXHiYS463LvwQK9MQmKuBgbybPT7yEY\/0mv4OEWo7zcu9uasoiQk5mig3+l+9DTSQM0blzwuLULT9iFLiXY2TkLi3VSUU\/h9ZtEz6GP0KpI8G54ivDnemKPI8XQRfYucZMaKHAkcqyE1L\/JGJCUxNwLtWtLwB\/odXUAvIMewlJGMxBwJ\/AenNPEyMmH+GRl25VET6OLCkv8kIzE3AkNbNXEdXURX0XmUJYk5EohXxSxpN7ZLryGJyZLEXAnEryKyJzFXAoOBEpg1ibkTGIzMlsTcCQwGZktiWwgMRmZHYlsIDAYGEp2Vf0HusiTb2wsfQtmfJ\/oZ5qHuarun6Pcoom0Ghu+9U2XYxTGF+ga5i7OCNLGIthkoGe5QS97jyF2aVKsTqtoSgUTJO4QCiR4vSGzbGKh5S+gl9EHvdcrdGqosDZ\/FuBJ6ETl0eONcat5uC4GBvP18aUlItU9IVbVCysOepJ\/tGFiszdtiYBiLUu1Q13Kt5CRNews5JnpTr+Zu4KTJw6NNIYmaeBvZrWdyNzAX8vCqiJBG\/cpfF9GVXA1sSp6z5XVkg30oLzlOQNY7blinifwG+gn5gOpGrgY2JU\/zTiFTjWPICWAJaeI4EcjTuGW0is6hm7kZKCE+kTPPG2e2DeQ5yF9CPlDfh9zRCUSOSmJM3o\/UY71u9v6FsktjHJiPI2fb99BB5KBdNwJ5EvIp0sDP0ZNIiqx3CdUlcSB51OFEsicnAmd6DXuK0kePB5Bdr070k7fGRZppFzY0QnIk3OP+XUXiUPK4vohcDHSgn0ca9y7yZxxNyHOAt4uFCGR6UzSvikTPsdtvGfM4VpBHWUQOBkqe456m2TAb6Tq3zjKzijyqKcLzrvVeV5FYi7xeXRMvJO85tIS+Rt8jf8P3L5KCKrku\/Qi9iez6ThjDUpW9vO+wsIg+QWeQpIXP0TzpvYBeRyeQy0dvcmlMksB+8jRgFmnqMBNsSF3yPDeOKhK9cRtoy2wbV5LD650mr7\/Ng0i8wolvoEryQoWTIHAS5IX2hrKfRLvwOjLt2ZTnhQsGlVVdZdB14x6XvKPI2XYZhYnDGykVVeFE4ApjFZnnOV6ZqmjAOOFnOtu78nkeaew55AzuJmpl7CSBOZDXb0ggMaxYfN8bsilV6b9oEn9Pesyr02ZvsBopdoLAJuTZNaXDrrvWU7zC4FCyqNVl+z9tuw2UvHlUNubVGX\/N0VzLOuZ9hvylVrzC4M\/JxnYa2JQ8xyJpi8nT0KmIpmOeRp1BrhYWkDNlnVma03Y2toPAFOSZnkieOZndVhKnIlKSt4hjrluzJC\/czZQEpibPXWVn36mIqSMv3NUUBNrFNHAWuaMyyq6KeZ6zbTzmtYq8pgaay7kHdxC9jY4g17dukNbJ88zpvkJOFieR5rVqwmhioOR5fRl5VQN\/68mj3Y1Cunz0uIBOoxUkObeQ5lQpzvNaMdvSptIYh8B+8nyOYbd1S8ixcFh05OFOR16EyCgEduRFxo36shvzBjhWh8COvAHm1TnckVfh0jACO\/IqzBv2dkfeMHei98oIbEKeVftswfWtT\/cvo1atbfm+jaIpeZp3Hp1FLyOT7LKbxOHdEXHjUpDnv3WwjsJOsj\/G9vcmuz5SkvcKbj2L3GD1puzqkMDU5Dn2+Qsnn+679t314e\/fFtBptIJG2VWJx7ypIg+fipDAR5EPb\/xV\/Byqs6vCacVsG495U0eeJmjga0gDj\/XKqi0pTiu65w+UpikfojBx+D+iTEW3pZ1FaOCoYbedevJi03yOcRx9iczhNEiKyuR7nnMWTeWYR7s3hQSaq5lyrCFnZB8QaZ7H4tC8jrzYkd5rTdMsczepki4p07BAoa891pGHCXFI4B1kzmbuZkLtKsIykMjLjjxNKAuNChES6sMcmEPLyEeWhub2z7YaP\/URz8JlJPqY0tDAqczzitYP+U9MYDgtJjEYbH5n17bsyAtOUZYZ6Nsej\/\/FcCcTf6ts2UXkwCADPUUS4+jIi93oXqdx4H9uTccw8Bmi2QAAAABJRU5ErkJggg=="
        },
        {
          "BTTTouchBarButtonName" : "Help",
          "BTTTriggerType" : 629,
          "BTTTriggerClass" : "BTTTriggerTypeTouchBar",
          "BTTPredefinedActionType" : -1,
          "BTTPredefinedActionName" : "No Action",
          "BTTShortcutToSend" : "59,20",
          "BTTEnabled" : 1,
          "BTTOrder" : 5,
          "BTTIconData" : "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSCAktEAEpoTdBepUaepcONkISIJSICUHEji4q2AuiWNFVEEXXAsiiImJnUbDXhyIqyrpYsKHyJgV0fe175\/vmzp8z55z5z9wzk7kAKNuy8\/JyUBUAcgX5wpggP2ZScgqT9BigQAuQgQcgsTmiPN\/o6HAAZaT\/u7y\/CRBJf81aEutfx\/+rqHJ5Ig4ASDTEaVwRJxfiowDgmpw8YT4AhHaoN5qZnyfBAxCrCyFBAIi4BGfIsKYEp8nwOKlNXAwLYh8AyFQ2W5gBgJKEN7OAkwHjKEk42gq4fAHEWyD24mSyuRDfh3hcbu50iJXJEJun\/RAn428x00ZjstkZo1iWi1TI\/nxRXg571v+5HP9bcnPEI3MYwkbNFAbHSHKG61adPT1MgqkQNwvSIqMgVoP4Ap8rtZfgu5ni4Hi5fT9HxIJrBhgAvm4u2z8MYh2IGeLseF85tmcLpb7QHo3k54fEyXGacHqMPD5aIMiJDJfHWZrJCxnB23iigNgRm3R+YAjEsNLQo0WZcYkynmhbAT8hEmIliK+KsmPD5L4PizJZkSM2QnGMhLMxxO\/ShYExMhtMM1c0khdmw2FL54K1gPnkZ8YFy3yxJJ4oKXyEA5fnHyDjgHF5gng5NwxWl1+M3LckLydabo9t4+UExcjWGTskKogd8e3KhwUmWwfscRY7NFo+1\/u8\/Og4GTccBeGABfwBE4hhSwPTQRbgd\/Q39MNfspFAwAZCkAF4wFquGfFIlI4I4DMWFIE\/IeIB0aifn3SUBwqg\/uuoVva0BunS0QKpRzZ4CnEuro174R54OHz6wGaPu+JuI35M5ZFZiQFEf2IwMZBoMcqDA1nnwCYE\/H+jC4M9D2Yn4SIYyeF7PMJTQifhMeEGoZtwBySAJ9Iocqtp\/GLhT8yZIAJ0w2iB8uzSfswON4WsnXA\/3BPyh9xxBq4NrHFHmIkv7g1zc4LaHxmKR7l9X8uf55Ow\/jEfuV7JUslJziJt9M2wRq1+jsL6YY24sA\/72RJbih3BzmOnsYtYM9YAmNgprBFrx05I8GglPJFWwshsMVJu2TAOf8TGtta2z\/bLT3Oz5fNL1kuUzyvMl2wG1vS8WUJ+RmY+0xeexjxmiIBjM45pb2vnCoDkbJcdHW8Z0jMbYVz6riteAYCn4\/DwcPN3XbgyAEdhTVN6vuvM3eF2LQTgwkqOWFgg00mOY0AAFKAMd4UW0ANGwBzmYw+c4X+IDwgAoSAKxIFkMBWueCbIhZxngjlgISgBZWA12AA2g+1gF6gGB8Bh0ACawWlwDlwGV8ENcA\/WRS94CQbAezCEIAgJoSF0RAvRR0wQK8QecUW8kAAkHIlBkpFUJAMRIGJkDrIIKUPWIpuRnUgN8htyHDmNXEQ6kTvII6QPeYN8RjGUiqqjuqgpOh51RX3RMDQOnYJmoDPQInQxuhKtQKvQ\/Wg9ehq9jN5Au9GX6CAGMEWMgRlg1pgrxsKisBQsHRNi87BSrByrwuqwJvier2HdWD\/2CSfidJyJW8PaDMbjcQ4+A5+HL8c349V4Pd6GX8Mf4QP4NwKNoEOwIrgTQghJhAzCTEIJoZywh3CMcBbum17CeyKRyCCaEV3gvkwmZhFnE5cTtxIPEluIncQe4iCJRNIiWZE8SVEkNimfVELaRNpPOkXqIvWSPpIVyfpke3IgOYUsIBeTy8n7yCfJXeRn5CEFFQUTBXeFKAWuwiyFVQq7FZoUrij0KgxRVClmFE9KHCWLspBSQamjnKXcp7xVVFQ0VHRTnKjIV1ygWKF4SPGC4iPFT1Q1qiWVRZ1MFVNXUvdSW6h3qG9pNJopzYeWQsunraTV0M7QHtI+KtGVbJRClLhK85UqleqVupReKSsomyj7Kk9VLlIuVz6ifEW5X0VBxVSFpcJWmadSqXJc5ZbKoCpd1U41SjVXdbnqPtWLqs\/VSGqmagFqXLXFarvUzqj10DG6EZ1F59AX0XfTz9J71YnqZuoh6lnqZeoH1DvUBzTUNBw1EjQKNSo1Tmh0MzCGKSOEkcNYxTjMuMn4PEZ3jO8Y3phlY+rGdI35oDlW00eTp1mqeVDzhuZnLaZWgFa21hqtBq0H2ri2pfZE7Zna27TPavePVR\/rMZYztnTs4bF3dVAdS50Yndk6u3TadQZ19XSDdPN0N+me0e3XY+j56GXprdc7qdenT9f30ufrr9c\/pf+CqcH0ZeYwK5htzAEDHYNgA7HBToMOgyFDM8N4w2LDg4YPjChGrkbpRuuNWo0GjPWNI4znGNca3zVRMHE1yTTZaHLe5IOpmWmi6RLTBtPnZppmIWZFZrVm981p5t7mM8yrzK9bEC1cLbIttlpctUQtnSwzLSstr1ihVs5WfKutVp3jCOPcxgnGVY27ZU219rUusK61fmTDsAm3KbZpsHk13nh8yvg148+P\/2brZJtju9v2np2aXahdsV2T3Rt7S3uOfaX9dQeaQ6DDfIdGh9eOVo48x22Ot53oThFOS5xanb46uzgLneuc+1yMXVJdtrjcclV3jXZd7nrBjeDm5zbfrdntk7uze777Yfe\/PKw9sj32eTyfYDaBN2H3hB5PQ0+2507Pbi+mV6rXDq9ubwNvtneV92MfIx+uzx6fZ74Wvlm++31f+dn6Cf2O+X1gubPmslr8Mf8g\/1L\/jgC1gPiAzQEPAw0DMwJrAweCnIJmB7UEE4LDgtcE3wrRDeGE1IQMhLqEzg1tC6OGxYZtDnscbhkuDG+KQCNCI9ZF3I80iRRENkSBqJCodVEPos2iZ0T\/PpE4MXpi5cSnMXYxc2LOx9Jjp8Xui30f5xe3Ku5evHm8OL41QTlhckJNwodE\/8S1id1J45PmJl1O1k7mJzemkFISUvakDE4KmLRhUu9kp8klk29OMZtSOOXiVO2pOVNPTFOexp52JJWQmpi6L\/ULO4pdxR5MC0nbkjbAYXE2cl5yfbjruX08T95a3rN0z\/S16c8zPDPWZfRlemeWZ\/bzWfzN\/NdZwVnbsz5kR2XvzR7OScw5mEvOTc09LlATZAvaputNL5zemWeVV5LXPcN9xoYZA8Iw4R4RIpoiasxXh9ecdrG5+BfxowKvgsqCjzMTZh4pVC0UFLbPspy1bNazosCiX2fjszmzW+cYzFk459Fc37k75yHz0ua1zjeav3h+74KgBdULKQuzF\/5RbFu8tvjdosRFTYt1Fy9Y3PNL0C+1JUolwpJbSzyWbF+KL+Uv7VjmsGzTsm+l3NJLZbZl5WVflnOWX1pht6JixfDK9JUdq5xXbVtNXC1YfXON95rqtapri9b2rItYV7+eub50\/bsN0zZcLHcs376RslG8sbsivKJxk\/Gm1Zu+bM7cfKPSr\/LgFp0ty7Z82Mrd2rXNZ1vddt3tZds\/7+DvuL0zaGd9lWlV+S7iroJdT3cn7D7\/q+uvNXu095Tt+bpXsLe7Oqa6rcalpmafzr5VtWituLZv\/+T9Vw\/4H2iss67beZBxsOwQOCQ+9OK31N9uHg473HrE9UjdUZOjW47Rj5XWI\/Wz6gcaMhu6G5MbO4+HHm9t8mg69rvN73ubDZorT2icWHWScnLxyeFTRacGW\/Ja+k9nnO5pndZ670zSmettE9s6zoadvXAu8NyZ877nT13wvNB80f3i8UuulxouO1+ub3dqP\/aH0x\/HOpw76q+4XGm86na1qXNC58ku767T1\/yvnbsecv3yjcgbnTfjb96+NflW923u7ed3cu68vltwd+jegvuE+6UPVB6UP9R5WPUPi38c7HbuPvHI\/1H749jH93o4PS+fiJ586V38lPa0\/Jn+s5rn9s+b+wL7rr6Y9KL3Zd7Lof6SP1X\/3PLK\/NXRv3z+ah9IGuh9LXw9\/Gb5W623e985vmsdjB58+D73\/dCH0o9aH6s\/uX46\/znx87OhmV9IXyq+Wnxt+hb27f5w7vBwHlvIll4FMNjQ9HQA3uwFgJYMAP0qvD8oyb69pILIvhelCPwnLPs+k4ozAHWwk1y5WS0AHILNFDbaAgAkV+84H4A6OIw2uYjSHexlsajwC4bwcXj4rS4ApCYAvgqHh4e2Dg9\/3Q3J3gGgZYbsm08iRHi\/3yGN0cUoXAB+kn8CCCdsu7q1Xy8AAAAJcEhZcwAAFiUAABYlAUlSJPAAAA5mSURBVHgB7drNi2RXHcZxE5M4ji+JeZnMa3eDMy4GkQRi0KDSC8VXcBOCBJRsBIniQsSNmzArCaL4H0hAF5KFiAlZhDgkxMUkYMA4EmYW3fM+k0wyScwkxkn0+dyp06mqruq+t+6tqgn4gy\/31q1b95zznOf3O6eq+6oPzDe2pPnbwtbw0eD13nBdGBVv5+LRcDGc6R29dn0ucc0MWr06bVwViOT8Q70jkQi2PXw4eN97N4Rrw6j4Ty7eHN7qvflmjq79u3ft3d65o3v+G5xPLaYtIOE+ErjrS8Hg7whE2h8ISEj3FaH1yetRQZBLgSjwmvsI+Wx4eejIqf8K7ptKTENAzyQG4TiJaB8Pi+GmsBSuD15zXBfBbefCx8KLvePZHIlHVC59PRThc9pNjJvpSZ\/+wXxwW\/hEuDvsCMuBA0elcFftcxgRCSSduZRwBDwUToeHwivBtc4c2ZUDOY5IHLUncBqHbQ8LgXjTDBOhjgrOJ5DXUpgTlQn9co1DObITIbtygBTluN3hO0GNMxCOJF5X7eRRtaPUSM4kGCG58g\/hVHg0ELFVtHGgWSWQuiNlOY6AuwJB5x2yQhRH6qtz\/XSuvFwI0nri2jipMywOS0G9uz\/ozP4gRQg66XPz0alFEemNtCC1D4UT4VdBWk+U0pM4kPNsP3b2UONuDTeGcfu3vDX34EjYAei\/fhu\/cbhOYKneaFPe1CnFedL0QNA49+kIYd8vYZEhlBX7SLBKPxjOhJVAyFrRxIH9zrOiEU4KS9umE5GPzDX0146hOFBN5EhOtNh4v5ETc\/+GwXn7wnJ4MhwNZu+dYDbfz3CbbzLGZGzLwViNedOo48BxztNAF85Te0zApd7RxIwKbUF\/ynHUfU2v0YADZZN+KE+McbJ31L+xsZkARFoKHnogqHnSV6NXh7ahw1Y\/oh0Lvm4d7r3OYS20tTUoF1Z7m\/alUMslua9OEEravhD05adBbdxwdd7IgTqtg2ZmRw\/nrm0mfG5ZF8VpF\/OOGSYa19mLOV8JOrsavO4P7RHN6mkvZ6tUMsM5BxVn5nSi6B+vtLazKOmtnyNjnBClwx5yICyE24MBaKhpFKcR6Mlg33UwEG8lFDHdNyxeLlWhTyCUBWBvuCXcF0wsZ7reNohlkp8Kq+GBoL8jY5wDieTbhG8Yvs8W500inrQwk6cDwXRKhxxfDceC95uELCCk4r8SiL47SHMQetKgiYlQroh5Q\/B1cGQqj2uIePeExfD9QEgdbhqEWQmnwgPBPuul4LpBl7qT08ahPyZUCnPij4P6vByI2CZkgok\/H34dTPKjgYgDMcqB\/TNgFhTuScTTEIEsDL5vngwEHDmTud40DFBwB6ccDwyhPUflZpxB8taG4XNcaCJ2Bc\/nehNmTGuh+PaH19J1Kfw8fC6Y4Uk7cjGffSz8IzwdXgtmt+sg5t97SGWTtD2MMkgu1w6iqf+e+UQwWbJnLYYboLB09b1W7kvltuGZZeYGZq\/tg\/s+77lcrh1ONy6uaRue57szTejBAExhF1HFsAPddF+4K9we2LhN6IBn6sAjQXpNM7jjhXAkfCu0NYDM40I6vRrUWs9e2yn0O9A5wVgfw+LmUuMgoBIANUktLbUrpwPhXh3Wj1IypDsnOdZxL2e8HOwZucXktV2V9YuINCGcc9eq\/hSRHNW+hfCT8JnQtuE8omrIIiSeunyoXDgsBsEMGvvCrqDDNwWC6zB31QmCe95SuBDUL4NuEyZ1Z7Avfjy8Far+FAfqoM4Xt9jtdxXa4DxiKO4nAgE50sQR2D3cIgOWQhmwTrrXfu9k8JqgGy1E7ieilMPwZOVS46APbdRDOjFXVQuLgDr+2bAY2ta9PGJdaPzucCycDRq\/I6gp3wgWraWgbX3iICElpeK58GA4FQ6HtRqU81FB6JVA6MopObYJ\/WEq\/bwzcOIT4WIRkBO8Ceddh2duC5yxEDjKZBHQ0SLjenFeTqsgAPFd12nCccNm4XMmCc67CO3qh0zh7EqnIqA0Wg4GU2pWTjuL4nCO+mIwqP4ULp0bbrDMPPG\/GVbDc8EEbBRc517RhQMvP+lyhtyZFybzsfA6AXXeUX7D666DEGVi1JAmoT9mW92BZ9UJtRJdhr4M6EQ4A2LLpTAqjXJ57sGxb\/ToKiUnGZTFcH9QD51XbpNKUgzVxRyvtHg3HSoCOq8TxtL1eDiQXvDsazhwb7DnGi7guXTFhJp3MKhrm9W\/3FKNRT1Hl+NSPrjPNxx1+e2Swi7WrS25deohTW1Wi\/Nsfc6HV3rXctgwjEVpQtfjKjVZxm4pDrR96XKm8rhWYbvyTHgxWO3OhaeCbUmdhcHg1CoOdN51WNRswd4hYKmBXc9U3U5zGfzQYJvDeYQ6Fgi4ErjPtc020Lmlqk3GNLBaeqPDoNWaA9l8XiksVS0OUvOhcDo8G3zl86OAjbf3CVzHebKI6xbCXWFnmKYDr+JAOU3ReTiQ4y6ElwLHnQqrgduISOC6YRwE3BZsdBmj7D1z2mkMOLDTJzd8mLT9bSDeI+G1IIUJ10Q8A5JFhPth4EApPK0oNfBaDpxncKBUVeOIJ12bRrUfy4c4b0cP5wY5rTBhnl\/tA6fVSJ3nEtBCAedNo9Q8zrs\/qHm3B4uI96Ye83agAVooUDdK3ZayatyesD1IW85zbdriKS8m\/NKVIGD6UTtK7fHzl98XpeyXg28Gfvby\/rTFSxOVeLLmPAFtEZoW7XxkpqHmqHW2JLvDzWExcJ603RpmGfSyJ32LgLYLZs3FKzWI55sF8Q4ENU8Kc9y0tip59NgotfssAW0bqHklC1j2XZwmbQk4zxhw4NH0RIe6\/OV2noObRdvFgWc40P7LzM7Lgb5BYLOoe99mz2n7vjWDgGs10N8YbAHeDrOO8g1CPXM+LureN+7zXV1nMpt9pjsXKgeWGkhRIirYswqu8pXLnxSKiGa4P9wjQ9zj3jpuzW1TCX272INWa\/vA\/r9iLeWNWeyl0ky1d7OfOxu4zK8ydgWlnLjmRwH7vnuDWm2\/N69gsmfCanBe\/TXOkbL+1olhB+TS1IKb\/MeC2BOIY4ZLH\/rdRzz3ztuBAzpZRMSb4WBYDHvDNH5Dy2PXBadrk3j7AuGKeDmtgmCQ4o6zyo40tS647nBY50Cril9FOMD5LKPUXD8AXMlhYn1nV2ZQTXRxYMltK0uV2zn+P95ToKy+fvg9GI4FWTtQAxVvP3DCUm3lU8RnETqoXe63upVU1j6H+somO0o653SmoT\/6Z\/tS9Kk6UByo4y9WVy7\/15G6tByIOIvQqYeDDDgUylZBvdsfLCDfC1Zjq\/KsJjZNVcFQjwa1b+BH3yKgu+S39D0TLCKzqIVm9nxQU6SFth11kogEtHjo16lgu6Vv+s2Nswh91K6+YUNdyox\/PTeeDFJrmnDcj8K3g5+m\/K63JUhbIhUBb8r5cvhuOBKkkoFNs2+erQ1tWXk\/H3YF5WQt+h3oog9wg3rjr2XSxXnXKaNzvgGpu8cD12l31AJm9tVFE6of+rW1x8Bgcq3roId9n75pt0xcTi\/HcAfKwAzO72zSZm\/oel9IKDv658Pvwkog0rgwEGJDSqvXnwpd9yuPHAiC\/T78NTwd1GoarcWwA71RauGJnBPYcl3SKqedBEF0Rgc9f5TzcnkgCGxi1UyZ4RnTimIkE8b5jCQT1rU5SsDcV830wznuDF8Ie8JSuDZ0ETqzEqxqzusGEdUji8xGjq37vHH3lQxRWv4YfFcfWH3LB8cJSGnuKGlMuF2BI7ta\/TgdTYIzCI6BVGrykE3uNTF2AKcD56l\/smVkjBOw3OzDvwwLveOOHKVP20XF5z2n6bN8Tg1E2z7kEevCxKwGafubQECLyNjYTEB7HlsNDjQjHKhw+1wbJ\/osd6M8a119yXvD4XN2BWjT\/vBzvdY+AY3XWC1Ufh\/YcN83vArn\/oEoxdQS\/kJ4LtwWrusxqQuKkzjwL0HN0fnNwjeRH4TPhk+GrmqycapxNsoPhsfDkWCB27BUbObAfH5tVWZnAzc7jm2cyD3Euz5sC+oOEdXEUU50v3ttqG\/pHbtyoLa1W5xnfM5N6Ki+5PJ7sZkDy50eZEln63+Gtk40eOnrm4cV\/tPh+aDTo5x4Q67fH74avhJ2B1kwaQbko1VoazUcDZz3p\/C3YKs0qh+5PBh1HFg+YabYmRNFORJDKhlQk9A2F+\/sfaiIciGv1R2TRiDP5byF4B4LiM+1CeMwHvvKk4HrjoezQdrWEi\/3TTSDBmQA+4LB\/yxsD0uBkE2iDESHDUQd4kSDsJXwvP3BoqHmaXdraOs85eJw0OYvgtpHxEuBsLWjiQPLQzXAIRp0lAIalo7Ssmwx6tQoQhDFpJgMjrDn6hdwMa+JpgbWLTm5dV1wNEySyTkWTgTjUJq0aUIbRZuZJL4BGZjV8d6wK3wtELPpHs\/giiMdvdY\/4jpOMtn5WBWep4bb0z0cCKfeKRevhCJuTptFm05xHc4FzlkNUvFUMMNWSym4JRBgM0eW97m4iygTwnH69VKwr9NPfeQ8orYKA+siDJ7jpONiUPTvCbeGO4IUbOrIfGTiKI7zdfSxQKw\/B46TrgQlnvtaRRsH9jdstnVWSnsmB5ppziQi8W4M3iMywYszc9oqitPUZudlP8ltUnYlEPBE0MdOhMtzqujKgeV5jkQikMVEChOOgHf2zjlSzfSaiG2iOM1EHQ4c9mwg3qFArOI4k0pgdBYG23Woi4IjTJDVTQpzohVWbXTcFtS7skjktBK+\/7Vr\/UGw4jTX7QKIpg2Od+5IwOOBaJ06Ls8biGk4cKCBvOBG7XCb8\/4Udr7Qu8al7tnfe53Duij7N44jjNcrvWMR1jUucw\/BO3VcnjcQ03DgQAN5UQZgNRTcV4LbykpNQE7dGcalNlHOheIsrzmNeHOJ\/wES39wijoywpgAAAABJRU5ErkJggg==",
          "BTTTriggerConfig" : {
            "BTTTouchBarItemIconHeight" : 22,
            "BTTTouchBarItemIconWidth" : 22,
            "BTTTouchBarItemPlacement" : 2,
            "BTTTouchBarItemPadding" : 0,
            "BTTTouchBarFreeSpaceAfterButton" : "5.000000",
            "BTTTouchBarButtonColor" : "58.650001, 58.650001, 58.650001, 255.000000",
            "BTTTouchBarAlwaysShowButton" : "0",
            "BTTTouchBarAlternateBackgroundColor" : "0.000000, 0.000000, 0.000000, 0.000000"
          }
        },
        {
          "BTTTouchBarButtonName" : "Check",
          "BTTTriggerType" : 629,
          "BTTTriggerClass" : "BTTTriggerTypeTouchBar",
          "BTTPredefinedActionType" : -1,
          "BTTPredefinedActionName" : "No Action",
          "BTTShortcutToSend" : "56,55,14",
          "BTTEnabled" : 1,
          "BTTOrder" : 4,
          "BTTIconData" : "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSCAktEAEpoTdBepUaepcONkISIJSICUHEji4q2AuiWNFVEEXXAsiiImJnUbDXhyIqyrpYsKHyJgV0fe175\/vmzp8z55z5z9wzk7kAKNuy8\/JyUBUAcgX5wpggP2ZScgqT9BigQAuQgQcgsTmiPN\/o6HAAZaT\/u7y\/CRBJf81aEutfx\/+rqHJ5Ig4ASDTEaVwRJxfiowDgmpw8YT4AhHaoN5qZnyfBAxCrCyFBAIi4BGfIsKYEp8nwOKlNXAwLYh8AyFQ2W5gBgJKEN7OAkwHjKEk42gq4fAHEWyD24mSyuRDfh3hcbu50iJXJEJun\/RAn428x00ZjstkZo1iWi1TI\/nxRXg571v+5HP9bcnPEI3MYwkbNFAbHSHKG61adPT1MgqkQNwvSIqMgVoP4Ap8rtZfgu5ni4Hi5fT9HxIJrBhgAvm4u2z8MYh2IGeLseF85tmcLpb7QHo3k54fEyXGacHqMPD5aIMiJDJfHWZrJCxnB23iigNgRm3R+YAjEsNLQo0WZcYkynmhbAT8hEmIliK+KsmPD5L4PizJZkSM2QnGMhLMxxO\/ShYExMhtMM1c0khdmw2FL54K1gPnkZ8YFy3yxJJ4oKXyEA5fnHyDjgHF5gng5NwxWl1+M3LckLydabo9t4+UExcjWGTskKogd8e3KhwUmWwfscRY7NFo+1\/u8\/Og4GTccBeGABfwBE4hhSwPTQRbgd\/Q39MNfspFAwAZCkAF4wFquGfFIlI4I4DMWFIE\/IeIB0aifn3SUBwqg\/uuoVva0BunS0QKpRzZ4CnEuro174R54OHz6wGaPu+JuI35M5ZFZiQFEf2IwMZBoMcqDA1nnwCYE\/H+jC4M9D2Yn4SIYyeF7PMJTQifhMeEGoZtwBySAJ9Iocqtp\/GLhT8yZIAJ0w2iB8uzSfswON4WsnXA\/3BPyh9xxBq4NrHFHmIkv7g1zc4LaHxmKR7l9X8uf55Ow\/jEfuV7JUslJziJt9M2wRq1+jsL6YY24sA\/72RJbih3BzmOnsYtYM9YAmNgprBFrx05I8GglPJFWwshsMVJu2TAOf8TGtta2z\/bLT3Oz5fNL1kuUzyvMl2wG1vS8WUJ+RmY+0xeexjxmiIBjM45pb2vnCoDkbJcdHW8Z0jMbYVz6riteAYCn4\/DwcPN3XbgyAEdhTVN6vuvM3eF2LQTgwkqOWFgg00mOY0AAFKAMd4UW0ANGwBzmYw+c4X+IDwgAoSAKxIFkMBWueCbIhZxngjlgISgBZWA12AA2g+1gF6gGB8Bh0ACawWlwDlwGV8ENcA\/WRS94CQbAezCEIAgJoSF0RAvRR0wQK8QecUW8kAAkHIlBkpFUJAMRIGJkDrIIKUPWIpuRnUgN8htyHDmNXEQ6kTvII6QPeYN8RjGUiqqjuqgpOh51RX3RMDQOnYJmoDPQInQxuhKtQKvQ\/Wg9ehq9jN5Au9GX6CAGMEWMgRlg1pgrxsKisBQsHRNi87BSrByrwuqwJvier2HdWD\/2CSfidJyJW8PaDMbjcQ4+A5+HL8c349V4Pd6GX8Mf4QP4NwKNoEOwIrgTQghJhAzCTEIJoZywh3CMcBbum17CeyKRyCCaEV3gvkwmZhFnE5cTtxIPEluIncQe4iCJRNIiWZE8SVEkNimfVELaRNpPOkXqIvWSPpIVyfpke3IgOYUsIBeTy8n7yCfJXeRn5CEFFQUTBXeFKAWuwiyFVQq7FZoUrij0KgxRVClmFE9KHCWLspBSQamjnKXcp7xVVFQ0VHRTnKjIV1ygWKF4SPGC4iPFT1Q1qiWVRZ1MFVNXUvdSW6h3qG9pNJopzYeWQsunraTV0M7QHtI+KtGVbJRClLhK85UqleqVupReKSsomyj7Kk9VLlIuVz6ifEW5X0VBxVSFpcJWmadSqXJc5ZbKoCpd1U41SjVXdbnqPtWLqs\/VSGqmagFqXLXFarvUzqj10DG6EZ1F59AX0XfTz9J71YnqZuoh6lnqZeoH1DvUBzTUNBw1EjQKNSo1Tmh0MzCGKSOEkcNYxTjMuMn4PEZ3jO8Y3phlY+rGdI35oDlW00eTp1mqeVDzhuZnLaZWgFa21hqtBq0H2ri2pfZE7Zna27TPavePVR\/rMZYztnTs4bF3dVAdS50Yndk6u3TadQZ19XSDdPN0N+me0e3XY+j56GXprdc7qdenT9f30ufrr9c\/pf+CqcH0ZeYwK5htzAEDHYNgA7HBToMOgyFDM8N4w2LDg4YPjChGrkbpRuuNWo0GjPWNI4znGNca3zVRMHE1yTTZaHLe5IOpmWmi6RLTBtPnZppmIWZFZrVm981p5t7mM8yrzK9bEC1cLbIttlpctUQtnSwzLSstr1ihVs5WfKutVp3jCOPcxgnGVY27ZU219rUusK61fmTDsAm3KbZpsHk13nh8yvg148+P\/2brZJtju9v2np2aXahdsV2T3Rt7S3uOfaX9dQeaQ6DDfIdGh9eOVo48x22Ot53oThFOS5xanb46uzgLneuc+1yMXVJdtrjcclV3jXZd7nrBjeDm5zbfrdntk7uze777Yfe\/PKw9sj32eTyfYDaBN2H3hB5PQ0+2507Pbi+mV6rXDq9ubwNvtneV92MfIx+uzx6fZ74Wvlm++31f+dn6Cf2O+X1gubPmslr8Mf8g\/1L\/jgC1gPiAzQEPAw0DMwJrAweCnIJmB7UEE4LDgtcE3wrRDeGE1IQMhLqEzg1tC6OGxYZtDnscbhkuDG+KQCNCI9ZF3I80iRRENkSBqJCodVEPos2iZ0T\/PpE4MXpi5cSnMXYxc2LOx9Jjp8Xui30f5xe3Ku5evHm8OL41QTlhckJNwodE\/8S1id1J45PmJl1O1k7mJzemkFISUvakDE4KmLRhUu9kp8klk29OMZtSOOXiVO2pOVNPTFOexp52JJWQmpi6L\/ULO4pdxR5MC0nbkjbAYXE2cl5yfbjruX08T95a3rN0z\/S16c8zPDPWZfRlemeWZ\/bzWfzN\/NdZwVnbsz5kR2XvzR7OScw5mEvOTc09LlATZAvaputNL5zemWeVV5LXPcN9xoYZA8Iw4R4RIpoiasxXh9ecdrG5+BfxowKvgsqCjzMTZh4pVC0UFLbPspy1bNazosCiX2fjszmzW+cYzFk459Fc37k75yHz0ua1zjeav3h+74KgBdULKQuzF\/5RbFu8tvjdosRFTYt1Fy9Y3PNL0C+1JUolwpJbSzyWbF+KL+Uv7VjmsGzTsm+l3NJLZbZl5WVflnOWX1pht6JixfDK9JUdq5xXbVtNXC1YfXON95rqtapri9b2rItYV7+eub50\/bsN0zZcLHcs376RslG8sbsivKJxk\/Gm1Zu+bM7cfKPSr\/LgFp0ty7Z82Mrd2rXNZ1vddt3tZds\/7+DvuL0zaGd9lWlV+S7iroJdT3cn7D7\/q+uvNXu095Tt+bpXsLe7Oqa6rcalpmafzr5VtWituLZv\/+T9Vw\/4H2iss67beZBxsOwQOCQ+9OK31N9uHg473HrE9UjdUZOjW47Rj5XWI\/Wz6gcaMhu6G5MbO4+HHm9t8mg69rvN73ubDZorT2icWHWScnLxyeFTRacGW\/Ja+k9nnO5pndZ670zSmettE9s6zoadvXAu8NyZ877nT13wvNB80f3i8UuulxouO1+ub3dqP\/aH0x\/HOpw76q+4XGm86na1qXNC58ku767T1\/yvnbsecv3yjcgbnTfjb96+NflW923u7ed3cu68vltwd+jegvuE+6UPVB6UP9R5WPUPi38c7HbuPvHI\/1H749jH93o4PS+fiJ586V38lPa0\/Jn+s5rn9s+b+wL7rr6Y9KL3Zd7Lof6SP1X\/3PLK\/NXRv3z+ah9IGuh9LXw9\/Gb5W623e985vmsdjB58+D73\/dCH0o9aH6s\/uX46\/znx87OhmV9IXyq+Wnxt+hb27f5w7vBwHlvIll4FMNjQ9HQA3uwFgJYMAP0qvD8oyb69pILIvhelCPwnLPs+k4ozAHWwk1y5WS0AHILNFDbaAgAkV+84H4A6OIw2uYjSHexlsajwC4bwcXj4rS4ApCYAvgqHh4e2Dg9\/3Q3J3gGgZYbsm08iRHi\/3yGN0cUoXAB+kn8CCCdsu7q1Xy8AAAAJcEhZcwAAFiUAABYlAUlSJPAAABORSURBVHgB7dhtqGXnVQfw2tgmTWrzNnPzMpk7NzYaGfMhNJOgEJsLVkgTFZHYDwplUMRShKo04BejjlK0CUWhDZWIhPpFIfhB1M4HiTMSaDsJ2IpONPXl3jt33l+axDaZ2DT6\/52eddl333P22efec9v5MAv+s5+z97OfZ63\/+q\/17DtXvO2ydTHwnjz8heCe4Mxw4jdz\/b\/h+G3fW4PL13UMfE9+XR18X3BdcEVwU3BlcDF4M3gruGxjGEDeQ8GvBF8NTgXPBp8L7ggQi+TLCkRCw5ByVYCg24LdwY3Bu4e\/Pb8lUMJvBJR42RoMIO\/9gb73YnAh+FZQhL2S8aGglPie70QPtIfMvTN4e6CPuELdz7DTBPC\/gausV0D6kJ7k91aNf0qXwnYFO4PrgzLP+XtrYN9rg4vbTaDmyxGO7Q005H2BkpgL3hXMB5zrMuVyNPhGcC74n+CF4GvBkQCJW7F35OU9AeI+FiAJQW2refz\/QHB+1gSWoqjrmgAxeokNOYhAV78RqGQWAo51GQKR91ogGV8PzgZ6lQbvPhKVm3kU0rc\/8dX+NwelvBsylvy2UXrBXm8JeJamPClN430ooL4fCQSNTM7OsoSRpE8hlCJPB88ElHkmEGSXiV8yle0TgWq4MyhfM1xnVQkruftocHoWCrQGYkgeUZxAXF0pkNLGGbXIKjJcm1aKdkV806jPfEqnQIq0z54AKdYTsGf2gKbxme9zAQLBmBo9a5v3vxmcCI4HLwevblWBZG5ThH0k0DvuDvQ2gXFEUOP2QQD1CHQlcFA0rRTdJq85pxLQLGHB\/UUg0IPBq4F9KkH8QbKP48eC+cBfG3wdJary03ri5Oux4M1Rk3N\/onGA2gSGNI4sBPoINAMWIAjQFVmuyFJiReBSxjLcNOtIhCtf7VvlJUkSVL\/NY\/78MqZE71AWYuxFlfaVeP1Yq9kdiKH2yHCDefdcoEWAsXsj2XZ\/knH+gcDGvxjsCDhK\/gIqk7lvBMrohUC\/cqWQo8HrAQIrOPObVoRZ1x6I2Rv4vFgcXv1uJiw\/B4fLI7lK2o8FJ4MnA8EvBe8O9gdIvitAuj3GmZ76RLAcrAbiGfg6rQJl3OY23B3ob04uAQlOwKU4xFHUucBnh80R6IpAVwEisE1cbq0ziuE01dvHekuB0tR7JVRPNI\/a+MFH9\/knTv4iyX7mui\/p1mwmPT\/XjF\/2QuDx4EQgJjEOzEbTGKdkljMfDhDHGcSCDTkosIOBzP9N8EqAvGrsVcLmrzmTcZchgb9VssgxVgV6MH9uDu4LkFimZO3LF359MUD0TwXiQeA4Hsx\/JlgJ\/iRApPXWEt5XgchBFMLmAwTuCDhQZmHZUSY2Wgo4vRpwBLFrG2c8rSGB2YNRBiK+FVDncuAZ31QJX\/mNZMqbC9znP8IoF3mjjJ\/ise6xIYzfCNbZOObXTcoPm+0PbP5IoAEjtN7n+FJwKvjk8CogG9r4rSFymbkRQSUYIe8LtJbfCCgTafzkA2K0DdalPH4fDZD3aCAuArDGOpukQBvbCIGcghsCTjEOIchhQGn6hE3PBEqWOrbbSpnns5EglZt7\/HHdFVAgJTI9tMuQRBAnAmuoJhW0KUPeQ8FHgv8MLFSZRJ6D4m8D\/eGHg7ngymBSYjJlW0zC7S\/BdwaLwaHgpaB6F7\/HQWxifDF4ILg96Iyl66GMIfCW4NagDowM15RnM1mSdRmTre+mIUb5UZBefMVwvKH0cn+UqRjVczLw\/rmgs4rGEUjyewLy\/+UAgXpemZI9HCjXTwVnAyftpWKUqFQdcqpCL0TmJNMGDgTLAVHol5Iy1sYRaLMdwU3BjYFDwz2LKQWlS3kIRN53W3lxYc3enhEB8B9xVw1\/I3WciUsvV1HHA9VExROVO45AatsfUKFvK6XMgebp9Ef57XS6FJUn8Y8G84GDD4ldBFZFUd6\/BUSBwInWJtAmyCJ9TpC\/bMqqbFhUdqhPf7iUlBd3BlVSlaP9SH7573mXia+Jrrlrz9oE+rp\/IJC5ewIl4DAhcWVL3o8H+sOlRl5cGhx0H89V5dwdEAMCJ5mT+97AgUmxzGHSeYCYRFlN0+dkzUI2tzBVWujlQJPtdTpl3nfSxOErgfoQ4NAjBsnvY94Xr88f61wbtLnJrY3WVqDe90ggg8Zl\/mx6OtAj9D1qpMpLxQT80UDl\/GigBfUlL1MHInFqa1kPB+L8s4BoOq0IpDIbytoNgSxUBnzNXwx8G4FTWK9omvdljznNkNue49mszb6Ug0DKA0SonC7jW7WgilW8yh2JDhXVONGKQOTtDShP89X7LKB0zwYrwbPBamDxtnH6g4GA\/jH4+hDbrVIJfyCYDx4M+O3eJEPeE8NJeqZPNVbrLWf86cGdCf8UgdiXCUBm3a9MXcg9Zax021aZ03e8p3++ErwRUK81Zm0S5dNEmynlUaFEdhlfxKGXHwus4zcVW08sKsm61kKo6hsrhCLKxMWAApsZtPgzgYwYt40DNnLw\/FxA\/h8MnNYHgtMBNY51IM82Y0r03mB38OuBTy4ETjIxPB2sBM8G1nkumA\/uC5AoHm3Mb+seCZA40opAzJcCjcuU8JkhjNtmroZ9XeB9G8uy+5SIuFkrUYVQjPWr3di7y\/jxWvBKsDoEMn3XKmcxVHx81wvFZH5nLywCmz3QuEzwzwcUaNw22aI4yqU+myoBivy9YCU4EMxKiQKzF+I+FmgbfZSnbx8O+PNXwdlAO1JBFMa\/+wPxMMosBVLoqNZl3qBnIbGyKrMywJy2pCtzQFlNM09AyAJj96zFMcHJPKW4SsBWemLtZ12luzOg+E6F5Lk4+H8yOBFQXLUjqvOpIgmlwAwHceiDUHy4v8GQVwS4copDpE11YAMZQEIZgmRrLng40EMqexkOyKQUPWQWSqz9KO+3A\/shkf+ejbOK43gmPBUgsMjzTlWYNtWssFK6OcZjjQMaJ8ma6DejtvoUGaUaWaneJ3ujMjUrJdqLX5JFzWDM3y7yxIBA5FDf+YAYmkozhzrhYkCt5TdBQNceA8eaysv8gVnoaLAcGLfNws3eN26jyuRmlch5a3v\/sWB+CEnvCky1qBq97TPBSkB9SGpWUs1D7NLw2UKu5XeGkxWo770raDpkYZu1N8ytQU+wAeLBuKtPVEZvzTzrUpCrkhml7txeM+3kuuDGQMlaY9J+mTJQGVKojvqokBoprm3uUSV\/gG+M3yA2GPXuQIF35KFmzLEyi1QJ14KelSKU0MMBRVDIJLP2nmBaJWoN+4fv3pWrttH0Mz9Hmj73dLAc\/EvwaoDAcSZGih3X68U48nu22QP7KNAckBGqnVRKmbJm0yjR+sijvF0B1aoUa3QZIvQz328nAurze1Qbyu018x6CwbhMrFcO0SZ3MAeBvtsw3CTQhl09kOTPBggUqID7WF8llvIo\/CcDZSxhk+z1TDgcrAQHAz66N8kqHkkyLit\/\/aZqJb7OECh45DUJlIV2T8itQXY807tk1wYU4l1r9bEuJXJeUpzstw1hLMGTTNKpjV\/UR4VU08fE5H1oK1CM0OQnP79tfYOu+RbnlAz\/biDIJwIkzgWafh+rzLZ7oizvC3YHPxvsDPqQp\/SWg+PBUwECrdXXJM5BQ+VNBU58v4tAz0Y9dxpx2IYIKyVen7Es9S3nUUpUuspWQqzn9yRr+sOX84ETeCoiMn9UvATTRH6ut1EEmYEY2VcSo1RVSlzN88cDQf9OQFF66ki5537b2kpExg8FVcbt+e3f5cfpPPhMoDKob9TnV26PtSvzZG+wJzAuq\/VVnfEGQ6DNONycgAALwTgyKvOnMqeUaK4yGJeYPNpgTSV6uCNAbB+jsguBw+JkoCpUB9+mtYq3+R5ORp0Fa3ME+uVA\/9JAyyYpsOZxdjmggN8K9gSfDChxGisleqcveeY6KJ4M+PDPgW81Ps3KKj7rj1wXgcqUCptZoySqBOMuq5NL6SD+XEBVTs++\/TBTJ37jmVNGGcj6WmBf6hNHUwT52dvEWAqsePEB1hy7LgI50q5xKlgILNZHEZUpAf1xQIkfDRwE22H8\/Xyg5x0KHBwbvtFyr69JePVAYwmyR5uX3FpvCKQ+m3upDHEOg2kOBFmiguMB5QnK1Uk6jRIzvdOa+6xmps+V1zvf6H6IsFKfK9Nb8dKuTM\/WGQL\/I6CgZo1XRnyDGfc1gRwO\/OXg3flgf6CcZ2Gl9GNZ7KngRDDN917bB9WlWqBZaZJ0NFgOxpZvng1OSxMoUPCg71GMqxMVEa6y0VRpfm4wz8megqnDOhq9RF0duL9Z048QeCpAnNPX2lsx\/kkuNKtEHP4DAjpjFhhD4PPBmeC+AHmIu2H4+6ZcjwRI7GPVo3Zm8nsDSlwMkLgZq8Q4LB4PVgL9dqtGGIvBnsC4DB+lQOOxVgSqeV\/vMmHMZISslaNMO2H7moCVlvVXA2sh1RpawrRK5FP7e6\/8zKNNWcXnuxOa8VG7mMF4rBWBWKaw08H9AfUxTZUiKfC5AAnTGAeeDG4eYneue4Nq1hn2slpHT9KzfTlshUAJFCPiFgMV0lbg87lnv6kVqM9gXYZkRRlTk6AR\/mbQ16yj1Ly3Gljv9kAAfQ4nSkaWNfQ9JayNTONDpm8wsfnPWdXl6mvDPfs5E5wFYrZ3pwJLtiYpYS88GMiGAJXwruDG4B8CjlvcRtMYp\/41+EpAgfbjeO2f4Uij+M8HXwr+MqC+TkXkeR\/zafUzwb3BYsAXBPJT73spsN+ZoFPplMEE9FqAQOWiF14dCJDUbbAzMMf3Xeeied428\/2FYr1jAQVKjN8cb1spAYGUCxQheVs1+xFGtRXj8gEPZ4dA5kSlC6DMZAsgzyI\/GFSvMm9ueO8LuSJyWrM+Ev4p+HJwf+C0p3SENq2UcDQ3\/yB4LvDutMrPK+vMPsr1luDRYF9wfVAEahWfDsT41WAigaXAzB2Qp\/+dChBnjFCLI1DGBKZv6EPUOk1A1rKmspD1k8OrdtH0g9PKVM+jvAuBqpiFiUNP3xGIo\/5KKsVTOP8IiL8TralAkzmPQIt8IBAchdTGsiVL1LgcIHRas4fSlOGvBPcHVMGUOueXgseCg8HpoFcwmTfJ+P9rwU8E7wvq8CjF\/3vuPR38d9Artmbm887A0VdzfTlAlAxVL6TKa4LbAgEZU5SNNqNECpMYvVGS7IVcyaNOxHk2bb\/NKxtMFVnfYbgrUML2dF8s4iAc+2pPKqCXtRXoJUGAMhPk3uCqgNnU7zuDFwOkKrFpg+S0FiBRyvOl4PsDSftE4OSlBiU1TXIyfaQ5BPcHPx78dPDeoGJSDYj7\/eDvg+NB73jaCsy7axlBnucCRZQNZezagOpuC2xE7oLsJfnMKzNftjnsfVdKWA2owXirpVuHhtLlL1Ci1sTeDCTxfDBLxQ9ORRvdEfx5cDgoNQhWcP8VPBcsBj8QvCOY1gSoFWjs9wT6kn0pfRamx30o+Hgg0dqTpIgBVM+B4JcCPhDMVDbuBYv7bBAIJSKHWijQvSsCJxln5gNEKEUkU6z3+5h5Ssipbh9mn94lNHhj\/T98BIm4LtgdUB4Vusfsax8+21cJ84EipzKBdxmCdwW3Bp8YjhdyRSjyBKpvnQ\/+NFgN\/i5A4rRWyZw6iMZG4qE6ZH04QNxDgR6IPMQy5B0KVoI\/DJz87vVNfKZ+28rp+t2+IojMEXYs4OBNgY3eGXBoLtAjS4lOuDockNyXkL7zsuQG4xd\/+HlzoDr2BBLv5L06YKU8pSzZQIWqYFM2SYEWRRKilQKHDgRUuRBwmCFKycvikeBE8NlAZs8EErGdJoF7A+T9aiDJtwfuI6\/iLOURw6cC\/iGQ\/5uySQq0qMUdGojgiM0Z5TmZS4lOZ7\/nh\/dc\/ZZ1J67+aC1j9zZrEsoPaxs7UcF+Eqxsdwb80auZ\/SS4lCcG5Gk\/W7LKTJ9FmkqU6d8MKFHmZZoVWRQns5x+NuDsoYDDR4PeH6qZ2zT+6nFUtS9Qqu8PlOndgfuIK18zHBjingkQ97mAH1tSXt4fWB8F1lzqKSUiajlwT8\/j+DWBAIvMCmY+9yhkIdDMkXoxKCVas49Zl6KcrEjcEyBuIbg+kNTaO8NB7+UfohC2EiDwXIDQmZiApzXZBU7DzwfK5sEAQYKrdRGt7whECTsoHDDuUaLTeimYRCJi9gbWvytQvs0S5o9WUvuqgDMB5X820JO\/FNjXnvyZiU2jwNrQ5sA5pSiziOEkB\/UfhwsFCogymc8IhLpSoJOPGhk1dhkCKQ6BDrOm0vJzYOWXdfnFn9PBUnAqoMLNto68OtoqY6OfTr4r8xRXASqpDwXKaTFAYtsEisjNlDB\/m0qrta1HWUrzYHAy+OuA2o0pvPbLcHa2GQU2d0cGp\/Uma1HgcsDZ1QC5Ss1zJFepZbj296jxtIYwKrY\/VamAC4F+txQgjQL5RpHmb4ttVYFNpxCIICWLrPlAye0LKHMxcEIuBPX9mOGmDHlHAmX5QoA8v6lQa6E4yUQwbJttVYFNx6iAUR\/FSQ4F6okCo0wnKJIRbG9z6uq+302jHOsWEX5b34G0EiDOuq5OWKTZa9sUl7XXWdvhdQ+3+KOIaZawe0hE4ELg82ZH4PdcgPimUdJSQHFngiJO2TZLGMF+I25bFZf115mAtstKkc1PFCpz8iIVHDKSaOxwaPtDbecCyjo1vFKc+5eE\/T8FSG+nk0aUuQAAAABJRU5ErkJggg=="
        },
        {
          "BTTTouchBarButtonName" : "Document",
          "BTTTriggerType" : 629,
          "BTTTriggerClass" : "BTTTriggerTypeTouchBar",
          "BTTPredefinedActionType" : -1,
          "BTTPredefinedActionName" : "No Action",
          "BTTShortcutToSend" : "56,55,2",
          "BTTEnabled" : 1,
          "BTTOrder" : 2,
          "BTTIconData" : "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSCAktEAEpoTdBepUaepcONkISIJSICUHEji4q2AuiWNFVEEXXAsiiImJnUbDXhyIqyrpYsKHyJgV0fe175\/vmzp8z55z5z9wzk7kAKNuy8\/JyUBUAcgX5wpggP2ZScgqT9BigQAuQgQcgsTmiPN\/o6HAAZaT\/u7y\/CRBJf81aEutfx\/+rqHJ5Ig4ASDTEaVwRJxfiowDgmpw8YT4AhHaoN5qZnyfBAxCrCyFBAIi4BGfIsKYEp8nwOKlNXAwLYh8AyFQ2W5gBgJKEN7OAkwHjKEk42gq4fAHEWyD24mSyuRDfh3hcbu50iJXJEJun\/RAn428x00ZjstkZo1iWi1TI\/nxRXg571v+5HP9bcnPEI3MYwkbNFAbHSHKG61adPT1MgqkQNwvSIqMgVoP4Ap8rtZfgu5ni4Hi5fT9HxIJrBhgAvm4u2z8MYh2IGeLseF85tmcLpb7QHo3k54fEyXGacHqMPD5aIMiJDJfHWZrJCxnB23iigNgRm3R+YAjEsNLQo0WZcYkynmhbAT8hEmIliK+KsmPD5L4PizJZkSM2QnGMhLMxxO\/ShYExMhtMM1c0khdmw2FL54K1gPnkZ8YFy3yxJJ4oKXyEA5fnHyDjgHF5gng5NwxWl1+M3LckLydabo9t4+UExcjWGTskKogd8e3KhwUmWwfscRY7NFo+1\/u8\/Og4GTccBeGABfwBE4hhSwPTQRbgd\/Q39MNfspFAwAZCkAF4wFquGfFIlI4I4DMWFIE\/IeIB0aifn3SUBwqg\/uuoVva0BunS0QKpRzZ4CnEuro174R54OHz6wGaPu+JuI35M5ZFZiQFEf2IwMZBoMcqDA1nnwCYE\/H+jC4M9D2Yn4SIYyeF7PMJTQifhMeEGoZtwBySAJ9Iocqtp\/GLhT8yZIAJ0w2iB8uzSfswON4WsnXA\/3BPyh9xxBq4NrHFHmIkv7g1zc4LaHxmKR7l9X8uf55Ow\/jEfuV7JUslJziJt9M2wRq1+jsL6YY24sA\/72RJbih3BzmOnsYtYM9YAmNgprBFrx05I8GglPJFWwshsMVJu2TAOf8TGtta2z\/bLT3Oz5fNL1kuUzyvMl2wG1vS8WUJ+RmY+0xeexjxmiIBjM45pb2vnCoDkbJcdHW8Z0jMbYVz6riteAYCn4\/DwcPN3XbgyAEdhTVN6vuvM3eF2LQTgwkqOWFgg00mOY0AAFKAMd4UW0ANGwBzmYw+c4X+IDwgAoSAKxIFkMBWueCbIhZxngjlgISgBZWA12AA2g+1gF6gGB8Bh0ACawWlwDlwGV8ENcA\/WRS94CQbAezCEIAgJoSF0RAvRR0wQK8QecUW8kAAkHIlBkpFUJAMRIGJkDrIIKUPWIpuRnUgN8htyHDmNXEQ6kTvII6QPeYN8RjGUiqqjuqgpOh51RX3RMDQOnYJmoDPQInQxuhKtQKvQ\/Wg9ehq9jN5Au9GX6CAGMEWMgRlg1pgrxsKisBQsHRNi87BSrByrwuqwJvier2HdWD\/2CSfidJyJW8PaDMbjcQ4+A5+HL8c349V4Pd6GX8Mf4QP4NwKNoEOwIrgTQghJhAzCTEIJoZywh3CMcBbum17CeyKRyCCaEV3gvkwmZhFnE5cTtxIPEluIncQe4iCJRNIiWZE8SVEkNimfVELaRNpPOkXqIvWSPpIVyfpke3IgOYUsIBeTy8n7yCfJXeRn5CEFFQUTBXeFKAWuwiyFVQq7FZoUrij0KgxRVClmFE9KHCWLspBSQamjnKXcp7xVVFQ0VHRTnKjIV1ygWKF4SPGC4iPFT1Q1qiWVRZ1MFVNXUvdSW6h3qG9pNJopzYeWQsunraTV0M7QHtI+KtGVbJRClLhK85UqleqVupReKSsomyj7Kk9VLlIuVz6ifEW5X0VBxVSFpcJWmadSqXJc5ZbKoCpd1U41SjVXdbnqPtWLqs\/VSGqmagFqXLXFarvUzqj10DG6EZ1F59AX0XfTz9J71YnqZuoh6lnqZeoH1DvUBzTUNBw1EjQKNSo1Tmh0MzCGKSOEkcNYxTjMuMn4PEZ3jO8Y3phlY+rGdI35oDlW00eTp1mqeVDzhuZnLaZWgFa21hqtBq0H2ri2pfZE7Zna27TPavePVR\/rMZYztnTs4bF3dVAdS50Yndk6u3TadQZ19XSDdPN0N+me0e3XY+j56GXprdc7qdenT9f30ufrr9c\/pf+CqcH0ZeYwK5htzAEDHYNgA7HBToMOgyFDM8N4w2LDg4YPjChGrkbpRuuNWo0GjPWNI4znGNca3zVRMHE1yTTZaHLe5IOpmWmi6RLTBtPnZppmIWZFZrVm981p5t7mM8yrzK9bEC1cLbIttlpctUQtnSwzLSstr1ihVs5WfKutVp3jCOPcxgnGVY27ZU219rUusK61fmTDsAm3KbZpsHk13nh8yvg148+P\/2brZJtju9v2np2aXahdsV2T3Rt7S3uOfaX9dQeaQ6DDfIdGh9eOVo48x22Ot53oThFOS5xanb46uzgLneuc+1yMXVJdtrjcclV3jXZd7nrBjeDm5zbfrdntk7uze777Yfe\/PKw9sj32eTyfYDaBN2H3hB5PQ0+2507Pbi+mV6rXDq9ubwNvtneV92MfIx+uzx6fZ74Wvlm++31f+dn6Cf2O+X1gubPmslr8Mf8g\/1L\/jgC1gPiAzQEPAw0DMwJrAweCnIJmB7UEE4LDgtcE3wrRDeGE1IQMhLqEzg1tC6OGxYZtDnscbhkuDG+KQCNCI9ZF3I80iRRENkSBqJCodVEPos2iZ0T\/PpE4MXpi5cSnMXYxc2LOx9Jjp8Xui30f5xe3Ku5evHm8OL41QTlhckJNwodE\/8S1id1J45PmJl1O1k7mJzemkFISUvakDE4KmLRhUu9kp8klk29OMZtSOOXiVO2pOVNPTFOexp52JJWQmpi6L\/ULO4pdxR5MC0nbkjbAYXE2cl5yfbjruX08T95a3rN0z\/S16c8zPDPWZfRlemeWZ\/bzWfzN\/NdZwVnbsz5kR2XvzR7OScw5mEvOTc09LlATZAvaputNL5zemWeVV5LXPcN9xoYZA8Iw4R4RIpoiasxXh9ecdrG5+BfxowKvgsqCjzMTZh4pVC0UFLbPspy1bNazosCiX2fjszmzW+cYzFk459Fc37k75yHz0ua1zjeav3h+74KgBdULKQuzF\/5RbFu8tvjdosRFTYt1Fy9Y3PNL0C+1JUolwpJbSzyWbF+KL+Uv7VjmsGzTsm+l3NJLZbZl5WVflnOWX1pht6JixfDK9JUdq5xXbVtNXC1YfXON95rqtapri9b2rItYV7+eub50\/bsN0zZcLHcs376RslG8sbsivKJxk\/Gm1Zu+bM7cfKPSr\/LgFp0ty7Z82Mrd2rXNZ1vddt3tZds\/7+DvuL0zaGd9lWlV+S7iroJdT3cn7D7\/q+uvNXu095Tt+bpXsLe7Oqa6rcalpmafzr5VtWituLZv\/+T9Vw\/4H2iss67beZBxsOwQOCQ+9OK31N9uHg473HrE9UjdUZOjW47Rj5XWI\/Wz6gcaMhu6G5MbO4+HHm9t8mg69rvN73ubDZorT2icWHWScnLxyeFTRacGW\/Ja+k9nnO5pndZ670zSmettE9s6zoadvXAu8NyZ877nT13wvNB80f3i8UuulxouO1+ub3dqP\/aH0x\/HOpw76q+4XGm86na1qXNC58ku767T1\/yvnbsecv3yjcgbnTfjb96+NflW923u7ed3cu68vltwd+jegvuE+6UPVB6UP9R5WPUPi38c7HbuPvHI\/1H749jH93o4PS+fiJ586V38lPa0\/Jn+s5rn9s+b+wL7rr6Y9KL3Zd7Lof6SP1X\/3PLK\/NXRv3z+ah9IGuh9LXw9\/Gb5W623e985vmsdjB58+D73\/dCH0o9aH6s\/uX46\/znx87OhmV9IXyq+Wnxt+hb27f5w7vBwHlvIll4FMNjQ9HQA3uwFgJYMAP0qvD8oyb69pILIvhelCPwnLPs+k4ozAHWwk1y5WS0AHILNFDbaAgAkV+84H4A6OIw2uYjSHexlsajwC4bwcXj4rS4ApCYAvgqHh4e2Dg9\/3Q3J3gGgZYbsm08iRHi\/3yGN0cUoXAB+kn8CCCdsu7q1Xy8AAAAJcEhZcwAAFiUAABYlAUlSJPAAAAcgSURBVHgB5Zw9kxVFFIYX\/AL8QCn5EHGXwB8AAbFGFEZGlCmx\/8GlClNj\/4iBJgZUWaUZVhFoYLCsy+KKiAICfoC+zzhna2jv3Jme233n9Hiq3p27uzPT3e88fbp7Znb3rKysnJRekM7VW23cxX3V6AvpF+k76Q\/JRTytWqBnpJekFyWP8awqdUSinlt1BV2YiHnHpKPSe\/VWG3fxSDU6L92Q1qXr0ob0pzRqGIFsD9YatUIthf+tn0Mf8YbE9zvSHmlUEp9SBU5K5MB36q027gKjMJAUc1o6I30r7ZXuSo+lUQLySgnMwkRyIQS6ILEUAuVXFe5ILIlAM9EViaURaCa6ITGGQBI1ueevemuNybHFIOrGFuJmhQsS+xqIcb9Jv0ubUu6pAxPnVek5iRkCRs4Kfv68xL4XpW1pXVraPDHGQMxjSfW99FDKGRjIiHtAwkTq6ZLEvgYy478mYR5X+EcpZ2Dgm9IJifJYKbkksa+BdGFMhELMYxWQM6Bvn0S5LN\/oqi5J7DsKM3D8IP0qfSaRD3PGY538nnRT+kb6WjolQSaalxMxf2krlr4Eqk5LDwYqCNyWMMwliV4JlF9VuCfRM4Fm4iIk0r7DEimIHEr3buv++lV8lGAgrbJZAIPXB9Kq1Gd01m55oxQDcSGWRLo\/5DEQMXNgBsGFIK8mi5IMpNF9ScQkZgoQe0li9XRFYgHAOZJFaQbS8C4S2QfyoI6Rm8k\/I\/kDKal5Ol+1RGJbWrSRyM1WAvOykleVoi8lEmh1n0UiD58IDOwiz9oOrYPDTjL4BCMfGJLI8o8g183Lecx\/md4QmG3GVz+I+VK6gbS1SSLzPAJj23IeS0HmhK9J3OFhwGGEZpnKyB0VUzCQBhuJzcbzszAweE06LpEjucPzlcQg87F0W4qKqRhIoyFxXkAeXRzzmIiz5aYDhja7dBSJUzJQPrSGkfe69oA8zOOxKMa9Ld2RWOJtSlEk\/h8MbJKHaeQ+pju2LuaRAOadkMiHNrj0InHqBraRR7ubNxX26\/u3pGgSp2xgF3nyazcwcxCJUzWwL3m7DtYfokmcooEx5IUGRpM4NQOHkhca2ZvEKRm4CHmhgb1JnIqBqcgLjewkcQoGpiQvNLCTxNINzEVeaGQriSUbmJO80MCQRO7aMG98UKqByyIvNNJI5C0N3tDYKdHAZZIXGshambs+iM\/F3dIfizy8IrjBwN0a7tp8Kd0ticAxySPnYR7PmHnWsiXx2PRhKQZ6Iu8TGcddGx4ZFNGFvZEHifcxj\/BOoFvy\/rXPt4GuyfNuoHvyPBtYBHleDSyGPK8G2pqTv54\/KjWfnlmdc2xnzfOeGG3bCvU2ChuBa6rwy5I96Gmrf6qfN1cYT8zzugrY27XDkn\/P+pJXMnhXhX8wQcOgI1dwbl7naK4wKJNVRrXW1XZueCOQ2f1liS5MA1al96VXpBwxmDyrjDcDMc2u\/pY+kxOhgzgopeoxg3NeVZPGF28GWtVyk7gweVbRsQyELHsZEhogj3tsFrlITEaeVXQsA3nB8YzEqMsborxRuiGF7\/SlJjEZeaprFWMZSC5joIBCVh7c3diRIDMHicnJUz2rGMtAm+\/xvt5ZCQPXpevShpSaxOTkqY5VjGUgpGEiBLLioAvz7h65LyWJ2chTPasYy0ArHyNZbbBkuyhtSylJzEae6lnF2AZSCfIhNGIiBKYgsQ955F6CfYlBfy\/iwUAqbySu6nMKErvIsxzMO9LkX3LuoL8X8WKg6p+ERKPpls5nT88wE5MsII\/ce1yi\/Qxc5GAuYnR4MpDKL0oif7ZA8B\/dZt1VMfIY\/S9JpI+PJEy8IUV3474G0jAKZwJMrooNyIAKI2Te8YvkxAP1iSFuHnn2tj67QyRdOSuBduUOq6APJZCPCW4IcMXtxkDXsUNJNBMYjFjFWFj9jTy6LyZyUe0Y2zdqG0Mg9BEU3FwtVD\/s+MKxXOWYGELirPM3c56RRy\/CVMpYKGIMZL5GFyHPcIVjYlM727Qh5rhYEsNzt5FHuxcizwrqayD729WKJYljIbBZYS7AI4mkTTowuvVxZrA\/a2e6JWmEY7YkztGWV+eR16yLTjE8YgwcXsp\/j6Th5EMuxlWJv5rsCozC8HcllnvoJ4n\/cBT2iOzkqcwqxjIQM3hBB\/Lo3qSHPsFx0MfENzSteTwX5lWJdXb4t3HN\/Rb+PJaBdMXLEmnhUymmS2EiYprCdpaR5OkL0pp0TCJ3x5Sh3fvFWAbSaJ59EEx6UwcXhmcoiDZa\/tbHtJHtxGmr6fdsXB3mdDyHZTGdKxgw6G7LCuvit1Ugg409fwnLp820vSunhsftfk9eYFJJAafrrTbJg6XV5xLbZYS1h9x3SGrraQxIVyTq9bMUfZGNQB1bTSuGTHY5tiuoaHTluk465\/dGoNHVZiC9j7rFrqx2i\/4HVWqBYcJfxRMAAAAASUVORK5CYII="
        },
        {
          "BTTTouchBarButtonName" : "Knit",
          "BTTTriggerType" : 629,
          "BTTTriggerClass" : "BTTTriggerTypeTouchBar",
          "BTTPredefinedActionType" : -1,
          "BTTPredefinedActionName" : "No Action",
          "BTTShortcutToSend" : "56,55,40",
          "BTTEnabled" : 1,
          "BTTOrder" : 0,
          "BTTIconData" : "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSCAktEAEpoTdBepUaepcONkISIJSICUHEji4q2AuiWNFVEEXXAsiiImJnUbDXhyIqyrpYsKHyJgV0fe175\/vmzp8z55z5z9wzk7kAKNuy8\/JyUBUAcgX5wpggP2ZScgqT9BigQAuQgQcgsTmiPN\/o6HAAZaT\/u7y\/CRBJf81aEutfx\/+rqHJ5Ig4ASDTEaVwRJxfiowDgmpw8YT4AhHaoN5qZnyfBAxCrCyFBAIi4BGfIsKYEp8nwOKlNXAwLYh8AyFQ2W5gBgJKEN7OAkwHjKEk42gq4fAHEWyD24mSyuRDfh3hcbu50iJXJEJun\/RAn428x00ZjstkZo1iWi1TI\/nxRXg571v+5HP9bcnPEI3MYwkbNFAbHSHKG61adPT1MgqkQNwvSIqMgVoP4Ap8rtZfgu5ni4Hi5fT9HxIJrBhgAvm4u2z8MYh2IGeLseF85tmcLpb7QHo3k54fEyXGacHqMPD5aIMiJDJfHWZrJCxnB23iigNgRm3R+YAjEsNLQo0WZcYkynmhbAT8hEmIliK+KsmPD5L4PizJZkSM2QnGMhLMxxO\/ShYExMhtMM1c0khdmw2FL54K1gPnkZ8YFy3yxJJ4oKXyEA5fnHyDjgHF5gng5NwxWl1+M3LckLydabo9t4+UExcjWGTskKogd8e3KhwUmWwfscRY7NFo+1\/u8\/Og4GTccBeGABfwBE4hhSwPTQRbgd\/Q39MNfspFAwAZCkAF4wFquGfFIlI4I4DMWFIE\/IeIB0aifn3SUBwqg\/uuoVva0BunS0QKpRzZ4CnEuro174R54OHz6wGaPu+JuI35M5ZFZiQFEf2IwMZBoMcqDA1nnwCYE\/H+jC4M9D2Yn4SIYyeF7PMJTQifhMeEGoZtwBySAJ9Iocqtp\/GLhT8yZIAJ0w2iB8uzSfswON4WsnXA\/3BPyh9xxBq4NrHFHmIkv7g1zc4LaHxmKR7l9X8uf55Ow\/jEfuV7JUslJziJt9M2wRq1+jsL6YY24sA\/72RJbih3BzmOnsYtYM9YAmNgprBFrx05I8GglPJFWwshsMVJu2TAOf8TGtta2z\/bLT3Oz5fNL1kuUzyvMl2wG1vS8WUJ+RmY+0xeexjxmiIBjM45pb2vnCoDkbJcdHW8Z0jMbYVz6riteAYCn4\/DwcPN3XbgyAEdhTVN6vuvM3eF2LQTgwkqOWFgg00mOY0AAFKAMd4UW0ANGwBzmYw+c4X+IDwgAoSAKxIFkMBWueCbIhZxngjlgISgBZWA12AA2g+1gF6gGB8Bh0ACawWlwDlwGV8ENcA\/WRS94CQbAezCEIAgJoSF0RAvRR0wQK8QecUW8kAAkHIlBkpFUJAMRIGJkDrIIKUPWIpuRnUgN8htyHDmNXEQ6kTvII6QPeYN8RjGUiqqjuqgpOh51RX3RMDQOnYJmoDPQInQxuhKtQKvQ\/Wg9ehq9jN5Au9GX6CAGMEWMgRlg1pgrxsKisBQsHRNi87BSrByrwuqwJvier2HdWD\/2CSfidJyJW8PaDMbjcQ4+A5+HL8c349V4Pd6GX8Mf4QP4NwKNoEOwIrgTQghJhAzCTEIJoZywh3CMcBbum17CeyKRyCCaEV3gvkwmZhFnE5cTtxIPEluIncQe4iCJRNIiWZE8SVEkNimfVELaRNpPOkXqIvWSPpIVyfpke3IgOYUsIBeTy8n7yCfJXeRn5CEFFQUTBXeFKAWuwiyFVQq7FZoUrij0KgxRVClmFE9KHCWLspBSQamjnKXcp7xVVFQ0VHRTnKjIV1ygWKF4SPGC4iPFT1Q1qiWVRZ1MFVNXUvdSW6h3qG9pNJopzYeWQsunraTV0M7QHtI+KtGVbJRClLhK85UqleqVupReKSsomyj7Kk9VLlIuVz6ifEW5X0VBxVSFpcJWmadSqXJc5ZbKoCpd1U41SjVXdbnqPtWLqs\/VSGqmagFqXLXFarvUzqj10DG6EZ1F59AX0XfTz9J71YnqZuoh6lnqZeoH1DvUBzTUNBw1EjQKNSo1Tmh0MzCGKSOEkcNYxTjMuMn4PEZ3jO8Y3phlY+rGdI35oDlW00eTp1mqeVDzhuZnLaZWgFa21hqtBq0H2ri2pfZE7Zna27TPavePVR\/rMZYztnTs4bF3dVAdS50Yndk6u3TadQZ19XSDdPN0N+me0e3XY+j56GXprdc7qdenT9f30ufrr9c\/pf+CqcH0ZeYwK5htzAEDHYNgA7HBToMOgyFDM8N4w2LDg4YPjChGrkbpRuuNWo0GjPWNI4znGNca3zVRMHE1yTTZaHLe5IOpmWmi6RLTBtPnZppmIWZFZrVm981p5t7mM8yrzK9bEC1cLbIttlpctUQtnSwzLSstr1ihVs5WfKutVp3jCOPcxgnGVY27ZU219rUusK61fmTDsAm3KbZpsHk13nh8yvg148+P\/2brZJtju9v2np2aXahdsV2T3Rt7S3uOfaX9dQeaQ6DDfIdGh9eOVo48x22Ot53oThFOS5xanb46uzgLneuc+1yMXVJdtrjcclV3jXZd7nrBjeDm5zbfrdntk7uze777Yfe\/PKw9sj32eTyfYDaBN2H3hB5PQ0+2507Pbi+mV6rXDq9ubwNvtneV92MfIx+uzx6fZ74Wvlm++31f+dn6Cf2O+X1gubPmslr8Mf8g\/1L\/jgC1gPiAzQEPAw0DMwJrAweCnIJmB7UEE4LDgtcE3wrRDeGE1IQMhLqEzg1tC6OGxYZtDnscbhkuDG+KQCNCI9ZF3I80iRRENkSBqJCodVEPos2iZ0T\/PpE4MXpi5cSnMXYxc2LOx9Jjp8Xui30f5xe3Ku5evHm8OL41QTlhckJNwodE\/8S1id1J45PmJl1O1k7mJzemkFISUvakDE4KmLRhUu9kp8klk29OMZtSOOXiVO2pOVNPTFOexp52JJWQmpi6L\/ULO4pdxR5MC0nbkjbAYXE2cl5yfbjruX08T95a3rN0z\/S16c8zPDPWZfRlemeWZ\/bzWfzN\/NdZwVnbsz5kR2XvzR7OScw5mEvOTc09LlATZAvaputNL5zemWeVV5LXPcN9xoYZA8Iw4R4RIpoiasxXh9ecdrG5+BfxowKvgsqCjzMTZh4pVC0UFLbPspy1bNazosCiX2fjszmzW+cYzFk459Fc37k75yHz0ua1zjeav3h+74KgBdULKQuzF\/5RbFu8tvjdosRFTYt1Fy9Y3PNL0C+1JUolwpJbSzyWbF+KL+Uv7VjmsGzTsm+l3NJLZbZl5WVflnOWX1pht6JixfDK9JUdq5xXbVtNXC1YfXON95rqtapri9b2rItYV7+eub50\/bsN0zZcLHcs376RslG8sbsivKJxk\/Gm1Zu+bM7cfKPSr\/LgFp0ty7Z82Mrd2rXNZ1vddt3tZds\/7+DvuL0zaGd9lWlV+S7iroJdT3cn7D7\/q+uvNXu095Tt+bpXsLe7Oqa6rcalpmafzr5VtWituLZv\/+T9Vw\/4H2iss67beZBxsOwQOCQ+9OK31N9uHg473HrE9UjdUZOjW47Rj5XWI\/Wz6gcaMhu6G5MbO4+HHm9t8mg69rvN73ubDZorT2icWHWScnLxyeFTRacGW\/Ja+k9nnO5pndZ670zSmettE9s6zoadvXAu8NyZ877nT13wvNB80f3i8UuulxouO1+ub3dqP\/aH0x\/HOpw76q+4XGm86na1qXNC58ku767T1\/yvnbsecv3yjcgbnTfjb96+NflW923u7ed3cu68vltwd+jegvuE+6UPVB6UP9R5WPUPi38c7HbuPvHI\/1H749jH93o4PS+fiJ586V38lPa0\/Jn+s5rn9s+b+wL7rr6Y9KL3Zd7Lof6SP1X\/3PLK\/NXRv3z+ah9IGuh9LXw9\/Gb5W623e985vmsdjB58+D73\/dCH0o9aH6s\/uX46\/znx87OhmV9IXyq+Wnxt+hb27f5w7vBwHlvIll4FMNjQ9HQA3uwFgJYMAP0qvD8oyb69pILIvhelCPwnLPs+k4ozAHWwk1y5WS0AHILNFDbaAgAkV+84H4A6OIw2uYjSHexlsajwC4bwcXj4rS4ApCYAvgqHh4e2Dg9\/3Q3J3gGgZYbsm08iRHi\/3yGN0cUoXAB+kn8CCCdsu7q1Xy8AAAAJcEhZcwAAFiUAABYlAUlSJPAAAAeiSURBVHgB7dy5jh1FGMVxMPu+GYMlLxNA4ogAUkBkECMegocg4D0QMRIxZAhEhpyAhEM0HpvNmH3fOb\/x\/aTmuu\/SU\/eObKk+6Ux3V1dVV\/3rVPWdoPqGG3p0Ap3AdUzgxjXbLt+t0U3RQ9GRqOKvnPwb\/TM4uvfn7Np99\/6YXTvfZmib9t4cOddu19oupLse5pMutO3r6O+o2pvTxaGydUIjzkQnoleihyMBzuXo9+hSBNovs+vdHH+O9qIfo3PRb9FPEeDbCGDuim6LTkX3RNp9Z3Qskn50dhxeF4evcu\/V6GKkvfq1NKrg0ky5qWEeriEno0ciYaSkeZAjgADVgwEU0pz\/Gn0XAS+P8qACKm1KaLt23R5xl\/ZJu392vpPj3dHpCNQxcMoAWe5Ul35IV\/fKWBfgooo8mBsBOD471hQdm8Kg7UYgvhuZLmcjgC9FgK4T9VyAnowsK89G4O1EBdM0XTWFC16yTo9WgJ44pQ5QAb4v4gwATJsfIoMAMKfKM+9IzwGkpqiZcG+kHgAd1Wvq3hIdSkzp\/CYapGM6CtDjUU1hDnwn+jx6M\/o2GjqyHPdA0l+MuP25yAAMpzDAhwYvz9p3jwZ48PDtyg0Wfh3UuU2GKSU8t+KXnHCOewBbh76JakoDeDSyXMgH4IlIvm2GdVCbPH+Mz78c+ESkYa\/MjjnsTylOuBg5eqVvM+5I5c9EgD0f7UWc5vnC1Hw5Au+pCDhlth3gnYkM1hifHwCsN8\/JnNfb9fucmy7WJu7cdhhpa5uwrhmw4eKuDdrzYOSnydC9udxaaJcXkgEb43PkMOBsrXfXQsUdYOModIAdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YEdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YEdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YEdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YEdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YEdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YEdYCOBxuLdgR1gI4HG4t2BHWAjgcbi3YGNAO3WXCfsH7YV1S5yOzntlqyNzQbBrkZR+2uvXF2bf4df49CvCrvl9U0f9dW96ldOx2NdgCq0NV\/lb0Q2H9u7ayuoXeO2ptoS6nonKrg5vabCJwfOR2DZ5P1PJFz7Ssfl6JPIjnl9XslnZYZUIoxGPfRCzu1mF7WXFkAgXQsbldVtBB25FFRH7q30nG4sDK52+goIMAA4Vjrn0V6kL3bilwNdA2uXPLCu615OF8e6AMuBvrLxeVRA1OwcEOFY8GzRt6t8JwLXzm9f1Xhydn0sx+Gm6lweOKp9oJyNbBTfjcBw9CEL4Apiwaljbu1DA3w4ACtn0roAPcBICo1YFcBofLkNQDvSrTE+L2DXuXrABll+S4ABMCCrQsfVD5xv0ajrs8jMOB8B6ChPHXdzbgpvNKYAnPLgcgQgX0SgvD871gvo0Vxz5AuRTw08HQFNqwK096JL0VuRqfdJZOoNHTScwhuHl2ctXSS5gg4a5dixhquXaziSQ3T6QuTjEhzKScPp5VweU9ESok5T8stIeYv+xWjsWUluCiZbaLRFN3TwWGSkWyCm+GiUQ62pr0XWmtejB6KXIiDAquCsD6IPo48igAGUro2ctg14+m5AaZTDIoCmnHXKFDsVmVb1NtNYjqg3VV3Xfc4auieXo1EOld9UV5+XAEcpX\/dzur\/WXc5RJ4CTz88qAzE1rLuep4+EgWuDWOmO8lm3LS9Ho0kALezPRBr4fKSTOqCT1h2L87mIS8bS5ZsSgKmLm16fFayfSi7de3uWbirLfxB4IJ2OrMNmmH4yCFA70Xz6bUm7MwLP8nJVLHKgEVCp8B0XjVURMI46RECOpctXTiyHmmI6zlnSKl0+UWnWxflQbgh0eH+Ko+Q9FQEHIGA7kb6OpW\/sZ4wReDjSkeOzoy8euS4QwykMoKnGUZcirtmNQB9LP+j6NdVRAK4LfCW81LU\/\/2vkdVLH6wHuD2PerUZvURQQdRptIAWAYj6dC8ccu5959keHzAzrlmPBGHPOgR2VepcFw2in9jPMX6BYkDXm48jvqTORud8S5QwPfCxyXDaFNWqZM9W3E82vUQWxwIKrT67ngbtuDe209vv18Gn0k4ehaS27EMlg9ACshmgUlTPrbVT3Kz1Z\/hfS141FjtUeoa4xp20KynApWjbQZuj5iNH89vzZSGkcQH62gONaw7y6gbTYelFwJrhj6a2OTbUHmsLKtQRYuxETWastNXuRpWY3mk8H0KBa9uTd\/\/xdjbLMFUByJTCOwBGQY+nylSPHHGug6v4iZy5KT9FJoT\/rOkpewA78stOxRVEdHgOiTN1fx7FeJDuRAXDcxNRLNVfFVEcBOAX4VQ8EYVEM\/xOQp96kY\/lXOdbib7C4WIBO0mqAakAKrvRhmDZi2RrV7Kgrj1j\/rw5sKgpAASlA4HpO3S94Yy8FgKVbEvz7WO0zJf1aqN+Xi9aoZkflGZNimQMnVZTMUxxbLuNMzjbFRTkUQNO9XMh91l4AdyMA96L5tYs7DzVqhA\/1obOHlUPLseXQglvwqm3rTOHK24\/XC4H\/AJAhQASdnXZeAAAAAElFTkSuQmCC",
          "BTTTriggerConfig" : {
            "BTTTouchBarItemIconHeight" : 22,
            "BTTTouchBarItemIconWidth" : 22,
            "BTTTouchBarItemPadding" : 0,
            "BTTTouchBarFreeSpaceAfterButton" : "5.000000",
            "BTTTouchBarButtonColor" : "58.650001, 58.650001, 58.650001, 255.000000",
            "BTTTouchBarAlwaysShowButton" : "0",
            "BTTTouchBarAlternateBackgroundColor" : "0.000000, 0.000000, 0.000000, 0.000000"
          }
        }
      ]
    },

There’s a yuge chance you’re reading this post (at least initially) on R-Bloggers right now (though you should also check out R Weekly and add their live feed to your RSS reader pronto!). It’s a central “watering hole” for R folks and is read by many (IIRC over 20,000 Feedly users have it in their OPML).

I’m addicted to Feedly and waited years for them to publish their API. They have and there will eventually be a package for it (go for it if you want to get’er done before me since I won’t have time to do it justice for a while). As just parenthetically noted, I’ve started work on one and have scaffolded just enough to give R folks a present: almost 5 years of R-Bloggers data — posts, engagement rates, authors, etc). But, you’ll have to put up with some expository, first.

Digging In

We’ll need some packages to help this expository and extraction. Plus, you’ll need to go to https://developer.feedly.com/ to get your developer token (NOTE: this requires a “Pro” account or a regular account and you manually doing the OAuth dance to get an access token; any final “Feedly package” by myself or others will likely use OAuth) and store it in your ~/.Renviron in FEEDLY_ACCESS_TOKEN.

I’ve sliced and diced bits from the (non-published) fledgling package to give a peek behind the API covers. There’s plenty of exposition in the following code block comment header to describe what it does:

#' Simplifying some example package setup for this non-pkg example
.pkgenv <- new.env(parent=emptyenv())
.pkgenv$token <- Sys.getenv("FEEDLY_ACCESS_TOKEN")

#' In reality, this is more complex since the non-toy example has to
#' refresh tokens when they expire.
.feedly_token <- function() {
  return(.pkgenv$token)
}

#' Get a chunk of a Feedly "stream"
#'
#' For the purposes of this short example, consider a
#' "stream" to be all the historical items in a feed.
#' (Note: the definition is more complex than that)
#'
#' Max "page size" (mad numbner of items returned in a single call)
#' is 1,000. For example simplicity, there's a blanket assumption
#' that if `continuation` is actually present, the caller is
#' savvy and asked for a large number of items (e.g. 10,000).
#' Therefore, assume we're paging by the thousands.
#'
#' @md
#' @param feed_id the id of the stream (for this examplea feed id)
#' @param ct numnber of items to retrieve (API will only return 1,000
#'        items for a single response and populate `continuation`
#'        with a value that should be passed to subsequent calls
#'        to page through the results; `ct` will be reset to 1,000
#'        internally if this is the case)
#' @param continuation see `ct`
#' @references <https://developer.feedly.com/v3/streams/>
#' @return for this example, an ugly `list`
feedly_stream <- function(stream_id, ct=100L, continuation=NULL) {

  ct <- as.integer(ct)

  if (!is.null(continuation)) ct <- 1000L

  httr::GET(
    url = "https://cloud.feedly.com/v3/streams/contents",
    httr::add_headers(
      `Authorization` = sprintf("OAuth %s", .feedly_token())
    ),
    query = list(
      streamId = stream_id,
      count = ct,
      continuation = continuation
    )
  ) -> res

  httr::stop_for_status(res)

  res <- httr::content(res, as="text")
  res <- jsonlite::fromJSON(res)

  res

}

We’ll grab 10,000 Feedly entries for the R-Bloggers feed stream:

r_bloggers_feed_id <- "feed/http://feeds.feedburner.com/RBloggers"

rb_stream <- feedly_stream(r_bloggers_feed_id, 10000L)

# preallocate space
streams <- vector("list", 10)
streams[1L] <- list(rb_stream)

# gotta catch'em all!
idx <- 2L
while(length(rb_stream$continuation) > 0) {
  cat(".", sep="") # poor dude's progress par
  feedly_stream(
    stream_id = r_bloggers_feed_id,
    ct = 1000L,
    continuation = rb_stream$continuation
  ) -> rb_stream
  streams[idx] <- list(rb_stream)
  idx <- idx + 1L
}
cat("\n")

For those who aren’t used to piecing together bits from API’s like this (and for those who do not have a Pro account, those who didn’t want to write OAuth code or those who don’t use Feedly and cannot reproduce the post example), here’s some dissection:

str(streams, 1)
## List of 12
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 7
##  $ :List of 6 # No "continuation" in this one

str(streams[[1]], 1)
## List of 7
##  $ id          : chr "feed/http://feeds.feedburner.com/RBloggers"
##  $ title       : chr "R-bloggers"
##  $ direction   : chr "ltr"
##  $ updated     : num 1.52e+12
##  $ alternate   :'data.frame':	1 obs. of  2 variables:
##  $ continuation: chr "15f457e2b66:160d6e:8cbd7d4f"
##  $ items       :'data.frame':	1000 obs. of  22 variables:

glimpse(streams[[1]]$items)
## Observations: 1,000
## Variables: 22
## $ id             <chr> "XGq6cYRY3hH9/vdZr0WOJiPdAe0u6dQ2ddUFEsTqP10=_1628f55fc26:7feb...
## $ keywords       <list> ["R bloggers", "R bloggers", "R bloggers", "R bloggers", "R b...
## $ originId       <chr> "https://tjmahr.github.io/ridgelines-in-bayesplot-1-5-0-releas...
## $ fingerprint    <chr> "f96c93f7", "9b2344db", "ca3762c8", "980635d0", "fbd60fac", "6...
## $ content        <data.frame> c("<p><div><div><div><div data-show-faces=\"false\" dat...
## $ title          <chr> "Ridgelines in bayesplot 1.5.0", "Mathematical art in R", "R a...
## $ published      <dbl> 1.522732e+12, 1.522796e+12, 1.522714e+12, 1.522714e+12, 1.5227...
## $ crawled        <dbl> 1.522823e+12, 1.522809e+12, 1.522794e+12, 1.522793e+12, 1.5227...
## $ canonical      <list> [<https://www.r-bloggers.com/ridgelines-in-bayesplot-1-5-0/, ...
## $ origin         <data.frame> c("feed/http://feeds.feedburner.com/RBloggers", "feed/h...
## $ author         <chr> "Higher Order Functions", "David Smith", "R Views", "rOpenSci ...
## $ alternate      <list> [<http://feedproxy.google.com/~r/RBloggers/~3/O5DIWloFJO8/, t...
## $ summary        <data.frame> c("At the end of March, Jonah Gabry and I released\nbay...
## $ visual         <data.frame> c("feedly-nikon-v3.1", "feedly-nikon-v3.1", "feedly-nik...
## $ unread         <lgl> TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, F...
## $ categories     <list> [<user/c45e5b02-5a96-464c-bf77-4eea75409c3d/category/big data...
## $ engagement     <int> 9, 37, 52, 15, 78, 35, 31, 9, 28, 2, 21, 8, 25, 11, 21, 29, 12...
## $ engagementRate <dbl> 0.41, 1.37, 1.58, 0.45, 2.23, 0.97, 0.84, 0.23, 0.72, 0.05, 0....
## $ recrawled      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 1.522807e+12, NA, NA, NA, NA, ...
## $ tags           <list> [NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ...
## $ decorations    <data.frame> c("NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA",...
## $ enclosure      <list> [NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ...

That entries structure is defined in the Feedly API docs.

We’ll extract the bits we want to use for the rest of the post and clean it up a bit:

map_df(streams, ~{
  select(.x$items, title, author, published, engagement) %>%
    mutate(published = anytime::anydate(published / 1000)) %>% # overly-high-resolution timestamp
    tbl_df()
}) -> xdf

glimpse(xdf)
## Observations: 11,421
## Variables: 4
## $ title      <chr> "Ridgelines in bayesplot 1.5.0", "Mathematical art in R", "R and T...
## $ author     <chr> "Higher Order Functions", "David Smith", "R Views", "rOpenSci - op...
## $ published  <date> 2018-04-03, 2018-04-03, 2018-04-02, 2018-04-02, 2018-04-03, 2018-...
## $ engagement <int> 9, 37, 52, 15, 78, 35, 31, 9, 28, 2, 21, 8, 25, 11, 21, 29, 12, 11...

Using an arbitrary “10,000” extract didn’t give us full months:

range(xdf$published)
## [1] "2013-05-31" "2018-04-03"

so we’ll filter out the incomplete bits and add in some additional temporal metadata:

xdf %>%
  filter(
    published > as.Date("2013-05-31"),  # complete months
    published < as.Date("2018-04-01")
  ) %>%
  mutate(
    year = as.integer(lubridate::year(published)),
    month = lubridate::month(published, label=TRUE, abbr=TRUE),
    wday = lubridate::wday(published, label=TRUE, abbr=TRUE),
    ym = as.Date(format(published, "%Y-%m-01"))
  ) -> xdf

I’m only going to do some light analysis work with engagement data (how “popular” a post was) but the full post summary and body content is available in the data dump you’re going to get at the end (this is reminding me of the Sesame Street “Monster at the End of This Book” story). That means enterprising folk can do some tidy text mining to cluster away some additional insights.

Thankfully, there’s not a ton of missing engagement data:

sum(is.na(xdf$engagement)) / nrow(xdf)
## [1] 0.06506849

broom::tidy((summary(xdf$engagement)))
##   minimum q1 median     mean q3 maximum  na
## 1       0  5     20 69.27219 75    4785 741

Let’s look at post count over time, first:

count(xdf, ym) %>%
  arrange(ym) %>%
  ggplot(aes(ym, n)) +
  ggforce::geom_bspline0(color="lightslategray") +
  scale_x_date(expand=c(0,0.5)) +
  labs(
    x=NULL, y="Post count",
    title="R-Bloggers Post Count",
    subtitle="June 2013 — March 2018"
  ) +
  theme_ipsum_ps(grid="XY")

It’ll be interesting to watch that over this year and compare 2017 to 2018 given how “hot” 2017 seems to have been. To turn a Mythbuster phrase: a neat “try this at home” exercise would be to tease out some “whys” for various spikes (which likely means some post content spelunking).

Let’s see if any days are more popular than others:

count(xdf, wday) %>%
  ggplot(aes(wday, n)) +
  geom_col(fill="lightslategray", width=0.65) +
  scale_y_comma() +
  labs(
    x=NULL, y="Post count",
    title="R-Bloggers Aggregate Post Count By Day of Week"
  ) +
  theme_ipsum_ps(grid="Y")

Weekends are sleepy and there are some “go-getters” at the beginning of the week. More “try this at home” would be to see if any individuals have “patterns” by day of week (or even time of day, since that’s also available in the published time stamp).

The summary() above told us we have a pretty skewed engagement distribution, but it’s always nice to visualise just how bad it is:

ggplot(xdf, aes(engagement)) +
  geom_density(aes(y=calc(count)), fill="lightslategray", alpha=2/3) +
  scale_x_comma() +
  scale_y_comma() +
  labs(
    x=NULL, y="Engagement",
    title = "R-Bloggers Post Engagement Distribution",
    subtitle = "June 2013 — March 2018"
  ) +
  theme_ipsum_ps(grid="XY")

That graph is the story of my daily life dealing with internet data. Couldn’t even get a break when trying to have some fun. #sigh

We’ll close with the “all time top 10” based on total engagement:

count(xdf, author, wt=engagement, sort=TRUE)
## # A tibble: 1,065 x 2
##    author               n
##  1 David Smith      87381
##  2 Tal Galili       29302
##  3 Joseph Rickert   16846
##  4 DataCamp Blog    14402
##  5 DataCamp         14208
##  6 John Mount       13274
##  7 Francis Smart     8506
##  8 hadleywickham     8129
##  9 hrbrmstr          7855
## 10 Sharp Sight Labs  7620
## # ... with 1,055 more rows

@revodavid is a blogging machine, and that top-spot is well-deserved given the plethora of interesting, useful and fun content he shares. And, it looks like someone only needs to blog a bit more this year to overtake @hadley (I’m comin’ fer ya, Hadley!).

FIN

As promised, you can get the data in a ~30MB RDS file via https://rud.is/dl/r-bloggers-feedly-streams.rds and can then use the extraction-to-data-frame example from above to work with the bits you care about.

Hopefully folks will have some fun with this and share their results!

The 2018 IEEE Security & Privacy Conference is in May but they’ve posted their full proceedings and it’s better to grab them early than to wait for it to become part of a paid journal offering.

There are alot of papers. Not all match my interests but (fortunately?) many did and I’ve filtered down a list of the more interesting (to me) ones. It’s encouraging to see academic cybersecurity researchers branching out across a whole host of areas.

I can’t promise a the morning paper-esque daily treatment of these on the blog but I’ll likely exposit a few of them over the coming weeks. I’ve emoji’d a few that stood out. Order is the order I read them in (no other meaning to the order).

A while back, Medium blogger ‘Nykolas Z’ posted results from a globally distributed DNS resolver test to find the speediest provider (NOTE: speed is not the only consideration when choosing an alternative DNS provider). While the test methodology is not provided (the “scientific method” has yet to fully penetrate “cyber”) the data is provided…in in text form in <blockquote>s. O_o

While Nykolas ranked them, a visual comparison teases out some interesting differences between the providers. However, Cloudflare seems to be the clear winner (click/tap chart for larger version):

I’m going to give Cloudflare a few weeks to “settle in” and setup a series of geographically distributed RIPE Atlas probes for them and the others on the list Nykolas provided, then measure them with the same probe sets and frequencies for a few months and report back.

Some enterprising internet explorers have already begun monitoring 1.1.1.1 (that link may take a few seconds to show data since it performs a live search; a screen shot of the first page of results is below).

You have to have been living under a rock to not know about Cloudflare’s new 1.1.1.1 DNS offering. I won’t go into “privacy”, “security” or “speed” concepts in this post since that’s a pretty huge topic to distill for folks given the, now, plethora of confusing (and pretty technical) options that exist to support one or more of those goals.

Instead, I’ll remind R folks about the gdns? package which provides a query interface to Google’s DNS-over-HTTPS JSON API and announce dnsflare? which wraps the new and similar offering by Cloudflare. In fact, Cloudflare adopted Google’s response format so they’re pretty interchangeable:

str(gdns::query("r-project.org"))
## List of 10
##  $ Status            : int 0
##  $ TC                : logi FALSE
##  $ RD                : logi TRUE
##  $ RA                : logi TRUE
##  $ AD                : logi FALSE
##  $ CD                : logi FALSE
##  $ Question          :'data.frame': 1 obs. of  2 variables:
##   ..$ name: chr "r-project.org."
##   ..$ type: int 1
##  $ Answer            :'data.frame': 1 obs. of  4 variables:
##   ..$ name: chr "r-project.org."
##   ..$ type: int 1
##   ..$ TTL : int 2095
##   ..$ data: chr "137.208.57.37"
##  $ Additional        : list()
##  $ edns_client_subnet: chr "0.0.0.0/0"

str(dnsflare::query("r-project.org"))
## List of 8
##  $ Status  : int 0
##  $ TC      : logi FALSE
##  $ RD      : logi TRUE
##  $ RA      : logi TRUE
##  $ AD      : logi FALSE
##  $ CD      : logi FALSE
##  $ Question:'data.frame': 1 obs. of  2 variables:
##   ..$ name: chr "r-project.org."
##   ..$ type: int 1
##  $ Answer  :'data.frame': 1 obs. of  4 variables:
##   ..$ name: chr "r-project.org."
##   ..$ type: int 1
##   ..$ TTL : int 1420
##   ..$ data: chr "137.208.57.37"
##  - attr(*, "class")= chr "cf_dns_result"

The packages are primarily of use for internet researchers who need to lookup DNS-y things either as a data source in-and-of itself or to add metadata to names or IP addresses in other data sets.

I need to do some work on ensuring they both are on-par feature-wise (named-classes, similar print and batch query methods, etc) and should, perhaps, consider retiring gdns in favour of a new meta-DNS package that wraps all of these since I suspect all the cool kids will be setting these up, soon. (Naming suggestions welcome!)

There’s also getdns? which has very little but stub test code in it (for now) since it was unclear how quickly these new, modern DNS services would take off. But, since they have, that project will be revisited this year (jump in if ye like!) as is is (roughly) a “non-JSON” version of what gns and dnsflare are.

If you know of other, similar services that can be wrapped, drop a note in the comments or as an issue on one of those repos and also file an issue there if you have preferred response formats or have functionality you’d like implemented.

The fs package makes it super quick and easy to find out just how much “package hoarding” you’ve been doing:

library(fs)
library(ggalt) # devtools::install_github("hrbrmstr/ggalt")
library(igraph) 
library(ggraph) # devtools::install_github("thomasp85/ggraph")
library(hrbrthemes) # devtools::install_github("hrbrmstr/hrbrthemes")
library(tidyverse)

installed.packages() %>%
  as_data_frame() %>%
  mutate(pkg_dir = sprintf("%s/%s", LibPath, Package)) %>%
  select(pkg_dir) %>%
  mutate(pkg_dir_size = map_dbl(pkg_dir, ~{
    fs::dir_info(.x, all=TRUE, recursive=TRUE) %>%
      summarise(tot_dir_size = sum(size)) %>% 
      pull(tot_dir_size)
  })) %>% 
  summarise(
    total_size_of_all_installed_packages=ggalt::Gb(sum(pkg_dir_size))
  ) %>% 
  unlist()
## total_size_of_all_installed_packages 
##                             "1.6 Gb"

While you can modify the above and peruse the list of packages/directories in tabular format or programmatically, you can also do a bit more work to get a visual overview of package size (click/tap the image for a larger view):

installed.packages() %>%
  as_data_frame() %>%
  mutate(pkg_dir = sprintf("%s/%s", LibPath, Package)) %>%
  mutate(dir_info = map(pkg_dir, fs::dir_info, all=TRUE, recursive=TRUE)) %>% 
  mutate(dir_size = map_dbl(dir_info, ~sum(.x$size))) -> xdf

select(xdf, Package, dir_size) %>% 
  mutate(grp = "ROOT") %>% 
  add_row(grp = "ROOT", Package="ROOT", dir_size=0) %>% 
  select(grp, Package, dir_size) %>% 
  arrange(desc(dir_size)) -> gdf

select(gdf, -grp) %>% 
  mutate(lab = sprintf("%s\n(%s)", Package, ggalt::Mb(dir_size))) %>% 
  mutate(lab = ifelse(dir_size > 1500000, lab, "")) -> vdf

g <- graph_from_data_frame(gdf, vertices=vdf)

ggraph(g, "treemap", weight=dir_size) +
  geom_node_tile(fill="lightslategray", size=0.25) +
  geom_text(
    aes(x, y, label=lab, size=dir_size), 
    color="#cccccc", family=font_ps, lineheight=0.875
  ) +
  scale_x_reverse(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0)) +
  scale_size_continuous(trans="sqrt", range = c(0.5, 8)) +
  ggraph::theme_graph(base_family = font_ps) +
  theme(legend.position="none")

treemap of package disk consumption

Challenge

Do some wrangling with the above data and turn it into a package “disk explorer” with @timelyportfolio’s d3treeR? package.

(R⁶ == brief, low-expository posts)

@yoniceedee suggested I look at the Cambridge Analytics “whistleblower” testimony proceedings:

I value the resources @yoniceedee tosses my way (they often end me down twisted paths like this one, though :-) but I really dislike spending any amount of time on youtube and can consume text context much faster than even accelerated video playback.

Google auto-generated captions for that video and you can display them by clicking below the video on the right and enabling the transcript which slowly (well, in my frame of reference) loads into the upper-right. That’s still sub-optimal since we need to be on the youtube page to read/scroll. There’s no “export” option and my initial instinct was to go to Developer Tools and look for the https://www.youtube.com/service_ajax?name=getTranscriptEndpoint URL and “Copy the Response” to the clipboard and save it to a file then do some JSON/list wrangling (the transcript JSON URL is in the snippet below):

library(tidyverse)

trscrpt <- jsonlite::fromJSON("https://rud.is/dl/ca-transcript.json")

runs <- trscrpt$data$actions$openTranscriptAction$transcriptRenderer$transcriptRenderer$body$transcriptBodyRenderer$cueGroups[[1]]$transcriptCueGroupRenderer$formattedStartOffset$runs
cues <- trscrpt$data$actions$openTranscriptAction$transcriptRenderer$transcriptRenderer$body$transcriptBodyRenderer$cueGroups[[1]]$transcriptCueGroupRenderer$cues

data_frame(
  mark = map_chr(runs, ~.x$text),
  text = map_chr(cues, ~.x$transcriptCueRenderer$cue$runs[[1]]$text)  
) %>% 
  separate(mark, c("minute", "second"), sep=":", remove = FALSE, convert = TRUE) 
## # A tibble: 3,247 x 4
##    mark  minute second text                                    
##    <chr>  <int>  <int> <chr>                                   
##  1 00:00      0      0 all sort of yeah web of things if it's a
##  2 00:02      0      2 franchise then there's a kind of        
##  3 00:03      0      3 ultimately there's a there's a there's a
##  4 00:05      0      5 coordinator of that franchise or someone
##  5 00:07      0      7 who's a you got a that franchise is well
##  6 00:09      0      9 well when I was there that was Alexander
##  7 00:13      0     13 Nixon Steve banning but that's that's a 
##  8 00:16      0     16 question you should be asking aiq yeah  
##  9 00:18      0     18 yeah and just got to a IQ and the GSR   
## 10 00:24      0     24 state from gts-r that's other Hogan data
## # ... with 3,237 more rows

But, then I remembered YouTube has an API for this and threw together a quick script to grab them that way as well:

# the API needs these scopes

c(
  "https://www.googleapis.com/auth/youtube.force-ssl",
  "https://www.googleapis.com/auth/youtubepartner"
) -> scope_list

# oauth dance

httr::oauth_app(
  appname = "google",
  key = Sys.getenv("GOOGLE_APP_SECRET"),
  secret = Sys.getenv("GOOGLE_APP_KEY")
) -> captions_app

httr::oauth2.0_token(
  endpoint = httr::oauth_endpoints("google"),
  app = captions_app,
  scope = scope_list,
  cache = TRUE
) -> google_token

# list the available captions for this video
# (captions can be in one or more languages)

httr::GET(
  url = "https://www.googleapis.com/youtube/v3/captions",
  query = list(
    part = "snippet",
    videoId = "f2Sxob3fl0k" # the v=string in the YouTube URL
  ),
  httr::config(token = google_token)
) -> caps_list

# I'm cheating since I know there's only one but you'd want
# to introspect `caps_list` before blindly doing this for 
# other videos.

httr::GET(
  url = sprintf(
    "https://www.googleapis.com/youtube/v3/captions/%s",
    httr::content(caps_list)$items[[1]]$id
  ),
  httr::config(token = google_token)
) -> caps

# strangely enough, the JSON response "feels" better than this
# one, though this is a standard format that's parseable quite well.

cat(rawToChar(httr::content(caps)))
## 0:00:00.000,0:00:03.659
## all sort of yeah web of things if it's a
## 
## 0:00:02.490,0:00:05.819
## franchise then there's a kind of
## 
## 0:00:03.659,0:00:07.589
## ultimately there's a there's a there's a
## 
## 0:00:05.819,0:00:09.660
## coordinator of that franchise or someone
## 
## 0:00:07.589,0:00:13.139
## who's a you got a that franchise is well
## 
## 0:00:09.660,0:00:16.230
## well when I was there that was Alexander
## ...

Neither a reflection on active memory nor a quick Duck Duck Go search (I try not to use Google Search anymore) seemed to point to an existing R resource for this, hence the quick post in the event the snippet is helpful to anyone else.

If you do know of an R package/snippet that does this already, please shoot a note into the comments so others can find it.