Analysis of Text from Telegram

Install packages

Install packages tm, SnowallC, wordcloud, RColorBrewer, syuzhet, and ggplot2.

Prepare text for analysis

library("ggplot2")
# Read the text file from local machine , choose file interactively
text <- readLines(file.choose())
# Load the data as a corpus
TextDoc <- Corpus(VectorSource(text))
 #Replacing "/", "@" and "|" with space
toSpace <- content_transformer(function (x , pattern ) gsub(pattern, " ", x))
install.packages("tm")
TextDoc <- tm_map(TextDoc, toSpace, "/")
TextDoc <- tm_map(TextDoc, toSpace, "@")
TextDoc <- tm_map(TextDoc, toSpace, "\\|")
# Convert the text to lower case
TextDoc <- tm_map(TextDoc, content_transformer(tolower))
# Remove numbers
TextDoc <- tm_map(TextDoc, removeNumbers)
# Remove english common stopwords
TextDoc <- tm_map(TextDoc, removeWords, stopwords("english"))
# Remove your own stop word
# specify your custom stopwords as a character vector
TextDoc <- tm_map(TextDoc, removeWords, c("s", "company", "team")) 
# Remove punctuations
TextDoc <- tm_map(TextDoc, removePunctuation)
# Eliminate extra white spaces
TextDoc <- tm_map(TextDoc, stripWhitespace)
# Text stemming - which reduces words to their root form
TextDoc <- tm_map(TextDoc, stemDocument)

Build a term document matrix

# Build a term-document matrix
TextDoc_dtm <- TermDocumentMatrix(TextDoc)
dtm_m <- as.matrix(TextDoc_dtm)

Identify most frequently used words

# Sort by descending value of frequency
dtm_v <- sort(rowSums(dtm_m),decreasing=TRUE)
dtm_d <- data.frame(word = names(dtm_v),freq=dtm_v)

# Display the top 500 most frequent words
library("DT")
datatable(head(dtm_d, 500))
write.csv((head(dtm_d, 500)), file = "tme_top_500.csv")
# Plot the most frequent words
barplot(dtm_d[1:50,]$freq, las = 2, names.arg = dtm_d[1:50,]$word,
        col ="lightgreen", main ="Top 50 most frequent words",
        ylab = "Word frequencies")

Word cloud of top 100 words

#generate word cloud
set.seed(1234)
wordcloud(words = dtm_d$word, freq = dtm_d$freq, min.freq = 5,
          max.words=100, random.order=FALSE, rot.per=0.40, 
          colors=brewer.pal(8, "Dark2"))

Find associations between words

# Find associations 
findAssocs(TextDoc_dtm, terms = c("putin","ukraine","ukrainian","power","war","russia","russian","prigozhin","money","pay","work","job","military", "conscript","kids","children","death"), corlimit = 0.5)
$putin
numeric(0)

$ukraine
numeric(0)

$ukrainian
numeric(0)

$power
numeric(0)

$war
  neocon  aggress   depend superpow   pandem 
    0.56     0.55     0.52     0.52     0.51 

$russia
numeric(0)

$russian
numeric(0)

$prigozhin
numeric(0)

$money
numeric(0)

$pay
numeric(0)

$work
numeric(0)

$job
    rtvi       ⭕️     erad  fratern greatest  ottoman  rebuild 
    0.53     0.53     0.53     0.53     0.53     0.53     0.53 

$military
numeric(0)

$conscript
    enlist  eroshenko     kavkaz       rite yeroshenko  “satanic”  commissar 
      0.71       0.71       0.71       0.71       0.71       0.71       0.71 

$kids
numeric(0)

$children
  indigen irrespons   kabanov multiethn   sadovod  tsargrad   migrant    parent 
     0.63      0.63      0.63      0.63      0.63      0.63      0.62      0.60 
   academ    ghetto kotelniki    school    greedi 
     0.60      0.60      0.60      0.53      0.53 

$death
berlusconi     silvio 
      0.54       0.51 
# Find associations for words that occur at least 180 times
findAssocs(TextDoc_dtm, terms = findFreqTerms(TextDoc_dtm, lowfreq = 180), corlimit = 0.5)
$author
numeric(0)

$day
  apostol      lent pentecost 
      0.6       0.6       0.6 

