Extracting Tweets

Data yang akan digunakan dalam melakukan analisis text mining adalah dengan menggunakan data yang ada pada Twitter. Sbelum melakukan pengambilan data pastikan sudah memiliki akses token dan juga APIs pada https://developer.twitter.com

Retrieve tweets from Twitter

Pastikan package untuk mengambil data pada Twitter telah ter-install (rtweet) dan package yang akan mendukung output yang diinginkan (ex:tidyverse)

# Load packages
library(rtweet)
library(tidyverse)

Memasukkan keempat nilai token yang telah di peroleh dari developer twitter

# Twitter authentication
create_token(
  app             = "my_twitter_research_app",
  consumer_key    = consumer_key,
  consumer_secret = consumer_secret,
  access_token    = access_token,
  access_secret   = access_secret)

Mengembalikan status Twitter yang cocok dengan permintaan pencarian yang disediakan pengguna. Hanya dapat mengambil data dari 6-9 hari sebelumnya. Untuk mengembalikan lebih dari 18.000 status dalam satu panggilan, atur “retryonratelimit” ke TRUE.

Pada kasus kali ini, ingin mengetahui perbedaan antara media belajar online (education) ruangguru dan juga zenius.

# Retrieve tweets
tweets1 <- search_tweets("ruangguru", n = 20000, tweet_mode="extended")
tweets1 <- distinct(tweets1, text, .keep_all=TRUE)
tweets2 <- search_tweets("zenius", n = 20000, tweet_mode="extended")
tweets2 <- distinct(tweets2, text, .keep_all=TRUE)

Tweets Description

## plot time series of tweets
par(mfrow=c(1,2))
ts_plot(tweets1, "2 hours") +
  theme_minimal() +
  theme(plot.title = ggplot2::element_text(face = "bold")) +
  labs(
    x = NULL, y = NULL,
    title = "Frequency of RUANGGURU Twitter statuses from past 9 days",
    subtitle = "Twitter status (tweet) counts aggregated using three-hour intervals",
    caption = "\nSource: Data collected from Twitter's REST API via rtweet"
  )

ts_plot(tweets2, "2 hours") +
  theme_minimal() +
  theme(plot.title = ggplot2::element_text(face = "bold")) +
  labs(
    x = NULL, y = NULL,
    title = "Frequency of ZENIUS Twitter statuses from past 9 days",
    subtitle = "Twitter status (tweet) counts aggregated using three-hour intervals",
    caption = "\nSource: Data collected from Twitter's REST API via rtweet"
  )

Dengan menggunakan plot time series, maka dapat diketahui trend dari kata “ruangguru” dan “zenius” pada 9 hari terakhir dengan interval waktu sebesar 2 jam.Maka dapat diketahui kira-kira pukul berapa para pengguna Twitter akan membuat status dengan menggunakan unsur kedua kata tersebut. Dari kedua timeseries dapat diketahui antara tanggal 4 dan 5 November 2018 kata ruangguru dan juga zenius memiliki trend yang signifikan meningkat.

tail(tweets1, 10)
tail(tweets2, 10)

Text Cleaning

Sebelum membuat wordclod dari kata yang diinginkan, maka perlu melakukan penghapusan kata-kata ataupun atribut yang diprediksikan akan sering muncul namun tidak memiliki makna yang berarti terhadap kata yang diinginkan. Jika hal ini tidak dilakukan, maka akan memberikan hasil analisis yang kurang sesuai.

library(tm)
library(NLP)

Build corpus


