Skip navigation

Author Archives: hrbrmstr

Don't look at me…I do what he does — just slower. #rstats avuncular • ?Resistance Fighter • Cook • Christian • [Master] Chef des Données de Sécurité @ @rapid7

Since I just railed against Congress for being a bit two-faced about privacy I thought some rud.is site disclosure would be in order.

At present, third-party tracking is limited to:

  • Something in my WordPress configuration adding a DNS pre-fetch for fonts.googleapis.com. There are a few more other DNS pre-fetches that I’m also going to try to eradicate (but that aren’t showing up in my uBlock Origin likely to to /etc/hosts blocks);
  • Gravatar (which displays logos near comment author names). I’m torn on this one but Gravatar is owned by Automattic (who owns WordPress). See next bullet on that;
  • WordPress. Vain site stats tracking, JetPack uptime warnings and some other WordPress pings happen (including some automatic short-linking) as well as the previous bullet bits. I’m not likely going to do the site surgery necessary to stop this but you have full disclosure and can easily avoid pings to those sites via uBlock Origin site-specific rules;
  • SendPulse; I’m running an experiment on user behaviours when it comes to authorizing web notifications (and I just kinda ruined said experiment). I’ll be disabling it later this year (after a full year of it being on so I can have more than just a few sentences to say).

The above came from an in-browser uBlock Origin report.

I ran a splashr::render_har() — which is how I measured things for the Congressional privacy post — on one of my pages and this is the result:

tld                 n
1 rud.is           67
2 wp.com           21
3 gravatar.com      6
4 wordpress.com     3
5 w.org             3
6 sendpulse.com     2

Props on WordPress capturing w.org! I’m still ticked Microsoft stole bob.com from me ages ago.

As you can see, most resources load from my site and none come from Twitter, Facebook or Google Plus.

I run WordPress for a ton of reasons too long to go into for this post, so I’m likely not going to change anything about that list (apart from the DNS pre-fetching).

Hopefully that will abate any concerns visitors might have, especially after reading the post about Congress.

I apologize up-front for using bad words in this post.

Said bad words include “Facebook”, “Mark Zuckerberg” and many referrals to entities within the U.S. Government. Given the topic, it cannot be helped.

I’ve also left the R tag on this despite only showing some ggplot2 plots and Markdown tables. See the end of the post for how to get access to the code & data. R was used solely and extensively for the work behind the words.


This week Congress put on a show as they summoned the current Facebook CEO — Mark Zuckerberg — down to Washington, D.C. to demonstrate how little most of them know about how the modern internet and social networks actually work plus chest-thump to prove to their constituents they really and truly care about you.

These Congress-critters offered such proof in the guise of railing against Facebook for how they’ve handled your data. Note that I should really say our data since they do have an extensive profile database on me and most everyone else even if they’re not Facebook platform users (full disclosure: I do not have a Facebook account).

Ostensibly, this data-mishandling impacted your privacy. Most of the committee members wanted any constituent viewers to come away believing they and their fellow Congress-critters truly care about your privacy.

Fortunately, we have a few ways to measure this “caring” and the remainder of this post will explore how much members of the U.S. House and Senate care about your privacy when you visit their official .gov web sites. Future posts may explore campaign web sites and other metrics, but what better place to show they care about you then right there in their digital houses.

Privacy Primer

When you visit a web site with any browser, the main URL pulls in resources to aid in the composition and functionality of the page. These could be:

  • HTML (the main page is very likely HTML unless it’s just a media URL)
  • images (png, jpg, gif, “svg”, etc),
  • fonts
  • CSS (the “style sheet” that tells the browser how to decorate and position elements on the page)
  • binary objects (such as embedded PDF files or “protocol buffer” content)
  • XML or JSON
  • JavaScript

(plus some others)

When you go to, say, www.example.com the site does not have to load all the resources from example.com domains. In fact, it’s rare to find a modern site which does not use resources from one or more third party sites.

When each resource is loaded (generally) some information about you goes along for the ride. At a minimum, the request time and source (your) IP address is exposed and — unless you’re really careful/paranoid — the referring site, browser configuration and even cookies are even available to the third party sites. It does not take many of these data points to (pretty much) uniquely identify you. And, this is just for “benign” content like images. We’ll get to JavaScript in a bit.

As you move along the web, these third-party touch-points add up. To demonstrate this, I did my best to de-privatize my browser and OS configuration and visited 12 web sites while keeping a fresh install of Firefox Lightbeam running. Here’s the result:

