Contexto

El análisis de redes sociales que trabajaremos en este espacio es también conocido como minado de redes sociales es un conjunto de técnicas de procesamiento de datos obtenidos de las plataformas digitales de redes sociales. Los datos con los que se trabaja en esta área suelen no estar estructurados.

Lo más importante en este tipo de análisis es determinar el objetivo perseguido. Debido a la cantidad de información ofrecida, un estudio sin derrotero puede resultar inútil o meramente exploratorio. Estos objetivos se pueden traducir en indicadores clave o KPI por sus siglas en inglés (key performance indicators). Por ejemplo: el número de seguidores, likes y veces que se compartió una publicación pueden ser una medidad del impacto publicitario de cierta campaña.

Técnicas de procesamiento del lenguaje natural o de análisis de sentimiento pueden servir para conocer las temáticas de las que se habla o la idea que tiene cierto de grupo de usuarios sobre un tema en específico.

Análisis

Flujo del análisis

El flujo del análisis se puede entender de la siguiente manera:

  1. Obtención de los datos

  2. Procesamiento y normalización de los datos

  3. Análisis de los datos

  4. Extracción de conclusiones

Requerimientos

  • Un objetivo que le dará forma al estudio y permitirá establecer KPIs

  • Un conjunto de datos: Estos pueden ser obtenidos de diversas formas, generalmente suelen ser estas dos: diréctamente usando la API oficial o realizando un proceso de web crawling y scraping. No es necesario, aunque sí más eficiente, realizar una obtención automática.

  • Una plataforma de análisis y procesamiento de la información.

Obtención de los datos

Conectándonos a Twitter

Para conectarnos con esta API vamos a necesitar de una cuenta

#install.packages("twitteR")
library(twitteR)

api_key <- "XXIngresa tu API KEYXX"
api_secret_token <- "XXIngresa tu API SECRET KEYXX"
token <- "XXIngresa tu Acces tokenXX"
token_secret <- "XXIngresa tu Access Token secretXX"

# Conexión con la API
setup_twitter_oauth(api_key, api_secret_token, token, token_secret)

En este primer código, se establecen las credenciales y se hace referencia a un servicio de almacenado de twitts en caché.

Obtención de tweets por cuenta

En el siguiente ejercicio obtendremos los útlimos 10 tweets de la cuenta de la UAM Cuajimalpa.


help(getUser)

UsuarioTwitter <- getUser("mni_cdmx")

tweets <- userTimeline(UsuarioTwitter, n = 10) #Obtiene los últimos 10 tweets de la línea del tiempo de este usuariro.


class(tweets[[1]])

tweets[[1]]$getClass()

tweets[[1]]$getFavoriteCount()

tweets[[2]]$getFavoriteCount()

tweets[[2]]$favoriteCount

tweets[[3]]$created

tweetsFirstDF <- twListToDF(tweets)

View(tweetsFirstDF)

Obtención de tweets por tema

En el siguiente ejercicio obtendremos 500 tweets por palabras clave en español:

terminos_busqueda <- "#lgbtiq OR mudar OR mudarme"
tweets <- searchTwitter(terminos_busqueda, n = 500, lang = "es")

Procesamiento y normalización de los datos

De listas a DataFrames

Lo siguiente que haremos será guardar los tweets extraídos como un dataframe

tweets.df <- twListToDF(tweets)
summary(tweets.df)
View(tweets.df)

Convirtiendo en UTF-8

Ahora haremos una breve limpieza a los datos: convertiremos los tweets a una codificación ‘UTF-8’

tweets.df$text <- sapply(tweets.df$text, function(x) iconv(x, to='UTF-8'))

Análisis de los datos

Ahora veamos los datos con los que contamos y comprendamos sus diferentes aspectos.

Graficado por tiempo de publicación

Una primer pregunta de la que vamos a partir será: ¿cuándo fueron publicados estos tweets? Para dar respuesta a esta interrogante, veremos un histograma con el tiempo en el eje x

