Clifton_Baldwin

Data Science Journal of Clif Baldwin

Analyzing Tweets about the Philadelphia Flyers

7 April ’18

Twitter Analysis of Philly Flyers

I decided to analyze the tweets that mention the Philadelphia Flyers during most of March 2018. At the moment, I have two R Notebooks. Well, actually one completed, which I am posting here, and one R Notebook I am working on, which I will post later.

Here is part 1 for the Philly Flyers R Notebook Analysis: <!DOCTYPE html>

Flyers Tweets - Tweeters

Through my position at Stockton University, I have heard that the Philadelphia Flyers are looking for ways to increase ticket sales for games. I determined several data research questions related to how word can be spread for the Philadelphia Flyers. As a start, I asked the following questions: 1. Who tweeted the most texts over the data collection time period? 2. Of those who tweeted, who had the most followers? 3. Of those who tweeted, Who had the most retweeted texts?

I believe answering these questions will provide the Twitter users who currently promote the Flyers and which ones have the potential to influence other Twitter users. If I were looking for ways to promote the Flyers, I might try to persuade these users further to use their social media contacts.

In a follow on study, I plan to ask questions related to the textual content of the texted tweets.

In March 2018, I scraped Twitter several times in order to gather all tweets that had the hashtags #Flyers, #FlyersNation, or #LETSGOFLYERS. I used these hashtags based on anecdotal evidence only. Future studies may want to consider additional hashtags if there is any doubt that these hashtags are not truly representative. Twitter limits the number of tweets that can be obtained at any one time, which in part is why I scraped the data over three time periods. Although there are other workarounds, these three hashtags returned just shy of the limit of available tweets (15,000 tweets limit). Nonetheless it appears the data is sufficient for this initial study.

The dates of the collections were March 11, March 20, and March 26, 2018. The following is the code I used each time.

# load twitter library - the rtweet library is recommended now over twitteR
library(rtweet)

# Enter your Twitter credentials
appname <- "Flyers_Data"  # name I assigned my app in Twitter
key <- "XXXXXXXXX"  # I am not sharing my actual credentials
secret <- "XXXXXXXXXXXXXXXXXXX" # Anyone wishing to duplicate this code can sign up for their own Twitter app - it is free

# create token named "twitter_token"
twitter_token <- create_token(
  app = appname,
  consumer_key = key,
  consumer_secret = secret)
saveRDS(twitter_token, "~/.rtweet-oauth.rds")

# Scrape tweets
rstats_tweets <- search_tweets2(q = "#Flyers OR #FlyersNation OR #LETSGOFLYERS", n = 15000, parse = TRUE, type="mixed")

# Save tweets to a RData file
save(rstats_tweets, file="rtweets2018MMDD.RData")

After all the data was scraped and saved to R dataset files, we can start working with the data. First, several R libraries are needed. Note, I tried to use only high quality libraries, such as those developed by the RStudio group.

library(rtweet) # for users_data()
library(tidyverse) # Instead of just ggplot2 and dplyr
── Attaching packages ──────────────────── tidyverse 1.2.1 ──
✔ ggplot2 2.2.1     ✔ purrr   0.2.4
✔ tibble  1.4.2     ✔ dplyr   0.7.4
✔ tidyr   0.8.0     ✔ stringr 1.3.0
✔ readr   1.1.1     ✔ forcats 0.3.0
── Conflicts ─────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(tidytext)  # For Twitter text manipulation
library("RColorBrewer") # Because I want to print with Flyers colors!

Read the three datasets back into memory and combine into one master dataset.

# Load the RData files that were saved after scraping Twitter
load(file="rtweets20180311.RData")
tw11 <- rstats_tweets
users11 <- users_data(rstats_tweets)
load(file="rtweets20180320.RData")
tw20 <- rstats_tweets
users20 <- users_data(rstats_tweets)
load(file="rtweets20180326.RData")
# Combine the two datasets
tw <- bind_rows(tw11, tw20, rstats_tweets)
users <- users_data(rstats_tweets)
users <- bind_rows(users11, users20, users)
# Delete the indivual datasets since we now have the master file
rm(tw11, users11, tw20, users20, rstats_tweets)

Due to the times when the data was scraped, there are some overlapping time periods in the data. For example, the scraping run of March 20 collected some tweets that were scraped on March 11. We do not want these duplicates.

users <- unique(users)

As I worked with the data, I found some users that I rather exclude from my analysis.

# user_id = "19618527" is the Philadelphia Flyers. We know the Flyers tweet for the team.
# user_id = "471268712" is PHLFlyersNation. We know the PHLFlyers tweet for the team.
# user_id = "154699499" is sportstalkphl We know the sportstalkphl tweet for the team.
# user_id = "426029765" is XFINITYLive We know XFINITYLive tweet for the team.
# user_id = "19276719" and "321035743" are not real people and directs to naughty websites
# user_id = "493658381" is FlyersNation. We know the Flyers tweet for the team.
# user_id = "938072969552826368" is the Philly Sports Network Flyers.
users <- users[!(users$user_id %in% c("19618527", "471268712", "154699499", "426029765", "19276719", "493658381", "938072969552826368", "321035743")),]