Each main circle is a distinct/main site and the triangles are resources the site tried to load. The red triangles indicate a common third-party resource that was loaded by two or more sites. Each of those red triangles knows where you’ve been (again, unless you’ve been very careful/paranoid) and can use that information to enhance their knowledge about you.

It gets a bit worse with JavaScript content since a much stronger fingerprint can be created for you (you can learn more about fingerprints at this spiffy EFF site). Plus, JavaScript code can try to pilfer cookies, “hack” the browser, serve up malicious adverts, measure time-on-site, and even enlist you in a cryptomining army.

There are other issues with trusting loaded browser content, but we’ll cover that a bit further into the investigation.

Measuring “Caring”

The word “privacy” was used over 100 times each day by both Zuckerberg and our Congress-critters. Senators and House members made it pretty clear Facebook should care more about your privacy. Implicit in said posit is that they, themselves, must care about your privacy. I’m sure they’ll be glad to point out all along the midterm campaign trails just how much they’re doing to protect your privacy.

We don’t just have to take their word for it. After berating Facebook’s chief college dropout and chastising the largest social network on the planet we can see just how much of “you” these representatives give to Facebook (and other sites) and also how much they protect you when you decide to pay them[] [] a digital visit.

For this metrics experiment, I built a crawler using R and my splashr? package which, in turn, uses ScrapingHub’s open source Splash. Splash is an automation framework that lets you programmatically visit a site just like a human would with a real browser.

Normally when one scrapes content from the internet they’re just grabbing the plain, single HTML file that is at the target of a URL. Splash lets us behave like a browser and capture all the resources — images, CSS, fonts, JavaScript — the site loads and will also execute any JavaScript, so it will also capture resources each script may itself load.

By capturing the entire browser experience for the main page of each member of Congress we can get a pretty good idea of just how much each one cares about your digital privacy, and just how much they secretly love Facebook.

Let’s take a look, first, at where you go when you digitally visit a Congress-critter.

Network/Hosting/DNS

Each House and Senate member has an official (not campaign) site that is hosted on a .gov domain and served up from a handful of IP addresses across the following (n is the number of Congress-critter web sites):

asn aso n
AS5511 Orange 425
AS7016 Comcast Cable Communications, LLC 95
AS20940 Akamai International B.V. 13
AS1999 U.S. House of Representatives 6
AS7843 Time Warner Cable Internet LLC 1
AS16625 Akamai Technologies, Inc. 1

“Orange” is really Akamai and Akamai is a giant content delivery network which helps web sites efficiently provide content to your browser and can offer Denial of Service (DoS) protection. Most sites are behind Akamai, which means you “touch” Akamai every time you visit the site. They know you were there, but I know a sufficient body of folks who work at Akamai and I’m fairly certain they’re not too evil. Virtually no representative solely uses House/Senate infrastructure, but this is almost a necessity given how easy it is to take down a site with a DoS attack and how polarized politics is in America.

To get to those IP addresses, DNS names like www.king.senate.gov (one of the Senators from my state) needs to be translated to IP addresses. DNS queries are also data gold mines and everyone from your ISP to the DNS server that knows the name-to-IP mapping likely sees your IP address. Here are the DNS servers that serve up the directory lookups for all of the House and Senate domains:

nameserver gov_hosted
e4776.g.akamaiedge.net. FALSE
wc.house.gov.edgekey.net. FALSE
e509.b.akamaiedge.net. FALSE
evsan2.senate.gov.edgekey.net. FALSE
e485.b.akamaiedge.net. FALSE
evsan1.senate.gov.edgekey.net. FALSE
e483.g.akamaiedge.net. FALSE
evsan3.senate.gov.edgekey.net. FALSE
wwwhdv1.house.gov. TRUE
firesideweb02cc.house.gov. TRUE
firesideweb01cc.house.gov. TRUE
firesideweb03cc.house.gov. TRUE
dchouse01cc.house.gov. TRUE
c3pocc.house.gov. TRUE
ceweb.house.gov. TRUE
wwwd2-cdn.house.gov. TRUE
45press.house.gov. TRUE
gopweb1a.house.gov. TRUE
eleven11web.house.gov. TRUE
frontierweb.house.gov. TRUE
primitivesocialweb.house.gov. TRUE

Akamai kinda does need to serve up DNS for the sites they host, so this list also makes sense. But, you’ve now had two touch-points logged and we haven’t even loaded a single web page yet.

Safe? & Secure? Connections

When we finally make a connection to a Congress-critter’s site, it is going to be over SSL/TLS. They all support it (which is ?, but SSL/TLS confidentiality is not as bullet-proof as many “HTTPS Everywhere” proponents would like to con you into believing). However, I took a look at the SSL certificates for House and Senate sites. Here’s a sampling from, again, my state (one House representative):