library(ggplot2)
ggplot(data= tweets.df, aes(x = created)) + geom_histogram(aes(fill= ..count..)) + theme(legend.position = "none") + xlab("Fecha") + ylab("Número de twits") + scale_fill_gradient(low = "midnightblue", high = "aquamarine4")

Graficado por servicio

Una segunda pregunta sería ¿de qué servicios se han publicado los twitts? Tratemos de dar respuesta a esta interrogante a partir de las fuentes de los twits. Para esto vamos a usar una función de limpieza del servicio Identificación de servicio

help(head)
head(tweets.df$statusSource, n=5)
tweets.df$servicio <- sapply(tweets.df$statusSource, identificaServicio)

View(tweets.df)
unique(tweets.df$servicio)

ggplot(tweets.df, aes(servicio)) + geom_bar(fill="aquamarine4") + theme(legend.position = 'none', axis.title.x = element_blank(), axis.text.x = element_text(angle = 45, hjust=1)) + ylab("Número de twits") + ggtitle("Twits por servicio")

Análisis textual

Ahora buscaremos interpretar los datos obtenidos a partir de un análisis textual. Este lo haremos, en primera instancia, extrayendo las cuentas mencionadas en los twits.

library(stringr)
library(tm)
library(wordcloud)

unique(sapply(tweets.df$text, function(twit) str_extract_all(twit, "@\\w+")))

cuentasMencionadas <- str_extract_all(tweets.df$text, "@\\w+")
corpusNombres <- Corpus(VectorSource(cuentasMencionadas))
View(corpusNombres)

Habiendo generado un corpus con las cuentas extraídas ahora lo visualizaremos con una nube de palabras

pal <- brewer.pal(9,"YlGnBu")
pal <- pal[-(1:4)]

set.seed(42)
wordcloud(words = corpusNombres, scale = c(1.5, 0.75), max.words = 50, random.order = FALSE, rot.per = 0.35, use.r.layout = TRUE, colors = pal)

Otra manera de hacerlo y de conocer la frecuencia de nuestros datos es la siguiente

dtm <- TermDocumentMatrix(corpusNombres) 
matrix <- as.matrix(dtm) 
cuentas <- sort(rowSums(matrix),decreasing=TRUE) 
df <- data.frame(cuenta = names(cuentas),freq=cuentas)
df

Exportando los twits

Podemos exportar nuestro dataframe al formato que querramos, para mapearlos usaremos un formato csv

write.csv2(tweets.df, file="lgbtiqTweets.csv")

Funciones de apoyo

La función unique

Con la función unique, podemos obtener los valores sin repetir de un cierto conjunto de datos.

unique(sort(tweets.df$statusSource))

Identificación de servicio

En el siguiente bloque de código crearemos una función que permita identificar el servicio y limpiar la salida entregando una sola palabra

library("sjmisc")
identificaServicio <- function(fuenteDelServicio){
  if(str_contains(fuenteDelServicio, "Android")){
    return("android")
  } else if(str_contains(fuenteDelServicio, "iPad") || str_contains(fuenteDelServicio, "iPhone")){
    return("iOS")
  } else if(str_contains(fuenteDelServicio, "Web")){
    return("web")
  } else if(str_contains(fuenteDelServicio, "Bots")){
    return("bot")
  } else{
    return("Otro")
  }
}
sapply(tweets.df$statusSource, identificaServicio)

Referencias

