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 entreR
e 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 emR
o 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…
toupper
o 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 elapsed
Para 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_ai
que 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
n
da 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!