The *.house.gov “Common Name (CN)” is a wildcard certificate. Many SSL certificates have just one valid CN, but it’s also possible to list alternate, valid “alt” names that can all use the same, single certificate. Wildcard certificates ease the burden of administration but it also means that if, say, I managed to get my hands on the certificate chain and private key file, I could setup vladimirputin.house.gov somewhere and your browser would think it’s A-OK. Granted, there are far more Representatives than there are Senators and their tenure length is pretty erratic these days, so I can sort of forgive them for taking the easy route, but I also in no way, shape or form believe they protect those chains and private keys well.

In contrast, the Senate can and does embed the alt-names:

Are We There Yet?

We’ve got the IP address of the site and established a “secure” connection. Now it’s time to grab the index page and all the rest of the resources that come along for the ride. As noted in the Privacy Primer (above), the loading of third-party resources is problematic from a privacy (and security) perspective. Just how many third party resources do House and Senate member sites rely on?

To figure that out, I tallied up all of the non-.gov resources loaded by each web site and plotted the distribution of House and Senate (separately) in a “beeswarm” plot with a boxplot shadowing underneath so you can make out the pertinent quantiles:

As noted, the median is around 30 for both House and Senate member sites. In other words, they value your browsing privacy so little that most Congress-critters gladly share your browser session with many other sites.

We also talked about confidentiality above. If an https site loads http resources the contents of what you see on the page cannot but guaranteed. So, how responsible are they when it comes to at least ensuring these third-party resources are loaded over https?

You’re mostly covered from a pseudo-confidentiality perspective, but what are they serving up to you? Here’s a summary of the MIME types being delivered to you:

MIME Type Number of Resources Loaded
image/jpeg 6,445
image/png 3,512
text/html 2,850
text/css 1,830
image/gif 1,518
text/javascript 1,512
font/ttf 1,266
video/mp4 974
application/json 673
application/javascript 670
application/x-javascript 353
application/octet-stream 187
application/font-woff2 99
image/bmp 44
image/svg+xml 39
text/plain 33
application/xml 15
image/jpeg, video/mp2t 12
application/x-protobuf 9
binary/octet-stream 5
font/woff 4
image/jpg 4
application/font-woff 2
application/vnd.google.gdata.error+xml 1

We’ll cover some of these in more detail a bit further into the post.

Facebook & “Friends”

Facebook started all this, so just how cozy are these Congress-critters with Facebook?

Turns out that both Senators and House members are very comfortable letting you give Facebook a love-tap when you come visit their sites since over 60% of House and 40% of Senate sites use 2 or more Facebook resources. Not all Facebook resources are created equal[ly evil] and we’ll look at some of the more invasive ones soon.

Facebook is not the only devil out there. I added in the public filter list from Disconnect and the numbers go up from 60% to 70% for the House and from 40% to 60% for the Senate when it comes to a larger corpus of known tracking sites/resources.

Here’s a list of some (first 20) of the top domains (with one of Twitter’s media-serving domains taking the individual top-spot):

Main third-party domain # of ‘pings’ %
twimg.com 764 13.7%
fbcdn.net 655 11.8%
twitter.com 573 10.3%
google-analytics.com 489 8.8%
doubleclick.net 462 8.3%
facebook.com 451 8.1%
gstatic.com 385 6.9%
fonts.googleapis.com 270 4.9%
youtube.com 246 4.4%
google.com 183 3.3%
maps.googleapis.com 144 2.6%
webtrendslive.com 95 1.7%
instagram.com 75 1.3%
bootstrapcdn.com 68 1.2%
cdninstagram.com 63 1.1%
fonts.net 51 0.9%
ajax.googleapis.com 50 0.9%
staticflickr.com 34 0.6%
translate.googleapis.com 34 0.6%
sharethis.com 32 0.6%

So, when you go to check out what your representative is ‘officially’ up to, you’re being served…up on a silver platter to a plethora of sites where you are the product.

It’s starting to look like Congress-folk aren’t as sincere about your privacy as they may have led us all to believe this week.

A [Java]Script for Success[ful Privacy Destruction]

As stated earlier, not all third-party content is created equally malicious. JavaScript resources run code in your browser on your device and while there are limits to what it can do, those limits diminish weekly as crafty coders figure out more ways to use JavaScript to collect information and perform shady or malicious deeds.

So, how many House/Senate sites load one or more third-party JavaScript resources?

Virtually all of them.

