The general idea

Data transfer is highly controlled. The key notions are authentication and protocol.

Downloading tweets with rtweet

There are several packages that run an interface with twitter: rtweet, RTwitterAPI, streamR and twitteR.
Recent packages are better because firms update their API policies (and access), thus old protocols sometimes do not work!

First things first

First, the packages. Download…

if(!require(rtweet)){install.packages("rtweet")}

… and activate.

library(tidyverse)
library(plotly)
library(rtweet)

Authentication

Second: you need your twitter credentials (you need a twitter account). You also need a developer account: https://developer.twitter.com/en/apply-for-access Login on twitter and go to: https://developer.twitter.com

The next step is crucial: we need to retrieve identification credentials.
In order to do that, you need to create a Twitter app. Below, you can see mine. To create one, simply click on the “Create an app” button (on the right)

If you click on the “details” of an app, you can see this:

The second tab is called “Keys and tokens\(\rightarrow\) that’s where the info is!!!

Now we are ready to proceed. The lines below open the connexion with the API.

consumer_key <- "your_consumer_key"
consumer_secret <- "you_consumer_secret"
access_token <- "your_access_token"
access_secret <- "your_access_secret"

create_token(app = "the_name_of_your_app",
             consumer_key = consumer_key, 
             consumer_secret = consumer_secret, 
             access_token = access_token, 
             access_secret = access_secret
             )
<Token>
<oauth_endpoint>
 request:   https://api.twitter.com/oauth/request_token
 authorize: https://api.twitter.com/oauth/authenticate
 access:    https://api.twitter.com/oauth/access_token
<oauth_app> Big Doudou
  key:    xqMm10Vwwl1XAx31CAzYNiqoi
  secret: <hidden>
<credentials> oauth_token, oauth_token_secret
---

Authentication is an important part of the process. For more info on that:
- https://cran.r-project.org/web/packages/googlesheets/vignettes/managing-auth-tokens.html
- https://httr.r-lib.org/reference/index.html (section Authentication)

Extraction

If no error appears, we are ready to query. Depending on the number of requested tweets, this can take some time.

search_term <- "confinement"
tweets <- search_tweets(
  search_term,          # What to search for
  n = 2000,             # Number of tweets to download
  include_rts = FALSE   # Exclude re-tweets
)

For large queries, the progress bar helps.
Note that many options are available, like: exclude retweets, limit search to particular geographical zones (inside radiuses).

Text mining

References

The reference book is: https://www.tidytextmining.com
A great interactive tutorial: https://juliasilge.shinyapps.io/learntidytext/
And the package is:

if(!require(tidytext)){install.packages("tidytext", repos = "https://cloud.r-project.org/")}
library(tidytext)