LS0tDQp0aXRsZTogIkFuw6FsaXNpcyBkZSByZWRlcyBzb2NpYWxlcyBjb24gUiB8IFByaW1lcmEgcGFydGUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIENvbnRleHRvDQoNCkVsIGFuw6FsaXNpcyBkZSByZWRlcyBzb2NpYWxlcyBxdWUgdHJhYmFqYXJlbW9zIGVuIGVzdGUgZXNwYWNpbyBlcyB0YW1iacOpbiBjb25vY2lkbyBjb21vIG1pbmFkbyBkZSByZWRlcyBzb2NpYWxlcyBlcyB1biBjb25qdW50byBkZSB0w6ljbmljYXMgZGUgcHJvY2VzYW1pZW50byBkZSBkYXRvcyBvYnRlbmlkb3MgZGUgbGFzIHBsYXRhZm9ybWFzIGRpZ2l0YWxlcyBkZSByZWRlcyBzb2NpYWxlcy4gTG9zIGRhdG9zIGNvbiBsb3MgcXVlIHNlIHRyYWJhamEgZW4gZXN0YSDDoXJlYSBzdWVsZW4gbm8gZXN0YXIgZXN0cnVjdHVyYWRvcy4NCg0KTG8gbcOhcyBpbXBvcnRhbnRlIGVuIGVzdGUgdGlwbyBkZSBhbsOhbGlzaXMgZXMgZGV0ZXJtaW5hciBlbCBvYmpldGl2byBwZXJzZWd1aWRvLiBEZWJpZG8gYSBsYSBjYW50aWRhZCBkZSBpbmZvcm1hY2nDs24gb2ZyZWNpZGEsIHVuIGVzdHVkaW8gc2luIGRlcnJvdGVybyBwdWVkZSByZXN1bHRhciBpbsO6dGlsIG8gbWVyYW1lbnRlIGV4cGxvcmF0b3Jpby4gRXN0b3Mgb2JqZXRpdm9zIHNlIHB1ZWRlbiB0cmFkdWNpciBlbiBpbmRpY2Fkb3JlcyBjbGF2ZSBvIEtQSSBwb3Igc3VzIHNpZ2xhcyBlbiBpbmdsw6lzICgqa2V5IHBlcmZvcm1hbmNlIGluZGljYXRvcnMqKS4gUG9yIGVqZW1wbG86IGVsIG7Dum1lcm8gZGUgc2VndWlkb3JlcywgbGlrZXMgeSB2ZWNlcyBxdWUgc2UgY29tcGFydGnDsyB1bmEgcHVibGljYWNpw7NuIHB1ZWRlbiBzZXIgdW5hIG1lZGlkYWQgZGVsIGltcGFjdG8gcHVibGljaXRhcmlvIGRlIGNpZXJ0YSBjYW1wYcOxYS4NCg0KVMOpY25pY2FzIGRlIHByb2Nlc2FtaWVudG8gZGVsIGxlbmd1YWplIG5hdHVyYWwgbyBkZSBhbsOhbGlzaXMgZGUgc2VudGltaWVudG8gcHVlZGVuIHNlcnZpciBwYXJhIGNvbm9jZXIgbGFzIHRlbcOhdGljYXMgZGUgbGFzIHF1ZSBzZSBoYWJsYSBvIGxhIGlkZWEgcXVlIHRpZW5lIGNpZXJ0byBkZSBncnVwbyBkZSB1c3VhcmlvcyBzb2JyZSB1biB0ZW1hIGVuIGVzcGVjw61maWNvLg0KDQojIEFuw6FsaXNpcyB7LnRhYnNldH0NCg0KIyMgRmx1am8gZGVsIGFuw6FsaXNpcw0KDQpFbCBmbHVqbyBkZWwgYW7DoWxpc2lzIHNlIHB1ZWRlIGVudGVuZGVyIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCjEuICBPYnRlbmNpw7NuIGRlIGxvcyBkYXRvcw0KDQoyLiAgUHJvY2VzYW1pZW50byB5IG5vcm1hbGl6YWNpw7NuIGRlIGxvcyBkYXRvcw0KDQozLiAgQW7DoWxpc2lzIGRlIGxvcyBkYXRvcw0KDQo0LiAgRXh0cmFjY2nDs24gZGUgY29uY2x1c2lvbmVzDQoNCiMjIFJlcXVlcmltaWVudG9zDQoNCi0gICBVbiBvYmpldGl2byBxdWUgbGUgZGFyw6EgZm9ybWEgYWwgZXN0dWRpbyB5IHBlcm1pdGlyw6EgZXN0YWJsZWNlciBLUElzDQoNCi0gICBVbiBjb25qdW50byBkZSBkYXRvczogRXN0b3MgcHVlZGVuIHNlciBvYnRlbmlkb3MgZGUgZGl2ZXJzYXMgZm9ybWFzLCBnZW5lcmFsbWVudGUgc3VlbGVuIHNlciBlc3RhcyBkb3M6IGRpcsOpY3RhbWVudGUgdXNhbmRvIGxhIEFQSSBvZmljaWFsIG8gcmVhbGl6YW5kbyB1biBwcm9jZXNvIGRlIHdlYiBjcmF3bGluZyB5IHNjcmFwaW5nLiBObyBlcyBuZWNlc2FyaW8sIGF1bnF1ZSBzw60gbcOhcyBlZmljaWVudGUsIHJlYWxpemFyIHVuYSBvYnRlbmNpw7NuIGF1dG9tw6F0aWNhLg0KDQotICAgVW5hIHBsYXRhZm9ybWEgZGUgYW7DoWxpc2lzIHkgcHJvY2VzYW1pZW50byBkZSBsYSBpbmZvcm1hY2nDs24uDQoNCiMjIE9idGVuY2nDs24gZGUgbG9zIGRhdG9zDQoNCiMjIyBDb25lY3TDoW5kb25vcyBhIFR3aXR0ZXINCg0KUGFyYSBjb25lY3Rhcm5vcyBjb24gZXN0YSBBUEkgdmFtb3MgYSBuZWNlc2l0YXIgZGUgdW5hIGN1ZW50YSANCg0KYGBge3IgUHJpbWVyIGNvbmV4acOzbn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJ0d2l0dGVSIikNCmxpYnJhcnkodHdpdHRlUikNCg0KYXBpX2tleSA8LSAiWFhJbmdyZXNhIHR1IEFQSSBLRVlYWCINCmFwaV9zZWNyZXRfdG9rZW4gPC0gIlhYSW5ncmVzYSB0dSBBUEkgU0VDUkVUIEtFWVhYIg0KdG9rZW4gPC0gIlhYSW5ncmVzYSB0dSBBY2NlcyB0b2tlblhYIg0KdG9rZW5fc2VjcmV0IDwtICJYWEluZ3Jlc2EgdHUgQWNjZXNzIFRva2VuIHNlY3JldFhYIg0KDQojIENvbmV4acOzbiBjb24gbGEgQVBJDQpzZXR1cF90d2l0dGVyX29hdXRoKGFwaV9rZXksIGFwaV9zZWNyZXRfdG9rZW4sIHRva2VuLCB0b2tlbl9zZWNyZXQpDQoNCg0KYGBgDQoNCkVuIGVzdGUgcHJpbWVyIGPDs2RpZ28sIHNlIGVzdGFibGVjZW4gbGFzIGNyZWRlbmNpYWxlcyB5IHNlIGhhY2UgcmVmZXJlbmNpYSBhIHVuIHNlcnZpY2lvIGRlIGFsbWFjZW5hZG8gZGUgdHdpdHRzIGVuIGNhY2jDqS4NCg0KIyMjIE9idGVuY2nDs24gZGUgdHdlZXRzIHBvciBjdWVudGENCkVuIGVsIHNpZ3VpZW50ZSBlamVyY2ljaW8gb2J0ZW5kcmVtb3MgbG9zIMO6dGxpbW9zIDEwIHR3ZWV0cyBkZSBsYSBjdWVudGEgZGUgbGEgVUFNIEN1YWppbWFscGEuDQoNCmBgYHtyIDEwIHR3ZWV0cyBkZSBsYSBVQU0gQ3VhamltYWxwYX0NCg0KaGVscChnZXRVc2VyKQ0KDQpVc3VhcmlvVHdpdHRlciA8LSBnZXRVc2VyKCJtbmlfY2RteCIpDQoNCnR3ZWV0cyA8LSB1c2VyVGltZWxpbmUoVXN1YXJpb1R3aXR0ZXIsIG4gPSAxMCkgI09idGllbmUgbG9zIMO6bHRpbW9zIDEwIHR3ZWV0cyBkZSBsYSBsw61uZWEgZGVsIHRpZW1wbyBkZSBlc3RlIHVzdWFyaXJvLg0KDQoNCmNsYXNzKHR3ZWV0c1tbMV1dKQ0KDQp0d2VldHNbWzFdXSRnZXRDbGFzcygpDQoNCnR3ZWV0c1tbMV1dJGdldEZhdm9yaXRlQ291bnQoKQ0KDQp0d2VldHNbWzJdXSRnZXRGYXZvcml0ZUNvdW50KCkNCg0KdHdlZXRzW1syXV0kZmF2b3JpdGVDb3VudA0KDQp0d2VldHNbWzNdXSRjcmVhdGVkDQoNCnR3ZWV0c0ZpcnN0REYgPC0gdHdMaXN0VG9ERih0d2VldHMpDQoNClZpZXcodHdlZXRzRmlyc3RERikNCmBgYA0KIyMjIE9idGVuY2nDs24gZGUgdHdlZXRzIHBvciB0ZW1hDQoNCkVuIGVsIHNpZ3VpZW50ZSBlamVyY2ljaW8gb2J0ZW5kcmVtb3MgNTAwIHR3ZWV0cyBwb3IgcGFsYWJyYXMgY2xhdmUgZW4gZXNwYcOxb2w6DQoNCmBgYHtyfQ0KdGVybWlub3NfYnVzcXVlZGEgPC0gIiNsZ2J0aXEgT1IgbXVkYXIgT1IgbXVkYXJtZSINCnR3ZWV0cyA8LSBzZWFyY2hUd2l0dGVyKHRlcm1pbm9zX2J1c3F1ZWRhLCBuID0gNTAwLCBsYW5nID0gImVzIikNCg0KDQoNCmBgYA0KDQojIyBQcm9jZXNhbWllbnRvIHkgbm9ybWFsaXphY2nDs24gZGUgbG9zIGRhdG9zDQoNCiMjIyBEZSBsaXN0YXMgYSBEYXRhRnJhbWVzDQpMbyBzaWd1aWVudGUgcXVlIGhhcmVtb3Mgc2Vyw6EgZ3VhcmRhciBsb3MgdHdlZXRzIGV4dHJhw61kb3MgY29tbyB1biBkYXRhZnJhbWUNCg0KYGBge3J9DQp0d2VldHMuZGYgPC0gdHdMaXN0VG9ERih0d2VldHMpDQpzdW1tYXJ5KHR3ZWV0cy5kZikNClZpZXcodHdlZXRzLmRmKQ0KYGBgDQoNCiMjIyBDb252aXJ0aWVuZG8gZW4gVVRGLTgNCkFob3JhIGhhcmVtb3MgdW5hIGJyZXZlIGxpbXBpZXphIGEgbG9zIGRhdG9zOiBjb252ZXJ0aXJlbW9zIGxvcyB0d2VldHMgYSB1bmEgY29kaWZpY2FjacOzbiAnVVRGLTgnDQoNCmBgYHtyfQ0KdHdlZXRzLmRmJHRleHQgPC0gc2FwcGx5KHR3ZWV0cy5kZiR0ZXh0LCBmdW5jdGlvbih4KSBpY29udih4LCB0bz0nVVRGLTgnKSkNCmBgYA0KDQojIyBBbsOhbGlzaXMgZGUgbG9zIGRhdG9zDQoNCkFob3JhIHZlYW1vcyBsb3MgZGF0b3MgY29uIGxvcyBxdWUgY29udGFtb3MgeSBjb21wcmVuZGFtb3Mgc3VzIGRpZmVyZW50ZXMgYXNwZWN0b3MuIA0KDQojIyMgR3JhZmljYWRvIHBvciB0aWVtcG8gZGUgcHVibGljYWNpw7NuDQpVbmEgcHJpbWVyIHByZWd1bnRhIGRlIGxhIHF1ZSB2YW1vcyBhIHBhcnRpciBzZXLDoTogwr9jdcOhbmRvIGZ1ZXJvbiBwdWJsaWNhZG9zIGVzdG9zIHR3ZWV0cz8gDQpQYXJhIGRhciByZXNwdWVzdGEgYSBlc3RhIGludGVycm9nYW50ZSwgdmVyZW1vcyB1biBoaXN0b2dyYW1hIGNvbiBlbCB0aWVtcG8gZW4gZWwgZWplIHgNCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpnZ3Bsb3QoZGF0YT0gdHdlZXRzLmRmLCBhZXMoeCA9IGNyZWF0ZWQpKSArIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsPSAuLmNvdW50Li4pKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyB4bGFiKCJGZWNoYSIpICsgeWxhYigiTsO6bWVybyBkZSB0d2l0cyIpICsgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAibWlkbmlnaHRibHVlIiwgaGlnaCA9ICJhcXVhbWFyaW5lNCIpDQpgYGANCg0KIyMjIEdyYWZpY2FkbyBwb3Igc2VydmljaW8NClVuYSBzZWd1bmRhIHByZWd1bnRhIHNlcsOtYSDCv2RlIHF1w6kgc2VydmljaW9zIHNlIGhhbiBwdWJsaWNhZG8gbG9zIHR3aXR0cz8gVHJhdGVtb3MgZGUgZGFyIHJlc3B1ZXN0YSBhIGVzdGEgaW50ZXJyb2dhbnRlIGEgcGFydGlyIGRlIGxhcyBmdWVudGVzIGRlIGxvcyB0d2l0cy4gUGFyYSBlc3RvIHZhbW9zIGEgdXNhciB1bmEgZnVuY2nDs24gZGUgbGltcGllemEgZGVsIHNlcnZpY2lvIA0KW0lkZW50aWZpY2FjacOzbiBkZSBzZXJ2aWNpb10NCg0KYGBge3J9DQpoZWxwKGhlYWQpDQpoZWFkKHR3ZWV0cy5kZiRzdGF0dXNTb3VyY2UsIG49NSkNCnR3ZWV0cy5kZiRzZXJ2aWNpbyA8LSBzYXBwbHkodHdlZXRzLmRmJHN0YXR1c1NvdXJjZSwgaWRlbnRpZmljYVNlcnZpY2lvKQ0KDQpWaWV3KHR3ZWV0cy5kZikNCnVuaXF1ZSh0d2VldHMuZGYkc2VydmljaW8pDQoNCmdncGxvdCh0d2VldHMuZGYsIGFlcyhzZXJ2aWNpbykpICsgZ2VvbV9iYXIoZmlsbD0iYXF1YW1hcmluZTQiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJywgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdD0xKSkgKyB5bGFiKCJOw7ptZXJvIGRlIHR3aXRzIikgKyBnZ3RpdGxlKCJUd2l0cyBwb3Igc2VydmljaW8iKQ0KYGBgDQojIyMgQW7DoWxpc2lzIHRleHR1YWwNCg0KQWhvcmEgYnVzY2FyZW1vcyBpbnRlcnByZXRhciBsb3MgZGF0b3Mgb2J0ZW5pZG9zIGEgcGFydGlyIGRlIHVuIGFuw6FsaXNpcyB0ZXh0dWFsLiBFc3RlIGxvIGhhcmVtb3MsIGVuIHByaW1lcmEgaW5zdGFuY2lhLCBleHRyYXllbmRvIGxhcyBjdWVudGFzIG1lbmNpb25hZGFzIGVuIGxvcyB0d2l0cy4NCg0KYGBge3IgRXh0cmFjY2nDs24gZGUgY3VlbnRhc30NCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkodG0pDQpsaWJyYXJ5KHdvcmRjbG91ZCkNCg0KdW5pcXVlKHNhcHBseSh0d2VldHMuZGYkdGV4dCwgZnVuY3Rpb24odHdpdCkgc3RyX2V4dHJhY3RfYWxsKHR3aXQsICJAXFx3KyIpKSkNCg0KY3VlbnRhc01lbmNpb25hZGFzIDwtIHN0cl9leHRyYWN0X2FsbCh0d2VldHMuZGYkdGV4dCwgIkBcXHcrIikNCmNvcnB1c05vbWJyZXMgPC0gQ29ycHVzKFZlY3RvclNvdXJjZShjdWVudGFzTWVuY2lvbmFkYXMpKQ0KVmlldyhjb3JwdXNOb21icmVzKQ0KYGBgDQoNCkhhYmllbmRvIGdlbmVyYWRvIHVuIGNvcnB1cyBjb24gbGFzIGN1ZW50YXMgZXh0cmHDrWRhcyBhaG9yYSBsbyB2aXN1YWxpemFyZW1vcyBjb24gdW5hIG51YmUgZGUgcGFsYWJyYXMNCmBgYHtyfQ0KcGFsIDwtIGJyZXdlci5wYWwoOSwiWWxHbkJ1IikNCnBhbCA8LSBwYWxbLSgxOjQpXQ0KDQpzZXQuc2VlZCg0MikNCndvcmRjbG91ZCh3b3JkcyA9IGNvcnB1c05vbWJyZXMsIHNjYWxlID0gYygxLjUsIDAuNzUpLCBtYXgud29yZHMgPSA1MCwgcmFuZG9tLm9yZGVyID0gRkFMU0UsIHJvdC5wZXIgPSAwLjM1LCB1c2Uuci5sYXlvdXQgPSBUUlVFLCBjb2xvcnMgPSBwYWwpDQpgYGANCg0KT3RyYSBtYW5lcmEgZGUgaGFjZXJsbyB5IGRlIGNvbm9jZXIgbGEgZnJlY3VlbmNpYSBkZSBudWVzdHJvcyBkYXRvcyBlcyBsYSBzaWd1aWVudGUNCg0KYGBge3J9DQpkdG0gPC0gVGVybURvY3VtZW50TWF0cml4KGNvcnB1c05vbWJyZXMpIA0KbWF0cml4IDwtIGFzLm1hdHJpeChkdG0pIA0KY3VlbnRhcyA8LSBzb3J0KHJvd1N1bXMobWF0cml4KSxkZWNyZWFzaW5nPVRSVUUpIA0KZGYgPC0gZGF0YS5mcmFtZShjdWVudGEgPSBuYW1lcyhjdWVudGFzKSxmcmVxPWN1ZW50YXMpDQpkZg0KYGBgDQoNCg0KIyMgRXhwb3J0YW5kbyBsb3MgdHdpdHMNCg0KUG9kZW1vcyBleHBvcnRhciBudWVzdHJvIGRhdGFmcmFtZSBhbCBmb3JtYXRvIHF1ZSBxdWVycmFtb3MsIHBhcmEgbWFwZWFybG9zIHVzYXJlbW9zIHVuIGZvcm1hdG8gY3N2DQoNCmBgYHtyfQ0Kd3JpdGUuY3N2Mih0d2VldHMuZGYsIGZpbGU9ImxnYnRpcVR3ZWV0cy5jc3YiKQ0KYGBgDQoNCi0tLQ0KDQojIEZ1bmNpb25lcyBkZSBhcG95bw0KDQojIyBMYSBmdW5jacOzbiB1bmlxdWUNCkNvbiBsYSBmdW5jacOzbiB1bmlxdWUsIHBvZGVtb3Mgb2J0ZW5lciBsb3MgdmFsb3JlcyBzaW4gcmVwZXRpciBkZSB1biBjaWVydG8gY29uanVudG8gZGUgZGF0b3MuDQoNCmBgYHtyfQ0KdW5pcXVlKHNvcnQodHdlZXRzLmRmJHN0YXR1c1NvdXJjZSkpDQpgYGANCg0KDQojIyBJZGVudGlmaWNhY2nDs24gZGUgc2VydmljaW8NCkVuIGVsIHNpZ3VpZW50ZSBibG9xdWUgZGUgY8OzZGlnbyBjcmVhcmVtb3MgdW5hIGZ1bmNpw7NuIHF1ZSBwZXJtaXRhIGlkZW50aWZpY2FyIGVsIHNlcnZpY2lvIHkgbGltcGlhciBsYSBzYWxpZGEgZW50cmVnYW5kbyB1bmEgc29sYSBwYWxhYnJhDQoNCmBgYHtyfQ0KbGlicmFyeSgic2ptaXNjIikNCmlkZW50aWZpY2FTZXJ2aWNpbyA8LSBmdW5jdGlvbihmdWVudGVEZWxTZXJ2aWNpbyl7DQogIGlmKHN0cl9jb250YWlucyhmdWVudGVEZWxTZXJ2aWNpbywgIkFuZHJvaWQiKSl7DQogICAgcmV0dXJuKCJhbmRyb2lkIikNCiAgfSBlbHNlIGlmKHN0cl9jb250YWlucyhmdWVudGVEZWxTZXJ2aWNpbywgImlQYWQiKSB8fCBzdHJfY29udGFpbnMoZnVlbnRlRGVsU2VydmljaW8sICJpUGhvbmUiKSl7DQogICAgcmV0dXJuKCJpT1MiKQ0KICB9IGVsc2UgaWYoc3RyX2NvbnRhaW5zKGZ1ZW50ZURlbFNlcnZpY2lvLCAiV2ViIikpew0KICAgIHJldHVybigid2ViIikNCiAgfSBlbHNlIGlmKHN0cl9jb250YWlucyhmdWVudGVEZWxTZXJ2aWNpbywgIkJvdHMiKSl7DQogICAgcmV0dXJuKCJib3QiKQ0KICB9IGVsc2V7DQogICAgcmV0dXJuKCJPdHJvIikNCiAgfQ0KfQ0Kc2FwcGx5KHR3ZWV0cy5kZiRzdGF0dXNTb3VyY2UsIGlkZW50aWZpY2FTZXJ2aWNpbykNCmBgYA0KDQotLS0NCg0KIyBSZWZlcmVuY2lhcw0KICogTGVhcm5pbmcgU29jaWFsIE1lZGlhIEFuYWx5dGljcyB3aXRoIFINCiAqIFtBIGJlZ2lubmVyJ3MgZ3VpZGUgdG8gY29sbGVjdGluZyBhbmQgbWFwcGluZyBUd2l0dGVyIGRhdGEgdXNpbmcgUl0oaHR0cHM6Ly9vcGVuc291cmNlLmNvbS9hcnRpY2xlLzE3LzYvY29sbGVjdGluZy1hbmQtbWFwcGluZy10d2l0dGVyLWRhdGEtdXNpbmctcikNCiAqIFtHaXRIdWIgZGUgTGVhcm5pbmcgU29jaWFsIE1lZGlhIEFuYWx5dGljcyB3aXRoIFJdKGh0dHBzOi8vZ2l0aHViLmNvbS9QYWNrdFB1Ymxpc2hpbmcvTGVhcm5pbmctU29jaWFsLU1lZGlhLUFuYWx5dGljcy13aXRoLVIvYmxvYi9tYXN0ZXIvQ2hhcHRlcjAyL0IwNjA1Nl8wMl8wMl9jb2RlLlIpDQo=