$fact
numeric(0)

$just
numeric(0)

$militari
numeric(0)

$can
numeric(0)

$countri
numeric(0)

$even
numeric(0)

$governor
numeric(0)

$offic
numeric(0)

$one
numeric(0)

$power
numeric(0)

$will
numeric(0)

$first
numeric(0)

$`“`
numeric(0)

$putin
numeric(0)

$said
numeric(0)

$ukrainian
numeric(0)

$vladimir
numeric(0)

$year
numeric(0)

$russia
numeric(0)

$russian
numeric(0)

$state
numeric(0)

$time
numeric(0)

$work
numeric(0)

$accord
numeric(0)

$case
crimin 
  0.57 

$new
numeric(0)

$peopl
numeric(0)

$main
numeric(0)

$say
numeric(0)

$deputi
numeric(0)

$https
numeric(0)

$everyth
numeric(0)

$forc
 arm 
0.57 

$region
numeric(0)

$territori
numeric(0)

$ukrain
numeric(0)

$war
  neocon  aggress   depend superpow   pandem 
    0.56     0.55     0.52     0.52     0.51 

$also
numeric(0)

$feder
numeric(0)

$attack
 uav 
0.54 

$ministri
numeric(0)

$now
numeric(0)

$presid
numeric(0)

$citi
numeric(0)

$public
  tighten    alrosa      bike candidaci  chemezov    kinder       nas      nasa 
     0.51      0.50      0.50      0.50      0.50      0.50      0.50      0.50 
 siluanov      smut  thievish     vaino     vouch 
     0.50      0.50      0.50      0.50      0.50 

$alreadi
 primari conceptu sidyakin  turchak   idioci 
    0.52     0.52     0.52     0.52     0.51 

$call
boorish 
   0.51 

$well
numeric(0)

$person
numeric(0)

$head
numeric(0)

$moscow
numeric(0)

$offici
numeric(0)

$hous
numeric(0)

$court
numeric(0)

$rubl
billion million 
   0.56    0.54 

Evaluate sentiments

# regular sentiment score using get_sentiment() function and method of your choice
# please note that different methods may have different scales
syuzhet_vector <- get_sentiment(text, method="syuzhet")
# see the first row of the vector
head(syuzhet_vector)
[1]  0.00 -0.10  2.30  1.50  2.65  1.50
# see summary statistics of the vector
summary(syuzhet_vector)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-10.0500  -1.9500  -0.3250  -0.4793   1.1500   8.0000 
# bing
bing_vector <- get_sentiment(text, method="bing")
head(bing_vector)
[1]  0  0 -1  3  1  3
summary(bing_vector)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-12.000  -3.000  -1.000  -1.335   0.000   7.000 
#affin
afinn_vector <- get_sentiment(text, method="afinn")
head(afinn_vector)
[1]  0 -2  0  9  4  9
summary(afinn_vector)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-27.000  -7.000  -3.000  -3.587   1.000  19.000 
#compare the first row of each vector using sign function
rbind(
  sign(head(syuzhet_vector)),
  sign(head(bing_vector)),
  sign(head(afinn_vector))
)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    0   -1    1    1    1    1
[2,]    0    0   -1    1    1    1
[3,]    0   -1    0    1    1    1

Evaluation emotion

# run nrc sentiment analysis to return data frame with each row classified as one of the following
# emotions, rather than a score: 
# anger, anticipation, disgust, fear, joy, sadness, surprise, trust 
# It also counts the number of positive and negative emotions found in each row
d<-get_nrc_sentiment(text)
[WARNING] This document format requires a nonempty <title> element.
  Defaulting to 'tme.knit' as the title.
  To specify a title, use 'title' in metadata or --metadata title="...".
#See lines of the get_nrc_sentiment dataframe
write.csv(d, file = "tme_nrc.csv")
datatable(d)
#transpose
td<-data.frame(t(d))
#The function rowSums computes column sums across rows for each level of a grouping variable.
td_new <- data.frame(rowSums(td[2:1881]))
#Transformation and cleaning
names(td_new)[1] <- "count"
td_new <- cbind("sentiment" = rownames(td_new), td_new)
rownames(td_new) <- NULL
td_new2<-td_new[1:8,]
#Plot One - count of words associated with each sentiment
quickplot(sentiment, data=td_new2, weight=count, geom="bar", fill=sentiment, ylab="count")+ggtitle("Posts on Telegram sentiments")