# build a corpus, and specify the source to be character vectors 
myCorpus1 <- Corpus(VectorSource(tweets1$text))
myCorpus2 <- Corpus(VectorSource(tweets2$text))
# convert to lower case
myCorpus1 <- tm_map(myCorpus1,function(x) iconv(enc2utf8(x), sub="byte"))
myCorpus1 <- tm_map(myCorpus1, content_transformer(tolower))
myCorpus2 <- tm_map(myCorpus2,function(x) iconv(enc2utf8(x), sub="byte"))
myCorpus2 <- tm_map(myCorpus2, content_transformer(tolower))
# remove URLs
removeURL <- function(x) gsub("http[^[:space:]]*", "", x)
myCorpus1 <- tm_map(myCorpus1, content_transformer(removeURL))
myCorpus2 <- tm_map(myCorpus2, content_transformer(removeURL))
# remove anything other than English letters or space 
removeNumPunct <- function(x) gsub("[^[:alpha:][:space:]]*", "", x) 
myCorpus1 <- tm_map(myCorpus1, content_transformer(removeNumPunct))
myCorpus2 <- tm_map(myCorpus2, content_transformer(removeNumPunct))
# remove stopwords
myStopwords <- c(setdiff(stopwords('english'), c("r", "big")), "use", "see", "used", "via", "amp","ruangguru", "zenius","ààààà","àààà","ààà","àà","à","ðÿ")
stopwords_id <- read.table('stopwords-id.txt', header = FALSE)
myStopwords <- c(myStopwords, as.matrix(stopwords_id$V1), "hi", "yg")
myCorpus1 <- tm_map(myCorpus1, removeWords, myStopwords)
myCorpus2 <- tm_map(myCorpus2, removeWords, myStopwords)
# remove extra whitespace
myCorpus1<- tm_map(myCorpus1, stripWhitespace)
myCorpus2<- tm_map(myCorpus2, stripWhitespace)
# keep a copy for stem completion later
myCorpusCopy1 <- myCorpus1
myCorpusCopy2 <- myCorpus2

Frequent Words

Build Term Document Matrix

tdm1 <- TermDocumentMatrix(myCorpus1, control = list(wordLengths = c(1, Inf)))
tdm2 <- TermDocumentMatrix(myCorpus2, control = list(wordLengths = c(1, Inf)))
tdm1
tdm2

Top Frequent Terms

freq.terms1 <- findFreqTerms(tdm1, lowfreq = 10)
freq.terms2 <- findFreqTerms(tdm2, lowfreq = 10)
freq.terms1[1:75]
freq.terms2[1:75]
term.freq1 <- rowSums(as.matrix(tdm1))
term.freq1 <- subset(term.freq1, term.freq1 >= 10)
df1 <- data.frame(term = names(term.freq1), freq = term.freq1)
term.freq2 <- rowSums(as.matrix(tdm2))
term.freq2 <- subset(term.freq2, term.freq2 >= 10)
df2 <- data.frame(term = names(term.freq2), freq = term.freq2)
par(mfrow=c(1,2))
library(ggplot2)
ggplot(df1, aes(x=term, y=freq)) + geom_bar(stat="identity") +
  xlab("Terms") + ylab("Count") + coord_flip() +
  theme(axis.text=element_text(size=7))

ggplot(df2, aes(x=term, y=freq)) + geom_bar(stat="identity") +
  xlab("Terms") + ylab("Count") + coord_flip() +
  theme(axis.text=element_text(size=7))

Dari histogram di atas, maka dapat diketahui kata-kata apa saja yang sering diungkapkan bersamaan dengan kata yang ingin diketahui (ruangguru vs zenius)

Wordcloud

Build Wordcloud

library(wordcloud)
m1 <- as.matrix(tdm1)
m2 <- as.matrix(tdm2)
# calculate the frequency of words and sort it by frequency 
word.freq1 <- sort(rowSums(m1), decreasing = T)
word.freq2 <- sort(rowSums(m2), decreasing = T)
# colors
pal <- brewer.pal(9, "BuGn")[-(1:4)]
wordcloud(words = names(word.freq1), freq = word.freq1, min.freq = 10,
    random.order = F, colors = pal)

RUANGGURU >> dari wordcloud diatas dapat diketahui bahwa kata ruangguru sangat erat kaitannya dengan kata kode, diskon, aplikasi dan squad jika dilihat pada status Twitter 9 hari terakhir.

wordcloud(words = names(word.freq2), freq = word.freq1, min.freq = 10,
    random.order = F, colors = pal)

ZENIUS >> dari wordcloud diatas dapat diketahui bahwa kata zenius sangat erat kaitannya dengan kata edc, educationfess dan belajar jika dilihat pada status Twitter 9 hari terakhir.