There are many people who tweet about the Flyers that do not live locally. I do not doubt they are fans, but they are probably not attending many games (due to their geographic locations). Perhaps they should be included in further analysis, but for now I am only keeping tweets by users who identify their location as Pennsylvania, New Jersey, or Delaware. This restriction may lose a few local people who do not identify their location as local, but it is unavoidable.

# Only analyze "local" tweeters - location identified as PA, NJ, or DE
select <- grepl("Phil", users$location, ignore.case = TRUE) | grepl("PA", users$location, ignore.case = FALSE) | grepl("NJ", users$location, ignore.case = FALSE) | grepl("DE", users$location, ignore.case = FALSE)
users <- users[select,]
rm(select)

Twitter allows certain users to be verified. Verified accounts include professional radio, TV, and news stations (e.g. NBC), and some celebrity names (a spot check identifies the selected as broadcastsers and reporters). I am sure they are quite helpful in boosting Flyers ticket sales, but then there is no reason to keep them in the analysis. We know they already are working on boosting sales. For the purposes of this study, the verified accounts will not be considered.

users <- users[!users$verified,] # Save only nonverified accounts

I am sure the remaining users include a few bots that I might have missed, but the resulting group is a start for this study.

Twitter data is formatted so that it can be saved in two datasets. The users data and the tweets data. The users data includes the following features: user_id, name, screen_name, location, description, url, protected, followers_count, friends_count, listed_count, statuses_count, favourites_count, account_created_at, verified, profile_url, profile_expanded_url, account_lang, profile_banner_url, profile_background_url, profile_image_url. We need to select the tweets from only the users that remain in the users dataset.

# There are a few tweets repeated due to overlapping dates. Save only one instance of each.
tw <- unique(tw)
# Now select only the tweets that belong to the remaining user_ids
tw <- tw[tw$user_id %in% users$user_id,]
# Save only the tweets that are in English - at this time
tw <- tw[tw$lang=="en",]

The tweets dataset has the following features: status_id, created_at, user_id, screen_name, text, source, reply_to_status_id, reply_to_user_id, reply_to_screen_name, is_quote, is_retweet, favorite_count, retweet_count, hashtags, symbols, urls_url, urls_t.co, urls_expanded_url, media_url, media_t.co, media_expanded_url, media_type, ext_media_url, ext_media_t.co, ext_media_expanded_url, ext_media_type, mentions_user_id, mentions_screen_name, lang, quoted_status_id, quoted_text, retweet_status_id, retweet_text, place_url, place_name, place_full_name, place_type, country, country_code, geo_coords, coords_coords, bbox_coords, query.

The collected tweets span the time from 2018-03-04 20:56:42 to 2018-03-26 13:00:21. The resulting count of users who tweeted is 1957 and 7359 tweets, although many are retweets.

Now we can address the first data research question. ## 1. What Twitter user had the most tweets over the selected time period?

tw %>%
  count(user_id, sort = TRUE) %>%
  filter(n > 60) %>%
  inner_join(distinct(users[,c(1:3)], user_id, .keep_all = TRUE)) %>%
#  ggplot(aes(x = reorder(name, n), y = n)) + # If I wanted to show actual names
  ggplot(aes(x = reorder(user_id, n), y = n)) +
  geom_col(aes(fill = n)) +
  scale_fill_distiller(palette="Oranges") +
  theme(legend.position = "none") +
  xlab("User_Ids") + ylab("Number of Tweets") +
  labs(title="Users with Most Tweets", subtitle="In Shades of Orange!") +
  coord_flip()

If I was doing this to get the actual names of the Twitter users, I could have listed the user names, as they are available in the dataset, but since this exercise is only academic, I print the user_id numbers, to maintain some anonymity.

Next let us look at the number of followers the users have. We could choose to see all regardless of followers. Viewing all users with their followers overloads the graph, and I do not want just a list of names. A spot check shows a large number of users have more than 20,000 followers. Specifically, 20 have more than 20,000 followers. If it turns out that these users are not real people, we could remove them from the users dataset or look at users with followers between 100 and 500, or whatever span we think appropriate.

2. Of those who tweeted, who had the most followers?

unique(users[,c(1,2,3,8)]) %>%
  filter(followers_count > 20000) %>%
  ggplot(aes(x = reorder(user_id, followers_count), y = followers_count)) +
  geom_col(aes(fill = followers_count)) +
  scale_fill_distiller(palette="Oranges") +
  theme(legend.position = "none") +
  xlab("User_Ids") + ylab("Number of Followers") +
  labs(title="Users who Tweeted #Flyers with Most Followers", subtitle="More than 20,000 followers") +
  coord_flip()

Again, I could have printed the graph with user names instead of user ids, but I am protecting identities for this initial study.

Finally, let us find the originators of tweets that were highly retweeted. It is possible that others retweeted the retweeted texts, but the following chart considers only the original authors of the tweets.

3. Of those who tweeted, Who had the most retweeted text?

tw[!tw$is_retweet,c(3,13)]  %>%
  group_by(user_id) %>%
  summarise(n=max(retweet_count)) %>%
  filter(n > 10) %>%
  inner_join(distinct(users[,c(1,2,3,8,13)], user_id, .keep_all = TRUE)) %>%
  ggplot(aes(x = reorder(user_id, n), y = n)) +
  geom_col(aes(fill = n)) +
  scale_fill_distiller(palette="Oranges") +
  theme(legend.position = "none") +
  xlab("User_Ids") + ylab("Retweet Count") +
  labs(title="Users who had the Most Retweets") +
  coord_flip()