#Plot two - count of words associated with each sentiment, expressed as a percentage
barplot(
  sort(colSums(prop.table(d[, 1:8]))), 
  horiz = TRUE, 
  cex.names = 0.7, 
  las = 1, 
  main = "Emotions in Posts on Telegram", xlab="Percentage"
)

library(DT)
library(readr)
tme_textonly_t <- read_csv("tme_textonly_t.csv", 
    col_types = cols(...1 = col_skip()))
datatable(tme_textonly_t)
DQotLS0NCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KIyBBbmFseXNpcyBvZiBUZXh0IGZyb20gVGVsZWdyYW0gIHsudW5udW1iZXJlZH0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUgPSBUUlVFKQ0KYGBgDQoNCiMjIyBJbnN0YWxsIHBhY2thZ2VzDQoNCkluc3RhbGwgcGFja2FnZXMgdG0sIFNub3dhbGxDLCB3b3JkY2xvdWQsIFJDb2xvckJyZXdlciwgc3l1emhldCwgYW5kIGdncGxvdDIuDQoNCmBgYHtyIHBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFLCBwYWdlZC5wcmludD1UUlVFfQ0KIyBJbnN0YWxsDQppbnN0YWxsLnBhY2thZ2VzKCJ0bSIpICAjIGZvciB0ZXh0IG1pbmluZw0KaW5zdGFsbC5wYWNrYWdlcygiU25vd2JhbGxDIikgIyBmb3IgdGV4dCBzdGVtbWluZw0KaW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIikgIyB3b3JkLWNsb3VkIGdlbmVyYXRvciANCmluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpICMgY29sb3IgcGFsZXR0ZXMNCmluc3RhbGwucGFja2FnZXMoInN5dXpoZXQiKSAjIGZvciBzZW50aW1lbnQgYW5hbHlzaXMNCmluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKSAjIGZvciBwbG90dGluZyBncmFwaHMNCmBgYA0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBMb2FkDQpsaWJyYXJ5KCJ0bSIpDQpsaWJyYXJ5KCJTbm93YmFsbEMiKQ0KbGlicmFyeSgid29yZGNsb3VkIikNCmxpYnJhcnkoIlJDb2xvckJyZXdlciIpDQpsaWJyYXJ5KCJzeXV6aGV0IikNCmxpYnJhcnkoImdncGxvdDIiKQ0KYGBgDQoNCiMjIyBQcmVwYXJlIHRleHQgZm9yIGFuYWx5c2lzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQojIFJlYWQgdGhlIHRleHQgZmlsZSBmcm9tIGxvY2FsIG1hY2hpbmUgLCBjaG9vc2UgZmlsZSBpbnRlcmFjdGl2ZWx5DQp0ZXh0IDwtIHJlYWRMaW5lcyhmaWxlLmNob29zZSgpKQ0KIyBMb2FkIHRoZSBkYXRhIGFzIGEgY29ycHVzDQpUZXh0RG9jIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGV4dCkpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9VFJVRX0NCiAjUmVwbGFjaW5nICIvIiwgIkAiIGFuZCAifCIgd2l0aCBzcGFjZQ0KdG9TcGFjZSA8LSBjb250ZW50X3RyYW5zZm9ybWVyKGZ1bmN0aW9uICh4ICwgcGF0dGVybiApIGdzdWIocGF0dGVybiwgIiAiLCB4KSkNClRleHREb2MgPC0gdG1fbWFwKFRleHREb2MsIHRvU3BhY2UsICIvIikNClRleHREb2MgPC0gdG1fbWFwKFRleHREb2MsIHRvU3BhY2UsICJAIikNClRleHREb2MgPC0gdG1fbWFwKFRleHREb2MsIHRvU3BhY2UsICJcXHwiKQ0KIyBDb252ZXJ0IHRoZSB0ZXh0IHRvIGxvd2VyIGNhc2UNClRleHREb2MgPC0gdG1fbWFwKFRleHREb2MsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQojIFJlbW92ZSBudW1iZXJzDQpUZXh0RG9jIDwtIHRtX21hcChUZXh0RG9jLCByZW1vdmVOdW1iZXJzKQ0KIyBSZW1vdmUgZW5nbGlzaCBjb21tb24gc3RvcHdvcmRzDQpUZXh0RG9jIDwtIHRtX21hcChUZXh0RG9jLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpDQojIFJlbW92ZSB5b3VyIG93biBzdG9wIHdvcmQNCiMgc3BlY2lmeSB5b3VyIGN1c3RvbSBzdG9wd29yZHMgYXMgYSBjaGFyYWN0ZXIgdmVjdG9yDQpUZXh0RG9jIDwtIHRtX21hcChUZXh0RG9jLCByZW1vdmVXb3JkcywgYygicyIsICJjb21wYW55IiwgInRlYW0iKSkgDQojIFJlbW92ZSBwdW5jdHVhdGlvbnMNClRleHREb2MgPC0gdG1fbWFwKFRleHREb2MsIHJlbW92ZVB1bmN0dWF0aW9uKQ0KIyBFbGltaW5hdGUgZXh0cmEgd2hpdGUgc3BhY2VzDQpUZXh0RG9jIDwtIHRtX21hcChUZXh0RG9jLCBzdHJpcFdoaXRlc3BhY2UpDQojIFRleHQgc3RlbW1pbmcgLSB3aGljaCByZWR1Y2VzIHdvcmRzIHRvIHRoZWlyIHJvb3QgZm9ybQ0KVGV4dERvYyA8LSB0bV9tYXAoVGV4dERvYywgc3RlbURvY3VtZW50KQ0KYGBgDQoNCiMjIyBCdWlsZCBhIHRlcm0gZG9jdW1lbnQgbWF0cml4DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQojIEJ1aWxkIGEgdGVybS1kb2N1bWVudCBtYXRyaXgNClRleHREb2NfZHRtIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChUZXh0RG9jKQ0KZHRtX20gPC0gYXMubWF0cml4KFRleHREb2NfZHRtKQ0KYGBgDQoNCiMjIyBJZGVudGlmeSBtb3N0IGZyZXF1ZW50bHkgdXNlZCB3b3Jkcw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1UUlVFfQ0KIyBTb3J0IGJ5IGRlc2NlbmRpbmcgdmFsdWUgb2YgZnJlcXVlbmN5DQpkdG1fdiA8LSBzb3J0KHJvd1N1bXMoZHRtX20pLGRlY3JlYXNpbmc9VFJVRSkNCmR0bV9kIDwtIGRhdGEuZnJhbWUod29yZCA9IG5hbWVzKGR0bV92KSxmcmVxPWR0bV92KQ0KDQojIERpc3BsYXkgdGhlIHRvcCA1MDAgbW9zdCBmcmVxdWVudCB3b3Jkcw0KbGlicmFyeSgiRFQiKQ0KZGF0YXRhYmxlKGhlYWQoZHRtX2QsIDUwMCkpDQp3cml0ZS5jc3YoKGhlYWQoZHRtX2QsIDUwMCkpLCBmaWxlID0gInRtZV90b3BfNTAwLmNzdiIpDQpgYGANCg0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQojIFBsb3QgdGhlIG1vc3QgZnJlcXVlbnQgd29yZHMNCmJhcnBsb3QoZHRtX2RbMTo1MCxdJGZyZXEsIGxhcyA9IDIsIG5hbWVzLmFyZyA9IGR0bV9kWzE6NTAsXSR3b3JkLA0KICAgICAgICBjb2wgPSJsaWdodGdyZWVuIiwgbWFpbiA9IlRvcCA1MCBtb3N0IGZyZXF1ZW50IHdvcmRzIiwNCiAgICAgICAgeWxhYiA9ICJXb3JkIGZyZXF1ZW5jaWVzIikNCmBgYA0KDQojIyMgV29yZCBjbG91ZCBvZiB0b3AgMTAwIHdvcmRzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQojZ2VuZXJhdGUgd29yZCBjbG91ZA0Kc2V0LnNlZWQoMTIzNCkNCndvcmRjbG91ZCh3b3JkcyA9IGR0bV9kJHdvcmQsIGZyZXEgPSBkdG1fZCRmcmVxLCBtaW4uZnJlcSA9IDUsDQogICAgICAgICAgbWF4LndvcmRzPTEwMCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuNDAsIA0KICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQ0KYGBgDQoNCiMjIyBGaW5kIGFzc29jaWF0aW9ucyBiZXR3ZWVuIHdvcmRzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQojIEZpbmQgYXNzb2NpYXRpb25zIA0KZmluZEFzc29jcyhUZXh0RG9jX2R0bSwgdGVybXMgPSBjKCJwdXRpbiIsInVrcmFpbmUiLCJ1a3JhaW5pYW4iLCJwb3dlciIsIndhciIsInJ1c3NpYSIsInJ1c3NpYW4iLCJwcmlnb3poaW4iLCJtb25leSIsInBheSIsIndvcmsiLCJqb2IiLCJtaWxpdGFyeSIsICJjb25zY3JpcHQiLCJraWRzIiwiY2hpbGRyZW4iLCJkZWF0aCIpLCBjb3JsaW1pdCA9IDAuNSkNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1UUlVFfQ0KIyBGaW5kIGFzc29jaWF0aW9ucyBmb3Igd29yZHMgdGhhdCBvY2N1ciBhdCBsZWFzdCAxODAgdGltZXMNCmZpbmRBc3NvY3MoVGV4dERvY19kdG0sIHRlcm1zID0gZmluZEZyZXFUZXJtcyhUZXh0RG9jX2R0bSwgbG93ZnJlcSA9IDE4MCksIGNvcmxpbWl0ID0gMC41KQ0KYGBgDQoNCiMjIyBFdmFsdWF0ZSBzZW50aW1lbnRzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQojIHJlZ3VsYXIgc2VudGltZW50IHNjb3JlIHVzaW5nIGdldF9zZW50aW1lbnQoKSBmdW5jdGlvbiBhbmQgbWV0aG9kIG9mIHlvdXIgY2hvaWNlDQojIHBsZWFzZSBub3RlIHRoYXQgZGlmZmVyZW50IG1ldGhvZHMgbWF5IGhhdmUgZGlmZmVyZW50IHNjYWxlcw0Kc3l1emhldF92ZWN0b3IgPC0gZ2V0X3NlbnRpbWVudCh0ZXh0LCBtZXRob2Q9InN5dXpoZXQiKQ0KIyBzZWUgdGhlIGZpcnN0IHJvdyBvZiB0aGUgdmVjdG9yDQpoZWFkKHN5dXpoZXRfdmVjdG9yKQ0KIyBzZWUgc3VtbWFyeSBzdGF0aXN0aWNzIG9mIHRoZSB2ZWN0b3INCnN1bW1hcnkoc3l1emhldF92ZWN0b3IpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9VFJVRX0NCiMgYmluZw0KYmluZ192ZWN0b3IgPC0gZ2V0X3NlbnRpbWVudCh0ZXh0LCBtZXRob2Q9ImJpbmciKQ0KaGVhZChiaW5nX3ZlY3RvcikNCnN1bW1hcnkoYmluZ192ZWN0b3IpDQojYWZmaW4NCmFmaW5uX3ZlY3RvciA8LSBnZXRfc2VudGltZW50KHRleHQsIG1ldGhvZD0iYWZpbm4iKQ0KaGVhZChhZmlubl92ZWN0b3IpDQpzdW1tYXJ5KGFmaW5uX3ZlY3RvcikNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1UUlVFfQ0KI2NvbXBhcmUgdGhlIGZpcnN0IHJvdyBvZiBlYWNoIHZlY3RvciB1c2luZyBzaWduIGZ1bmN0aW9uDQpyYmluZCgNCiAgc2lnbihoZWFkKHN5dXpoZXRfdmVjdG9yKSksDQogIHNpZ24oaGVhZChiaW5nX3ZlY3RvcikpLA0KICBzaWduKGhlYWQoYWZpbm5fdmVjdG9yKSkNCikNCmBgYA0KDQojIyMgRXZhbHVhdGlvbiBlbW90aW9uDQoNCmBgYHtyIGVtb3Rpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQojIHJ1biBucmMgc2VudGltZW50IGFuYWx5c2lzIHRvIHJldHVybiBkYXRhIGZyYW1lIHdpdGggZWFjaCByb3cgY2xhc3NpZmllZCBhcyBvbmUgb2YgdGhlIGZvbGxvd2luZw0KIyBlbW90aW9ucywgcmF0aGVyIHRoYW4gYSBzY29yZTogDQojIGFuZ2VyLCBhbnRpY2lwYXRpb24sIGRpc2d1c3QsIGZlYXIsIGpveSwgc2FkbmVzcywgc3VycHJpc2UsIHRydXN0IA0KIyBJdCBhbHNvIGNvdW50cyB0aGUgbnVtYmVyIG9mIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBlbW90aW9ucyBmb3VuZCBpbiBlYWNoIHJvdw0KZDwtZ2V0X25yY19zZW50aW1lbnQodGV4dCkNCg0KI1NlZSBsaW5lcyBvZiB0aGUgZ2V0X25yY19zZW50aW1lbnQgZGF0YWZyYW1lDQp3cml0ZS5jc3YoZCwgZmlsZSA9ICJ0bWVfbnJjLmNzdiIpDQpkYXRhdGFibGUoZCkNCmBgYA0KDQpgYGB7ciBzZW50aW1lbnRzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0NCiN0cmFuc3Bvc2UNCnRkPC1kYXRhLmZyYW1lKHQoZCkpDQojVGhlIGZ1bmN0aW9uIHJvd1N1bXMgY29tcHV0ZXMgY29sdW1uIHN1bXMgYWNyb3NzIHJvd3MgZm9yIGVhY2ggbGV2ZWwgb2YgYSBncm91cGluZyB2YXJpYWJsZS4NCnRkX25ldyA8LSBkYXRhLmZyYW1lKHJvd1N1bXModGRbMjoxODgxXSkpDQojVHJhbnNmb3JtYXRpb24gYW5kIGNsZWFuaW5nDQpuYW1lcyh0ZF9uZXcpWzFdIDwtICJjb3VudCINCnRkX25ldyA8LSBjYmluZCgic2VudGltZW50IiA9IHJvd25hbWVzKHRkX25ldyksIHRkX25ldykNCnJvd25hbWVzKHRkX25ldykgPC0gTlVMTA0KdGRfbmV3MjwtdGRfbmV3WzE6OCxdDQojUGxvdCBPbmUgLSBjb3VudCBvZiB3b3JkcyBhc3NvY2lhdGVkIHdpdGggZWFjaCBzZW50aW1lbnQNCnF1aWNrcGxvdChzZW50aW1lbnQsIGRhdGE9dGRfbmV3Miwgd2VpZ2h0PWNvdW50LCBnZW9tPSJiYXIiLCBmaWxsPXNlbnRpbWVudCwgeWxhYj0iY291bnQiKStnZ3RpdGxlKCJQb3N0cyBvbiBUZWxlZ3JhbSBzZW50aW1lbnRzIikNCg0KYGBgDQoNCmBgYHtyIGVtb3Rpb25zX3RtZX0NCiNQbG90IHR3byAtIGNvdW50IG9mIHdvcmRzIGFzc29jaWF0ZWQgd2l0aCBlYWNoIHNlbnRpbWVudCwgZXhwcmVzc2VkIGFzIGEgcGVyY2VudGFnZQ0KYmFycGxvdCgNCiAgc29ydChjb2xTdW1zKHByb3AudGFibGUoZFssIDE6OF0pKSksIA0KICBob3JpeiA9IFRSVUUsIA0KICBjZXgubmFtZXMgPSAwLjcsIA0KICBsYXMgPSAxLCANCiAgbWFpbiA9ICJFbW90aW9ucyBpbiBQb3N0cyBvbiBUZWxlZ3JhbSIsIHhsYWI9IlBlcmNlbnRhZ2UiDQopDQpgYGANCg0KYGBge3Igdmlld190bWUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9DQpsaWJyYXJ5KERUKQ0KbGlicmFyeShyZWFkcikNCnRtZV90ZXh0b25seV90IDwtIHJlYWRfY3N2KCJ0bWVfdGV4dG9ubHlfdC5jc3YiLCANCiAgICBjb2xfdHlwZXMgPSBjb2xzKC4uLjEgPSBjb2xfc2tpcCgpKSkNCmRhdGF0YWJsZSh0bWVfdGV4dG9ubHlfdCkNCmBgYA0K