To make matters worse, no .gov or third-party resource of any kind was loaded using subresource integrity validation. Subresource integrity validation means that the site owner — at some point — ensured that the resource being loaded was not malicious and then created a fingerprint for it and told your browser what that fingerprint is so it can compare it to what got loaded. If the fingerprints don’t match, the content is not loaded/executed. Using subresource integrity is not trivial since it requires a top-notch content management team and failure to synchronize/checkpoint third-party content fingerprints will result in resources failing to load.

Congress was quick to demand that Facebook implement stronger policies and controls, but they, themselves, cannot be bothered.

Future Work

There are plenty more avenues to explore in this data set (such as “security headers” — they all 100% use strict-transport-security pretty well, but are deeply deficient in others) and more targets for future works, such as the campaign sites of House and Senate members. I may follow up with a look at a specific slice from this data set (the members of the committees who were berating Zuckerberg this week).

The bottom line is that while the beating Facebook took this week was just, those inflicting the pain have a long way to go themselves before they can truly judge what other social media and general internet sites do when it comes to ensuring the safety and privacy of their visitors.

In other words, “Legislator, regulate thyself” before thy regulatists others.

FIN

Apart from some egregiously bad (or benign) examples, I tried not to “name and shame”. I also won’t answer any questions about facets by party since that really doesn’t matter too much as they’re all pretty bad when it comes to understanding and implementing privacy and safey on their sites.

The data set can be found over at Zenodo (alternately, click/tap/select the badge below). I converted the R data frame to ndjson/streaming JSON/jsonlines (however you refer to the format) and tested it out in Apache Drill.

I’ll toss up some R code using data extracts later this week (meaning by April 20th).

DOI

@RMHoge asked the following on Twitter:

Here’s one way to do that which doesn’t rely on pandoc (pandoc can easily do this and ships with RStudio but shelling out for this is cheating :-)

We’ll need some help (NOTE that 2 of these are “GitHub” packages)

library(archive) # install_github("jimhester/archive") + 3rd party library
library(hgr) # install_github("hrbrmstr/hgr")
library(stringi)
library(tidyverse)

We’ll use one of @hadleywickham’s books since it’s O’Reilly and they do epubs well. The archive package lets us treat the epub (which is really just a ZIP file) as a mini-filesystem and embraces “tidy” so we have lovely data frames to work with:

bk_src <- "~/Data/R Packages.epub"

bk <- archive::archive(bk_src)

bk
## # A tibble: 92 x 3
##    path                           size date               
##    <chr>                         <dbl> <dttm>             
##  1 mimetype                        20. 2015-03-24 21:49:16
##  2 OEBPS/assets/cover.png      211616. 2015-06-03 16:16:56
##  3 OEBPS/content.opf            10193. 2015-03-24 21:49:16
##  4 OEBPS/toc.ncx                30037. 2015-03-24 21:49:16
##  5 OEBPS/cover.html               315. 2015-03-24 21:49:16
##  6 OEBPS/titlepage01.html         466. 2015-03-24 21:49:16
##  7 OEBPS/copyright-page01.html   3286. 2015-03-24 21:49:16
##  8 OEBPS/toc01.html             17557. 2015-03-24 21:49:16
##  9 OEBPS/preface01.html         17784. 2015-03-24 21:49:16
## 10 OEBPS/part01.html              444. 2015-03-24 21:49:16
## # ... with 82 more rows

We care not about crufty bits and only want HTML files (NOTE: I use html for the pattern since they can be .xhtml files as well):

## # A tibble: 26 x 3
##    path                          size date               
##    <chr>                        <dbl> <dttm>             
##  1 OEBPS/cover.html              315. 2015-03-24 21:49:16
##  2 OEBPS/titlepage01.html        466. 2015-03-24 21:49:16
##  3 OEBPS/copyright-page01.html  3286. 2015-03-24 21:49:16
##  4 OEBPS/toc01.html            17557. 2015-03-24 21:49:16
##  5 OEBPS/preface01.html        17784. 2015-03-24 21:49:16
##  6 OEBPS/part01.html             444. 2015-03-24 21:49:16
##  7 OEBPS/ch01.html             12007. 2015-03-24 21:49:16
##  8 OEBPS/ch02.html             28633. 2015-03-24 21:49:18
##  9 OEBPS/part02.html             454. 2015-03-24 21:49:18
## 10 OEBPS/ch03.html             28629. 2015-03-24 21:49:18
## # ... with 16 more rows

Let’s read in one file (as a test) and convert it to text and show the first few lines of it:

archive::archive_read(bk, "OEBPS/preface01.html") %>%
  read_lines() %>%
  paste0(collapse = "\n") -> chapter