This post has examined the Twitter users that tweet about the Philly Flyers. In the next post, we will look at the tweets themselves. I will post again soon.

LS0tCnRpdGxlOiAiRmx5ZXJzIFR3ZWV0cyAtIFR3ZWV0ZXJzIgphdXRob3I6ICJEci4gQ2xpZnRvbiBCYWxkd2luIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpUaHJvdWdoIG15IHBvc2l0aW9uIGF0IFN0b2NrdG9uIFVuaXZlcnNpdHksIEkgaGF2ZSBoZWFyZCB0aGF0IHRoZSBQaGlsYWRlbHBoaWEgRmx5ZXJzIGFyZSBsb29raW5nIGZvciB3YXlzIHRvIGluY3JlYXNlIHRpY2tldCBzYWxlcyBmb3IgZ2FtZXMuIEkgZGV0ZXJtaW5lZCBzZXZlcmFsIGRhdGEgcmVzZWFyY2ggcXVlc3Rpb25zIHJlbGF0ZWQgdG8gaG93IHdvcmQgY2FuIGJlIHNwcmVhZCBmb3IgdGhlIFBoaWxhZGVscGhpYSBGbHllcnMuIEFzIGEgc3RhcnQsIEkgYXNrZWQgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6CjEuIFdobyB0d2VldGVkIHRoZSBtb3N0IHRleHRzIG92ZXIgdGhlIGRhdGEgY29sbGVjdGlvbiB0aW1lIHBlcmlvZD8KMi4gT2YgdGhvc2Ugd2hvIHR3ZWV0ZWQsIHdobyBoYWQgdGhlIG1vc3QgZm9sbG93ZXJzPwozLiBPZiB0aG9zZSB3aG8gdHdlZXRlZCwgV2hvIGhhZCB0aGUgbW9zdCByZXR3ZWV0ZWQgdGV4dHM/CgpJIGJlbGlldmUgYW5zd2VyaW5nIHRoZXNlIHF1ZXN0aW9ucyB3aWxsIHByb3ZpZGUgdGhlIFR3aXR0ZXIgdXNlcnMgd2hvIGN1cnJlbnRseSBwcm9tb3RlIHRoZSBGbHllcnMgYW5kIHdoaWNoIG9uZXMgaGF2ZSB0aGUgcG90ZW50aWFsIHRvIGluZmx1ZW5jZSBvdGhlciBUd2l0dGVyIHVzZXJzLiBJZiBJIHdlcmUgbG9va2luZyBmb3Igd2F5cyB0byBwcm9tb3RlIHRoZSBGbHllcnMsIEkgbWlnaHQgdHJ5IHRvIHBlcnN1YWRlIHRoZXNlIHVzZXJzIGZ1cnRoZXIgdG8gdXNlIHRoZWlyIHNvY2lhbCBtZWRpYSBjb250YWN0cy4KCkluIGEgZm9sbG93IG9uIHN0dWR5LCBJIHBsYW4gdG8gYXNrIHF1ZXN0aW9ucyByZWxhdGVkIHRvIHRoZSB0ZXh0dWFsIGNvbnRlbnQgb2YgdGhlIHRleHRlZCB0d2VldHMuCgpJbiBNYXJjaCAyMDE4LCBJIHNjcmFwZWQgVHdpdHRlciBzZXZlcmFsIHRpbWVzIGluIG9yZGVyIHRvIGdhdGhlciBhbGwgdHdlZXRzIHRoYXQgaGFkIHRoZSBoYXNodGFncyAjRmx5ZXJzLCAjRmx5ZXJzTmF0aW9uLCBvciAjTEVUU0dPRkxZRVJTLiBJIHVzZWQgdGhlc2UgaGFzaHRhZ3MgYmFzZWQgb24gYW5lY2RvdGFsIGV2aWRlbmNlIG9ubHkuIEZ1dHVyZSBzdHVkaWVzIG1heSB3YW50IHRvIGNvbnNpZGVyIGFkZGl0aW9uYWwgaGFzaHRhZ3MgaWYgdGhlcmUgaXMgYW55IGRvdWJ0IHRoYXQgdGhlc2UgaGFzaHRhZ3MgYXJlIG5vdCB0cnVseSByZXByZXNlbnRhdGl2ZS4gVHdpdHRlciBsaW1pdHMgdGhlIG51bWJlciBvZiB0d2VldHMgdGhhdCBjYW4gYmUgb2J0YWluZWQgYXQgYW55IG9uZSB0aW1lLCB3aGljaCBpbiBwYXJ0IGlzIHdoeSBJIHNjcmFwZWQgdGhlIGRhdGEgb3ZlciB0aHJlZSB0aW1lIHBlcmlvZHMuIEFsdGhvdWdoIHRoZXJlIGFyZSBvdGhlciB3b3JrYXJvdW5kcywgdGhlc2UgdGhyZWUgaGFzaHRhZ3MgcmV0dXJuZWQganVzdCBzaHkgb2YgdGhlIGxpbWl0IG9mIGF2YWlsYWJsZSB0d2VldHMgKDE1LDAwMCB0d2VldHMgbGltaXQpLiBOb25ldGhlbGVzcyBpdCBhcHBlYXJzIHRoZSBkYXRhIGlzIHN1ZmZpY2llbnQgZm9yIHRoaXMgaW5pdGlhbCBzdHVkeS4KClRoZSBkYXRlcyBvZiB0aGUgY29sbGVjdGlvbnMgd2VyZSBNYXJjaCAxMSwgTWFyY2ggMjAsIGFuZCBNYXJjaCAyNiwgMjAxOC4gVGhlIGZvbGxvd2luZyBpcyB0aGUgY29kZSBJIHVzZWQgZWFjaCB0aW1lLgoKYGBge3IsIGV2YWw9RkFMU0UsIGVjaG89VFJVRSwgZXJyb3I9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgbG9hZCB0d2l0dGVyIGxpYnJhcnkgLSB0aGUgcnR3ZWV0IGxpYnJhcnkgaXMgcmVjb21tZW5kZWQgbm93IG92ZXIgdHdpdHRlUgpsaWJyYXJ5KHJ0d2VldCkKCiMgRW50ZXIgeW91ciBUd2l0dGVyIGNyZWRlbnRpYWxzCmFwcG5hbWUgPC0gIkZseWVyc19EYXRhIiAgIyBuYW1lIEkgYXNzaWduZWQgbXkgYXBwIGluIFR3aXR0ZXIKa2V5IDwtICJYWFhYWFhYWFgiICAjIEkgYW0gbm90IHNoYXJpbmcgbXkgYWN0dWFsIGNyZWRlbnRpYWxzCnNlY3JldCA8LSAiWFhYWFhYWFhYWFhYWFhYWFhYWCIgIyBBbnlvbmUgd2lzaGluZyB0byBkdXBsaWNhdGUgdGhpcyBjb2RlIGNhbiBzaWduIHVwIGZvciB0aGVpciBvd24gVHdpdHRlciBhcHAgLSBpdCBpcyBmcmVlCgojIGNyZWF0ZSB0b2tlbiBuYW1lZCAidHdpdHRlcl90b2tlbiIKdHdpdHRlcl90b2tlbiA8LSBjcmVhdGVfdG9rZW4oCiAgYXBwID0gYXBwbmFtZSwKICBjb25zdW1lcl9rZXkgPSBrZXksCiAgY29uc3VtZXJfc2VjcmV0ID0gc2VjcmV0KQpzYXZlUkRTKHR3aXR0ZXJfdG9rZW4sICJ+Ly5ydHdlZXQtb2F1dGgucmRzIikKCiMgU2NyYXBlIHR3ZWV0cwpyc3RhdHNfdHdlZXRzIDwtIHNlYXJjaF90d2VldHMyKHEgPSAiI0ZseWVycyBPUiAjRmx5ZXJzTmF0aW9uIE9SICNMRVRTR09GTFlFUlMiLCBuID0gMTUwMDAsIHBhcnNlID0gVFJVRSwgdHlwZT0ibWl4ZWQiKQoKIyBTYXZlIHR3ZWV0cyB0byBhIFJEYXRhIGZpbGUKc2F2ZShyc3RhdHNfdHdlZXRzLCBmaWxlPSJydHdlZXRzMjAxOE1NREQuUkRhdGEiKQoKYGBgCgpBZnRlciBhbGwgdGhlIGRhdGEgd2FzIHNjcmFwZWQgYW5kIHNhdmVkIHRvIFIgZGF0YXNldCBmaWxlcywgd2UgY2FuIHN0YXJ0IHdvcmtpbmcgd2l0aCB0aGUgZGF0YS4gRmlyc3QsIHNldmVyYWwgUiBsaWJyYXJpZXMgYXJlIG5lZWRlZC4gTm90ZSwgSSB0cmllZCB0byB1c2Ugb25seSBoaWdoIHF1YWxpdHkgbGlicmFyaWVzLCBzdWNoIGFzIHRob3NlIGRldmVsb3BlZCBieSB0aGUgUlN0dWRpbyBncm91cC4KCmBgYHtyfQpsaWJyYXJ5KHJ0d2VldCkgIyBmb3IgdXNlcnNfZGF0YSgpCmxpYnJhcnkodGlkeXZlcnNlKSAjIEluc3RlYWQgb2YganVzdCBnZ3Bsb3QyIGFuZCBkcGx5cgpsaWJyYXJ5KHRpZHl0ZXh0KSAgIyBGb3IgVHdpdHRlciB0ZXh0IG1hbmlwdWxhdGlvbgpsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKSAjIEJlY2F1c2UgSSB3YW50IHRvIHByaW50IHdpdGggRmx5ZXJzIGNvbG9ycyEKYGBgCgoKUmVhZCB0aGUgdGhyZWUgZGF0YXNldHMgYmFjayBpbnRvIG1lbW9yeSBhbmQgY29tYmluZSBpbnRvIG9uZSBtYXN0ZXIgZGF0YXNldC4KYGBge3J9CiMgTG9hZCB0aGUgUkRhdGEgZmlsZXMgdGhhdCB3ZXJlIHNhdmVkIGFmdGVyIHNjcmFwaW5nIFR3aXR0ZXIKbG9hZChmaWxlPSJydHdlZXRzMjAxODAzMTEuUkRhdGEiKQp0dzExIDwtIHJzdGF0c190d2VldHMKdXNlcnMxMSA8LSB1c2Vyc19kYXRhKHJzdGF0c190d2VldHMpCmxvYWQoZmlsZT0icnR3ZWV0czIwMTgwMzIwLlJEYXRhIikKdHcyMCA8LSByc3RhdHNfdHdlZXRzCnVzZXJzMjAgPC0gdXNlcnNfZGF0YShyc3RhdHNfdHdlZXRzKQpsb2FkKGZpbGU9InJ0d2VldHMyMDE4MDMyNi5SRGF0YSIpCgojIENvbWJpbmUgdGhlIHR3byBkYXRhc2V0cwp0dyA8LSBiaW5kX3Jvd3ModHcxMSwgdHcyMCwgcnN0YXRzX3R3ZWV0cykKdXNlcnMgPC0gdXNlcnNfZGF0YShyc3RhdHNfdHdlZXRzKQp1c2VycyA8LSBiaW5kX3Jvd3ModXNlcnMxMSwgdXNlcnMyMCwgdXNlcnMpCgojIERlbGV0ZSB0aGUgaW5kaXZ1YWwgZGF0YXNldHMgc2luY2Ugd2Ugbm93IGhhdmUgdGhlIG1hc3RlciBmaWxlCnJtKHR3MTEsIHVzZXJzMTEsIHR3MjAsIHVzZXJzMjAsIHJzdGF0c190d2VldHMpCgpgYGAKCkR1ZSB0byB0aGUgdGltZXMgd2hlbiB0aGUgZGF0YSB3YXMgc2NyYXBlZCwgdGhlcmUgYXJlIHNvbWUgb3ZlcmxhcHBpbmcgdGltZSBwZXJpb2RzIGluIHRoZSBkYXRhLiBGb3IgZXhhbXBsZSwgdGhlIHNjcmFwaW5nIHJ1biBvZiBNYXJjaCAyMCBjb2xsZWN0ZWQgc29tZSB0d2VldHMgdGhhdCB3ZXJlIHNjcmFwZWQgb24gTWFyY2ggMTEuIFdlIGRvIG5vdCB3YW50IHRoZXNlIGR1cGxpY2F0ZXMuCgpgYGB7cn0KdXNlcnMgPC0gdW5pcXVlKHVzZXJzKQpgYGAKCkFzIEkgd29ya2VkIHdpdGggdGhlIGRhdGEsIEkgZm91bmQgc29tZSB1c2VycyB0aGF0IEkgcmF0aGVyIGV4Y2x1ZGUgZnJvbSBteSBhbmFseXNpcy4gCmBgYHtyfQojIHVzZXJfaWQgPSAiMTk2MTg1MjciIGlzIHRoZSBQaGlsYWRlbHBoaWEgRmx5ZXJzLiBXZSBrbm93IHRoZSBGbHllcnMgdHdlZXQgZm9yIHRoZSB0ZWFtLgojIHVzZXJfaWQgPSAiNDcxMjY4NzEyIiBpcyBQSExGbHllcnNOYXRpb24uIFdlIGtub3cgdGhlIFBITEZseWVycyB0d2VldCBmb3IgdGhlIHRlYW0uCiMgdXNlcl9pZCA9ICIxNTQ2OTk0OTkiIGlzIHNwb3J0c3RhbGtwaGwgV2Uga25vdyB0aGUgc3BvcnRzdGFsa3BobCB0d2VldCBmb3IgdGhlIHRlYW0uCiMgdXNlcl9pZCA9ICI0MjYwMjk3NjUiIGlzIFhGSU5JVFlMaXZlIFdlIGtub3cgWEZJTklUWUxpdmUgdHdlZXQgZm9yIHRoZSB0ZWFtLgojIHVzZXJfaWQgPSAiMTkyNzY3MTkiIGFuZCAiMzIxMDM1NzQzIiBhcmUgbm90IHJlYWwgcGVvcGxlIGFuZCBkaXJlY3RzIHRvIG5hdWdodHkgd2Vic2l0ZXMKIyB1c2VyX2lkID0gIjQ5MzY1ODM4MSIgaXMgRmx5ZXJzTmF0aW9uLiBXZSBrbm93IHRoZSBGbHllcnMgdHdlZXQgZm9yIHRoZSB0ZWFtLgojIHVzZXJfaWQgPSAiOTM4MDcyOTY5NTUyODI2MzY4IiBpcyB0aGUgUGhpbGx5IFNwb3J0cyBOZXR3b3JrIEZseWVycy4KCnVzZXJzIDwtIHVzZXJzWyEodXNlcnMkdXNlcl9pZCAlaW4lIGMoIjE5NjE4NTI3IiwgIjQ3MTI2ODcxMiIsICIxNTQ2OTk0OTkiLCAiNDI2MDI5NzY1IiwgIjE5Mjc2NzE5IiwgIjQ5MzY1ODM4MSIsICI5MzgwNzI5Njk1NTI4MjYzNjgiLCAiMzIxMDM1NzQzIikpLF0KCmBgYAoKVGhlcmUgYXJlIG1hbnkgcGVvcGxlIHdobyB0d2VldCBhYm91dCB0aGUgRmx5ZXJzIHRoYXQgZG8gbm90IGxpdmUgbG9jYWxseS4gSSBkbyBub3QgZG91YnQgdGhleSBhcmUgZmFucywgYnV0IHRoZXkgYXJlIHByb2JhYmx5IG5vdCBhdHRlbmRpbmcgbWFueSBnYW1lcyAoZHVlIHRvIHRoZWlyIGdlb2dyYXBoaWMgbG9jYXRpb25zKS4gUGVyaGFwcyB0aGV5IHNob3VsZCBiZSBpbmNsdWRlZCBpbiBmdXJ0aGVyIGFuYWx5c2lzLCBidXQgZm9yIG5vdyBJIGFtIG9ubHkga2VlcGluZyB0d2VldHMgYnkgdXNlcnMgd2hvIGlkZW50aWZ5IHRoZWlyIGxvY2F0aW9uIGFzIFBlbm5zeWx2YW5pYSwgTmV3IEplcnNleSwgb3IgRGVsYXdhcmUuIFRoaXMgcmVzdHJpY3Rpb24gbWF5IGxvc2UgYSBmZXcgbG9jYWwgcGVvcGxlIHdobyBkbyBub3QgaWRlbnRpZnkgdGhlaXIgbG9jYXRpb24gYXMgbG9jYWwsIGJ1dCBpdCBpcyB1bmF2b2lkYWJsZS4KCmBgYHtyfQojIE9ubHkgYW5hbHl6ZSAibG9jYWwiIHR3ZWV0ZXJzIC0gbG9jYXRpb24gaWRlbnRpZmllZCBhcyBQQSwgTkosIG9yIERFCnNlbGVjdCA8LSBncmVwbCgiUGhpbCIsIHVzZXJzJGxvY2F0aW9uLCBpZ25vcmUuY2FzZSA9IFRSVUUpIHwgZ3JlcGwoIlBBIiwgdXNlcnMkbG9jYXRpb24sIGlnbm9yZS5jYXNlID0gRkFMU0UpIHwgZ3JlcGwoIk5KIiwgdXNlcnMkbG9jYXRpb24sIGlnbm9yZS5jYXNlID0gRkFMU0UpIHwgZ3JlcGwoIkRFIiwgdXNlcnMkbG9jYXRpb24sIGlnbm9yZS5jYXNlID0gRkFMU0UpCgp1c2VycyA8LSB1c2Vyc1tzZWxlY3QsXQpybShzZWxlY3QpCgpgYGAKClR3aXR0ZXIgYWxsb3dzIGNlcnRhaW4gdXNlcnMgdG8gYmUgdmVyaWZpZWQuIFZlcmlmaWVkIGFjY291bnRzIGluY2x1ZGUgcHJvZmVzc2lvbmFsIHJhZGlvLCBUViwgYW5kIG5ld3Mgc3RhdGlvbnMgKGUuZy4gTkJDKSwgYW5kIHNvbWUgY2VsZWJyaXR5IG5hbWVzIChhIHNwb3QgY2hlY2sgaWRlbnRpZmllcyB0aGUgc2VsZWN0ZWQgYXMgYnJvYWRjYXN0c2VycyBhbmQgcmVwb3J0ZXJzKS4gSSBhbSBzdXJlIHRoZXkgYXJlIHF1aXRlIGhlbHBmdWwgaW4gYm9vc3RpbmcgRmx5ZXJzIHRpY2tldCBzYWxlcywgYnV0IHRoZW4gdGhlcmUgaXMgbm8gcmVhc29uIHRvIGtlZXAgdGhlbSBpbiB0aGUgYW5hbHlzaXMuIFdlIGtub3cgdGhleSBhbHJlYWR5IGFyZSB3b3JraW5nIG9uIGJvb3N0aW5nIHNhbGVzLiBGb3IgdGhlIHB1cnBvc2VzIG9mIHRoaXMgc3R1ZHksIHRoZSB2ZXJpZmllZCBhY2NvdW50cyB3aWxsIG5vdCBiZSBjb25zaWRlcmVkLgoKYGBge3J9CnVzZXJzIDwtIHVzZXJzWyF1c2VycyR2ZXJpZmllZCxdICMgU2F2ZSBvbmx5IG5vbnZlcmlmaWVkIGFjY291bnRzCmBgYAoKSSBhbSBzdXJlIHRoZSByZW1haW5pbmcgdXNlcnMgaW5jbHVkZSBhIGZldyBib3RzIHRoYXQgSSBtaWdodCBoYXZlIG1pc3NlZCwgYnV0IHRoZSByZXN1bHRpbmcgZ3JvdXAgaXMgYSBzdGFydCBmb3IgdGhpcyBzdHVkeS4gCgpUd2l0dGVyIGRhdGEgaXMgZm9ybWF0dGVkIHNvIHRoYXQgaXQgY2FuIGJlIHNhdmVkIGluIHR3byBkYXRhc2V0cy4gVGhlIHVzZXJzIGRhdGEgYW5kIHRoZSB0d2VldHMgZGF0YS4gVGhlIHVzZXJzIGRhdGEgaW5jbHVkZXMgdGhlIGZvbGxvd2luZyBmZWF0dXJlczogYHIgbmFtZXModXNlcnMpYC4gV2UgbmVlZCB0byBzZWxlY3QgdGhlIHR3ZWV0cyBmcm9tIG9ubHkgdGhlIHVzZXJzIHRoYXQgcmVtYWluIGluIHRoZSB1c2VycyBkYXRhc2V0LgoKYGBge3J9CiMgVGhlcmUgYXJlIGEgZmV3IHR3ZWV0cyByZXBlYXRlZCBkdWUgdG8gb3ZlcmxhcHBpbmcgZGF0ZXMuIFNhdmUgb25seSBvbmUgaW5zdGFuY2Ugb2YgZWFjaC4KdHcgPC0gdW5pcXVlKHR3KQoKIyBOb3cgc2VsZWN0IG9ubHkgdGhlIHR3ZWV0cyB0aGF0IGJlbG9uZyB0byB0aGUgcmVtYWluaW5nIHVzZXJfaWRzCnR3IDwtIHR3W3R3JHVzZXJfaWQgJWluJSB1c2VycyR1c2VyX2lkLF0KCiMgU2F2ZSBvbmx5IHRoZSB0d2VldHMgdGhhdCBhcmUgaW4gRW5nbGlzaCAtIGF0IHRoaXMgdGltZQp0dyA8LSB0d1t0dyRsYW5nPT0iZW4iLF0KCmBgYAoKVGhlIHR3ZWV0cyBkYXRhc2V0IGhhcyB0aGUgZm9sbG93aW5nIGZlYXR1cmVzOiBgciBuYW1lcyh0dylgLgoKVGhlIGNvbGxlY3RlZCB0d2VldHMgc3BhbiB0aGUgdGltZSBmcm9tIGByIG1pbih0dyRjcmVhdGVkX2F0KWAgdG8gYHIgbWF4KHR3JGNyZWF0ZWRfYXQpYC4gVGhlIHJlc3VsdGluZyBjb3VudCBvZiB1c2VycyB3aG8gdHdlZXRlZCBpcyBgciBsZW5ndGgodW5pcXVlKHR3JHVzZXJfaWQpKWAgYW5kIGByIG5yb3codHcpYCB0d2VldHMsIGFsdGhvdWdoIG1hbnkgYXJlIHJldHdlZXRzLgoKTm93IHdlIGNhbiBhZGRyZXNzIHRoZSBmaXJzdCBkYXRhIHJlc2VhcmNoIHF1ZXN0aW9uLgojIyAxLiBXaGF0IFR3aXR0ZXIgdXNlciBoYWQgdGhlIG1vc3QgdHdlZXRzIG92ZXIgdGhlIHNlbGVjdGVkIHRpbWUgcGVyaW9kPwoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnR3ICU+JQogIGNvdW50KHVzZXJfaWQsIHNvcnQgPSBUUlVFKSAlPiUKICBmaWx0ZXIobiA+IDYwKSAlPiUKICBpbm5lcl9qb2luKGRpc3RpbmN0KHVzZXJzWyxjKDE6MyldLCB1c2VyX2lkLCAua2VlcF9hbGwgPSBUUlVFKSkgJT4lCiMgIGdncGxvdChhZXMoeCA9IHJlb3JkZXIobmFtZSwgbiksIHkgPSBuKSkgKyAjIElmIEkgd2FudGVkIHRvIHNob3cgYWN0dWFsIG5hbWVzCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih1c2VyX2lkLCBuKSwgeSA9IG4pKSArCiAgZ2VvbV9jb2woYWVzKGZpbGwgPSBuKSkgKwogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGU9Ik9yYW5nZXMiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgeGxhYigiVXNlcl9JZHMiKSArIHlsYWIoIk51bWJlciBvZiBUd2VldHMiKSArCiAgbGFicyh0aXRsZT0iVXNlcnMgd2l0aCBNb3N0IFR3ZWV0cyIsIHN1YnRpdGxlPSJJbiBTaGFkZXMgb2YgT3JhbmdlISIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpJZiBJIHdhcyBkb2luZyB0aGlzIHRvIGdldCB0aGUgYWN0dWFsIG5hbWVzIG9mIHRoZSBUd2l0dGVyIHVzZXJzLCBJIGNvdWxkIGhhdmUgbGlzdGVkIHRoZSB1c2VyIG5hbWVzLCBhcyB0aGV5IGFyZSBhdmFpbGFibGUgaW4gdGhlIGRhdGFzZXQsIGJ1dCBzaW5jZSB0aGlzIGV4ZXJjaXNlIGlzIG9ubHkgYWNhZGVtaWMsIEkgcHJpbnQgdGhlIHVzZXJfaWQgbnVtYmVycywgdG8gbWFpbnRhaW4gc29tZSBhbm9ueW1pdHkuCgpOZXh0IGxldCB1cyBsb29rIGF0IHRoZSBudW1iZXIgb2YgZm9sbG93ZXJzIHRoZSB1c2VycyBoYXZlLiBXZSBjb3VsZCBjaG9vc2UgdG8gc2VlIGFsbCByZWdhcmRsZXNzIG9mIGZvbGxvd2Vycy4gVmlld2luZyBhbGwgdXNlcnMgd2l0aCB0aGVpciBmb2xsb3dlcnMgb3ZlcmxvYWRzIHRoZSBncmFwaCwgYW5kIEkgZG8gbm90IHdhbnQganVzdCBhIGxpc3Qgb2YgbmFtZXMuIEEgc3BvdCBjaGVjayBzaG93cyBhIGxhcmdlIG51bWJlciBvZiB1c2VycyBoYXZlIG1vcmUgdGhhbiAyMCwwMDAgZm9sbG93ZXJzLiBTcGVjaWZpY2FsbHksIGByIG5yb3codXNlcnNbdXNlcnMkZm9sbG93ZXJzX2NvdW50ID4gMjAwMDAsXSlgIGhhdmUgbW9yZSB0aGFuIDIwLDAwMCBmb2xsb3dlcnMuIElmIGl0IHR1cm5zIG91dCB0aGF0IHRoZXNlIHVzZXJzIGFyZSBub3QgcmVhbCBwZW9wbGUsIHdlIGNvdWxkIHJlbW92ZSB0aGVtIGZyb20gdGhlIHVzZXJzIGRhdGFzZXQgb3IgbG9vayBhdCB1c2VycyB3aXRoIGZvbGxvd2VycyBiZXR3ZWVuIDEwMCBhbmQgNTAwLCBvciB3aGF0ZXZlciBzcGFuIHdlIHRoaW5rIGFwcHJvcHJpYXRlLgoKIyMgMi4gT2YgdGhvc2Ugd2hvIHR3ZWV0ZWQsIHdobyBoYWQgdGhlIG1vc3QgZm9sbG93ZXJzPwoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnVuaXF1ZSh1c2Vyc1ssYygxLDIsMyw4KV0pICU+JQogIGZpbHRlcihmb2xsb3dlcnNfY291bnQgPiAyMDAwMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih1c2VyX2lkLCBmb2xsb3dlcnNfY291bnQpLCB5ID0gZm9sbG93ZXJzX2NvdW50KSkgKwogIGdlb21fY29sKGFlcyhmaWxsID0gZm9sbG93ZXJzX2NvdW50KSkgKwogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGU9Ik9yYW5nZXMiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgeGxhYigiVXNlcl9JZHMiKSArIHlsYWIoIk51bWJlciBvZiBGb2xsb3dlcnMiKSArCiAgbGFicyh0aXRsZT0iVXNlcnMgd2hvIFR3ZWV0ZWQgI0ZseWVycyB3aXRoIE1vc3QgRm9sbG93ZXJzIiwgc3VidGl0bGU9Ik1vcmUgdGhhbiAyMCwwMDAgZm9sbG93ZXJzIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCkFnYWluLCBJIGNvdWxkIGhhdmUgcHJpbnRlZCB0aGUgZ3JhcGggd2l0aCB1c2VyIG5hbWVzIGluc3RlYWQgb2YgdXNlciBpZHMsIGJ1dCBJIGFtIHByb3RlY3RpbmcgaWRlbnRpdGllcyBmb3IgdGhpcyBpbml0aWFsIHN0dWR5LgoKRmluYWxseSwgbGV0IHVzIGZpbmQgdGhlIG9yaWdpbmF0b3JzIG9mIHR3ZWV0cyB0aGF0IHdlcmUgaGlnaGx5IHJldHdlZXRlZC4gSXQgaXMgcG9zc2libGUgdGhhdCBvdGhlcnMgcmV0d2VldGVkIHRoZSByZXR3ZWV0ZWQgdGV4dHMsIGJ1dCB0aGUgZm9sbG93aW5nIGNoYXJ0IGNvbnNpZGVycyBvbmx5IHRoZSBvcmlnaW5hbCBhdXRob3JzIG9mIHRoZSB0d2VldHMuCgojIyAzLiBPZiB0aG9zZSB3aG8gdHdlZXRlZCwgV2hvIGhhZCB0aGUgbW9zdCByZXR3ZWV0ZWQgdGV4dD8KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0d1shdHckaXNfcmV0d2VldCxjKDMsMTMpXSAgJT4lCiAgZ3JvdXBfYnkodXNlcl9pZCkgJT4lCiAgc3VtbWFyaXNlKG49bWF4KHJldHdlZXRfY291bnQpKSAlPiUKICBmaWx0ZXIobiA+IDEwKSAlPiUKICBpbm5lcl9qb2luKGRpc3RpbmN0KHVzZXJzWyxjKDEsMiwzLDgsMTMpXSwgdXNlcl9pZCwgLmtlZXBfYWxsID0gVFJVRSkpICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIodXNlcl9pZCwgbiksIHkgPSBuKSkgKwogIGdlb21fY29sKGFlcyhmaWxsID0gbikpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlPSJPcmFuZ2VzIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHhsYWIoIlVzZXJfSWRzIikgKyB5bGFiKCJSZXR3ZWV0IENvdW50IikgKwogIGxhYnModGl0bGU9IlVzZXJzIHdobyBoYWQgdGhlIE1vc3QgUmV0d2VldHMiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKVGhpcyBwb3N0IGhhcyBleGFtaW5lZCB0aGUgVHdpdHRlciB1c2VycyB0aGF0IHR3ZWV0IGFib3V0IHRoZSBQaGlsbHkgRmx5ZXJzLiBJbiB0aGUgbmV4dCBwb3N0LCB3ZSB3aWxsIGxvb2sgYXQgdGhlIHR3ZWV0cyB0aGVtc2VsdmVzLiBJIHdpbGwgcG9zdCBhZ2FpbiBzb29uLg==