SUMMARY Walaupun pada awal deskripsi data menggunakan time series menunjukkan kedua media belajar online tersebut mengalami kenaikan nilai trends yang sangat signifikan dianatara tanggal 3 s.d 5 November 2018. Namun jika dilihat kata yang erat kaitannya (banyak dibahas di Twitter) dengan kedua media belajar online tersebut maka dapat dikatakan kata-kata pendukung dari kedua media belajar online tersebut adalah berbeda.

LS0tDQp0aXRsZTogIkVYRVJDSVNFIDItRURVQ0FUSU9OIg0KYXV0aG9yOiAiQWZpZmFoIE51ciBJc3dhcmkgKDA2MjExNTQwMDAwMTA5KSINCmRhdGU6ICIxMiBOb3ZlbWJlciAyMDE4Ig0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiMjIEV4dHJhY3RpbmcgVHdlZXRzDQpEYXRhIHlhbmcgYWthbiBkaWd1bmFrYW4gZGFsYW0gbWVsYWt1a2FuIGFuYWxpc2lzIHRleHQgbWluaW5nIGFkYWxhaCBkZW5nYW4gbWVuZ2d1bmFrYW4gZGF0YSB5YW5nIGFkYSBwYWRhIFR3aXR0ZXIuIFNiZWx1bSBtZWxha3VrYW4gcGVuZ2FtYmlsYW4gZGF0YSBwYXN0aWthbiBzdWRhaCBtZW1pbGlraSBha3NlcyB0b2tlbiBkYW4ganVnYSBBUElzIHBhZGEgaHR0cHM6Ly9kZXZlbG9wZXIudHdpdHRlci5jb20NCg0KIyMjIFJldHJpZXZlIHR3ZWV0cyBmcm9tIFR3aXR0ZXINClBhc3Rpa2FuIHBhY2thZ2UgdW50dWsgbWVuZ2FtYmlsIGRhdGEgcGFkYSBUd2l0dGVyIHRlbGFoIHRlci1pbnN0YWxsIChydHdlZXQpIGRhbiBwYWNrYWdlIHlhbmcgYWthbiBtZW5kdWt1bmcgb3V0cHV0IHlhbmcgZGlpbmdpbmthbiAoZXg6dGlkeXZlcnNlKQ0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgTG9hZCBwYWNrYWdlcw0KbGlicmFyeShydHdlZXQpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpgYGB7ciBpbmNsdWRlPUZBTFNFfQ0KIyBBY2Nlc3MgdG9rZW4gYW5kIEFQSXMNCmNvbnN1bWVyX2tleSAgICA8LSAiRER6VzEwQ3hPVkMxU1Q4NzdzOFpiREpmcSINCmNvbnN1bWVyX3NlY3JldCA8LSAiQmIxNFRKM3llaFV5emEyeHg2THJMS2s5c2FNR3pseVVuVjFVU2d2UlNSdkc3YWZSbFQiDQphY2Nlc3NfdG9rZW4gICAgPC0gIjIwNjUyNjE3MS1Gdzl6T2N2Z2NSdHNlOW54czJOamVMZHNENWFadE5qQmlnMzBJbjBqIg0KYWNjZXNzX3NlY3JldCAgIDwtICJXeEMyT1hYN3B5NTZaeldJMVp3SUlLcGxKWklSRWlhMVh1WTVnQVFSb2hvaEoiDQpgYGANCk1lbWFzdWtrYW4ga2VlbXBhdCBuaWxhaSB0b2tlbiB5YW5nIHRlbGFoIGRpIHBlcm9sZWggZGFyaSBkZXZlbG9wZXIgdHdpdHRlcg0KYGBge3J9DQojIFR3aXR0ZXIgYXV0aGVudGljYXRpb24NCmNyZWF0ZV90b2tlbigNCiAgYXBwICAgICAgICAgICAgID0gIm15X3R3aXR0ZXJfcmVzZWFyY2hfYXBwIiwNCiAgY29uc3VtZXJfa2V5ICAgID0gY29uc3VtZXJfa2V5LA0KICBjb25zdW1lcl9zZWNyZXQgPSBjb25zdW1lcl9zZWNyZXQsDQogIGFjY2Vzc190b2tlbiAgICA9IGFjY2Vzc190b2tlbiwNCiAgYWNjZXNzX3NlY3JldCAgID0gYWNjZXNzX3NlY3JldCkNCmBgYA0KTWVuZ2VtYmFsaWthbiBzdGF0dXMgVHdpdHRlciB5YW5nIGNvY29rIGRlbmdhbiBwZXJtaW50YWFuIHBlbmNhcmlhbiB5YW5nIGRpc2VkaWFrYW4gcGVuZ2d1bmEuIEhhbnlhIGRhcGF0IG1lbmdhbWJpbCBkYXRhIGRhcmkgNi05IGhhcmkgc2ViZWx1bW55YS4gVW50dWsgbWVuZ2VtYmFsaWthbiBsZWJpaCBkYXJpIDE4LjAwMCBzdGF0dXMgZGFsYW0gc2F0dSBwYW5nZ2lsYW4sIGF0dXIgInJldHJ5b25yYXRlbGltaXQiIGtlIFRSVUUuDQoNClBhZGEga2FzdXMga2FsaSBpbmksIGluZ2luIG1lbmdldGFodWkgcGVyYmVkYWFuIGFudGFyYSBtZWRpYSBiZWxhamFyIG9ubGluZSAoZWR1Y2F0aW9uKSBydWFuZ2d1cnUgZGFuIGp1Z2EgemVuaXVzLg0KYGBge3Igd2FybmluZz1GQUxTRX0NCiMgUmV0cmlldmUgdHdlZXRzDQp0d2VldHMxIDwtIHNlYXJjaF90d2VldHMoInJ1YW5nZ3VydSIsIG4gPSAyMDAwMCwgdHdlZXRfbW9kZT0iZXh0ZW5kZWQiKQ0KdHdlZXRzMSA8LSBkaXN0aW5jdCh0d2VldHMxLCB0ZXh0LCAua2VlcF9hbGw9VFJVRSkNCnR3ZWV0czIgPC0gc2VhcmNoX3R3ZWV0cygiemVuaXVzIiwgbiA9IDIwMDAwLCB0d2VldF9tb2RlPSJleHRlbmRlZCIpDQp0d2VldHMyIDwtIGRpc3RpbmN0KHR3ZWV0czIsIHRleHQsIC5rZWVwX2FsbD1UUlVFKQ0KYGBgDQoNCg0KIyMjIFR3ZWV0cyBEZXNjcmlwdGlvbg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KIyMgcGxvdCB0aW1lIHNlcmllcyBvZiB0d2VldHMNCnBhcihtZnJvdz1jKDEsMikpDQp0c19wbG90KHR3ZWV0czEsICIyIGhvdXJzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArDQogIGxhYnMoDQogICAgeCA9IE5VTEwsIHkgPSBOVUxMLA0KICAgIHRpdGxlID0gIkZyZXF1ZW5jeSBvZiBSVUFOR0dVUlUgVHdpdHRlciBzdGF0dXNlcyBmcm9tIHBhc3QgOSBkYXlzIiwNCiAgICBzdWJ0aXRsZSA9ICJUd2l0dGVyIHN0YXR1cyAodHdlZXQpIGNvdW50cyBhZ2dyZWdhdGVkIHVzaW5nIHRocmVlLWhvdXIgaW50ZXJ2YWxzIiwNCiAgICBjYXB0aW9uID0gIlxuU291cmNlOiBEYXRhIGNvbGxlY3RlZCBmcm9tIFR3aXR0ZXIncyBSRVNUIEFQSSB2aWEgcnR3ZWV0Ig0KICApDQp0c19wbG90KHR3ZWV0czIsICIyIGhvdXJzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArDQogIGxhYnMoDQogICAgeCA9IE5VTEwsIHkgPSBOVUxMLA0KICAgIHRpdGxlID0gIkZyZXF1ZW5jeSBvZiBaRU5JVVMgVHdpdHRlciBzdGF0dXNlcyBmcm9tIHBhc3QgOSBkYXlzIiwNCiAgICBzdWJ0aXRsZSA9ICJUd2l0dGVyIHN0YXR1cyAodHdlZXQpIGNvdW50cyBhZ2dyZWdhdGVkIHVzaW5nIHRocmVlLWhvdXIgaW50ZXJ2YWxzIiwNCiAgICBjYXB0aW9uID0gIlxuU291cmNlOiBEYXRhIGNvbGxlY3RlZCBmcm9tIFR3aXR0ZXIncyBSRVNUIEFQSSB2aWEgcnR3ZWV0Ig0KICApDQpgYGANCkRlbmdhbiBtZW5nZ3VuYWthbiBwbG90IHRpbWUgc2VyaWVzLCBtYWthIGRhcGF0IGRpa2V0YWh1aSB0cmVuZCBkYXJpIGthdGEgInJ1YW5nZ3VydSIgZGFuICJ6ZW5pdXMiIHBhZGEgOSBoYXJpIHRlcmFraGlyIGRlbmdhbiBpbnRlcnZhbCB3YWt0dSBzZWJlc2FyIDIgamFtLk1ha2EgZGFwYXQgZGlrZXRhaHVpIGtpcmEta2lyYSBwdWt1bCBiZXJhcGEgcGFyYSBwZW5nZ3VuYSBUd2l0dGVyIGFrYW4gbWVtYnVhdCBzdGF0dXMgZGVuZ2FuIG1lbmdndW5ha2FuIHVuc3VyIGtlZHVhIGthdGEgdGVyc2VidXQuIERhcmkga2VkdWEgdGltZXNlcmllcyBkYXBhdCBkaWtldGFodWkgYW50YXJhIHRhbmdnYWwgNCBkYW4gNSBOb3ZlbWJlciAyMDE4IGthdGEgcnVhbmdndXJ1IGRhbiBqdWdhIHplbml1cyBtZW1pbGlraSB0cmVuZCB5YW5nIHNpZ25pZmlrYW4gbWVuaW5na2F0LiANCg0KYGBge3J9DQp0YWlsKHR3ZWV0czEsIDEwKQ0KdGFpbCh0d2VldHMyLCAxMCkNCmBgYA0KDQojIyBUZXh0IENsZWFuaW5nDQpTZWJlbHVtIG1lbWJ1YXQgd29yZGNsb2QgZGFyaSBrYXRhIHlhbmcgZGlpbmdpbmthbiwgbWFrYSBwZXJsdSBtZWxha3VrYW4gcGVuZ2hhcHVzYW4ga2F0YS1rYXRhIGF0YXVwdW4gYXRyaWJ1dCB5YW5nIGRpcHJlZGlrc2lrYW4gYWthbiBzZXJpbmcgbXVuY3VsIG5hbXVuIHRpZGFrIG1lbWlsaWtpIG1ha25hIHlhbmcgYmVyYXJ0aSB0ZXJoYWRhcCBrYXRhIHlhbmcgZGlpbmdpbmthbi4gSmlrYSBoYWwgaW5pIHRpZGFrIGRpbGFrdWthbiwgbWFrYSBha2FuIG1lbWJlcmlrYW4gaGFzaWwgYW5hbGlzaXMgeWFuZyBrdXJhbmcgc2VzdWFpLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0bSkNCmxpYnJhcnkoTkxQKQ0KYGBgDQojIyMgQnVpbGQgY29ycHVzDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KDQojIGJ1aWxkIGEgY29ycHVzLCBhbmQgc3BlY2lmeSB0aGUgc291cmNlIHRvIGJlIGNoYXJhY3RlciB2ZWN0b3JzIA0KbXlDb3JwdXMxIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodHdlZXRzMSR0ZXh0KSkNCm15Q29ycHVzMiA8LSBDb3JwdXMoVmVjdG9yU291cmNlKHR3ZWV0czIkdGV4dCkpDQojIGNvbnZlcnQgdG8gbG93ZXIgY2FzZQ0KbXlDb3JwdXMxIDwtIHRtX21hcChteUNvcnB1czEsZnVuY3Rpb24oeCkgaWNvbnYoZW5jMnV0ZjgoeCksIHN1Yj0iYnl0ZSIpKQ0KbXlDb3JwdXMxIDwtIHRtX21hcChteUNvcnB1czEsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQpteUNvcnB1czIgPC0gdG1fbWFwKG15Q29ycHVzMixmdW5jdGlvbih4KSBpY29udihlbmMydXRmOCh4KSwgc3ViPSJieXRlIikpDQpteUNvcnB1czIgPC0gdG1fbWFwKG15Q29ycHVzMiwgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkNCiMgcmVtb3ZlIFVSTHMNCnJlbW92ZVVSTCA8LSBmdW5jdGlvbih4KSBnc3ViKCJodHRwW15bOnNwYWNlOl1dKiIsICIiLCB4KQ0KbXlDb3JwdXMxIDwtIHRtX21hcChteUNvcnB1czEsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlVVJMKSkNCm15Q29ycHVzMiA8LSB0bV9tYXAobXlDb3JwdXMyLCBjb250ZW50X3RyYW5zZm9ybWVyKHJlbW92ZVVSTCkpDQojIHJlbW92ZSBhbnl0aGluZyBvdGhlciB0aGFuIEVuZ2xpc2ggbGV0dGVycyBvciBzcGFjZSANCnJlbW92ZU51bVB1bmN0IDwtIGZ1bmN0aW9uKHgpIGdzdWIoIlteWzphbHBoYTpdWzpzcGFjZTpdXSoiLCAiIiwgeCkgDQpteUNvcnB1czEgPC0gdG1fbWFwKG15Q29ycHVzMSwgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVOdW1QdW5jdCkpDQpteUNvcnB1czIgPC0gdG1fbWFwKG15Q29ycHVzMiwgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVOdW1QdW5jdCkpDQojIHJlbW92ZSBzdG9wd29yZHMNCm15U3RvcHdvcmRzIDwtIGMoc2V0ZGlmZihzdG9wd29yZHMoJ2VuZ2xpc2gnKSwgYygiciIsICJiaWciKSksICJ1c2UiLCAic2VlIiwgInVzZWQiLCAidmlhIiwgImFtcCIsInJ1YW5nZ3VydSIsICJ6ZW5pdXMiLCLDoMOgw6DDoMOgIiwiw6DDoMOgw6AiLCLDoMOgw6AiLCLDoMOgIiwiw6AiLCLDsMO/IikNCnN0b3B3b3Jkc19pZCA8LSByZWFkLnRhYmxlKCdzdG9wd29yZHMtaWQudHh0JywgaGVhZGVyID0gRkFMU0UpDQpteVN0b3B3b3JkcyA8LSBjKG15U3RvcHdvcmRzLCBhcy5tYXRyaXgoc3RvcHdvcmRzX2lkJFYxKSwgImhpIiwgInlnIikNCm15Q29ycHVzMSA8LSB0bV9tYXAobXlDb3JwdXMxLCByZW1vdmVXb3JkcywgbXlTdG9wd29yZHMpDQpteUNvcnB1czIgPC0gdG1fbWFwKG15Q29ycHVzMiwgcmVtb3ZlV29yZHMsIG15U3RvcHdvcmRzKQ0KIyByZW1vdmUgZXh0cmEgd2hpdGVzcGFjZQ0KbXlDb3JwdXMxPC0gdG1fbWFwKG15Q29ycHVzMSwgc3RyaXBXaGl0ZXNwYWNlKQ0KbXlDb3JwdXMyPC0gdG1fbWFwKG15Q29ycHVzMiwgc3RyaXBXaGl0ZXNwYWNlKQ0KIyBrZWVwIGEgY29weSBmb3Igc3RlbSBjb21wbGV0aW9uIGxhdGVyDQpteUNvcnB1c0NvcHkxIDwtIG15Q29ycHVzMQ0KbXlDb3JwdXNDb3B5MiA8LSBteUNvcnB1czINCmBgYA0KIyMgRnJlcXVlbnQgV29yZHMNCg0KIyMjIEJ1aWxkIFRlcm0gRG9jdW1lbnQgTWF0cml4DQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KdGRtMSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgobXlDb3JwdXMxLCBjb250cm9sID0gbGlzdCh3b3JkTGVuZ3RocyA9IGMoMSwgSW5mKSkpDQp0ZG0yIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChteUNvcnB1czIsIGNvbnRyb2wgPSBsaXN0KHdvcmRMZW5ndGhzID0gYygxLCBJbmYpKSkNCnRkbTENCnRkbTINCmBgYA0KDQojIyMgVG9wIEZyZXF1ZW50IFRlcm1zDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpmcmVxLnRlcm1zMSA8LSBmaW5kRnJlcVRlcm1zKHRkbTEsIGxvd2ZyZXEgPSAxMCkNCmZyZXEudGVybXMyIDwtIGZpbmRGcmVxVGVybXModGRtMiwgbG93ZnJlcSA9IDEwKQ0KYGBgDQoNCmBgYHtyfQ0KZnJlcS50ZXJtczFbMTo3NV0NCmZyZXEudGVybXMyWzE6NzVdDQpgYGANCg0KYGBge3J9DQp0ZXJtLmZyZXExIDwtIHJvd1N1bXMoYXMubWF0cml4KHRkbTEpKQ0KdGVybS5mcmVxMSA8LSBzdWJzZXQodGVybS5mcmVxMSwgdGVybS5mcmVxMSA+PSAxMCkNCmRmMSA8LSBkYXRhLmZyYW1lKHRlcm0gPSBuYW1lcyh0ZXJtLmZyZXExKSwgZnJlcSA9IHRlcm0uZnJlcTEpDQp0ZXJtLmZyZXEyIDwtIHJvd1N1bXMoYXMubWF0cml4KHRkbTIpKQ0KdGVybS5mcmVxMiA8LSBzdWJzZXQodGVybS5mcmVxMiwgdGVybS5mcmVxMiA+PSAxMCkNCmRmMiA8LSBkYXRhLmZyYW1lKHRlcm0gPSBuYW1lcyh0ZXJtLmZyZXEyKSwgZnJlcSA9IHRlcm0uZnJlcTIpDQpgYGANCg0KYGBge3J9DQpwYXIobWZyb3c9YygxLDIpKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGRmMSwgYWVzKHg9dGVybSwgeT1mcmVxKSkgKyBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsNCiAgeGxhYigiVGVybXMiKSArIHlsYWIoIkNvdW50IikgKyBjb29yZF9mbGlwKCkgKw0KICB0aGVtZShheGlzLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9NykpDQpnZ3Bsb3QoZGYyLCBhZXMoeD10ZXJtLCB5PWZyZXEpKSArIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKw0KICB4bGFiKCJUZXJtcyIpICsgeWxhYigiQ291bnQiKSArIGNvb3JkX2ZsaXAoKSArDQogIHRoZW1lKGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT03KSkNCmBgYA0KRGFyaSBoaXN0b2dyYW0gZGkgYXRhcywgbWFrYSBkYXBhdCBkaWtldGFodWkga2F0YS1rYXRhIGFwYSBzYWphIHlhbmcgc2VyaW5nIGRpdW5na2Fwa2FuIGJlcnNhbWFhbiBkZW5nYW4ga2F0YSB5YW5nIGluZ2luIGRpa2V0YWh1aSAocnVhbmdndXJ1IHZzIHplbml1cykNCg0KIyMgV29yZGNsb3VkDQoNCiMjIyBCdWlsZCBXb3JkY2xvdWQNCmBgYHtyfQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpgYGANCg0KYGBge3J9DQptMSA8LSBhcy5tYXRyaXgodGRtMSkNCm0yIDwtIGFzLm1hdHJpeCh0ZG0yKQ0KIyBjYWxjdWxhdGUgdGhlIGZyZXF1ZW5jeSBvZiB3b3JkcyBhbmQgc29ydCBpdCBieSBmcmVxdWVuY3kgDQp3b3JkLmZyZXExIDwtIHNvcnQocm93U3VtcyhtMSksIGRlY3JlYXNpbmcgPSBUKQ0Kd29yZC5mcmVxMiA8LSBzb3J0KHJvd1N1bXMobTIpLCBkZWNyZWFzaW5nID0gVCkNCiMgY29sb3JzDQpwYWwgPC0gYnJld2VyLnBhbCg5LCAiQnVHbiIpWy0oMTo0KV0NCmBgYA0KDQoNCg0KYGBge3J9DQp3b3JkY2xvdWQod29yZHMgPSBuYW1lcyh3b3JkLmZyZXExKSwgZnJlcSA9IHdvcmQuZnJlcTEsIG1pbi5mcmVxID0gMTAsDQogICAgcmFuZG9tLm9yZGVyID0gRiwgY29sb3JzID0gcGFsKQ0KYGBgDQpSVUFOR0dVUlUgPj4gZGFyaSB3b3JkY2xvdWQgZGlhdGFzIGRhcGF0IGRpa2V0YWh1aSBiYWh3YSBrYXRhIHJ1YW5nZ3VydSBzYW5nYXQgZXJhdCBrYWl0YW5ueWEgZGVuZ2FuIGthdGEga29kZSwgZGlza29uLCBhcGxpa2FzaSBkYW4gc3F1YWQgamlrYSBkaWxpaGF0IHBhZGEgc3RhdHVzIFR3aXR0ZXIgOSBoYXJpIHRlcmFraGlyLiANCg0KYGBge3J9DQp3b3JkY2xvdWQod29yZHMgPSBuYW1lcyh3b3JkLmZyZXEyKSwgZnJlcSA9IHdvcmQuZnJlcTEsIG1pbi5mcmVxID0gMTAsDQogICAgcmFuZG9tLm9yZGVyID0gRiwgY29sb3JzID0gcGFsKQ0KYGBgDQpaRU5JVVMgPj4gZGFyaSB3b3JkY2xvdWQgZGlhdGFzIGRhcGF0IGRpa2V0YWh1aSBiYWh3YSBrYXRhIHplbml1cyBzYW5nYXQgZXJhdCBrYWl0YW5ueWEgZGVuZ2FuIGthdGEgZWRjLCBlZHVjYXRpb25mZXNzIGRhbiBiZWxhamFyIGppa2EgZGlsaWhhdCBwYWRhIHN0YXR1cyBUd2l0dGVyIDkgaGFyaSB0ZXJha2hpci4gDQoNCg0KU1VNTUFSWQ0KV2FsYXVwdW4gcGFkYSBhd2FsIGRlc2tyaXBzaSBkYXRhIG1lbmdndW5ha2FuIHRpbWUgc2VyaWVzIG1lbnVuanVra2FuIGtlZHVhIG1lZGlhIGJlbGFqYXIgb25saW5lIHRlcnNlYnV0IG1lbmdhbGFtaSBrZW5haWthbiBuaWxhaSB0cmVuZHMgeWFuZyBzYW5nYXQgc2lnbmlmaWthbiBkaWFuYXRhcmEgdGFuZ2dhbCAzIHMuZCA1IE5vdmVtYmVyIDIwMTguIE5hbXVuIGppa2EgZGlsaWhhdCBrYXRhIHlhbmcgZXJhdCBrYWl0YW5ueWEgKGJhbnlhayBkaWJhaGFzIGRpIFR3aXR0ZXIpIGRlbmdhbiBrZWR1YSBtZWRpYSBiZWxhamFyIG9ubGluZSB0ZXJzZWJ1dCBtYWthIGRhcGF0IGRpa2F0YWthbiBrYXRhLWthdGEgcGVuZHVrdW5nIGRhcmkga2VkdWEgbWVkaWEgYmVsYWphciBvbmxpbmUgdGVyc2VidXQgYWRhbGFoIGJlcmJlZGEuDQo=