hgr::clean_text(chapter) %>%
  stri_sub(1, 1000) %>%
  cat()
## Preface
## 
## 
## In This Book
## 
## This book will guide you from being a user of R packages to being a creator of R packages. In , you’ll learn why mastering this skill is so important, and why it’s easier than you think. Next, you’ll learn about the basic structure of a package, and the forms it can take, in . The subsequent chapters go into more detail about each component. They’re roughly organized in order of importance:
## 
## 
##  The most important directory is R/, where your R code lives. A package with just this directory is still a useful package. (And indeed, if you stop reading the book after this chapter, you’ll have still learned some useful new skills.)
##  
##  The DESCRIPTION lets you describe what your package needs to work. If you’re sharing your package, you’ll also use the DESCRIPTION to describe what it does, who can use it (the license), and who to contact if things go wrong.
##  
##  If you want other people (including “future you”!) to understand how to use the functions in your package, you’

hgr::clean_text() uses some XSLT magic to pull text. My jericho? can often do a better job but it’s rJava-based so a bit painful for some folks to get running.

Now, we’ll convert all the files:

filter(bk, stri_detect_fixed(path, "html")) %>%
  mutate(content = map_chr(path, ~{
    archive::archive_read(bk, .x) %>%
      read_lines() %>%
      paste0(collapse = "\n") %>%
      hgr::clean_text()
  })) %>%
  print(n=27)
## # A tibble: 26 x 4
##    path                          size date                content         
##    <chr>                        <dbl> <dttm>              <chr>           
##  1 OEBPS/cover.html              315. 2015-03-24 21:49:16 Cover           
##  2 OEBPS/titlepage01.html        466. 2015-03-24 21:49:16 "R Packages\n\n…
##  3 OEBPS/copyright-page01.html  3286. 2015-03-24 21:49:16 "R Packages\n\n…
##  4 OEBPS/toc01.html            17557. 2015-03-24 21:49:16 "navPrefaceIn T…
##  5 OEBPS/preface01.html        17784. 2015-03-24 21:49:16 "Preface\n\n\nI…
##  6 OEBPS/part01.html             444. 2015-03-24 21:49:16 Getting Started 
##  7 OEBPS/ch01.html             12007. 2015-03-24 21:49:16 "Introduction\n…
##  8 OEBPS/ch02.html             28633. 2015-03-24 21:49:18 "Package Struct…
##  9 OEBPS/part02.html             454. 2015-03-24 21:49:18 Package Compone…
## 10 OEBPS/ch03.html             28629. 2015-03-24 21:49:18 "R Code\n\nThe …
## 11 OEBPS/ch04.html             31275. 2015-03-24 21:49:18 "Package Metada…
## 12 OEBPS/ch05.html             42089. 2015-03-24 21:49:18 "Object Documen…
## 13 OEBPS/ch06.html             31484. 2015-03-24 21:49:18 "Vignettes: Lon…
## 14 OEBPS/ch07.html             28594. 2015-03-24 21:49:18 "Testing\n\nTes…
## 15 OEBPS/ch08.html             30808. 2015-03-24 21:49:18 "Namespace\n\nT…
## 16 OEBPS/ch09.html             12125. 2015-03-24 21:49:18 "External Data\…
## 17 OEBPS/ch10.html             42013. 2015-03-24 21:49:18 "Compiled Code\…
## 18 OEBPS/ch11.html              8933. 2015-03-24 21:49:18 "Installed File…
## 19 OEBPS/ch12.html              3897. 2015-03-24 21:49:18 "Other Componen…
## 20 OEBPS/part03.html             446. 2015-03-24 21:49:18 Best Practices  
## 21 OEBPS/ch13.html             59493. 2015-03-24 21:49:18 "Git and GitHub…
## 22 OEBPS/ch14.html             44702. 2015-03-24 21:49:18 "Automated Chec…
## 23 OEBPS/ch15.html             39450. 2015-03-24 21:49:18 "Releasing a Pa…
## 24 OEBPS/ix01.html             75277. 2015-03-24 21:49:20 IndexAad hoc te…
## 25 OEBPS/colophon01.html         974. 2015-03-24 21:49:20 "About the Auth…
## 26 OEBPS/colophon02.html        1653. 2015-03-24 21:49:20 "Colophon\n\nTh…

I’m not wrapping this into a package anytime soon but this is also a pretty basic flow that may not require a package. This has been wrapped into a small package dubbed pubcrawl?.

Drop a note in the comments with your hints/workflows on converting epub to plaintext!

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).