(see also: https://quanteda.io/index.html)

Data retrieval

Now, let’s move forward to simple text analysis. First, we need to prepare the data! (as usual)

tokens <- tweets %>% 
    mutate(id = row_number()) %>%    # This creates a tweet id
    select(id, text) %>%             # Keeps only id and text of the tweet
    unnest_tokens(word, text)        # Creates tokens!
tokens

Let’s have a look at word frequencies.

tokens %>%
    count(word, sort = TRUE)

This is polluted by small words. Let’s filter that (FIRST METHOD).

tokens %>% mutate(length = nchar(word))

Data frequencies

Now let’s omit the small words (smaller than 5 characters).
NOTE: all the thresholds below depend on the sample!

tokens_2 %>%
    mutate(length = nchar(word)) %>%
    filter(length > 4) %>%             # Keep words with length larger than 4
    count(word, sort = TRUE) %>%       # Count words
    head(21) %>%                       # Keep only top 12 words
    ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

A better way to proceed is to remove “stop words” like “a”, “I”, “of”, “the”, etc (SECOND METHOD). Also, it would make sense to remove the search item and “https”.

data("stop_words")
tidy_tokens <- tokens %>% 
    anti_join(stop_words)                    # Remove unrelevant terms
tidy_tokens %>%
    count(word, sort = TRUE) %>%             # Count words
    head(20) %>%                             # Keep only top 15 words
    ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

Problem: strange characters remain. We are going to remove them by converting the text to ASCII format and omit NA data.

tidy_tokens <- tokens %>% 
    anti_join(stop_words) %>%                            # Remove unrelevant
    mutate(word = iconv(word, from = "UTF-8", to = "ASCII")) %>% # Put in latin format
    na.omit() %>%                                        # Remove missing
    filter(nchar(word) > 2,                              # Remove small words
           !(word %in% c("https", "t.co", search_term))  # search_term defined above
    )
tidy_tokens %>%
    count(word, sort = TRUE) %>%         # Count words
    head(20) %>%                         # Keep only top words
    ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

Perfect!

Word cloud

This data can also be shown with a word cloud. We simply use the wordcloud package: https://cran.r-project.org/web/packages/wordcloud/index.html

The package wordcloud2 adds a few features: https://cran.r-project.org/web/packages/wordcloud2/vignettes/wordcloud.html

if(!require(wordcloud)){install.packages("wordcloud")}
library(wordcloud)
cloud_data <- tidy_tokens %>% count(word)
wordcloud(words = cloud_data$word, 
          freq = cloud_data$n, min.freq = 10,
          max.words = 100, random.order = FALSE, rot.per = 0.15, 
          colors = brewer.pal(8, "Dark2"))

n-grams

See https://www.tidytextmining.com/ngrams.html

tweets %>% 
    mutate(id = 1:nrow(tweets)) %>%    # This creates a tweet id
    select(id, text, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
  group_by(bigram) %>%
  count(sort = T) %>%
  head(20) %>%
  ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col()

Again: same issue with stop words! So we must remove them again. But it’s more complicated now. We can use the separate() function to help us.

tweets %>% 
    mutate(id = row_number()) %>%      # This creates a tweet id
    select(id, text, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
   separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
  filter(!(word1 %in% c(stop_words$word, "https", search_term)),
         !(word2 %in% c(stop_words$word, "https", search_term))) %>%
  group_by(bigram) %>%
  count(sort = T) %>%
  head(20) %>%
  ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col() + ylab("Bi-gram")

Sentiment

This section is inspired from: https://www.tidytextmining.com/sentiment.html
Sometimes, you may be asked in the process if you really want to download data (lexicons).
Just say yes in the console (type the correct answer: if not, you will be blocked/struck).

First, we need to load some sentiment lexicon. AFINN is one such sentiment database.

if(!require(textdata)){install.packages("textdata", repos = "https://cloud.r-project.org/")}
Loading required package: textdata
library(tidytext)
library(textdata)
afinn <- get_sentiments("afinn")
afinn

To create a nice visualization, we need to extract the time of the tweets.

tokens_time <- tweets %>% 
    mutate(id = 1:nrow(tweets)) %>%    # This creates a tweet id
    select(id, text, created_at) %>%   # Keeps id, text and date of the tweet
    unnest_tokens(word, text)          # Creates tokens!
tokens_time

We then use inner_join() to merge the two sets. This function removes the cases when a match does not occur.

library(lubridate)
sentiment <- tokens_time %>% 
  inner_join(afinn) %>%
  mutate(day = day(created_at),
         hour = hour(created_at) / 24,
         minute = minute(created_at) / 60 / 24,
         time = day + hour + minute)
sentiment

We then compute the average sentiment, minute-by-minute.
Of course, average sentiment can be misleading. Indeed, if a text contains the terms “I’m not happy”, then only “happy” will be tagged, which is the opposite of the intended meaning.

sentiment %>%
  group_by(time, day, hour, minute) %>%
  summarise(avg_sentiment = mean(value)) %>%
  mutate(time = make_datetime(year = 2020, month = 4, day = day, hour = hour*24, min = minute*24*60)) %>%
  ggplot(aes(x = time, y = avg_sentiment)) + geom_col() 
`summarise()` has grouped output by 'time', 'day', 'hour'. You can override using the `.groups` argument.

There are 24 bars per day, but the y-axis is not optimal…

What about emotions? The NRC lexicon categorizes emotions. Below, we order emotions. The most important impact is the dichotomy between positive & negative emotions.

nrc <- get_sentiments("nrc")
nrc <- nrc %>%
  mutate(sentiment = as.factor(sentiment),
         sentiment = recode_factor(sentiment,
                                   joy = "joy",
                                   trust = "trust",
                                   surprise = "surprise",
                                   anticipation = "anticipation",
                                   positive = "positive",
                                   negative = "negative",
                                   sadness = "sadness",
                                   anger = "anger",
                                   fear = "fear",
                                   digust = "disgust",
                                   .ordered = T))

We then create the merged dataset.

emotions <- tokens_time %>% 
  inner_join(nrc) %>%                  # Merge data with sentiment
  mutate(day = day(created_at),
         hour = hour(created_at)/24,
         minute = minute(created_at)/24/60,
         time = day + hour + minute)   # Create day column
emotions                               # Show the result

The merging has reduced the size of the dataset, but there still remains enough to pursue the study.
Finally, we move to the pivot-table that counts emotions for each day.

g <- emotions %>% 
  group_by(time, sentiment, day, hour, minute) %>%
  summarise(intensity = n()) %>%
  mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
  ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col() + 
  theme(axis.text.x = element_text(angle = 80, 
                                   size = 10,
                                   hjust = 1)) + xlab("Time") +
  scale_fill_viridis(option = "magma", discrete = T, direction = -1)
`summarise()` has grouped output by 'time', 'sentiment', 'day', 'hour'. You can override using the `.groups` argument.
ggplotly(g)

This can also be shown in percentage format.

g <- emotions %>% 
  group_by(time, sentiment, day, hour, minute) %>%
  summarise(intensity = n()) %>%
  mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
  ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
  theme(axis.text.x = element_text(angle = 80, 
                                   size = 10,
                                   hjust = 1)) + xlab("Time") +
  scale_fill_viridis(option = "magma", discrete = T, direction = -1)
ggplotly(g)
emotions %>% 
  mutate(sentiment = if_else(sentiment < "negative", "positive", "negative")) %>% 
  group_by(time, sentiment, day, hour, minute) %>%
  summarise(intensity = n()) %>%
  mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
  ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
  theme(axis.text.x = element_text(angle = 80, 
                                   size = 10,
                                   hjust = 1)) + xlab("Time") +
  scale_fill_manual(values = c("#001144", "#FFDD99"))
`summarise()` has grouped output by 'time', 'sentiment', 'day', 'hour'. You can override using the `.groups` argument.

Advanced sentiment

The problem with the preceding methods is that they don’t take into account valence shifters (i.e., negators, amplifiers (intensifiers), de-amplifiers (downtoners), and adversative conjunctions). If a tweet says not happy, counting the word happy is not a good idea! The package sentimentr is built to circumvent these issues: have a look at https://github.com/trinker/sentimentr
(see also: https://www.sentometrics.org and the book Supervised Machine Learning for Text Analysis in R hosted at https://smltar.com)

I haven’t tested aws.comprehend, but it seems promising: https://github.com/cloudyr/aws.comprehend

if(!require(sentimentr)){install.packages(c("sentimentr", "textcat"))}
library(sentimentr)
library(textcat)

First, let’s keep only the tweets written in English!

tweets_en <- tweets %>%
  mutate(language = textcat(text)) %>%
  filter(language == "english") %>%
  dplyr::select(created_at, text)

NOTE: the code above was used to show the function textcat: the language is already coded in the tweets via the lang column/variable. (it suffices to keep the instances for which lang == “en”)

Next, we compute advanced sentiment.

tweet_sent <- tweets_en$text %>%
  get_sentences() %>%  # Intermediate function
  sentiment()          # Sentiment!
tweet_sent

NOTE: depending on frequency issues, it is better to analyze at daily or hourly scales. If a word is very popular, then, higher frequencies are more relevant.

Resources

Below, a short list of resources (to access third-party data):

Possibly deprecated:
- Facebook: https://cran.r-project.org/web/packages/Rfacebook/index.html
- Instagram: https://cran.r-project.org/web/packages/instaR/index.html

LS0tCnRpdGxlOiAiVGhpcmQgcGFydHkgZGF0YSBhbmQgYmFzaWMgdGV4dCBtaW5pbmciCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIFRoZSBnZW5lcmFsIGlkZWEKCkRhdGEgdHJhbnNmZXIgaXMgaGlnaGx5IGNvbnRyb2xsZWQuIFRoZSBrZXkgbm90aW9ucyBhcmUgKiphdXRoZW50aWNhdGlvbioqIGFuZCAqKnByb3RvY29sKiouCgojIERvd25sb2FkaW5nIHR3ZWV0cyB3aXRoICpydHdlZXQqCgpUaGVyZSBhcmUgc2V2ZXJhbCBwYWNrYWdlcyB0aGF0IHJ1biBhbiBpbnRlcmZhY2Ugd2l0aCB0d2l0dGVyOiAqcnR3ZWV0KiwgKlJUd2l0dGVyQVBJKiwgKnN0cmVhbVIqIGFuZCAqdHdpdHRlUiouCQkKUmVjZW50IHBhY2thZ2VzIGFyZSBiZXR0ZXIgYmVjYXVzZSBmaXJtcyB1cGRhdGUgdGhlaXIgQVBJIHBvbGljaWVzIChhbmQgYWNjZXNzKSwgdGh1cyBvbGQgcHJvdG9jb2xzIHNvbWV0aW1lcyBkbyBub3Qgd29yayEKCiMjIEZpcnN0IHRoaW5ncyBmaXJzdAoqKkZpcnN0KiosIHRoZSBwYWNrYWdlcy4gRG93bmxvYWQuLi4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUocnR3ZWV0KSl7aW5zdGFsbC5wYWNrYWdlcygicnR3ZWV0Iil9CmBgYAoKLi4uIGFuZCBhY3RpdmF0ZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJ0d2VldCkKYGBgCgojIyBBdXRoZW50aWNhdGlvbgoKKipTZWNvbmQqKjogeW91IG5lZWQgeW91ciB0d2l0dGVyIGNyZWRlbnRpYWxzICh5b3UgbmVlZCBhIHR3aXR0ZXIgYWNjb3VudCkuCllvdSBhbHNvIG5lZWQgYSAqKmRldmVsb3BlciBhY2NvdW50Kio6IGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL2FwcGx5LWZvci1hY2Nlc3MgCkxvZ2luIG9uIHR3aXR0ZXIgYW5kIGdvIHRvOiBodHRwczovL2RldmVsb3Blci50d2l0dGVyLmNvbSAKCiFbXSh0d2l0dGVyMS5wbmcpCgpUaGUgbmV4dCBzdGVwIGlzIGNydWNpYWw6IHdlIG5lZWQgdG8gcmV0cmlldmUgaWRlbnRpZmljYXRpb24gY3JlZGVudGlhbHMuICAgCkluIG9yZGVyIHRvIGRvIHRoYXQsIHlvdSBuZWVkIHRvIGNyZWF0ZSBhIFR3aXR0ZXIgYXBwLiBCZWxvdywgeW91IGNhbiBzZWUgbWluZS4gClRvIGNyZWF0ZSBvbmUsIHNpbXBseSBjbGljayBvbiB0aGUgIipDcmVhdGUgYW4gYXBwKiIgIGJ1dHRvbiAob24gdGhlIHJpZ2h0KQoKIVtdKHR3aXR0ZXIyLnBuZykKCklmIHlvdSBjbGljayBvbiB0aGUgImRldGFpbHMiIG9mIGFuIGFwcCwgeW91IGNhbiBzZWUgdGhpczoKCiFbXSh0d2l0dGVyMy5wbmcpCgpUaGUgc2Vjb25kIHRhYiBpcyBjYWxsZWQgIioqS2V5cyBhbmQgdG9rZW5zKioiICRccmlnaHRhcnJvdyQgdGhhdCdzIHdoZXJlIHRoZSBpbmZvIGlzISEhCgohW10odHdpdHRlcjQucG5nKQoKCk5vdyB3ZSBhcmUgcmVhZHkgdG8gcHJvY2VlZC4gVGhlIGxpbmVzIGJlbG93IG9wZW4gdGhlIGNvbm5leGlvbiB3aXRoIHRoZSBBUEkuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0KY29uc3VtZXJfa2V5IDwtICJ5b3VyX2NvbnN1bWVyX2tleSIKY29uc3VtZXJfc2VjcmV0IDwtICJ5b3VfY29uc3VtZXJfc2VjcmV0IgphY2Nlc3NfdG9rZW4gPC0gInlvdXJfYWNjZXNzX3Rva2VuIgphY2Nlc3Nfc2VjcmV0IDwtICJ5b3VyX2FjY2Vzc19zZWNyZXQiCgpjcmVhdGVfdG9rZW4oYXBwID0gInRoZV9uYW1lX29mX3lvdXJfYXBwIiwKICAgICAgICAgICAgIGNvbnN1bWVyX2tleSA9IGNvbnN1bWVyX2tleSwgCiAgICAgICAgICAgICBjb25zdW1lcl9zZWNyZXQgPSBjb25zdW1lcl9zZWNyZXQsIAogICAgICAgICAgICAgYWNjZXNzX3Rva2VuID0gYWNjZXNzX3Rva2VuLCAKICAgICAgICAgICAgIGFjY2Vzc19zZWNyZXQgPSBhY2Nlc3Nfc2VjcmV0CiAgICAgICAgICAgICApCmBgYAoKCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KY29uc3VtZXJfa2V5IDwtICJ4cU1tMTBWd3dsMVhBeDMxQ0F6WU5pcW9pIgpjb25zdW1lcl9zZWNyZXQgPC0gIkRSTGNLanFQTGJpb3oyaTJWRVduV3JBUWpZUlVGQ0w0c1VuTmF6Q3hadVE4QnRUcWNkIgphY2Nlc3NfdG9rZW4gPC0gIjMyNjEwNTI4OTEtT0FxUGpFa1VRcmdrTU9rVXlFdnlXUkRWeGE3NkpGYTllNTJOZE5UIgphY2Nlc3Nfc2VjcmV0IDwtICI0ZjNza2pmbndMYXZPWkdOaHR1ZXRJbEU0Z3N4OENHWEVpMkdNd2FSZkY3bjAiCgpjcmVhdGVfdG9rZW4oYXBwID0gIkJpZyBEb3Vkb3UiLAogICAgICAgICAgICAgY29uc3VtZXJfa2V5ID0gY29uc3VtZXJfa2V5LCAKICAgICAgICAgICAgIGNvbnN1bWVyX3NlY3JldCA9IGNvbnN1bWVyX3NlY3JldCwgCiAgICAgICAgICAgICBhY2Nlc3NfdG9rZW4gPSBhY2Nlc3NfdG9rZW4sIAogICAgICAgICAgICAgYWNjZXNzX3NlY3JldCA9IGFjY2Vzc19zZWNyZXQKICAgICAgICAgICAgICkKYGBgCgpBdXRoZW50aWNhdGlvbiBpcyBhbiBpbXBvcnRhbnQgcGFydCBvZiB0aGUgcHJvY2Vzcy4gRm9yIG1vcmUgaW5mbyBvbiB0aGF0OiAgCi0gaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dvb2dsZXNoZWV0cy92aWduZXR0ZXMvbWFuYWdpbmctYXV0aC10b2tlbnMuaHRtbCAgIAotIGh0dHBzOi8vaHR0ci5yLWxpYi5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWwgKHNlY3Rpb24gQXV0aGVudGljYXRpb24pCgojIyBFeHRyYWN0aW9uCgpJZiBubyBlcnJvciBhcHBlYXJzLCB3ZSBhcmUgcmVhZHkgdG8gcXVlcnkuIERlcGVuZGluZyBvbiB0aGUgbnVtYmVyIG9mIHJlcXVlc3RlZCB0d2VldHMsIHRoaXMgY2FuIHRha2Ugc29tZSB0aW1lLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpzZWFyY2hfdGVybSA8LSAiY29uZmluZW1lbnQiCnR3ZWV0cyA8LSBzZWFyY2hfdHdlZXRzKAogIHNlYXJjaF90ZXJtLCAgICAgICAgICAjIFdoYXQgdG8gc2VhcmNoIGZvcgogIG4gPSAyMDAwLCAgICAgICAgICAgICAjIE51bWJlciBvZiB0d2VldHMgdG8gZG93bmxvYWQKICBpbmNsdWRlX3J0cyA9IEZBTFNFICAgIyBFeGNsdWRlIHJlLXR3ZWV0cwopCmBgYApGb3IgbGFyZ2UgcXVlcmllcywgdGhlIHByb2dyZXNzIGJhciBoZWxwcy4gICAKTm90ZSB0aGF0IG1hbnkgb3B0aW9ucyBhcmUgYXZhaWxhYmxlLCBsaWtlOiBleGNsdWRlIHJldHdlZXRzLCBsaW1pdCBzZWFyY2ggdG8gcGFydGljdWxhciBnZW9ncmFwaGljYWwgem9uZXMgKGluc2lkZSByYWRpdXNlcykuCgojIFRleHQgbWluaW5nCgojIyBSZWZlcmVuY2VzClRoZSByZWZlcmVuY2UgYm9vayBpczogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tICAgICAgCkEgZ3JlYXQgaW50ZXJhY3RpdmUgdHV0b3JpYWw6IGh0dHBzOi8vanVsaWFzaWxnZS5zaGlueWFwcHMuaW8vbGVhcm50aWR5dGV4dC8gICAgCkFuZCB0aGUgcGFja2FnZSBpczogIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZSh0aWR5dGV4dCkpe2luc3RhbGwucGFja2FnZXMoInRpZHl0ZXh0IiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpgYGAKIChzZWUgYWxzbzogaHR0cHM6Ly9xdWFudGVkYS5pby9pbmRleC5odG1sKQoKIyMgRGF0YSByZXRyaWV2YWwKCk5vdywgbGV0J3MgbW92ZSBmb3J3YXJkIHRvIHNpbXBsZSB0ZXh0IGFuYWx5c2lzLiBGaXJzdCwgd2UgbmVlZCB0byBwcmVwYXJlIHRoZSBkYXRhISAoYXMgdXN1YWwpCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyA8LSB0d2VldHMgJT4lIAogICAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUgICAgIyBUaGlzIGNyZWF0ZXMgYSB0d2VldCBpZAogICAgc2VsZWN0KGlkLCB0ZXh0KSAlPiUgICAgICAgICAgICAgIyBLZWVwcyBvbmx5IGlkIGFuZCB0ZXh0IG9mIHRoZSB0d2VldAogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAgICAgICAgIyBDcmVhdGVzIHRva2VucyEKdG9rZW5zCmBgYAoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgd29yZCBmcmVxdWVuY2llcy4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCmBgYAoKVGhpcyBpcyBwb2xsdXRlZCBieSBzbWFsbCB3b3Jkcy4gTGV0J3MgZmlsdGVyIHRoYXQgKCpGSVJTVCBNRVRIT0QqKS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JSBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpCmBgYAoKCiMjIERhdGEgZnJlcXVlbmNpZXMKTm93IGxldCdzIG9taXQgdGhlIHNtYWxsIHdvcmRzIChzbWFsbGVyIHRoYW4gNSBjaGFyYWN0ZXJzKS4gICAKKipOT1RFKio6IGFsbCB0aGUgdGhyZXNob2xkcyBiZWxvdyBkZXBlbmQgb24gdGhlIHNhbXBsZSEgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUKICAgIG11dGF0ZShsZW5ndGggPSBuY2hhcih3b3JkKSkgJT4lCiAgICBmaWx0ZXIobGVuZ3RoID4gNCkgJT4lICAgICAgICAgICAgICMgS2VlcCB3b3JkcyB3aXRoIGxlbmd0aCBsYXJnZXIgdGhhbiA0CiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjEpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdG9wIDEyIHdvcmRzCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiV29yZHMiKQpgYGAKCkEgYmV0dGVyIHdheSB0byBwcm9jZWVkIGlzIHRvIHJlbW92ZSAic3RvcCB3b3JkcyIgbGlrZSAiYSIsICJJIiwgIm9mIiwgInRoZSIsIGV0YyAoKlNFQ09ORCBNRVRIT0QqKS4KQWxzbywgaXQgd291bGQgbWFrZSBzZW5zZSB0byByZW1vdmUgdGhlIHNlYXJjaCBpdGVtIGFuZCAiaHR0cHMiLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpkYXRhKCJzdG9wX3dvcmRzIikKdGlkeV90b2tlbnMgPC0gdG9rZW5zICU+JSAKICAgIGFudGlfam9pbihzdG9wX3dvcmRzKSAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgdW5yZWxldmFudCB0ZXJtcwp0aWR5X3Rva2VucyAlPiUKICAgIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgICAgICAgICAgICAgIyBDb3VudCB3b3JkcwogICAgaGVhZCgyMCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEtlZXAgb25seSB0b3AgMTUgd29yZHMKICAgIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKKipQcm9ibGVtKio6IHN0cmFuZ2UgY2hhcmFjdGVycyByZW1haW4uIFdlIGFyZSBnb2luZyB0byByZW1vdmUgdGhlbSBieSBjb252ZXJ0aW5nIHRoZSB0ZXh0IHRvIEFTQ0lJIGZvcm1hdCBhbmQgb21pdCAqTkEqIGRhdGEuIAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp0aWR5X3Rva2VucyA8LSB0b2tlbnMgJT4lIAogICAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSB1bnJlbGV2YW50CiAgICBtdXRhdGUod29yZCA9IGljb252KHdvcmQsIGZyb20gPSAiVVRGLTgiLCB0byA9ICJBU0NJSSIpKSAlPiUgIyBQdXQgaW4gbGF0aW4gZm9ybWF0CiAgICBuYS5vbWl0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIG1pc3NpbmcKICAgIGZpbHRlcihuY2hhcih3b3JkKSA+IDIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgc21hbGwgd29yZHMKICAgICAgICAgICAhKHdvcmQgJWluJSBjKCJodHRwcyIsICJ0LmNvIiwgc2VhcmNoX3Rlcm0pKSAgIyBzZWFyY2hfdGVybSBkZWZpbmVkIGFib3ZlCiAgICApCnRpZHlfdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JSAgICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjApICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAjIEtlZXAgb25seSB0b3Agd29yZHMKICAgIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKUGVyZmVjdCEKCiMjIFdvcmQgY2xvdWQKClRoaXMgZGF0YSBjYW4gYWxzbyBiZSBzaG93biB3aXRoIGEgd29yZCBjbG91ZC4gV2Ugc2ltcGx5IHVzZSB0aGUgKndvcmRjbG91ZCogcGFja2FnZTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRjbG91ZC9pbmRleC5odG1sIAoKVGhlIHBhY2thZ2UgKndvcmRjbG91ZDIqIGFkZHMgYSBmZXcgZmVhdHVyZXM6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93b3JkY2xvdWQyL3ZpZ25ldHRlcy93b3JkY2xvdWQuaHRtbAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQppZighcmVxdWlyZSh3b3JkY2xvdWQpKXtpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKX0KbGlicmFyeSh3b3JkY2xvdWQpCmNsb3VkX2RhdGEgPC0gdGlkeV90b2tlbnMgJT4lIGNvdW50KHdvcmQpCndvcmRjbG91ZCh3b3JkcyA9IGNsb3VkX2RhdGEkd29yZCwgCiAgICAgICAgICBmcmVxID0gY2xvdWRfZGF0YSRuLCBtaW4uZnJlcSA9IDEwLAogICAgICAgICAgbWF4LndvcmRzID0gMTAwLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgcm90LnBlciA9IDAuMTUsIAogICAgICAgICAgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkKYGBgCgojIyBuLWdyYW1zCgpTZWUgaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL25ncmFtcy5odG1sCgpgYGB7ciBiaWdyYW1zLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnR3ZWV0cyAlPiUgCiAgICBtdXRhdGUoaWQgPSAxOm5yb3codHdlZXRzKSkgJT4lICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICBjb3VudChzb3J0ID0gVCkgJT4lCiAgaGVhZCgyMCkgJT4lCiAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihiaWdyYW0sIG4pLCB4ID0gbikpICsgZ2VvbV9jb2woKQpgYGAKCkFnYWluOiBzYW1lIGlzc3VlIHdpdGggc3RvcCB3b3JkcyEgU28gd2UgbXVzdCByZW1vdmUgdGhlbSBhZ2Fpbi4gQnV0IGl0J3MgbW9yZSBjb21wbGljYXRlZCBub3cuCldlIGNhbiB1c2UgdGhlICpzZXBhcmF0ZSooKSBmdW5jdGlvbiB0byBoZWxwIHVzLgoKYGBge3J9CnR3ZWV0cyAlPiUgCiAgICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JSAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiLCByZW1vdmUgPSBGKSAlPiUKICBmaWx0ZXIoISh3b3JkMSAlaW4lIGMoc3RvcF93b3JkcyR3b3JkLCAiaHR0cHMiLCBzZWFyY2hfdGVybSkpLAogICAgICAgICAhKHdvcmQyICVpbiUgYyhzdG9wX3dvcmRzJHdvcmQsICJodHRwcyIsIHNlYXJjaF90ZXJtKSkpICU+JQogIGdyb3VwX2J5KGJpZ3JhbSkgJT4lCiAgY291bnQoc29ydCA9IFQpICU+JQogIGhlYWQoMjApICU+JQogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIoYmlncmFtLCBuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJCaS1ncmFtIikKYGBgCgoKIyMgU2VudGltZW50CgpUaGlzIHNlY3Rpb24gaXMgaW5zcGlyZWQgZnJvbTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3NlbnRpbWVudC5odG1sICAgIApTb21ldGltZXMsIHlvdSBtYXkgYmUgYXNrZWQgaW4gdGhlIHByb2Nlc3MgaWYgeW91ICpyZWFsbHkqIHdhbnQgdG8gZG93bmxvYWQgZGF0YSAobGV4aWNvbnMpLiAgCkp1c3Qgc2F5IHllcyBpbiB0aGUgKipjb25zb2xlKiogKHR5cGUgdGhlIGNvcnJlY3QgYW5zd2VyOiBpZiBub3QsIHlvdSB3aWxsIGJlIGJsb2NrZWQvc3RydWNrKS4KCkZpcnN0LCB3ZSBuZWVkIHRvIGxvYWQgc29tZSBzZW50aW1lbnQgbGV4aWNvbi4gQUZJTk4gaXMgb25lIHN1Y2ggc2VudGltZW50IGRhdGFiYXNlLiAKCmBgYHtyfQppZighcmVxdWlyZSh0ZXh0ZGF0YSkpe2luc3RhbGwucGFja2FnZXMoInRleHRkYXRhIiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHRleHRkYXRhKQphZmlubiA8LSBnZXRfc2VudGltZW50cygiYWZpbm4iKQphZmlubgpgYGAKClRvIGNyZWF0ZSBhIG5pY2UgdmlzdWFsaXphdGlvbiwgd2UgbmVlZCB0byBleHRyYWN0IHRoZSAqKnRpbWUqKiBvZiB0aGUgdHdlZXRzLgoKYGBge3J9CnRva2Vuc190aW1lIDwtIHR3ZWV0cyAlPiUgCiAgICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JSAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgICAgICAgICAgIyBDcmVhdGVzIHRva2VucyEKdG9rZW5zX3RpbWUKYGBgCgpXZSB0aGVuIHVzZSAqKmlubmVyX2pvaW4qKigpIHRvIG1lcmdlIHRoZSB0d28gc2V0cy4gVGhpcyBmdW5jdGlvbiByZW1vdmVzIHRoZSBjYXNlcyB3aGVuIGEgbWF0Y2ggZG9lcyBub3Qgb2NjdXIuCgpgYGB7cn0KbGlicmFyeShsdWJyaWRhdGUpCnNlbnRpbWVudCA8LSB0b2tlbnNfdGltZSAlPiUgCiAgaW5uZXJfam9pbihhZmlubikgJT4lCiAgbXV0YXRlKGRheSA9IGRheShjcmVhdGVkX2F0KSwKICAgICAgICAgaG91ciA9IGhvdXIoY3JlYXRlZF9hdCkgLyAyNCwKICAgICAgICAgbWludXRlID0gbWludXRlKGNyZWF0ZWRfYXQpIC8gNjAgLyAyNCwKICAgICAgICAgdGltZSA9IGRheSArIGhvdXIgKyBtaW51dGUpCnNlbnRpbWVudApgYGAKCldlIHRoZW4gY29tcHV0ZSB0aGUgYXZlcmFnZSBzZW50aW1lbnQsIG1pbnV0ZS1ieS1taW51dGUuICAgCk9mIGNvdXJzZSwgYXZlcmFnZSBzZW50aW1lbnQgY2FuIGJlIG1pc2xlYWRpbmcuIEluZGVlZCwgaWYgYSB0ZXh0IGNvbnRhaW5zIHRoZSB0ZXJtcyAiKkknbSBub3QgaGFwcHkqIiwgdGhlbiBvbmx5ICIqaGFwcHkqIiB3aWxsIGJlIHRhZ2dlZCwgd2hpY2ggaXMgdGhlIG9wcG9zaXRlIG9mIHRoZSBpbnRlbmRlZCBtZWFuaW5nLgoKYGBge3J9CnNlbnRpbWVudCAlPiUKICBncm91cF9ieSh0aW1lLCBkYXksIGhvdXIsIG1pbnV0ZSkgJT4lCiAgc3VtbWFyaXNlKGF2Z19zZW50aW1lbnQgPSBtZWFuKHZhbHVlKSkgJT4lCiAgbXV0YXRlKHRpbWUgPSBtYWtlX2RhdGV0aW1lKHllYXIgPSAyMDIwLCBtb250aCA9IDQsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBhdmdfc2VudGltZW50KSkgKyBnZW9tX2NvbCgpIApgYGAKVGhlcmUgYXJlIDI0IGJhcnMgcGVyIGRheSwgYnV0IHRoZSAqeSotYXhpcyBpcyBub3Qgb3B0aW1hbC4uLiAgCiAKV2hhdCBhYm91dCBlbW90aW9ucz8gVGhlICoqTlJDKiogbGV4aWNvbiBjYXRlZ29yaXplcyAqZW1vdGlvbnMqLiBCZWxvdywgd2Ugb3JkZXIgZW1vdGlvbnMuIFRoZSBtb3N0IGltcG9ydGFudCBpbXBhY3QgaXMgdGhlIGRpY2hvdG9teSBiZXR3ZWVuIHBvc2l0aXZlICYgbmVnYXRpdmUgZW1vdGlvbnMuIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpucmMgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpCm5yYyA8LSBucmMgJT4lCiAgbXV0YXRlKHNlbnRpbWVudCA9IGFzLmZhY3RvcihzZW50aW1lbnQpLAogICAgICAgICBzZW50aW1lbnQgPSByZWNvZGVfZmFjdG9yKHNlbnRpbWVudCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqb3kgPSAiam95IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnVzdCA9ICJ0cnVzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VycHJpc2UgPSAic3VycHJpc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFudGljaXBhdGlvbiA9ICJhbnRpY2lwYXRpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gInBvc2l0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZWdhdGl2ZSA9ICJuZWdhdGl2ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FkbmVzcyA9ICJzYWRuZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmdlciA9ICJhbmdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhciA9ICJmZWFyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaWd1c3QgPSAiZGlzZ3VzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLm9yZGVyZWQgPSBUKSkKYGBgCgpXZSB0aGVuIGNyZWF0ZSB0aGUgbWVyZ2VkIGRhdGFzZXQuCgpgYGB7cn0KZW1vdGlvbnMgPC0gdG9rZW5zX3RpbWUgJT4lIAogIGlubmVyX2pvaW4obnJjKSAlPiUgICAgICAgICAgICAgICAgICAjIE1lcmdlIGRhdGEgd2l0aCBzZW50aW1lbnQKICBtdXRhdGUoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpLAogICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KS8yNCwKICAgICAgICAgbWludXRlID0gbWludXRlKGNyZWF0ZWRfYXQpLzI0LzYwLAogICAgICAgICB0aW1lID0gZGF5ICsgaG91ciArIG1pbnV0ZSkgICAjIENyZWF0ZSBkYXkgY29sdW1uCmVtb3Rpb25zICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU2hvdyB0aGUgcmVzdWx0CmBgYAoKVGhlIG1lcmdpbmcgaGFzIHJlZHVjZWQgdGhlIHNpemUgb2YgdGhlIGRhdGFzZXQsIGJ1dCB0aGVyZSBzdGlsbCByZW1haW5zIGVub3VnaCB0byBwdXJzdWUgdGhlIHN0dWR5LiAgIApGaW5hbGx5LCB3ZSBtb3ZlIHRvIHRoZSBwaXZvdC10YWJsZSB0aGF0IGNvdW50cyBlbW90aW9ucyBmb3IgZWFjaCBkYXkuCgpgYGB7cn0KZyA8LSBlbW90aW9ucyAlPiUgCiAgZ3JvdXBfYnkodGltZSwgc2VudGltZW50LCBkYXksIGhvdXIsIG1pbnV0ZSkgJT4lCiAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lCiAgbXV0YXRlKHRpbWUgPSBtYWtlX2RhdGV0aW1lKHllYXIgPSAyMDIxLCBtb250aCA9IDQsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKCJUaW1lIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFQsIGRpcmVjdGlvbiA9IC0xKQpnZ3Bsb3RseShnKQpgYGAKClRoaXMgY2FuIGFsc28gYmUgc2hvd24gaW4gcGVyY2VudGFnZSBmb3JtYXQuIAoKYGBge3J9CmcgPC0gZW1vdGlvbnMgJT4lIAogIGdyb3VwX2J5KHRpbWUsIHNlbnRpbWVudCwgZGF5LCBob3VyLCBtaW51dGUpICU+JQogIHN1bW1hcmlzZShpbnRlbnNpdHkgPSBuKCkpICU+JQogIG11dGF0ZSh0aW1lID0gbWFrZV9kYXRldGltZSh5ZWFyID0gMjAyMCwgbW9udGggPSAxMCwgZGF5ID0gZGF5LCBob3VyID0gaG91cioyNCwgbWluID0gbWludXRlKjI0KjYwKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGludGVuc2l0eSwgZmlsbCA9IHNlbnRpbWVudCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKCJUaW1lIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFQsIGRpcmVjdGlvbiA9IC0xKQpnZ3Bsb3RseShnKQpgYGAKCmBgYHtyfQplbW90aW9ucyAlPiUgCiAgbXV0YXRlKHNlbnRpbWVudCA9IGlmX2Vsc2Uoc2VudGltZW50IDwgIm5lZ2F0aXZlIiwgInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSAKICBncm91cF9ieSh0aW1lLCBzZW50aW1lbnQsIGRheSwgaG91ciwgbWludXRlKSAlPiUKICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICBtdXRhdGUodGltZSA9IG1ha2VfZGF0ZXRpbWUoeWVhciA9IDIwMjAsIG1vbnRoID0gMTAsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjMDAxMTQ0IiwgIiNGRkREOTkiKSkKYGBgCgoKCgojIyBBZHZhbmNlZCBzZW50aW1lbnQgCgpUaGUgcHJvYmxlbSB3aXRoIHRoZSBwcmVjZWRpbmcgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZG9uJ3QgdGFrZSBpbnRvIGFjY291bnQgKip2YWxlbmNlIHNoaWZ0ZXJzKiogKGkuZS4sIG5lZ2F0b3JzLCBhbXBsaWZpZXJzIChpbnRlbnNpZmllcnMpLCBkZS1hbXBsaWZpZXJzIChkb3dudG9uZXJzKSwgYW5kIGFkdmVyc2F0aXZlIGNvbmp1bmN0aW9ucykuIElmIGEgdHdlZXQgc2F5cyAqbm90IGhhcHB5KiwgY291bnRpbmcgdGhlIHdvcmQgKmhhcHB5KiBpcyBub3QgYSBnb29kIGlkZWEhIFRoZSBwYWNrYWdlICpzZW50aW1lbnRyKiBpcyBidWlsdCB0byBjaXJjdW12ZW50IHRoZXNlIGlzc3VlczogaGF2ZSBhIGxvb2sgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvc2VudGltZW50ciAgCihzZWUgYWxzbzogaHR0cHM6Ly93d3cuc2VudG9tZXRyaWNzLm9yZyBhbmQgdGhlIGJvb2sgKipTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFRleHQgQW5hbHlzaXMgaW4gUioqIGhvc3RlZCBhdCBodHRwczovL3NtbHRhci5jb20pCgpJIGhhdmVuJ3QgdGVzdGVkICoqYXdzLmNvbXByZWhlbmQqKiwgYnV0IGl0IHNlZW1zIHByb21pc2luZzogaHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkeXIvYXdzLmNvbXByZWhlbmQKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2VudGltZW50cikpe2luc3RhbGwucGFja2FnZXMoYygic2VudGltZW50ciIsICJ0ZXh0Y2F0IikpfQpsaWJyYXJ5KHNlbnRpbWVudHIpCmxpYnJhcnkodGV4dGNhdCkKYGBgCgpGaXJzdCwgbGV0J3Mga2VlcCBvbmx5IHRoZSB0d2VldHMgd3JpdHRlbiBpbiBFbmdsaXNoIQoKYGBge3J9CnR3ZWV0c19lbiA8LSB0d2VldHMgJT4lCiAgbXV0YXRlKGxhbmd1YWdlID0gdGV4dGNhdCh0ZXh0KSkgJT4lCiAgZmlsdGVyKGxhbmd1YWdlID09ICJlbmdsaXNoIikgJT4lCiAgZHBseXI6OnNlbGVjdChjcmVhdGVkX2F0LCB0ZXh0KQpgYGAKCioqTk9URSoqOiB0aGUgY29kZSBhYm92ZSB3YXMgdXNlZCB0byBzaG93IHRoZSBmdW5jdGlvbiAqdGV4dGNhdCo6IHRoZSBsYW5ndWFnZSBpcyBhbHJlYWR5IGNvZGVkIGluIHRoZSB0d2VldHMgdmlhIHRoZSAqKmxhbmcqKiBjb2x1bW4vdmFyaWFibGUuIChpdCBzdWZmaWNlcyB0byBrZWVwIHRoZSBpbnN0YW5jZXMgZm9yIHdoaWNoIGxhbmcgPT0gImVuIikKCk5leHQsIHdlIGNvbXB1dGUgYWR2YW5jZWQgc2VudGltZW50LiAKCmBgYHtyfQp0d2VldF9zZW50IDwtIHR3ZWV0c19lbiR0ZXh0ICU+JQogIGdldF9zZW50ZW5jZXMoKSAlPiUgICMgSW50ZXJtZWRpYXRlIGZ1bmN0aW9uCiAgc2VudGltZW50KCkgICAgICAgICAgIyBTZW50aW1lbnQhCnR3ZWV0X3NlbnQKYGBgCgoqKk5PVEUqKjogZGVwZW5kaW5nIG9uIGZyZXF1ZW5jeSBpc3N1ZXMsIGl0IGlzIGJldHRlciB0byBhbmFseXplIGF0IGRhaWx5IG9yIGhvdXJseSBzY2FsZXMuIElmIGEgd29yZCBpcyB2ZXJ5IHBvcHVsYXIsIHRoZW4sIGhpZ2hlciBmcmVxdWVuY2llcyBhcmUgbW9yZSByZWxldmFudC4gCgpgYGB7cn0KdHdlZXRzX2VuICU+JQogIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICMgVGhpcyBjcmVhdGVzIGEgbmV3IGNvbHVtbiB3aXRoIHJvdyBudW1iZXIKCnR3ZWV0c19lbiAlPiUKICByb3dpZF90b19jb2x1bW4oImVsZW1lbnRfaWQiKSAlPiUKICBsZWZ0X2pvaW4odHdlZXRfc2VudCwgYnkgPSAiZWxlbWVudF9pZCIpCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICBncm91cF9ieShkYXkgPSBkYXkoY3JlYXRlZF9hdCkpICU+JQogIHN1bW1hcmlzZShhdmdfc2VudCA9IG1lYW4oc2VudGltZW50KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGRheSksIHkgPSBhdmdfc2VudCkpICsgZ2VvbV9jb2woKSArIHhsYWIoImRheSIpCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICBmaWx0ZXIoc2VudGltZW50ICE9IDApICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihob3VyKGNyZWF0ZWRfYXQpKSwgeSA9IHNlbnRpbWVudCkpICsgCiAgZ2VvbV9qaXR0ZXIoc2l6ZSA9IDAuMikgKwogIGdlb21fYm94cGxvdChhZXMoY29sb3IgPSBhcy5mYWN0b3IoaG91cihjcmVhdGVkX2F0KSkpLCBhbHBoYSA9IDAuNSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyB4bGFiKCJob3VyIikKYGBgCgoKCiMgUmVzb3VyY2VzCgpCZWxvdywgYSBzaG9ydCBsaXN0IG9mIHJlc291cmNlcyAodG8gYWNjZXNzIHRoaXJkLXBhcnR5IGRhdGEpOiAgIAoKLSAqKnRleHQgbWluaW5nIHdpdGggUioqIChvbmxpbmUgYm9vayk6IGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbSAgICAgIAotICoqQmxvb21iZXJnKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9SYmxwYXBpL2luZGV4Lmh0bWwgICAKLSAqKmdtYWlsKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nbWFpbHIvdmlnbmV0dGVzL2dtYWlsci5odG1sICAgCi0gKipHb29nbGUgTWFwcyoqOiBodHRwczovL2NyYW4ucnN0dWRpby5jb20vd2ViL3BhY2thZ2VzL21hcHNhcGkvdmlnbmV0dGVzL2ludHJvLmh0bWwgIAotICoqR29vZ2xlIHRyZW5kcyoqOiBodHRwczovL2dpdGh1Yi5jb20vUE1hc3NpY290dGUvZ3RyZW5kc1IKLSAqKkdvb2dsZSBBUElzKiogKG1vcmUgZ2VuZXJhbGx5KTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dhcmdsZS92aWduZXR0ZXMvYXV0aC1mcm9tLXdlYi5odG1sCi0gKipGYWNlYm9vayBBUEkqKjogZGV2ZWxvcGVycy5mYWNlYm9vay5jb20vYWRzL2Jsb2cvcG9zdC92Mi8yMDE4LzA1LzE1L2ZhY2Vib29rLXJlYWNoLWZyZXF1ZW5jeS1hcGkvICAKClBvc3NpYmx5IGRlcHJlY2F0ZWQ6ICAKLSAqKkZhY2Vib29rKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9SZmFjZWJvb2svaW5kZXguaHRtbCAgICAKLSAqKkluc3RhZ3JhbSoqOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvaW5zdGFSL2luZGV4Lmh0bWwKCmBgYHtyfQoKYGBgCg==