Markov-Chain Carluxo - Criando um bot para Twitter usando o algorítmo de Markov-Chain
O primeiro passo a ser feito é instalar e carregar três bibliotecas, a tidyverse, rtweet, markovchain, caso você não tenha-as instaladas instale-as direto do CRAN usando a função install.packages(). Não sabe como fazer? Digite help(install.packages) no console!
Cada uma tem uma funcionalidade:
Tidyverse: Uma metabiblioteca que contém um conjunto de ferramentas que adicionam uma nova sintaxe noR. Por meio delas é possível escrever um código mais limpo e eficiente.rtweet: É a interface entreRe a API do twitter, é ela que será utilizada para capturarmos os tweets e depois fazermos a publicação de um novo.markovchain: é a biblioteca que implementa emRo algoritmo que será utilizado para a geração do texto. Eu poderia fazer explicações acerca da lógica que está por trás, porém, existem excelentes explicações onlines como 1. esta explicação visual (caso você goste de imagens); e 2. essa mais teórica, feita por acadêmicos de Princeton.tictoc: será utilizada para marcarmos tempo, não é necessária para o funcionamento do modelo.
library(tidyverse)
library(rtweet)
library(markovchain)
library(tictoc)Carregadas as bibliotecas, vamos fazer o login na API do twitter. Se você não possui as credenciais necessárias, sem problemas é bem fácil criar, e - descontado o tempo que o Twitter levar demorar para liberar sua conta de desenvolvedor - não deve demorar mais de 10 minutos. Tem um videozinho aqui explicando, é em inglês, mas pode ser assistido sem som, nada essencial de ser ouvido.
rtweet::create_token(consumer_key = 'CKQPkCiBAoiluqzi33PMTYV6p',
consumer_secret = 'GyCA2voQW1lSxgA8EG1FCdpeCKkxAM6eZfROvlg5HoPeKw30vJ',
access_token = '1191556232701714433-qQpNy6b7TZTPrjGpkqJmg8J3dmYLHY',
access_secret = 'PxWPn7YvudptAJuZXEfnO840Ov5nSgdRrcE1feOQvCWdC',
app = 'carluxobot') # essas keys foram invalidadas :)## <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> carluxobot
## key: CKQPkCiBAoiluqzi33PMTYV6p
## secret: <hidden>
## <credentials> oauth_token, oauth_token_secret
## ---tweets_carluxo <- rtweet::get_timeline('carlosbolsonaro', n = 3200)No código acima temos duas funções, a primeira é a que faz o Login na API do twitter, utilizando os dados que pegamos lá no app que criamos. A partir disso, nós usamos a função get_timeline, do {rtweet}, para coletar os últimos 3200 tweets feitos pelo Vereador Federal Carlos Bolsonaro. Este número, 3200, não foi escolhido por acaso, ele é o número máximo de tweets passíveis de serem coletados por 1 requisição, isso na versão gratuita da API do Twitter.
Isso vai nos retornar um dataframe bem grande, qual só iremos utilizar uma coluna, mas o leitor é livre para explorá-lo e encontrar outras utilidades. Veja-o.
| user_id | status_id | created_at | screen_name | text | source | display_text_width | reply_to_status_id | reply_to_user_id | reply_to_screen_name | is_quote | is_retweet | favorite_count | retweet_count | quote_count | reply_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 | quoted_created_at | quoted_source | quoted_favorite_count | quoted_retweet_count | quoted_user_id | quoted_screen_name | quoted_name | quoted_followers_count | quoted_friends_count | quoted_statuses_count | quoted_location | quoted_description | quoted_verified | retweet_status_id | retweet_text | retweet_created_at | retweet_source | retweet_favorite_count | retweet_retweet_count | retweet_user_id | retweet_screen_name | retweet_name | retweet_followers_count | retweet_friends_count | retweet_statuses_count | retweet_location | retweet_description | retweet_verified | place_url | place_name | place_full_name | place_type | country | country_code | geo_coords | coords_coords | bbox_coords | status_url | 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 68712576 | 1256335722052648960 | 1588368752 | CarlosBolsonaro |
| Twitter for Android | 18 | NA | NA | NA | FALSE | FALSE | 5112 | 1018 | NA | NA | NA | NA | NA | NA | NA | http://pbs.twimg.com/media/EW9mk1XWoAIm3nW.jpg | https://t.co/tgRoWDQwIc | https://twitter.com/CarlosBolsonaro/status/1256335722052648960/photo/1 | photo | http://pbs.twimg.com/media/EW9mk1XWoAIm3nW.jpg | https://t.co/tgRoWDQwIc | https://twitter.com/CarlosBolsonaro/status/1256335722052648960/photo/1 | NA | c(“861707648584085504”, “37717107”) | c(“govbr”, “minsaude”) | und | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(NA, NA) | c(NA, NA) | c(NA, NA, NA, NA, NA, NA, NA, NA) | https://twitter.com/CarlosBolsonaro/status/1256335722052648960 | Carlos Bolsonaro | Rio de Janeiro-RJ | Vereador da cidade do Rio de Janeiro (ainda podendo opinar sobre o que achar pertinente). | https://t.co/3z2ZiPnA8f | FALSE | 1790529 | 510 | 2046 | 15351 | 4127 | 1251212007 | TRUE | https://t.co/3z2ZiPnA8f | http://www.carlosbolsonaro.com.br | NA | https://pbs.twimg.com/profile_banners/68712576/1575806653 | http://abs.twimg.com/images/themes/theme1/bg.png | http://pbs.twimg.com/profile_images/1230681290120318977/iI2UkUQm_normal.jpg |
| 68712576 | 1256206931460587522 | 1588338046 | CarlosBolsonaro | https://t.co/GcdJFTYEgY | Twitter for Android | 0 | NA | NA | NA | FALSE | FALSE | 20218 | 3309 | NA | NA | NA | NA | NA | NA | NA | http://pbs.twimg.com/ext_tw_video_thumb/1256206860748828676/pu/img/0zd0Mn48lBIBpbv7.jpg | https://t.co/GcdJFTYEgY | https://twitter.com/CarlosBolsonaro/status/1256206931460587522/video/1 | photo | http://pbs.twimg.com/ext_tw_video_thumb/1256206860748828676/pu/img/0zd0Mn48lBIBpbv7.jpg | https://t.co/GcdJFTYEgY | https://twitter.com/CarlosBolsonaro/status/1256206931460587522/video/1 | NA | NA | NA | und | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(NA, NA) | c(NA, NA) | c(NA, NA, NA, NA, NA, NA, NA, NA) | https://twitter.com/CarlosBolsonaro/status/1256206931460587522 | Carlos Bolsonaro | Rio de Janeiro-RJ | Vereador da cidade do Rio de Janeiro (ainda podendo opinar sobre o que achar pertinente). | https://t.co/3z2ZiPnA8f | FALSE | 1790529 | 510 | 2046 | 15351 | 4127 | 1251212007 | TRUE | https://t.co/3z2ZiPnA8f | http://www.carlosbolsonaro.com.br | NA | https://pbs.twimg.com/profile_banners/68712576/1575806653 | http://abs.twimg.com/images/themes/theme1/bg.png | http://pbs.twimg.com/profile_images/1230681290120318977/iI2UkUQm_normal.jpg |
| 68712576 | 1256122766815870976 | 1588317979 | CarlosBolsonaro | @MarcosQuezado1 Se vi essa mulher na casa do meu pai 4 vezes foi muito. O que todos viam era alguém mais preocupada em se filmar em eventos públicos ao lado de Jair Bolsonaro para se promover e depois fazer o que fez, como muitos. Minha irmã!? É muito cara de pau, Meu Deus! | Twitter for iPhone | 258 | 1256113381943193607 | 1148318806303027205 | MarcosQuezado1 | FALSE | FALSE | 6634 | 1068 | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | 1148318806303027205 | MarcosQuezado1 | pt | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(NA, NA) | c(NA, NA) | c(NA, NA, NA, NA, NA, NA, NA, NA) | https://twitter.com/CarlosBolsonaro/status/1256122766815870976 | Carlos Bolsonaro | Rio de Janeiro-RJ | Vereador da cidade do Rio de Janeiro (ainda podendo opinar sobre o que achar pertinente). | https://t.co/3z2ZiPnA8f | FALSE | 1790529 | 510 | 2046 | 15351 | 4127 | 1251212007 | TRUE | https://t.co/3z2ZiPnA8f | http://www.carlosbolsonaro.com.br | NA | https://pbs.twimg.com/profile_banners/68712576/1575806653 | http://abs.twimg.com/images/themes/theme1/bg.png | http://pbs.twimg.com/profile_images/1230681290120318977/iI2UkUQm_normal.jpg |
| 68712576 | 1256119682911920129 | 1588317244 | CarlosBolsonaro | @ClaudeLuca_ @CrisBernart @FMouraBrasil @RevistaCrusoe @RevistaISTOE @VEJA Tudo engatado um no outro… acuse os do que você é….. | Twitter for iPhone | 56 | 1256091079343996929 | 186240150 | ClaudeLuca_ | FALSE | FALSE | 554 | 109 | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(“186240150”, “1081209715923795968”, “52849416”, “967904717446766593”, “29913589”, “17715048”) | c(“ClaudeLuca_”, “CrisBernart”, “FMouraBrasil”, “RevistaCrusoe”, “RevistaISTOE”, “VEJA”) | pt | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(NA, NA) | c(NA, NA) | c(NA, NA, NA, NA, NA, NA, NA, NA) | https://twitter.com/CarlosBolsonaro/status/1256119682911920129 | Carlos Bolsonaro | Rio de Janeiro-RJ | Vereador da cidade do Rio de Janeiro (ainda podendo opinar sobre o que achar pertinente). | https://t.co/3z2ZiPnA8f | FALSE | 1790529 | 510 | 2046 | 15351 | 4127 | 1251212007 | TRUE | https://t.co/3z2ZiPnA8f | http://www.carlosbolsonaro.com.br | NA | https://pbs.twimg.com/profile_banners/68712576/1575806653 | http://abs.twimg.com/images/themes/theme1/bg.png | http://pbs.twimg.com/profile_images/1230681290120318977/iI2UkUQm_normal.jpg |
| 68712576 | 1256107374919778304 | 1588314310 | CarlosBolsonaro | @kimpaim Interessante! | Twitter for Android | 13 | 1256102613306576896 | 75264300 | kimpaim | FALSE | FALSE | 4525 | 418 | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | 75264300 | kimpaim | pt | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(NA, NA) | c(NA, NA) | c(NA, NA, NA, NA, NA, NA, NA, NA) | https://twitter.com/CarlosBolsonaro/status/1256107374919778304 | Carlos Bolsonaro | Rio de Janeiro-RJ | Vereador da cidade do Rio de Janeiro (ainda podendo opinar sobre o que achar pertinente). | https://t.co/3z2ZiPnA8f | FALSE | 1790529 | 510 | 2046 | 15351 | 4127 | 1251212007 | TRUE | https://t.co/3z2ZiPnA8f | http://www.carlosbolsonaro.com.br | NA | https://pbs.twimg.com/profile_banners/68712576/1575806653 | http://abs.twimg.com/images/themes/theme1/bg.png | http://pbs.twimg.com/profile_images/1230681290120318977/iI2UkUQm_normal.jpg |
| 68712576 | 1256078557203386368 | 1588307439 | CarlosBolsonaro | Belo Horizonte (30/04/2020). Via @taoquei1 https://t.co/BowdIhGrlj | Twitter for Android | 42 | NA | NA | NA | FALSE | FALSE | 18349 | 4696 | NA | NA | NA | NA | NA | NA | NA | http://pbs.twimg.com/ext_tw_video_thumb/1256078371030798339/pu/img/GvaHj1cQSD9F4Scn.jpg | https://t.co/BowdIhGrlj | https://twitter.com/CarlosBolsonaro/status/1256078557203386368/video/1 | photo | http://pbs.twimg.com/ext_tw_video_thumb/1256078371030798339/pu/img/GvaHj1cQSD9F4Scn.jpg | https://t.co/BowdIhGrlj | https://twitter.com/CarlosBolsonaro/status/1256078557203386368/video/1 | NA | 1087259768 | taoquei1 | pt | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | c(NA, NA) | c(NA, NA) | c(NA, NA, NA, NA, NA, NA, NA, NA) | https://twitter.com/CarlosBolsonaro/status/1256078557203386368 | Carlos Bolsonaro | Rio de Janeiro-RJ | Vereador da cidade do Rio de Janeiro (ainda podendo opinar sobre o que achar pertinente). | https://t.co/3z2ZiPnA8f | FALSE | 1790529 | 510 | 2046 | 15351 | 4127 | 1251212007 | TRUE | https://t.co/3z2ZiPnA8f | http://www.carlosbolsonaro.com.br | NA | https://pbs.twimg.com/profile_banners/68712576/1575806653 | http://abs.twimg.com/images/themes/theme1/bg.png | http://pbs.twimg.com/profile_images/1230681290120318977/iI2UkUQm_normal.jpg |
Bonito, né? A API do Twitter nos retorna diversas informações que podem ser utilizadas para vários tipos de análises diferentes, vale a pena dar uma olhada no que o pessoal da DAPP/FGV tem feito com eles. Mas só a coluna text será utilizada por nós. Passemos ao código…
clean_tweets <- function(text_input){
text_input %>%
str_remove_all(pattern='\n') %>% # remove quebras de linha desnecessárias
str_remove_all(pattern= '[:punct:](\\S+[:space:])?') %>% #remove as @ citadas.
str_remove_all(pattern= 'http\\S+') %>% # remove as URLs
toupper() # Design Decision: para ficar parecido com um louco.
}
treat <- function(dados){
dados %>%
select(created_at, text, favorite_count, reply_count) %>% # seleciona as colunas desejadas.
mutate(text = clean_tweets(text)) %>% # passa os tweets pela função clean.
filter(nchar(text) > 20) #remove linhas com menos de 20 caracteres
}
dados <- tweets_carluxo %>%
treat() # RUN.Acima temos duas funções: clean_tweets e treat, a primeira será utilizada dentro da segunda, e é composta por 3 chamadas à função stringr::str_remove_all(), e uma à função toupper(). As 3 chamadas iniciais são ajustes no texto necessários para remover elementos indesejados.
/
'\n'/ é a expressão regular para que sejam removidas o conjunto de caracteres que indica quebra de linha./
[:punct:](\\S+[:space:])?/ remove todo agrupamento textual composto por pontuação-palavra-espaço, foi o mecanismo que recorri para remover as @’s que o Carluxo citou. Elas não serão úteis para nós, além disso, fazer um tweet com essas @s seria notificar alguém sobre nosso bot, o que não é nosso objetivo./
http\\S+/ remove links iniciados ou qualquer palavra que tenha http em seu inicio, como não existem muitas
Por fim, temos…
touppero que essa função, que faz parte do R-base (ou seja, ela já vem instalado no seu R), faz é transformar todos os caracteres em maíusculos.
Nós utilizaremos o artifício da capitalização por dois motivos, um de design, e outro técnico. Sobre o design, o motivo é que Carlos é conhecido por gritar, então deixar em maíusculo dá um tom cômico às publicações. Já a justificativa técnica é que nosso algoritmo funciona com probabilidade de uma determinada palavra aparecer, contudo em diversos momentos podemos ter a mesma palavra com capitalização diferenciada. Não é desejável para nós que o computador diferencia ‘canalha’ de ‘Canalha’, visto que as duas palavras tem o mesmo significado, então padronizamo-as com a capitalização total. Utilizar tolower() para caracteres todas minúsculos também é uma opção.
Em algumas situações, palavras com capitalização diferente podem significar diferentes coisas, por exemplo, "A Grande Sambista, a Marrom" se refere à gloriosa cantora Alcione, por outro lado, a sentença o carro marrom se refere a algum carro feio. Contudo, isso é a minoria dos casos, e caso aconteça em algum momento e o leitor queira fazer a diferenciação, é possível utilizar ‘Part-of-Speech Tagging’ para fazer a separação.
Então, temos uma segunda função, a treat, o que ela faz é receber nosso data.frame com os resultados da API, selecionar por meio da função dplyr::select() as colunas desejadas, e então, utilizando dplyr::mutate(), aliado com nossa função clean_tweets() trata a coluna text. Então, em um terceiro - e último - passo, filtramos os Tweets por tamanho, removendo todos com menos de 20 caracteres de extensão, essa remoção é necessária para que sejam removidas as linhas vazias ou com muito pouco conteúdo, dessa forma agilizamos nosso modelo.
Por fim, passo os nossos dados pelas funções que criamos, e… Voilá!
| created_at | text | favorite_count | reply_count |
|---|---|---|---|
| 2020-05-01 07:26:19 | SE VI ESSA MULHER NA CASA DO MEU PAI 4 VEZES FOI MUITO O QUE TODOS VIAM ERA ALGUÉM MAIS PREOCUPADA EM SE FILMAR EM EVENTOS PÚBLICOS AO LADO DE JAIR BOLSONARO PARA SE PROMOVER E DEPOIS FAZER O QUE FEZ COMO MUITOS MINHA IRMÃÉ MUITO CARA DE PAU MEU DEUS | 6634 | NA |
| 2020-05-01 07:14:04 | TUDO ENGATADO UM NO OUTROACUSE OS DO QUE VOCÊ É | 554 | NA |
| 2020-05-01 02:31:39 | O VALE CINZENTO DA RAZÃO VAI MUITO ALÉM DOS CALÇAS ENCRAVADAS | 12669 | NA |
| 2020-05-01 02:23:03 | PRUDÊNCIA E SOFISTICAÇÃOCÊ CURTE | 22640 | NA |
| 2020-04-30 17:24:11 | MAIS EXEMPLOS DE ATUAÇÕES DO NOS ÚLTIMOS DIAS 2 | 3825 | NA |
| 2020-04-30 17:19:06 | MAIS EXEMPLOS DE ATUAÇÕES DO NOS ÚLTIMOS DIAS 1 | 8820 | NA |
model_it <- dados %>% #remove pontuação
pull(text) %>% #puxa a coluna que contem o texto dos tweets.
str_split(' ') %>% #quebra a coluna, cada palavra vira um elemento de lista
unlist %>% # remove os caracteres das listas que a função anterior retorna
na.omit() # remove caracteres missing (NA), não deve haver nenhum, mas só por precaução.Passando agora os preparativos finais, é importante que o objeto inserido na função que calculará o modelo seja:
Grande: Quando maior o vetor inserido em nosso modelo, maiores serão as frases que ele conseguirá formular.
Tokenizado: O objeto inserido deve ser um vetor de caracteres, onde cada elemento é uma palavra.
Para que atendamos a essas obrigações precisamos que façamos alterações em nossos dados, que estão em formado de dataframe. Para resolver esse problema, nós extraímos somente a coluna text do dataframe - em formato de vetor - utilizando a função dplyr::pull(). A partir disso, quebramos nosso vetor em uma lista em que cada item é composto é um vetor de caracteres, nestes, cada palavra (usamos o espaço como separador) é um elemento. Contudo, listas não são o formato desejado, e utilizamos a função unlist() para remover os vetores, transformando-os em um large character vector. Por fim, removemos nos NA potenciais usando a na.omit().
tic()
model <- markovchainFit(model_it[1:20000]) # fita o modelo
toc()## 303.786 sec elapsedPara atingir o ponto que queriamos, então, fitamos o modelo utilizando o markovchain::markovchainFit(), aqui utilizaremos somente as primeiras 20000 palavras do nosso vetor, isso será feito por limitações computacionais. Quanto maior o vetor inserido, mais tempo a computação do modelo levará. Acima temos o tempo que levou para rodar no meu computador. O argumento n da função indica quantas palavras devem ser gerados pelo modelo.
for(i in 1:3){
markovchainSequence(model$estimate, n = 20) %>%
paste(collapse= ' ') %>%
print()
}## [1] "INVASÕES DE DESENVOLVIMENTO COMERCIAL COM ENTREGA DO SENADO PODEM FAZER ISSO E MUNICÍPIOS DE CONSERVAÇÃO NO MERCADO INTERNACIONAL O RISCO"
## [1] " ALGUÉM ACHA MESMO QUE DEMOCRACIA AMEAÇA NÓS JAMAIS PENSEI QUE HOJE SABEMOS A BASE SEJA DERRUBÁÀ FORÇA AGORA 24"
## [1] "ÀS DROGAS NO BRASIL O TEOR DA TRIBUTAÇÃO DO GOVERNO BOLSONARO ACABOU O MÊS DE 0NA COMPARAÇÃO COM NOME JAIR"Pronto, nosso modelo está pronto, acima temos 3 exemplos de palavras geradas por ele.
Iremos então fazer uma função para dar ajustes finais no tweet que nosso modelo gerar, e então fazer o Tweet caso este atenda ao critério para publicação que é: possuir menos de 280 caracteres (é o limite atual de caracteres de um tweet).
tuita_ai = function(model_here){
tweet = markovchainSequence(model_here$estimate, n = 30) %>%
paste(collapse = ' ') %>%
str_remove('[:space:](A|O)$')
if(nchar(tweet)<=280){
post_tweet(tweet)
} else {
message('Tweet maior que o desejado, tentando novamente, aguarde...')
tuita_ai(model_here)
}
}
tuita_ai(model) #Foi!## your tweet has been posted!A função acima faz o seguinte:
- Cria uma função chamada
tuita_aique receberá o nosso modelo, e fará:
- Cria dentro da função uma variável tweet - ela não ficará visivel em seu ambiente, para entender esse comportamento confira o capitulo 6.4 do manual Advanced R. Essa variável recebe o resultado de uma computação de nosso modelo, e é colapsada em uma
string. São removidos então os caracteres ‘A’ ou ‘O’ caso estejam sozinhos no fim da frase.
A remoção de ‘O’ ou ‘A’ foi feita pois percebi durante a escrita desse post que é normal a ocorrencia dessas letras, sozinhas, no fim da frase.
Irá conferir a quantidade de caracteres da string gerada, caso seja menor ou igual a 280, publicará o tweet.
Caso a string não seja maior ou igual, portanto maior, que 280 caracteres, irá exibir a mensagem de nova tentativa, e executará novamente nossa função por meio de uma recursão. Peço cuidado aqui, colocar um valor muito grande no
nda funçãomarkovchainSequence()pode gerar uma recursão infinita, o que é indesejado.
Enfim, rodamos nosso modelo, se tudo deu certo, ele deve exibir uma mensagem de sucesso, e seu tweet foi feito!
É isso, essa postagem ficou um pouco longo, mas queria mostrar que é possível nos divertirmos pouco esforço. :)
Abraços, até a proxima!